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