218c380b1b0b0df1b3ab22f7b8d5fbad5fea442e
[openafs.git] / src / util / ktime.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  *
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 #include <afsconfig.h>
11 #include <afs/param.h>
12
13 #include <roken.h>
14 #include <afs/opr.h>
15
16 #include <ctype.h>
17
18 #include <afs/opr.h>
19 #include "afsutil.h"
20 #include "ktime.h"
21
22 /* some date parsing routines */
23
24 struct token {
25     struct token *next;
26     char *key;
27 };
28
29 static char *day[] = {
30     "sun",
31     "mon",
32     "tue",
33     "wed",
34     "thu",
35     "fri",
36     "sat"
37 };
38
39 /* free token list returned by parseLine */
40 static void
41 LocalFreeTokens(struct token *alist)
42 {
43     struct token *nlist;
44     for (; alist; alist = nlist) {
45         nlist = alist->next;
46         free(alist->key);
47         free(alist);
48     }
49     return;
50 }
51
52 static int
53 space(int x)
54 {
55     if (x == 0 || x == ' ' || x == '\t' || x == '\n')
56         return 1;
57     else
58         return 0;
59 }
60
61 static int
62 LocalParseLine(char *aline, struct token **alist)
63 {
64     char tbuffer[256];
65     char *tptr = NULL;
66     int inToken;
67     struct token *first, *last;
68     struct token *ttok;
69     int tc;
70
71     inToken = 0;                /* not copying token chars at start */
72     first = NULL;
73     last = NULL;
74     while (1) {
75         tc = *aline++;
76         if (tc == 0 || space(tc)) {     /* terminating null gets us in here, too */
77             if (inToken) {
78                 inToken = 0;    /* end of this token */
79                 *tptr++ = 0;
80                 ttok = malloc(sizeof(struct token));
81                 ttok->next = NULL;
82                 ttok->key = strdup(tbuffer);
83                 if (last) {
84                     last->next = ttok;
85                     last = ttok;
86                 } else
87                     last = ttok;
88                 if (!first)
89                     first = ttok;
90             }
91         } else {
92             /* an alpha character */
93             if (!inToken) {
94                 tptr = tbuffer;
95                 inToken = 1;
96             }
97             if (tptr - tbuffer >= sizeof(tbuffer))
98                 return -1;      /* token too long */
99             *tptr++ = tc;
100         }
101         if (tc == 0) {
102             /* last token flushed 'cause space(0) --> true */
103             if (last)
104                 last->next = NULL;
105             *alist = first;
106             return 0;
107         }
108     }
109 }
110
111 /* keyword database for periodic date parsing */
112 static struct ptemp {
113     char *key;
114     afs_int32 value;
115 } ptkeys[] = {
116     {
117     "sun", 0x10000,}, {
118     "mon", 0x10001,}, {
119     "tue", 0x10002,}, {
120     "wed", 0x10003,}, {
121     "thu", 0x10004,}, {
122     "fri", 0x10005,}, {
123     "sat", 0x10006,}, {
124     "sunday", 0x10000,}, {
125     "monday", 0x10001,}, {
126     "tuesday", 0x10002,}, {
127     "wednesday", 0x10003,}, {
128     "thursday", 0x10004,}, {
129     "thur", 0x10004,}, {
130     "friday", 0x10005,}, {
131     "saturday", 0x10006,}, {
132     "am", 0x20000,}, {
133     "pm", 0x20001,}, {
134     "a.m.", 0x20000,}, {
135     "p.m.", 0x20001,}, {
136     0, 0,}
137 };
138
139 /* ktime_DateOf
140  * entry:
141  *      atime - time in seconds (Unix std)
142  * exit:
143  *      return value - ptr to time in text form. Ptr is to a static string.
144  */
145
146 char *
147 ktime_DateOf(afs_int32 atime)
148 {
149     static char tbuffer[30];
150     char *tp;
151     time_t t = atime;
152     tp = ctime(&t);
153     if (tp) {
154         strcpy(tbuffer, tp);
155         tbuffer[24] = 0;        /* get rid of new line */
156     } else
157         strcpy(tbuffer, "BAD TIME");
158     return tbuffer;
159 }
160
161 /* ParseTime
162  *      parse 12:33:12 or 12:33 or 12 into ktime structure
163  * entry:
164  *      astr - ptr to string to be parsed
165  *      ak - ptr to struct for return value.
166  * exit:
167  *      0 - ak holds parsed time.
168  *      -1 - error in format
169  */
170
171 static int
172 ParseTime(struct ktime *ak, char *astr)
173 {
174     int field;
175     afs_int32 temp;
176     char *tp;
177     int tc;
178
179     field = 0;                  /* 0=hour, 1=min, 2=sec */
180     temp = 0;
181
182     ak->mask |= (KTIME_HOUR | KTIME_MIN | KTIME_SEC);
183     for (tp = astr;;) {
184         tc = *tp++;
185         if (tc == 0 || tc == ':') {
186             if (field == 0)
187                 ak->hour = temp;
188             else if (field == 1)
189                 ak->min = temp;
190             else if (field == 2)
191                 ak->sec = temp;
192             temp = 0;
193             field++;
194             if (tc == 0)
195                 break;
196             continue;
197         } else if (!isdigit(tc))
198             return -1;          /* syntax error */
199         else {
200             /* digit */
201             temp *= 10;
202             temp += tc - '0';
203         }
204     }
205     if (ak->hour >= 24 || ak->min >= 60 || ak->sec >= 60)
206         return -1;
207     return 0;
208 }
209
210 afs_int32
211 ktime_Str2int32(char *astr)
212 {
213     struct ktime tk;
214
215     memset(&tk, 0, sizeof(tk));
216     if (ParseTime(&tk, astr))
217         return (-1);            /* syntax error */
218
219     return ((tk.hour * 60 + tk.min) * 60 + tk.sec);
220 }
221
222 /* ktime_ParsePeriodic
223  *      Parses periodic date of form
224  *              now | never | at [time spec] | every [time spec]
225  *      where [time spec] is a ktime string.
226  * entry:
227  *      adate - string to be parsed
228  *      ak - ptr to structure for returned ktime
229  * exit:
230  *      0 - parsed ktime in ak
231  *      -1 - specification error
232  */
233
234 /* -1 means error, 0 means now, otherwise returns time of next event */
235 int
236 ktime_ParsePeriodic(char *adate, struct ktime *ak)
237 {
238     struct token *tt;
239     afs_int32 code;
240     struct ptemp *tp;
241
242     memset(ak, 0, sizeof(*ak));
243     code = LocalParseLine(adate, &tt);
244     if (code)
245         return -1;
246     for (; tt; tt = tt->next) {
247         /* look at each token */
248         if (strcmp(tt->key, "now") == 0) {
249             ak->mask |= KTIME_NOW;
250             goto out;
251         }
252         if (strcmp(tt->key, "never") == 0) {
253             ak->mask |= KTIME_NEVER;
254             goto out;
255         }
256         if (strcmp(tt->key, "at") == 0)
257             continue;
258         if (strcmp(tt->key, "every") == 0)
259             continue;
260         if (isdigit(tt->key[0])) {
261             /* parse a time */
262             code = ParseTime(ak, tt->key);
263             if (code) {
264                 code = -1;
265                 goto out;
266             }
267             continue;
268         }
269         /* otherwise use keyword table */
270         for (tp = ptkeys;; tp++) {
271             if (tp->key == NULL) {
272                 code = -1;
273                 goto out;
274             }
275             if (strcmp(tp->key, tt->key) == 0)
276                 break;
277         }
278         /* now look at tp->value to see what we've got */
279         if ((tp->value >> 16) == 1) {
280             /* a day */
281             ak->mask |= KTIME_DAY;
282             ak->day = tp->value & 0xff;
283         }
284         if ((tp->value >> 16) == 2) {
285             /* am or pm token */
286             if ((tp->value & 0xff) == 1) {
287                 /* pm */
288                 if (!(ak->mask & KTIME_HOUR)) {
289                     code = -1;
290                     goto out;
291                 }
292                 if (ak->hour < 12) {
293                     ak->hour += 12;
294                 /* 12 is 12 PM */
295                 } else if (ak->hour != 12) {
296                     code = -1;
297                     goto out;
298                 }
299             } else {
300                 /* am is almost a noop, except that we map 12:01 am to 0:01 */
301                 if (ak->hour > 12) {
302                     code = -1;
303                     goto out;
304                 }
305                 if (ak->hour == 12)
306                     ak->hour = 0;
307             }
308         }
309     }
310 out:
311     LocalFreeTokens(tt);
312     return code;
313 }
314
315 /* ktime_DisplayString
316  *      Display ktime structure as English that could go into the ktime parser
317  * entry:
318  *      aparm - ktime to be converted to string
319  *      astring - ptr to string, for the result
320  * exit:
321  *      0 - astring contains ktime string.
322  */
323 int
324 ktime_DisplayString(struct ktime *aparm, char *astring)
325 {
326     char tempString[50];
327
328     if (aparm->mask & KTIME_NEVER) {
329         strcpy(astring, "never");
330         return 0;
331     } else if (aparm->mask & KTIME_NOW) {
332         strcpy(astring, "now");
333         return 0;
334     } else {
335         strcpy(astring, "at");
336         if (aparm->mask & KTIME_DAY) {
337             strcat(astring, " ");
338             strcat(astring, day[aparm->day]);
339         }
340         if (aparm->mask & KTIME_HOUR) {
341             if (aparm->hour > 12)
342                 sprintf(tempString, " %d", aparm->hour - 12);
343             else if (aparm->hour == 0)
344                 strcpy(tempString, " 12");
345             else
346                 sprintf(tempString, " %d", aparm->hour);
347             strcat(astring, tempString);
348         }
349         if (aparm->mask & KTIME_MIN) {
350             sprintf(tempString, ":%02d", aparm->min);
351             strcat(astring, tempString);
352         }
353         if ((aparm->mask & KTIME_SEC) && aparm->sec != 0) {
354             sprintf(tempString, ":%02d", aparm->sec);
355             strcat(astring, tempString);
356         }
357         if (aparm->mask & KTIME_HOUR) {
358             if (aparm->hour >= 12)
359                 strcat(astring, " pm");
360             else
361                 strcat(astring, " am");
362         }
363     }
364     return 0;
365 }
366
367 /* get next time that matches ktime structure after time afrom */
368 afs_int32
369 ktime_next(struct ktime * aktime, afs_int32 afrom)
370 {
371     /* try by guessing from now */
372     struct tm *tsp;
373     time_t start;               /* time to start looking */
374     time_t probe;               /* a placeholder to use for advancing day to day */
375     time_t time_next;           /* actual UTC time of probe, with time of day set */
376     afs_int32 tmask;
377     struct ktime_date tdate;
378
379     start = afrom + time(0);    /* time to start search */
380     tmask = aktime->mask;
381
382     /* handle some special cases */
383     if (tmask & KTIME_NEVER)
384         return KTIME_NEVERTIME;
385     if (tmask & KTIME_NOW)
386         return KTIME_NOWTIME;
387
388     /* Use probe to fill in members of *tsp. Add 23 hours each iteration until
389      * time_next is correct. Only add 23 hrs to avoid skipping spring
390      * daylight savings time day */
391     for (probe = start;; probe += (23 * 3600)) {
392         tsp = localtime(&probe);        /* find out what UTC time "probe" is */
393
394         tdate.year = tsp->tm_year;
395         tdate.month = tsp->tm_mon + 1;
396         tdate.day = tsp->tm_mday;
397         tdate.mask =
398             KTIMEDATE_YEAR | KTIMEDATE_MONTH | KTIMEDATE_DAY | KTIMEDATE_HOUR
399             | KTIMEDATE_MIN | KTIMEDATE_SEC;
400         tdate.hour = aktime->hour;      /* edit in our changes */
401         tdate.min = aktime->min;
402         tdate.sec = aktime->sec;
403         time_next = ktime_InterpretDate(&tdate);        /* Convert back to UTC time */
404         if (time_next < start)
405             continue;           /* "probe" time is already past */
406         if ((tmask & KTIME_DAY) == 0)   /* don't care about day, we're done */
407             break;
408         tsp = localtime(&time_next);
409         if (tsp->tm_wday == aktime->day)
410             break;              /* day matches, we're done */
411     }
412     return time_next;
413 }
414
415
416 /* compare date in both formats, and return as in strcmp */
417 #ifdef undef
418 static int
419 KTimeCmp(struct ktime *aktime, struct tm *atm)
420 {
421     afs_int32 tmask;
422
423     /* don't compare day of the week, since we can't tell the
424      * order in a cyclical set.  Caller must check for equality, if
425      * she cares */
426     tmask = aktime->mask;
427     if (tmask & KTIME_HOUR) {
428         if (aktime->hour > atm->tm_hour)
429             return 1;
430         if (aktime->hour < atm->tm_hour)
431             return -1;
432     }
433     if (tmask & KTIME_MIN) {
434         if (aktime->min > atm->tm_min)
435             return 1;
436         if (aktime->min < atm->tm_min)
437             return -1;
438     }
439     if (tmask & KTIME_SEC) {
440         if (aktime->sec > atm->tm_sec)
441             return 1;
442         if (aktime->sec < atm->tm_sec)
443             return -1;
444     }
445     return 0;
446 }
447 #endif
448
449 /* compare date in both formats, and return as in strcmp */
450 static int
451 KDateCmp(struct ktime_date *akdate, struct tm *atm)
452 {
453     if (akdate->year > atm->tm_year)
454         return 1;
455     if (akdate->year < atm->tm_year)
456         return -1;
457     if (akdate->month > atm->tm_mon)
458         return 1;
459     if (akdate->month < atm->tm_mon)
460         return -1;
461     if (akdate->day > atm->tm_mday)
462         return 1;
463     if (akdate->day < atm->tm_mday)
464         return -1;
465     if (akdate->mask & KTIMEDATE_HOUR) {
466         if (akdate->hour > atm->tm_hour)
467             return 1;
468         if (akdate->hour < atm->tm_hour)
469             return -1;
470     }
471     if (akdate->mask & KTIMEDATE_MIN) {
472         if (akdate->min > atm->tm_min)
473             return 1;
474         if (akdate->min < atm->tm_min)
475             return -1;
476     }
477     if (akdate->mask & KTIMEDATE_SEC) {
478         if (akdate->sec > atm->tm_sec)
479             return 1;
480         if (akdate->sec < atm->tm_sec)
481             return -1;
482     }
483     return 0;
484 }
485
486 /* ktime_ParseDate
487  *      parse date string into ktime_date structure
488  * entry:
489  *      adate - string to be parsed
490  *      akdate - ptr to ktime_date for result
491  * exit:
492  *      0 - akdate contains converted date
493  *      -1 - parsing failure
494  */
495
496 static afs_int32
497 ktime_ParseDate(char *adate, struct ktime_date *akdate)
498 {
499     int code;
500     afs_int32 month, day2, year, hour, min, sec;
501     char never[7];
502     char c[2];
503
504     lcstring(never, adate, sizeof(never));
505     if (strcmp(never, "never") == 0)
506         akdate->mask = KTIMEDATE_NEVER;
507     else if (strcmp(never, "now") == 0)
508         akdate->mask = KTIMEDATE_NOW;
509     else
510         akdate->mask = 0;
511     if (akdate->mask)
512         return 0;
513
514     /* Old ambiguous mm/dd/yy hh:mm:ss format */
515
516     code =
517         sscanf(adate, "%d / %d / %d %d : %d : %d%1s", &month, &day2, &year,
518                &hour, &min, &sec, &c[0]);
519     if (code != 6) {
520         sec = 0;
521         code =
522             sscanf(adate, "%d / %d / %d %d : %d%1s", &month, &day2, &year,
523                    &hour, &min, &c[0]);
524         if (code != 5) {
525             hour = min = 0;
526             code =
527                 sscanf(adate, "%d / %d / %d%1s", &month, &day2, &year, &c[0]);
528             if (code != 3) {
529                 code = -1;
530             }
531         }
532     }
533
534     /* New ISO 8601 (subset) format */
535
536     if (code < 0) {
537         hour = min = sec = 0;
538         code =
539             sscanf(adate, "%d-%d-%d %d:%d:%d%1s", &year, &month, &day2,
540                    &hour, &min, &sec, c);
541         if (code != 3 && code != 5 && code != 6)
542             code = -1;
543     }
544
545     if (code < 0)
546         return code;
547
548     if ((year < 0) || (month < 1) || (month > 12) || (day2 < 1) || (day2 > 31) ||       /* more or less */
549         (hour < 0) || (hour > 23) || (min < 0) || (min > 59) || (sec < 0)
550         || (sec > 59))
551         return -2;
552
553     if (year < 69)
554         year += 100;            /* make 1/1/1 => Jan 1, 2001 */
555     else if (year >= 1900)
556         year -= 1900;           /* allow 1/1/2001 to work */
557     else if (year > 99)
558         return -2;              /* only allow 2 or 4 digit years */
559
560     akdate->mask =
561         KTIMEDATE_YEAR | KTIMEDATE_MONTH | KTIMEDATE_DAY | KTIMEDATE_HOUR |
562         KTIMEDATE_MIN | KTIMEDATE_SEC;
563
564     akdate->year = year;
565     akdate->month = month;
566     akdate->day = day2;
567     akdate->hour = hour;
568     akdate->min = min;
569     akdate->sec = sec;
570
571     /* done successfully */
572     return 0;
573 }
574
575 /* ktime_DateToInt32
576  *      Converts a ktime date string into an afs_int32
577  * entry:
578  *      adate - ktime date string
579  *      aint32 - ptr to afs_int32
580  * exit:
581  *      0 - aint32 contains converted date.
582  */
583
584 afs_int32
585 ktime_DateToInt32(char *adate, afs_int32 * aint32)
586 {
587     struct ktime_date tdate;
588     afs_int32 code;
589     unsigned long l;
590     char c[2];
591
592     if (sscanf(adate, "%lu%1s", &l, c) == 1 && l > 200000000)
593         *aint32 = l;
594     else {
595         /* parse the date into a ktime_date structure */
596         code = ktime_ParseDate(adate, &tdate);
597         if (code)
598             return code;                /* failed to parse */
599         *aint32 = ktime_InterpretDate(&tdate);  /* interpret as seconds since 1970 */
600     }
601
602     return 0;
603 }
604
605 /* get useful error message to print about date input format */
606 char *
607 ktime_GetDateUsage(void)
608 {
609     return "date format is '(yyyy-mm-dd | mm/dd/yy) [hh:mm]', using a 24 hour clock";
610 }
611
612
613 /* ktime_InterpretDate
614  *      Converts ktime_date to an afs_int32
615  * entry:
616  *      akdate - date to be converted/interpreted
617  * exit:
618  *      returns KTIMEDATE_NEVERDATE - if never flag was set, or
619  *      date converted to afs_int32.
620  */
621
622 afs_int32
623 ktime_InterpretDate(struct ktime_date * akdate)
624 {
625     afs_uint32 tresult;
626     afs_uint32 tbit;
627     time_t temp;
628     struct tm *tsp;
629
630     if (akdate->mask & KTIMEDATE_NOW)
631         return time(0);
632     if (akdate->mask & KTIMEDATE_NEVER)
633         return KTIMEDATE_NEVERDATE;
634
635     tbit = 1 << 30;             /* start off at max signed result */
636     tresult = 0;                /* result to return */
637     while (tbit > 0) {
638         temp = tresult + tbit;  /* see if adding this bit keeps us < akdate */
639         tsp = localtime(&temp);
640         tsp->tm_mon++;
641 #ifdef notdef
642         if (tsp->tm_mon == 0) {
643             tsp->tm_mon = 12;
644             tsp->tm_year--;
645         }
646 #endif
647         if (KDateCmp(akdate, tsp) >= 0) {
648             /* if temp still represents earlier than date than we're searching
649              * for, add in bit as increment, otherwise use old value and look
650              * for smaller increment */
651             tresult = temp;
652         }
653         tbit = tbit >> 1;       /* try next bit */
654     }
655
656     return tresult;
657 }