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