-a/--average option implemented.
[timeplan.git] / timeplan.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <getopt.h>
4 #include <errno.h>
5 #include <string.h>
6 #include <time.h>
7 #include <assert.h>
8 #include <limits.h>
9 #include <ctype.h>
10 #include <regex.h>
11
12 #define ACTS_MAX 20
13 #define HASH_SIZE 211
14 #define CONFIGFILE "/.timeplanrc" /* HOME prepended */
15 #define DEF_TIMEPLAN "/.timeplan" /* HOME prepended */
16 #define MARK_DAY "_days"
17 #define DEF_FORMTOTAL ":6"
18 #define LENGTH(x) (sizeof((x))/sizeof(*(x)))
19 #define WHERE1 " in \"%s\" on line %d!\n"
20 #define WHERE2 finame,line
21 #define NL "\r\n"
22 #define ERRH1 stderr,"%s: "
23 #define ERRH2 pname
24 #define ERRNO1 ": %s!\n"
25 #define ERRNO2 strerror(errno)
26 #define FAKEUSE =0
27
28 static const char version[]="This is TimePlan, version 1.0\n";
29 static const char *pname;
30 static char *finame;
31 static FILE *fi;
32 static int verbose=0,tree=0,doaverage=0;
33 static char *formtotal=DEF_FORMTOTAL;
34 static char buf[LINE_MAX];
35 static int line=0;
36 static enum { SORT_NO,SORT_TIMETOT,SORT_STORES } sortby=SORT_TIMETOT;
37 static unsigned lifetime_days=0;
38
39 static void usage(void)
40 {
41         fprintf(stderr,"\
42 %s\
43 This command summarizes the timelog information:\n\
44 \n\
45 Usage: timeplan [-u|--unsort] [-s|--stores] [-T|--timetot]  [-t|--tree]\n\
46                 [-c|--condition <cond>] [-f|--formtotal <fmtstring>]\n\
47                 [-r|--rule <ruleL>:<ruleR>]\n\
48                 [-v|--verbose] [-h|--help] [-V|--version]\n\
49 \n\
50   -u, --unsort\t\tDon't sort the result in any way\n\
51   -s, --stores\t\tSort the result by stores count\n\
52   -T, --timetot\t\tSort the result by total time (default)\n\
53   -t, --tree\t\tOrganize data as hierarchy tree\n\
54   -a, --average\t\tDisplay all times as average-per-day value\n\
55   -c, --condition\tDefine condition variable\n\
56   -f, --formtotal\tFormat \"Total\" column (\"text *val/val:width text\")\n\
57   -r, --rule\t\tAdd to the end of .rc file this rule (':'->'\\t')\n\
58   -v, --verbose\t\tInform about phases of transfer\n\
59   -h, --help\t\tPrint a summary of the options\n\
60   -V, --version\t\tPrint the version number\n\
61 ",version);
62         exit(EXIT_FAILURE);
63 }
64
65 static const struct option longopts[]={
66 {"unsort"   ,0,0,'u'},
67 {"stores"   ,0,0,'s'},
68 {"timetot"  ,0,0,'T'},
69 {"tree"     ,0,0,'t'},
70 {"average"  ,0,0,'a'},
71 {"condition",1,0,'c'},
72 {"formtotal",1,0,'f'},
73 {"rule"     ,1,0,'r'},
74 {"verbose"  ,0,0,'v'},
75 {"help"     ,0,0,'h'},
76 {"version"  ,0,0,'V'}};
77
78 static int calctime(int timetot,int at,int of)
79 {
80         /* FIXME: better distribution */
81         return timetot/of;
82 }
83
84 static struct action {
85         struct action *next;
86         int timetot,stores;
87         char what[1];
88         } *hashtable[HASH_SIZE];
89 static int hashtable_tot=0;
90
91 static int dumpaction(const struct action *action)
92 {
93 int tot,mins FAKEUSE,hours FAKEUSE,days FAKEUSE,origtot;
94 char *s,fmt[]="%?d";
95
96         if (action) {
97                 tot=(action->timetot+30)/60;
98                 if (doaverage && lifetime_days)
99                         tot/=lifetime_days;
100                 mins=tot; hours=mins/60; days=hours/24;
101                 mins%=60; hours%=24;
102                 }
103         else tot=0;
104         origtot=tot;
105         for (s=formtotal;*s;s++)
106                 switch (*s) {
107                         case '*': case '/': {
108 long l;
109 char *end,*s2;
110
111                                 if (s[1]==*s) { s++; goto dump; }
112                                 for (s2=s+1;isdigit(*s2);s2++);
113                                 l=strtol(s+1,&end,10);
114                                 if (end!=s2) {
115                                         fprintf(ERRH1"Number parse error at column %d of formtotal string!\n",ERRH2,s-formtotal);
116                                         exit(EXIT_FAILURE);
117                                         }
118                                 if (*s=='*') tot*=l;
119                                 else         tot/=l;
120                                 s=s2-1;
121                                 break; }
122                         case ':':
123                                 if (s[1]==*s) { s++; goto dump; }
124                                 if (!isdigit(s[1])) goto dump;
125                                 if (action) {
126                                         fmt[1]=s[1];
127                                         printf(fmt,tot);
128                                         tot=origtot;
129                                         }
130                                 else origtot+=s[1]-'0';
131                                 s+=1;
132                                 break;
133                         default:
134 dump:
135                                 if (action) putchar(*s);
136                                 else origtot++;
137                         }
138         if (action)
139                 printf("=%03d/%02d:%02d %4d\t%s\n",days,hours,mins,
140                         action->stores,action->what);
141         return(origtot);
142 }
143
144 #define A (*Ap)
145 #define B (*Bp)
146 #define FUNC(which) \
147         static int sort_##which(const struct action **Ap,const struct action **Bp) \
148         { return (B->which>A->which)-(A->which>B->which); }
149 FUNC(timetot)
150 FUNC(stores)
151 #undef FUNC
152 #undef B
153 #undef A
154
155 static void dumphashtable(void)
156 {
157 int item;
158 struct action *action;
159 struct action **sorta FAKEUSE,**sorti FAKEUSE;
160
161         if (sortby!=SORT_NO) {
162 int totalwidth=dumpaction(NULL);
163
164                 while (totalwidth-->5) putchar(' ');
165                 puts("Total Day Hr Mi Stor\tDescription");
166                 if (!(sorta=malloc(sizeof(*sorta)*hashtable_tot))) {
167                         fprintf(ERRH1"malloc() of %d pointers"ERRNO1,ERRH2,hashtable_tot,ERRNO2);
168                         exit(EXIT_FAILURE);
169                         }
170                 sorti=sorta;
171                 }
172         for (item=0;item<LENGTH(hashtable);item++)
173                 for (action=hashtable[item];action;action=action->next)
174                         if (sortby==SORT_NO)
175                                 dumpaction(action);
176                         else
177                                 *sorti++=action;
178         if (sortby==SORT_NO) return;
179         assert(sorti==sorta+hashtable_tot);
180         qsort(sorta,hashtable_tot,sizeof(*sorta),
181                 (int (*)(const void *,const void *))(sortby==SORT_TIMETOT ? sort_timetot : sort_stores));
182         for (sorti=sorta;sorti<sorta+hashtable_tot;sorti++)
183                 dumpaction(*sorti);
184         free(sorta);
185 }
186
187 static unsigned calchash(const char *s)
188 {
189 unsigned r=57;
190
191         while (*s) r=r*7+11*toupper(*s++);
192         return r;
193 }
194
195 static void storeone(char *what,int length)
196 {
197 struct action **actionp,*action;
198
199         if (verbose) printf("storeone: %d: %s\n",length,what);
200         for (actionp=hashtable+(calchash(what)%HASH_SIZE);(action=*actionp);actionp=&action->next)
201                 if (!strcasecmp(action->what,what)) break;
202         if (!action) {
203                 if (!(action=malloc(sizeof(*action)+strlen(what)))) {
204                         fprintf(ERRH1"malloc() for \"%s\""ERRNO1,ERRH2,what,ERRNO2);
205                         exit(EXIT_FAILURE);
206                         }
207                 action->next=NULL;
208                 action->timetot=0;
209                 action->stores=0;
210                 strcpy(action->what,what);
211                 *actionp=action;
212                 hashtable_tot++;
213                 }
214         action->timetot+=length;
215         action->stores++;
216 }
217
218 struct textlist {
219         struct textlist *next;
220         int line;
221         char text[1];
222         };
223 struct textlist *conditions,**conditionstail=&conditions;
224
225 static int iscondition(const char *text)
226 {
227 struct textlist *cond;
228
229         for (cond=conditions;cond;cond=cond->next)
230                 if (!strcasecmp(text,cond->text)) return(1);
231         return(0);
232 }
233
234 static void addlist(struct textlist ***tail,const char *text,int line)
235 {
236 struct textlist *item;
237
238         if (!(item=malloc(sizeof(*item)+strlen(text)))) {
239                 fprintf(ERRH1"malloc() for \"%s\""ERRNO1,ERRH2,text,ERRNO2);
240                 exit(EXIT_FAILURE);
241                 }
242         strcpy(item->text,text);
243         item->next=NULL;
244         **tail=item;
245         *tail=&item->next;
246 }
247
248 static struct textlist *modifies,**modifiestail=&modifies,*modifiescmdl,**modifiescmdltail=&modifiescmdl;
249 static int modifies_tot;
250 static struct modistruct {
251         regex_t regex;
252         char *dst;
253         } *modistructs;
254
255 static void modify_load(void)
256 {
257 static int inited=0;
258 char *finame,*s,*s2;
259 FILE *fi;
260 int line=0,m;
261 struct textlist *item;
262 char buf[LINE_MAX];
263
264         if (inited) return;
265         inited=1;
266         if (asprintf(&finame,"%s"CONFIGFILE,getenv("HOME"))==-1) {
267                 fprintf(ERRH1"Config filename allocation",ERRH2);
268                 exit(EXIT_FAILURE);
269                 }
270         errno=0;
271         if (!(fi=fopen(finame,"rt"))) {
272                 if (errno!=ENOENT)
273                         fprintf(ERRH1"Config file \"%s\" read"ERRNO1,ERRH2,finame,ERRNO2);
274                 return;
275                 }
276
277         while (clearerr(fi),fgets(buf,sizeof(buf),fi)==buf) {
278                 line++;
279                 if ((s=strchr(buf,'\n')) && !s[1]) *s='\0';
280
281                 else {
282                         fprintf(ERRH1"fgets(3) results not newline-terminated"WHERE1,ERRH2,WHERE2);
283                         exit(EXIT_FAILURE);
284                         }
285                 if (!*buf || *buf=='#') {
286 nextline:
287                         continue;
288                         }
289                 if (*buf!='R') {
290                         fprintf(ERRH1"Unrecognized syntax"WHERE1,ERRH2,WHERE2);
291                         exit(EXIT_FAILURE);
292                         }
293                 s=buf+1;
294                 for (;;) {
295 int isplus;
296
297                         while (isspace(*s)) s++;
298                         if (*s==':') break;
299                         if (!isalpha(*s)) {
300                                 fprintf(ERRH1"Invalid character at offset %d"WHERE1,ERRH2,s-buf+1,WHERE2);
301                                 exit(EXIT_FAILURE);
302                                 }
303                         for (s2=s+1;isalpha(*s2);s2++);
304                         if (*s2!='+' && *s2!='-') {
305                                 fprintf(ERRH1"Only plus ('+') or minus ('-'), not '%c' expected at offset %d"WHERE1,ERRH2,*s2,s2-buf+1,WHERE2);
306                                 exit(EXIT_FAILURE);
307                                 }
308                         isplus=(*s2=='+'); *s2++='\0';
309                         if (iscondition(s)!=isplus)
310                                 goto nextline;
311                         s=s2;
312                         }
313                 s++; /* Skip ':' */
314                 addlist(&modifiestail,s,line);
315                 modifies_tot++;
316                 }
317         if (!feof(fi) || ferror(fi))  {
318                 fprintf(ERRH1"fgets(3) \"%s\""ERRNO1,ERRH2,finame,ERRNO2);
319                 exit(EXIT_FAILURE);
320                 }
321         if (fclose(fi))
322                 fprintf(ERRH1"fclose(3) \"%s\""ERRNO1,ERRH2,finame,ERRNO2);
323         *modifiestail=modifiescmdl; /* add all command-line arguments to the end */
324         if (!(modistructs=malloc(sizeof(*modistructs)*modifies_tot))) {
325                 fprintf(ERRH1"malloc() of %d modistructs's"ERRNO1,ERRH2,modifies_tot,ERRNO2);
326                 exit(EXIT_FAILURE);
327                 }
328         for (m=0,item=modifies;m<modifies_tot;m++,item=item->next) {
329 #define line (item->line)
330                 if (!(s=strchr(item->text,'\t'))) {
331                         fprintf(ERRH1"No delimiting tab-character found"WHERE1,ERRH2,WHERE2);
332                         exit(EXIT_FAILURE);
333                         }
334                 *s++='\0'; modistructs[m].dst=s;
335                 if (verbose) printf("regcomp: %s -> %s\n",item->text,s);
336                 if (regcomp(&modistructs[m].regex,item->text,REG_EXTENDED|REG_ICASE)) {
337                         fprintf(ERRH1"regcomp() failed for \"%s\" (%s)"WHERE1,ERRH2,item->text,ERRNO2,WHERE2);
338                         exit(EXIT_FAILURE);
339                         }
340 #undef line
341                 }
342         assert(!item);
343         free(finame);
344 }
345
346 static char *modify(char *what)
347 {
348 regmatch_t matches[10];
349 int i,m;
350 static char modbuf1[sizeof(buf)],modbuf2[sizeof(modbuf1)];
351 char *src=what,*dstbase=modbuf1,*dst=dstbase;
352 const char *start FAKEUSE,*end FAKEUSE,*patt;
353 const struct textlist *item;
354
355         if (verbose) printf("modify: %s\n",what);
356         modify_load();
357         for (m=0,item=modifies;item;m++,item=item->next) {
358 enum { PATT_START,PATT_MID,PATT_END,PATT_TERM } pattpos;
359
360                 i=regexec(&modistructs[m].regex,src,LENGTH(matches),matches,0);
361                 if (i==REG_NOMATCH) continue;
362                 if (i) {
363                         fprintf(ERRH1"regexec() failed for \"%s\""ERRNO1,ERRH2,item->text,ERRNO2);
364                         exit(EXIT_FAILURE);
365                         }
366                 if (verbose) printf("matched: %s -> %s\n",item->text,modistructs[m].dst);
367                 pattpos=PATT_START; patt=NULL;
368                 while (pattpos!=PATT_TERM) {
369                         switch (pattpos) {
370                                 case PATT_START:
371                                         start=src;
372                                         end=src+matches->rm_so;
373                                         pattpos=PATT_MID; patt=modistructs[m].dst;
374                                         break;
375                                 case PATT_MID:
376                                         if (!*patt) {
377                                                 pattpos=PATT_END; patt=NULL;
378                                                 continue;
379                                                 }
380                                         else if (*patt=='@') {
381                                                 start=src; end=src+strlen(src);
382                                                 }
383                                         else if (*patt>='0' && *patt<='9') {
384 regmatch_t *match=matches+(*patt-'0');
385                                                 if (match->rm_so==-1
386                                                  || match->rm_eo==-1) {
387                                                         fprintf(ERRH1"Trying to substitute '%c' but no \"matches\" entry not set for \"%s\""WHERE1,
388                                                                         ERRH2,*patt,item->text,WHERE2);
389                                                         exit(EXIT_FAILURE);
390                                                         }
391                                                 if (match->rm_so>match->rm_eo) {
392                                                         fprintf(ERRH1"Trying to substitute '%c' start>end (%d>%d) for \"%s\""WHERE1,
393                                                                         ERRH2,*patt,match->rm_so,match->rm_eo,item->text,WHERE2);
394                                                         exit(EXIT_FAILURE);
395                                                         }
396                                                 start=src+match->rm_so; end=src+match->rm_eo;
397                                                 }
398                                         else {
399                                                 start=patt; end=patt+1;
400                                                 }
401                                         patt++;
402                                         break;
403                                 case PATT_END:
404                                         start=src+matches->rm_eo;
405                                         end=src+strlen(src);
406                                         pattpos=PATT_TERM; /* assumed: patt=NULL; */
407                                         break;
408                                 default:
409                                         assert(0);
410                                 }
411                         if ((dst-dstbase+(end-start))>=sizeof(modbuf1)) {
412                                 fprintf(ERRH1"Maximum buffer size exceeded during substition for \"%s\""WHERE1,
413                                                 ERRH2,item->text,WHERE2);
414                                 exit(EXIT_FAILURE);
415                                 }
416                         memcpy(dst,start,end-start);
417                         dst+=end-start;
418                         }
419                 if (dst==dstbase) return(NULL);
420                 *dst='\0';
421                 if (src==what) {
422                         assert(dstbase==modbuf1);
423                         src=dstbase;
424                         dst=dstbase=modbuf2;
425                         }
426                 else {
427 char *swap;
428                         assert((src==modbuf1 && dstbase==modbuf2)
429                                          ||(src==modbuf2 && dstbase==modbuf1));
430                         swap=src; src=dstbase; dst=dstbase=swap;
431                         }
432                 }
433         return src;
434 }
435
436 static void store(char *what,int length)
437 {
438 char *ce;
439
440         if (!(what=modify(what))) {
441                 if (verbose) puts("discarded.");
442                 return;
443                 }
444         if (verbose) printf("store: %d: %s\n",length,what);
445         while ((ce=(tree?strrchr:strchr)(what,'-'))) {
446                 if (!tree) *ce='\0';
447                 storeone(what,length);
448                 if (!tree) what=ce+1;
449                 else *ce='\0';
450                 }
451         storeone(what,length);
452 }
453
454 static void hit(time_t t,char *bufaction)
455 {
456 static time_t last=-1;
457 static char bufbackup[sizeof(buf)];
458 char *acts[ACTS_MAX],*s;
459 int timetot,acti,i;
460
461         if (verbose) printf("hit: %ld: %s\n",t,bufaction);
462         if (last!=-1) {
463                 if (t<last) {
464                         fprintf(ERRH1"Time goes backward"WHERE1,ERRH2,WHERE2);
465                         t=last;
466                         }
467                 acts[0]=bufbackup;
468                 acti=1;
469                 while ((s=strchr(acts[acti-1],'+'))) {
470                         *s='\0';
471                         acts[acti++]=s+1;
472                         if (acti>=LENGTH(acts)) {
473                                 fprintf(ERRH1"Too many '+'-delimited actions (%d)"WHERE1,ERRH2,acti,WHERE2);
474                                 exit(EXIT_FAILURE);
475                                 }
476                         }
477                 timetot=t-last;
478                 for (i=0;i<acti;i++)
479                         store(acts[i],calctime(timetot,i,acti));
480                 }
481         strcpy(bufbackup,bufaction);
482         last=t;
483 }
484
485 int main(int argc,char **argv)
486 {
487 int optc;
488 time_t basetime=-1,currtime;
489 int hour,min,sec;
490
491         pname=argv[0];
492         while ((optc=getopt_long(argc,argv,"b:pw:qusTtac:f:r:vhV",longopts,NULL))!=EOF) switch (optc) {
493                 
494                 case 'u':
495                         sortby=SORT_NO;
496                         break;
497                 case 's':
498                         sortby=SORT_STORES;
499                         break;
500                 case 'T':
501                         sortby=SORT_TIMETOT;
502                         break;
503                 case 't':
504                         tree=1;
505                         break;
506                 case 'a':
507                         doaverage=1;
508                         break;
509                 case 'c':
510                         if (iscondition(optarg))
511                                 fprintf(ERRH1"Condition \"%s\" already set!\n",ERRH2,optarg);
512                         else addlist(&conditionstail,optarg,-1);
513                         break;
514                 case 'f':
515                         formtotal=optarg;
516                         break;
517                 case 'r': {
518 char *s,*dup;
519
520                         if (!(s=strchr(optarg,':'))) {
521                                 fprintf(ERRH1"No delimiting ':'-character found in -r option \"%s\"!\n",ERRH2,optarg);
522                                 exit(EXIT_FAILURE);
523                                 }
524                         if (!(dup=strdup(optarg))) {
525                                 fprintf(ERRH1"malloc() of \"%s\" string"ERRNO1,ERRH2,optarg,ERRNO2);
526                                 exit(EXIT_FAILURE);
527                                 }
528                         dup[s-optarg]='\t';
529                         addlist(&modifiescmdltail,dup,0);
530                         modifies_tot++;
531                         } break;
532                 case 'v':
533                         verbose=1;
534                         break;
535                 case 'V':
536                         fprintf(stderr,version);
537                         exit(EXIT_FAILURE);
538                 default: /* also 'h' */
539                         usage();
540                         break;
541                 }
542         if (optind==argc) {
543                 if (asprintf(&finame,"%s"DEF_TIMEPLAN,getenv("HOME"))==-1) {
544                         fprintf(ERRH1"Default timeplan filename allocation",ERRH2);
545                         exit(EXIT_FAILURE);
546                         }
547                 }
548         else if (optind+1!=argc) usage();
549         else if (!strcmp(argv[optind],"-")) {
550                 finame="<stdin>";
551                 fi=stdin;
552                 }
553         else finame=argv[optind];
554         if (!fi && !(fi=fopen(finame,"r"))) {
555                 fprintf(ERRH1"open \"%s\" for reading"ERRNO1,ERRH2,finame,ERRNO2);
556                 exit(EXIT_FAILURE);
557                 }
558
559         while (clearerr(fi),fgets(buf,sizeof(buf),fi)==buf) {
560 char *s;
561
562                 line++;
563                 if ((s=strchr(buf,'\n')) && !s[1]) *s='\0';
564                 else {
565                         fprintf(ERRH1"fgets(3) results not newline-terminated"WHERE1,ERRH2,WHERE2);
566                         exit(EXIT_FAILURE);
567                         }
568                 if (!*buf) continue;
569                 if (*buf==':') { /* ":12.7.2000 (St)" */
570 struct tm tm;
571 char wday[3];
572 int i,parsed;
573 time_t t;
574 const char *days[]={"Ne","Po","Ut","St","Ct","Pa","So"};
575
576                         assert(LENGTH(days)==7);
577                         i=sscanf(buf,":%d.%d.%d (%2s)%n",&tm.tm_mday,&tm.tm_mon,&tm.tm_year,wday,&parsed);
578                         if ((i!=4 && i!=5) || parsed!=strlen(buf)) { /* See note in sscanf(3) man page */
579                                 fprintf(ERRH1"Timestamp with incorrect format"WHERE1,ERRH2,WHERE2);
580                                 exit(EXIT_FAILURE);
581                                 }
582                         for (i=0;i<7;i++) if (!strcmp(days[i],wday)) break;
583                         if (i>=7)
584                                 fprintf(ERRH1"Non-parsable week-day name \"%s\""WHERE1,ERRH2,wday,WHERE2);
585                         tm.tm_sec=tm.tm_min=tm.tm_hour=0;
586                         tm.tm_wday=tm.tm_yday=-1;
587                         tm.tm_isdst=0; /* FIXME */
588                         tm.tm_mon--;
589                         tm.tm_year-=1900;
590                         t=mktime(&tm);
591                         if (t==-1 || tm.tm_wday<0 || tm.tm_wday>6
592                             || tm.tm_mday<1 || tm.tm_mday>31
593                                         || tm.tm_mon <1 || tm.tm_mon >12
594                                         || tm.tm_year<80 || tm.tm_year>150
595                                   ) {
596                                 fprintf(ERRH1"Incorrect timestamp \"%s\""WHERE1,ERRH2,buf,WHERE2);
597                                 exit(EXIT_FAILURE);
598                                 }
599                         if (i<7 && tm.tm_wday!=i)
600                                 fprintf(ERRH1"Non-matching week-day, given \"%s\", calculated \"%s\""WHERE1,ERRH2,days[i],days[tm.tm_wday],WHERE2);
601 #define WANTED (60*60*24)
602                         if (basetime!=-1 && basetime+WANTED!=t)
603                                 fprintf(ERRH1"Non-continuous timestamp (%ld, wanted %d) \"%s\""WHERE1,ERRH2,t-basetime,WANTED,buf,WHERE2);
604 #undef WANTED
605                         basetime=t;
606                         store(MARK_DAY,0);
607                         lifetime_days++;
608                         continue;
609                         }
610
611                 if (!(isdigit(buf[0]) && isdigit(buf[1]) && buf[2]==':' && isdigit(buf[3]) && isdigit(buf[4])
612                  && (buf[5]=='-' || (buf[5]==':' && isdigit(buf[6]) && isdigit(buf[7]) && buf[8]=='-'))
613                       )
614                  || (sec=0,sscanf(buf,(buf[5]=='-'?"%d:%d-":"%d:%d:%d-"),&hour,&min,&sec)!=2+(buf[5]==':'))
615                  || hour<0 || hour>23
616                  || min <0 || min >59
617                  || sec <0 || sec >59
618                     ) {
619                         fprintf(ERRH1"Incorrect day-time \"%s\""WHERE1,ERRH2,buf,WHERE2);
620                         exit(EXIT_FAILURE);
621                         }
622                 if (basetime==-1) {
623                         fprintf(ERRH1"Day-time found but no basetime timestamp set"WHERE1,ERRH2,WHERE2);
624                         exit(EXIT_FAILURE);
625                         }
626                 currtime=basetime+(hour*60+min)*60+sec;
627                 hit(currtime,buf+(buf[5]=='-'?6:9));
628                 }
629
630         if (!feof(fi) || ferror(fi))  {
631                 fprintf(ERRH1"fgets(3) \"%s\""ERRNO1,ERRH2,finame,ERRNO2);
632                 exit(EXIT_FAILURE);
633                 }
634         if (fi!=stdin && fclose(fi))
635                 fprintf(ERRH1"fclose(3) \"%s\""ERRNO1,ERRH2,finame,ERRNO2);
636
637         dumphashtable();
638         return(EXIT_SUCCESS);
639 }