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