backport to buster
[hcoop/debian/openafs.git] / src / util / ktime.c
CommitLineData
805e021f
CE
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
24struct token {
25 struct token *next;
26 char *key;
27};
28
29static 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 */
40static void
41LocalFreeTokens(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
52static int
53space(int x)
54{
55 if (x == 0 || x == ' ' || x == '\t' || x == '\n')
56 return 1;
57 else
58 return 0;
59}
60
61static int
62LocalParseLine(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 */
112static 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
146char *
147ktime_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
171static int
172ParseTime(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
210afs_int32
211ktime_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 */
235int
236ktime_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 }
310out:
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 */
323int
324ktime_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 */
368afs_int32
369ktime_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 0x7fffffff;
385 if (tmask & KTIME_NOW)
386 return 0;
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
418static int
419KTimeCmp(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 */
450static int
451KDateCmp(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
496static afs_int32
497ktime_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
584afs_int32
585ktime_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 */
606char *
607ktime_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
622afs_int32
623ktime_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}