Annotation of src/usr.bin/at/parsetime.c, Revision 1.10
1.10 ! millert 1: /* $OpenBSD: parsetime.c,v 1.9 2000/01/05 08:06:25 millert 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.10 ! millert 154: static const char rcsid[] = "$OpenBSD: parsetime.c,v 1.9 2000/01/05 08:06:25 millert 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
1.10 ! millert 163: parse_token(char *arg)
1.1 deraadt 164: {
1.4 millert 165: int i;
1.1 deraadt 166:
1.4 millert 167: for (i=0; i < sizeof(Specials) / sizeof(Specials[0]); i++) {
168: if (strcasecmp(Specials[i].name, arg) == 0) {
169: sc_tokplur = Specials[i].plural;
170: return (sc_tokid = Specials[i].value);
171: }
1.1 deraadt 172: }
173:
1.4 millert 174: /* not special - must be some random id */
175: return (ID);
1.1 deraadt 176: } /* parse_token */
177:
178:
179: /*
180: * init_scanner() sets up the scanner to eat arguments
181: */
182: static void
1.10 ! millert 183: init_scanner(int argc, char **argv)
1.1 deraadt 184: {
1.4 millert 185: scp = argv;
186: scc = argc;
187: need = 1;
188: sc_len = 1;
189: while (argc-- > 0)
190: sc_len += strlen(*argv++);
191:
192: if ((sc_token = (char *) malloc(sc_len)) == NULL)
193: panic("Insufficient virtual memory");
1.1 deraadt 194: } /* init_scanner */
195:
196: /*
197: * token() fetches a token from the input stream
198: */
199: static int
1.10 ! millert 200: token(void)
1.1 deraadt 201: {
1.4 millert 202: int idx;
1.1 deraadt 203:
1.10 ! millert 204: for (;;) {
1.4 millert 205: (void)memset(sc_token, 0, sc_len);
206: sc_tokid = EOF;
207: sc_tokplur = 0;
208: idx = 0;
1.1 deraadt 209:
1.4 millert 210: /*
211: * if we need to read another argument, walk along the
212: * argument list; when we fall off the arglist, we'll
213: * just return EOF forever
214: */
215: if (need) {
216: if (scc < 1)
217: return (sc_tokid);
218: sct = *scp;
219: scp++;
220: scc--;
221: need = 0;
222: }
223: /*
224: * eat whitespace now - if we walk off the end of the argument,
225: * we'll continue, which puts us up at the top of the while loop
226: * to fetch the next argument in
227: */
228: while (isspace(*sct))
229: ++sct;
230: if (!*sct) {
231: need = 1;
232: continue;
233: }
1.1 deraadt 234:
1.4 millert 235: /*
236: * preserve the first character of the new token
237: */
238: sc_token[0] = *sct++;
1.1 deraadt 239:
1.4 millert 240: /*
241: * then see what it is
242: */
243: if (isdigit(sc_token[0])) {
244: while (isdigit(*sct))
245: sc_token[++idx] = *sct++;
246: sc_token[++idx] = 0;
247: return ((sc_tokid = NUMBER));
248: } else if (isalpha(sc_token[0])) {
249: while (isalpha(*sct))
250: sc_token[++idx] = *sct++;
251: sc_token[++idx] = 0;
252: return (parse_token(sc_token));
253: }
254: else if (sc_token[0] == ':' || sc_token[0] == '.')
255: return ((sc_tokid = DOT));
256: else if (sc_token[0] == '+')
257: return ((sc_tokid = PLUS));
258: else if (sc_token[0] == '/')
259: return ((sc_tokid = SLASH));
260: else
261: return ((sc_tokid = JUNK));
262: } /* while (1) */
1.1 deraadt 263: } /* token */
264:
265:
266: /*
267: * plonk() gives an appropriate error message if a token is incorrect
268: */
269: static void
1.10 ! millert 270: plonk(int tok)
1.1 deraadt 271: {
1.4 millert 272: panic((tok == EOF) ? "incomplete time" : "garbled time");
1.1 deraadt 273: } /* plonk */
274:
275:
276: /*
277: * expect() gets a token and dies most horribly if it's not the token we want
278: */
279: static void
1.10 ! millert 280: expect(int desired)
1.1 deraadt 281: {
1.4 millert 282: if (token() != desired)
283: plonk(sc_tokid); /* and we die here... */
1.1 deraadt 284: } /* expect */
285:
286:
287: /*
288: * dateadd() adds a number of minutes to a date. It is extraordinarily
289: * stupid regarding day-of-month overflow, and will most likely not
290: * work properly
291: */
292: static void
1.10 ! millert 293: dateadd(int minutes, struct tm *tm)
1.1 deraadt 294: {
1.4 millert 295: /* increment days */
1.1 deraadt 296:
1.4 millert 297: while (minutes > 24*60) {
298: minutes -= 24*60;
299: tm->tm_mday++;
1.1 deraadt 300: }
301:
1.4 millert 302: /* increment hours */
303: while (minutes > 60) {
304: minutes -= 60;
305: tm->tm_hour++;
306: if (tm->tm_hour > 23) {
307: tm->tm_mday++;
308: tm->tm_hour = 0;
309: }
310: }
311:
312: /* increment minutes */
313: tm->tm_min += minutes;
1.1 deraadt 314:
1.4 millert 315: if (tm->tm_min > 59) {
316: tm->tm_hour++;
317: tm->tm_min -= 60;
318:
319: if (tm->tm_hour > 23) {
320: tm->tm_mday++;
321: tm->tm_hour = 0;
322: }
1.1 deraadt 323: }
324: } /* dateadd */
325:
326:
327: /*
328: * plus() parses a now + time
329: *
330: * at [NOW] PLUS NUMBER [MINUTES|HOURS|DAYS|WEEKS]
331: *
332: */
333: static void
1.10 ! millert 334: plus(struct tm *tm)
1.1 deraadt 335: {
1.4 millert 336: int delay;
337: int expectplur;
338:
339: expect(NUMBER);
1.1 deraadt 340:
1.4 millert 341: delay = atoi(sc_token);
342: expectplur = (delay != 1) ? 1 : 0;
1.1 deraadt 343:
1.4 millert 344: switch (token()) {
345: case WEEKS:
346: delay *= 7;
347: case DAYS:
348: delay *= 24;
349: case HOURS:
350: delay *= 60;
351: case MINUTES:
352: if (expectplur != sc_tokplur)
1.6 mickey 353: warnx("pluralization is wrong");
1.4 millert 354: dateadd(delay, tm);
355: return;
356: }
1.1 deraadt 357:
1.4 millert 358: plonk(sc_tokid);
1.1 deraadt 359: } /* plus */
360:
361:
362: /*
363: * tod() computes the time of day
364: * [NUMBER [DOT NUMBER] [AM|PM]]
365: */
366: static void
1.10 ! millert 367: tod(struct tm *tm)
1.1 deraadt 368: {
1.4 millert 369: int hour, minute = 0;
370: size_t tlen;
371:
372: hour = atoi(sc_token);
373: tlen = strlen(sc_token);
374:
375: /*
376: * first pick out the time of day - if it's 4 digits, we assume
377: * a HHMM time, otherwise it's HH DOT MM time
378: */
379: if (token() == DOT) {
380: expect(NUMBER);
381: minute = atoi(sc_token);
382: if (minute > 59)
383: panic("garbled time");
384: token();
385: } else if (tlen == 4) {
386: minute = hour % 100;
387: if (minute > 59)
388: panic("garbled time");
389: hour = hour / 100;
390: }
1.1 deraadt 391:
1.4 millert 392: /*
393: * check if an AM or PM specifier was given
394: */
395: if (sc_tokid == AM || sc_tokid == PM) {
396: if (hour > 12)
397: panic("garbled time");
398:
399: if (sc_tokid == PM) {
400: if (hour != 12) /* 12:xx PM is 12:xx, not 24:xx */
401: hour += 12;
402: } else {
403: if (hour == 12) /* 12:xx AM is 00:xx, not 12:xx */
404: hour = 0;
405: }
406: token();
407: } else if (hour > 23)
408: panic("garbled time");
1.1 deraadt 409:
1.4 millert 410: /*
411: * if we specify an absolute time, we don't want to bump the day even
412: * if we've gone past that time - but if we're specifying a time plus
413: * a relative offset, it's okay to bump things
414: */
415: if ((sc_tokid == EOF || sc_tokid == PLUS) && tm->tm_hour > hour) {
416: tm->tm_mday++;
417: tm->tm_wday++;
418: }
1.1 deraadt 419:
1.4 millert 420: tm->tm_hour = hour;
421: tm->tm_min = minute;
422: if (tm->tm_hour == 24) {
423: tm->tm_hour = 0;
424: tm->tm_mday++;
425: }
1.1 deraadt 426: } /* tod */
427:
428:
429: /*
430: * assign_date() assigns a date, wrapping to next year if needed
431: */
432: static void
1.10 ! millert 433: assign_date(struct tm *tm, int mday, int mon, int year)
1.1 deraadt 434: {
1.9 millert 435:
436: /*
437: * Convert year into tm_year format (year - 1900).
438: * We may be given the year in 2 digit, 4 digit, or tm_year format.
439: */
440: if (year != -1) {
441: if (year >= TM_YEAR_BASE)
442: year -= TM_YEAR_BASE; /* convert from 4 digit year */
443: else if (year < 100) {
444: /* Convert to tm_year assuming current century */
445: year += (tm->tm_year / 100) * 100;
446:
447: if (year == tm->tm_year - 1)
448: year++; /* Common off by one error */
449: else if (year < tm->tm_year)
450: year += 100; /* must be in next century */
451: }
1.4 millert 452: }
453:
454: if (year < 0 &&
455: (tm->tm_mon > mon ||(tm->tm_mon == mon && tm->tm_mday > mday)))
456: year = tm->tm_year + 1;
1.1 deraadt 457:
1.4 millert 458: tm->tm_mday = mday;
459: tm->tm_mon = mon;
1.1 deraadt 460:
1.4 millert 461: if (year >= 0)
462: tm->tm_year = year;
1.1 deraadt 463: } /* assign_date */
464:
465:
466: /*
467: * month() picks apart a month specification
468: *
469: * /[<month> NUMBER [NUMBER]] \
470: * |[TOMORROW] |
1.4 millert 471: * |[DAY OF WEEK] |
1.1 deraadt 472: * |NUMBER [SLASH NUMBER [SLASH NUMBER]]|
473: * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/
474: */
475: static void
1.10 ! millert 476: month(struct tm *tm)
1.1 deraadt 477: {
1.4 millert 478: int year = (-1);
479: int mday, wday, mon;
480: size_t tlen;
481:
482: switch (sc_tokid) {
483: case PLUS:
484: plus(tm);
485: break;
486:
487: case TOMORROW:
488: /* do something tomorrow */
489: tm->tm_mday++;
490: tm->tm_wday++;
491: case TODAY:
492: /* force ourselves to stay in today - no further processing */
1.1 deraadt 493: token();
1.4 millert 494: break;
1.1 deraadt 495:
1.4 millert 496: case JAN: case FEB: case MAR: case APR: case MAY: case JUN:
497: case JUL: case AUG: case SEP: case OCT: case NOV: case DEC:
498: /*
499: * do month mday [year]
500: */
501: mon = sc_tokid - JAN;
1.1 deraadt 502: expect(NUMBER);
1.4 millert 503: mday = atoi(sc_token);
504: if (token() == NUMBER) {
505: year = atoi(sc_token);
506: token();
1.1 deraadt 507: }
1.4 millert 508: assign_date(tm, mday, mon, year);
509: break;
510:
511: case SUN: case MON: case TUE:
512: case WED: case THU: case FRI:
513: case SAT:
514: /* do a particular day of the week */
515: wday = sc_tokid - SUN;
516:
517: mday = tm->tm_mday;
518:
519: /* if this day is < today, then roll to next week */
520: if (wday < tm->tm_wday)
521: mday += 7 - (tm->tm_wday - wday);
522: else
523: mday += (wday - tm->tm_wday);
524:
525: tm->tm_wday = wday;
526:
527: assign_date(tm, mday, tm->tm_mon, tm->tm_year);
528: break;
1.1 deraadt 529:
1.4 millert 530: case NUMBER:
1.1 deraadt 531: /*
1.4 millert 532: * get numeric MMDDYY, mm/dd/yy, or dd.mm.yy
1.1 deraadt 533: */
1.4 millert 534: tlen = strlen(sc_token);
535: mon = atoi(sc_token);
536: token();
1.1 deraadt 537:
1.4 millert 538: if (sc_tokid == SLASH || sc_tokid == DOT) {
539: int sep;
1.1 deraadt 540:
1.4 millert 541: sep = sc_tokid;
542: expect(NUMBER);
543: mday = atoi(sc_token);
544: if (token() == sep) {
545: expect(NUMBER);
546: year = atoi(sc_token);
547: token();
548: }
549:
550: /*
551: * flip months and days for european timing
552: */
553: if (sep == DOT) {
554: int x = mday;
555: mday = mon;
556: mon = x;
557: }
558: } else if (tlen == 6 || tlen == 8) {
559: if (tlen == 8) {
1.8 alex 560: year = (mon % 10000) - TM_YEAR_BASE;
1.4 millert 561: mon /= 10000;
562: } else {
563: year = mon % 100;
564: mon /= 100;
565: }
566: mday = mon % 100;
567: mon /= 100;
568: } else
569: panic("garbled time");
570:
571: mon--;
572: if (mon < 0 || mon > 11 || mday < 1 || mday > 31)
573: panic("garbled time");
574:
575: assign_date(tm, mday, mon, year);
576: break;
577: } /* case */
1.1 deraadt 578: } /* month */
579:
580:
581: /* Global functions */
582:
583: time_t
1.10 ! millert 584: parsetime(int argc, char **argv)
1.1 deraadt 585: {
1.4 millert 586: /*
587: * Do the argument parsing, die if necessary, and return the
588: * time the job should be run.
589: */
590: time_t nowtimer, runtimer;
591: struct tm nowtime, runtime;
592: int hr = 0;
593: /* this MUST be initialized to zero for midnight/noon/teatime */
594:
595: nowtimer = time(NULL);
596: nowtime = *localtime(&nowtimer);
597:
598: runtime = nowtime;
599: runtime.tm_sec = 0;
600: runtime.tm_isdst = 0;
601:
602: if (argc <= optind)
603: usage();
604:
605: init_scanner(argc - optind, argv + optind);
606:
607: switch (token()) {
608: case NOW: /* now is optional prefix for PLUS tree */
1.5 millert 609: token();
610: if (sc_tokid == EOF) {
611: runtime = nowtime;
612: break;
613: }
614: else if (sc_tokid != PLUS)
615: plonk(sc_tokid);
1.4 millert 616: case PLUS:
617: plus(&runtime);
618: break;
619:
620: case NUMBER:
621: tod(&runtime);
622: month(&runtime);
623: break;
624:
625: /*
626: * evil coding for TEATIME|NOON|MIDNIGHT - we've initialised
627: * hr to zero up above, then fall into this case in such a
628: * way so we add +12 +4 hours to it for teatime, +12 hours
629: * to it for noon, and nothing at all for midnight, then
630: * set our runtime to that hour before leaping into the
631: * month scanner
632: */
633: case TEATIME:
634: hr += 4;
635: case NOON:
636: hr += 12;
637: case MIDNIGHT:
638: if (runtime.tm_hour >= hr) {
639: runtime.tm_mday++;
640: runtime.tm_wday++;
641: }
642: runtime.tm_hour = hr;
643: runtime.tm_min = 0;
644: token();
645: /* fall through to month setting */
646: default:
647: month(&runtime);
648: break;
649: } /* ugly case statement */
650: expect(EOF);
651:
652: /*
653: * adjust for daylight savings time
654: */
655: runtime.tm_isdst = -1;
1.1 deraadt 656: runtimer = mktime(&runtime);
1.4 millert 657: if (runtime.tm_isdst > 0) {
658: runtimer -= 3600;
659: runtimer = mktime(&runtime);
660: }
1.1 deraadt 661:
1.4 millert 662: if (runtimer < 0)
663: panic("garbled time");
1.1 deraadt 664:
1.4 millert 665: if (nowtimer > runtimer)
666: panic("Trying to travel back in time");
1.1 deraadt 667:
1.4 millert 668: return (runtimer);
1.1 deraadt 669: } /* parsetime */