Annotation of src/usr.bin/at/parsetime.c, Revision 1.9
1.9 ! millert 1: /* $OpenBSD: parsetime.c,v 1.8 1999/03/21 04:04:42 alex Exp $ */
1.1 deraadt 2: /* $NetBSD: parsetime.c,v 1.3 1995/03/25 18:13:36 glass Exp $ */
3:
4: /*
5: * parsetime.c - parse time for at(1)
1.4 millert 6: * Copyright (C) 1993, 1994 Thomas Koenig
1.1 deraadt 7: *
8: * modifications for english-language times
9: * Copyright (C) 1993 David Parsons
10: *
11: * Redistribution and use in source and binary forms, with or without
12: * modification, are permitted provided that the following conditions
13: * are met:
14: * 1. Redistributions of source code must retain the above copyright
15: * notice, this list of conditions and the following disclaimer.
16: * 2. The name of the author(s) may not be used to endorse or promote
17: * products derived from this software without specific prior written
18: * permission.
19: *
20: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
21: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
1.4 millert 23: * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
1.1 deraadt 24: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25: * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27: * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29: * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30: *
31: * at [NOW] PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS
32: * /NUMBER [DOT NUMBER] [AM|PM]\ /[MONTH NUMBER [NUMBER]] \
33: * |NOON | |[TOMORROW] |
1.4 millert 34: * |MIDNIGHT | |[DAY OF WEEK] |
35: * \TEATIME / |NUMBER [SLASH NUMBER [SLASH NUMBER]]|
36: * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/
1.1 deraadt 37: */
38:
39: /* System Headers */
40:
41: #include <sys/types.h>
42: #include <errno.h>
43: #include <stdio.h>
44: #include <stdlib.h>
45: #include <string.h>
46: #include <time.h>
1.8 alex 47: #include <tzfile.h>
1.1 deraadt 48: #include <unistd.h>
49: #include <ctype.h>
1.6 mickey 50: #include <err.h>
1.1 deraadt 51:
52: /* Local headers */
53:
54: #include "at.h"
55: #include "panic.h"
56:
57:
58: /* Structures and unions */
59:
60: enum { /* symbols */
1.4 millert 61: MIDNIGHT, NOON, TEATIME,
62: PM, AM, TOMORROW, TODAY, NOW,
63: MINUTES, HOURS, DAYS, WEEKS,
64: NUMBER, PLUS, DOT, SLASH, ID, JUNK,
65: JAN, FEB, MAR, APR, MAY, JUN,
66: JUL, AUG, SEP, OCT, NOV, DEC,
67: SUN, MON, TUE, WED, THU, FRI, SAT
1.1 deraadt 68: };
69:
70: /*
71: * parse translation table - table driven parsers can be your FRIEND!
72: */
73: struct {
1.4 millert 74: char *name; /* token name */
75: int value; /* token id */
76: int plural; /* is this plural? */
1.1 deraadt 77: } Specials[] = {
1.4 millert 78: { "midnight", MIDNIGHT, 0 }, /* 00:00:00 of today or tomorrow */
79: { "noon", NOON, 0 }, /* 12:00:00 of today or tomorrow */
80: { "teatime", TEATIME, 0 }, /* 16:00:00 of today or tomorrow */
81: { "am", AM, 0 }, /* morning times for 0-12 clock */
82: { "pm", PM, 0 }, /* evening times for 0-12 clock */
83: { "tomorrow", TOMORROW, 0 }, /* execute 24 hours from time */
84: { "today", TODAY, 0 }, /* execute today - don't advance time */
85: { "now", NOW, 0 }, /* opt prefix for PLUS */
86:
87: { "minute", MINUTES, 0 }, /* minutes multiplier */
88: { "min", MINUTES, 0 },
89: { "m", MINUTES, 0 },
90: { "minutes", MINUTES, 1 }, /* (pluralized) */
91: { "hour", HOURS, 0 }, /* hours ... */
92: { "hr", HOURS, 0 }, /* abbreviated */
93: { "h", HOURS, 0 },
94: { "hours", HOURS, 1 }, /* (pluralized) */
95: { "day", DAYS, 0 }, /* days ... */
96: { "d", DAYS, 0 },
97: { "days", DAYS, 1 }, /* (pluralized) */
98: { "week", WEEKS, 0 }, /* week ... */
99: { "w", WEEKS, 0 },
100: { "weeks", WEEKS, 1 }, /* (pluralized) */
101: { "jan", JAN, 0 },
102: { "feb", FEB, 0 },
103: { "mar", MAR, 0 },
104: { "apr", APR, 0 },
105: { "may", MAY, 0 },
106: { "jun", JUN, 0 },
107: { "jul", JUL, 0 },
108: { "aug", AUG, 0 },
109: { "sep", SEP, 0 },
110: { "oct", OCT, 0 },
111: { "nov", NOV, 0 },
112: { "dec", DEC, 0 },
1.7 deraadt 113: { "january", JAN,0 },
114: { "february", FEB,0 },
115: { "march", MAR,0 },
116: { "april", APR,0 },
117: { "may", MAY,0 },
118: { "june", JUN,0 },
119: { "july", JUL,0 },
120: { "august", AUG,0 },
121: { "september", SEP,0 },
122: { "october", OCT,0 },
123: { "november", NOV,0 },
124: { "december", DEC,0 },
1.4 millert 125: { "sunday", SUN, 0 },
126: { "sun", SUN, 0 },
127: { "monday", MON, 0 },
128: { "mon", MON, 0 },
129: { "tuesday", TUE, 0 },
130: { "tue", TUE, 0 },
131: { "wednesday", WED, 0 },
132: { "wed", WED, 0 },
133: { "thursday", THU, 0 },
134: { "thu", THU, 0 },
135: { "friday", FRI, 0 },
136: { "fri", FRI, 0 },
137: { "saturday", SAT, 0 },
138: { "sat", SAT, 0 },
139: };
1.1 deraadt 140:
141: /* File scope variables */
142:
143: static char **scp; /* scanner - pointer at arglist */
144: static char scc; /* scanner - count of remaining arguments */
145: static char *sct; /* scanner - next char pointer in current argument */
146: static int need; /* scanner - need to advance to next argument */
147:
148: static char *sc_token; /* scanner - token buffer */
149: static size_t sc_len; /* scanner - lenght of token buffer */
150: static int sc_tokid; /* scanner - token id */
1.4 millert 151: static int sc_tokplur; /* scanner - is token plural? */
1.1 deraadt 152:
153: #ifndef lint
1.9 ! millert 154: static char rcsid[] = "$OpenBSD: parsetime.c,v 1.8 1999/03/21 04:04:42 alex Exp $";
1.1 deraadt 155: #endif
156:
157: /* Local functions */
158:
159: /*
160: * parse a token, checking if it's something special to us
161: */
162: static int
163: parse_token(arg)
164: char *arg;
165: {
1.4 millert 166: int i;
1.1 deraadt 167:
1.4 millert 168: for (i=0; i < sizeof(Specials) / sizeof(Specials[0]); i++) {
169: if (strcasecmp(Specials[i].name, arg) == 0) {
170: sc_tokplur = Specials[i].plural;
171: return (sc_tokid = Specials[i].value);
172: }
1.1 deraadt 173: }
174:
1.4 millert 175: /* not special - must be some random id */
176: return (ID);
1.1 deraadt 177: } /* parse_token */
178:
179:
180: /*
181: * init_scanner() sets up the scanner to eat arguments
182: */
183: static void
184: init_scanner(argc, argv)
185: int argc;
186: char **argv;
187: {
1.4 millert 188: scp = argv;
189: scc = argc;
190: need = 1;
191: sc_len = 1;
192: while (argc-- > 0)
193: sc_len += strlen(*argv++);
194:
195: if ((sc_token = (char *) malloc(sc_len)) == NULL)
196: panic("Insufficient virtual memory");
1.1 deraadt 197: } /* init_scanner */
198:
199: /*
200: * token() fetches a token from the input stream
201: */
202: static int
203: token()
204: {
1.4 millert 205: int idx;
1.1 deraadt 206:
1.4 millert 207: while (1) {
208: (void)memset(sc_token, 0, sc_len);
209: sc_tokid = EOF;
210: sc_tokplur = 0;
211: idx = 0;
1.1 deraadt 212:
1.4 millert 213: /*
214: * if we need to read another argument, walk along the
215: * argument list; when we fall off the arglist, we'll
216: * just return EOF forever
217: */
218: if (need) {
219: if (scc < 1)
220: return (sc_tokid);
221: sct = *scp;
222: scp++;
223: scc--;
224: need = 0;
225: }
226: /*
227: * eat whitespace now - if we walk off the end of the argument,
228: * we'll continue, which puts us up at the top of the while loop
229: * to fetch the next argument in
230: */
231: while (isspace(*sct))
232: ++sct;
233: if (!*sct) {
234: need = 1;
235: continue;
236: }
1.1 deraadt 237:
1.4 millert 238: /*
239: * preserve the first character of the new token
240: */
241: sc_token[0] = *sct++;
1.1 deraadt 242:
1.4 millert 243: /*
244: * then see what it is
245: */
246: if (isdigit(sc_token[0])) {
247: while (isdigit(*sct))
248: sc_token[++idx] = *sct++;
249: sc_token[++idx] = 0;
250: return ((sc_tokid = NUMBER));
251: } else if (isalpha(sc_token[0])) {
252: while (isalpha(*sct))
253: sc_token[++idx] = *sct++;
254: sc_token[++idx] = 0;
255: return (parse_token(sc_token));
256: }
257: else if (sc_token[0] == ':' || sc_token[0] == '.')
258: return ((sc_tokid = DOT));
259: else if (sc_token[0] == '+')
260: return ((sc_tokid = PLUS));
261: else if (sc_token[0] == '/')
262: return ((sc_tokid = SLASH));
263: else
264: return ((sc_tokid = JUNK));
265: } /* while (1) */
1.1 deraadt 266: } /* token */
267:
268:
269: /*
270: * plonk() gives an appropriate error message if a token is incorrect
271: */
272: static void
273: plonk(tok)
274: int tok;
275: {
1.4 millert 276: panic((tok == EOF) ? "incomplete time" : "garbled time");
1.1 deraadt 277: } /* plonk */
278:
279:
280: /*
281: * expect() gets a token and dies most horribly if it's not the token we want
282: */
283: static void
284: expect(desired)
285: int desired;
286: {
1.4 millert 287: if (token() != desired)
288: plonk(sc_tokid); /* and we die here... */
1.1 deraadt 289: } /* expect */
290:
291:
292: /*
293: * dateadd() adds a number of minutes to a date. It is extraordinarily
294: * stupid regarding day-of-month overflow, and will most likely not
295: * work properly
296: */
297: static void
298: dateadd(minutes, tm)
299: int minutes;
300: struct tm *tm;
301: {
1.4 millert 302: /* increment days */
1.1 deraadt 303:
1.4 millert 304: while (minutes > 24*60) {
305: minutes -= 24*60;
306: tm->tm_mday++;
1.1 deraadt 307: }
308:
1.4 millert 309: /* increment hours */
310: while (minutes > 60) {
311: minutes -= 60;
312: tm->tm_hour++;
313: if (tm->tm_hour > 23) {
314: tm->tm_mday++;
315: tm->tm_hour = 0;
316: }
317: }
318:
319: /* increment minutes */
320: tm->tm_min += minutes;
1.1 deraadt 321:
1.4 millert 322: if (tm->tm_min > 59) {
323: tm->tm_hour++;
324: tm->tm_min -= 60;
325:
326: if (tm->tm_hour > 23) {
327: tm->tm_mday++;
328: tm->tm_hour = 0;
329: }
1.1 deraadt 330: }
331: } /* dateadd */
332:
333:
334: /*
335: * plus() parses a now + time
336: *
337: * at [NOW] PLUS NUMBER [MINUTES|HOURS|DAYS|WEEKS]
338: *
339: */
340: static void
341: plus(tm)
342: struct tm *tm;
343: {
1.4 millert 344: int delay;
345: int expectplur;
346:
347: expect(NUMBER);
1.1 deraadt 348:
1.4 millert 349: delay = atoi(sc_token);
350: expectplur = (delay != 1) ? 1 : 0;
1.1 deraadt 351:
1.4 millert 352: switch (token()) {
353: case WEEKS:
354: delay *= 7;
355: case DAYS:
356: delay *= 24;
357: case HOURS:
358: delay *= 60;
359: case MINUTES:
360: if (expectplur != sc_tokplur)
1.6 mickey 361: warnx("pluralization is wrong");
1.4 millert 362: dateadd(delay, tm);
363: return;
364: }
1.1 deraadt 365:
1.4 millert 366: plonk(sc_tokid);
1.1 deraadt 367: } /* plus */
368:
369:
370: /*
371: * tod() computes the time of day
372: * [NUMBER [DOT NUMBER] [AM|PM]]
373: */
374: static void
375: tod(tm)
376: struct tm *tm;
377: {
1.4 millert 378: int hour, minute = 0;
379: size_t tlen;
380:
381: hour = atoi(sc_token);
382: tlen = strlen(sc_token);
383:
384: /*
385: * first pick out the time of day - if it's 4 digits, we assume
386: * a HHMM time, otherwise it's HH DOT MM time
387: */
388: if (token() == DOT) {
389: expect(NUMBER);
390: minute = atoi(sc_token);
391: if (minute > 59)
392: panic("garbled time");
393: token();
394: } else if (tlen == 4) {
395: minute = hour % 100;
396: if (minute > 59)
397: panic("garbled time");
398: hour = hour / 100;
399: }
1.1 deraadt 400:
1.4 millert 401: /*
402: * check if an AM or PM specifier was given
403: */
404: if (sc_tokid == AM || sc_tokid == PM) {
405: if (hour > 12)
406: panic("garbled time");
407:
408: if (sc_tokid == PM) {
409: if (hour != 12) /* 12:xx PM is 12:xx, not 24:xx */
410: hour += 12;
411: } else {
412: if (hour == 12) /* 12:xx AM is 00:xx, not 12:xx */
413: hour = 0;
414: }
415: token();
416: } else if (hour > 23)
417: panic("garbled time");
1.1 deraadt 418:
1.4 millert 419: /*
420: * if we specify an absolute time, we don't want to bump the day even
421: * if we've gone past that time - but if we're specifying a time plus
422: * a relative offset, it's okay to bump things
423: */
424: if ((sc_tokid == EOF || sc_tokid == PLUS) && tm->tm_hour > hour) {
425: tm->tm_mday++;
426: tm->tm_wday++;
427: }
1.1 deraadt 428:
1.4 millert 429: tm->tm_hour = hour;
430: tm->tm_min = minute;
431: if (tm->tm_hour == 24) {
432: tm->tm_hour = 0;
433: tm->tm_mday++;
434: }
1.1 deraadt 435: } /* tod */
436:
437:
438: /*
439: * assign_date() assigns a date, wrapping to next year if needed
440: */
441: static void
442: assign_date(tm, mday, mon, year)
443: struct tm *tm;
1.4 millert 444: int mday, mon, year;
1.1 deraadt 445: {
1.9 ! millert 446:
! 447: /*
! 448: * Convert year into tm_year format (year - 1900).
! 449: * We may be given the year in 2 digit, 4 digit, or tm_year format.
! 450: */
! 451: if (year != -1) {
! 452: if (year >= TM_YEAR_BASE)
! 453: year -= TM_YEAR_BASE; /* convert from 4 digit year */
! 454: else if (year < 100) {
! 455: /* Convert to tm_year assuming current century */
! 456: year += (tm->tm_year / 100) * 100;
! 457:
! 458: if (year == tm->tm_year - 1)
! 459: year++; /* Common off by one error */
! 460: else if (year < tm->tm_year)
! 461: year += 100; /* must be in next century */
! 462: }
1.4 millert 463: }
464:
465: if (year < 0 &&
466: (tm->tm_mon > mon ||(tm->tm_mon == mon && tm->tm_mday > mday)))
467: year = tm->tm_year + 1;
1.1 deraadt 468:
1.4 millert 469: tm->tm_mday = mday;
470: tm->tm_mon = mon;
1.1 deraadt 471:
1.4 millert 472: if (year >= 0)
473: tm->tm_year = year;
1.1 deraadt 474: } /* assign_date */
475:
476:
477: /*
478: * month() picks apart a month specification
479: *
480: * /[<month> NUMBER [NUMBER]] \
481: * |[TOMORROW] |
1.4 millert 482: * |[DAY OF WEEK] |
1.1 deraadt 483: * |NUMBER [SLASH NUMBER [SLASH NUMBER]]|
484: * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/
485: */
486: static void
487: month(tm)
488: struct tm *tm;
489: {
1.4 millert 490: int year = (-1);
491: int mday, wday, mon;
492: size_t tlen;
493:
494: switch (sc_tokid) {
495: case PLUS:
496: plus(tm);
497: break;
498:
499: case TOMORROW:
500: /* do something tomorrow */
501: tm->tm_mday++;
502: tm->tm_wday++;
503: case TODAY:
504: /* force ourselves to stay in today - no further processing */
1.1 deraadt 505: token();
1.4 millert 506: break;
1.1 deraadt 507:
1.4 millert 508: case JAN: case FEB: case MAR: case APR: case MAY: case JUN:
509: case JUL: case AUG: case SEP: case OCT: case NOV: case DEC:
510: /*
511: * do month mday [year]
512: */
513: mon = sc_tokid - JAN;
1.1 deraadt 514: expect(NUMBER);
1.4 millert 515: mday = atoi(sc_token);
516: if (token() == NUMBER) {
517: year = atoi(sc_token);
518: token();
1.1 deraadt 519: }
1.4 millert 520: assign_date(tm, mday, mon, year);
521: break;
522:
523: case SUN: case MON: case TUE:
524: case WED: case THU: case FRI:
525: case SAT:
526: /* do a particular day of the week */
527: wday = sc_tokid - SUN;
528:
529: mday = tm->tm_mday;
530:
531: /* if this day is < today, then roll to next week */
532: if (wday < tm->tm_wday)
533: mday += 7 - (tm->tm_wday - wday);
534: else
535: mday += (wday - tm->tm_wday);
536:
537: tm->tm_wday = wday;
538:
539: assign_date(tm, mday, tm->tm_mon, tm->tm_year);
540: break;
1.1 deraadt 541:
1.4 millert 542: case NUMBER:
1.1 deraadt 543: /*
1.4 millert 544: * get numeric MMDDYY, mm/dd/yy, or dd.mm.yy
1.1 deraadt 545: */
1.4 millert 546: tlen = strlen(sc_token);
547: mon = atoi(sc_token);
548: token();
1.1 deraadt 549:
1.4 millert 550: if (sc_tokid == SLASH || sc_tokid == DOT) {
551: int sep;
1.1 deraadt 552:
1.4 millert 553: sep = sc_tokid;
554: expect(NUMBER);
555: mday = atoi(sc_token);
556: if (token() == sep) {
557: expect(NUMBER);
558: year = atoi(sc_token);
559: token();
560: }
561:
562: /*
563: * flip months and days for european timing
564: */
565: if (sep == DOT) {
566: int x = mday;
567: mday = mon;
568: mon = x;
569: }
570: } else if (tlen == 6 || tlen == 8) {
571: if (tlen == 8) {
1.8 alex 572: year = (mon % 10000) - TM_YEAR_BASE;
1.4 millert 573: mon /= 10000;
574: } else {
575: year = mon % 100;
576: mon /= 100;
577: }
578: mday = mon % 100;
579: mon /= 100;
580: } else
581: panic("garbled time");
582:
583: mon--;
584: if (mon < 0 || mon > 11 || mday < 1 || mday > 31)
585: panic("garbled time");
586:
587: assign_date(tm, mday, mon, year);
588: break;
589: } /* case */
1.1 deraadt 590: } /* month */
591:
592:
593: /* Global functions */
594:
595: time_t
596: parsetime(argc, argv)
597: int argc;
598: char **argv;
599: {
1.4 millert 600: /*
601: * Do the argument parsing, die if necessary, and return the
602: * time the job should be run.
603: */
604: time_t nowtimer, runtimer;
605: struct tm nowtime, runtime;
606: int hr = 0;
607: /* this MUST be initialized to zero for midnight/noon/teatime */
608:
609: nowtimer = time(NULL);
610: nowtime = *localtime(&nowtimer);
611:
612: runtime = nowtime;
613: runtime.tm_sec = 0;
614: runtime.tm_isdst = 0;
615:
616: if (argc <= optind)
617: usage();
618:
619: init_scanner(argc - optind, argv + optind);
620:
621: switch (token()) {
622: case NOW: /* now is optional prefix for PLUS tree */
1.5 millert 623: token();
624: if (sc_tokid == EOF) {
625: runtime = nowtime;
626: break;
627: }
628: else if (sc_tokid != PLUS)
629: plonk(sc_tokid);
1.4 millert 630: case PLUS:
631: plus(&runtime);
632: break;
633:
634: case NUMBER:
635: tod(&runtime);
636: month(&runtime);
637: break;
638:
639: /*
640: * evil coding for TEATIME|NOON|MIDNIGHT - we've initialised
641: * hr to zero up above, then fall into this case in such a
642: * way so we add +12 +4 hours to it for teatime, +12 hours
643: * to it for noon, and nothing at all for midnight, then
644: * set our runtime to that hour before leaping into the
645: * month scanner
646: */
647: case TEATIME:
648: hr += 4;
649: case NOON:
650: hr += 12;
651: case MIDNIGHT:
652: if (runtime.tm_hour >= hr) {
653: runtime.tm_mday++;
654: runtime.tm_wday++;
655: }
656: runtime.tm_hour = hr;
657: runtime.tm_min = 0;
658: token();
659: /* fall through to month setting */
660: default:
661: month(&runtime);
662: break;
663: } /* ugly case statement */
664: expect(EOF);
665:
666: /*
667: * adjust for daylight savings time
668: */
669: runtime.tm_isdst = -1;
1.1 deraadt 670: runtimer = mktime(&runtime);
1.4 millert 671: if (runtime.tm_isdst > 0) {
672: runtimer -= 3600;
673: runtimer = mktime(&runtime);
674: }
1.1 deraadt 675:
1.4 millert 676: if (runtimer < 0)
677: panic("garbled time");
1.1 deraadt 678:
1.4 millert 679: if (nowtimer > runtimer)
680: panic("Trying to travel back in time");
1.1 deraadt 681:
1.4 millert 682: return (runtimer);
1.1 deraadt 683: } /* parsetime */