Annotation of src/usr.bin/at/parsetime.c, Revision 1.2
1.1 deraadt 1: /* $NetBSD: parsetime.c,v 1.3 1995/03/25 18:13:36 glass Exp $ */
2:
3: /*
4: * parsetime.c - parse time for at(1)
5: * Copyright (C) 1993 Thomas Koenig
6: *
7: * modifications for english-language times
8: * Copyright (C) 1993 David Parsons
9: * All rights reserved.
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.
23: * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
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] |
34: * |MIDNIGHT | |NUMBER [SLASH NUMBER [SLASH NUMBER]]|
35: * \TEATIME / \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/
36: */
37:
38: /* System Headers */
39:
40: #include <sys/types.h>
41: #include <errno.h>
42: #include <stdio.h>
43: #include <stdlib.h>
44: #include <string.h>
45: #include <time.h>
46: #include <unistd.h>
47: #include <ctype.h>
48:
49: /* Local headers */
50:
51: #include "at.h"
52: #include "panic.h"
53:
54:
55: /* Structures and unions */
56:
57: enum { /* symbols */
58: MIDNIGHT, NOON, TEATIME,
59: PM, AM, TOMORROW, TODAY, NOW,
60: MINUTES, HOURS, DAYS, WEEKS,
61: NUMBER, PLUS, DOT, SLASH, ID, JUNK,
62: JAN, FEB, MAR, APR, MAY, JUN,
63: JUL, AUG, SEP, OCT, NOV, DEC
64: };
65:
66: /*
67: * parse translation table - table driven parsers can be your FRIEND!
68: */
69: struct {
70: char *name; /* token name */
71: int value; /* token id */
72: } Specials[] = {
73: { "midnight", MIDNIGHT }, /* 00:00:00 of today or tomorrow */
74: { "noon", NOON }, /* 12:00:00 of today or tomorrow */
75: { "teatime", TEATIME }, /* 16:00:00 of today or tomorrow */
76: { "am", AM }, /* morning times for 0-12 clock */
77: { "pm", PM }, /* evening times for 0-12 clock */
78: { "tomorrow", TOMORROW }, /* execute 24 hours from time */
79: { "today", TODAY }, /* execute today - don't advance time */
80: { "now", NOW }, /* opt prefix for PLUS */
81:
82: { "minute", MINUTES }, /* minutes multiplier */
83: { "min", MINUTES },
84: { "m", MINUTES },
85: { "minutes", MINUTES }, /* (pluralized) */
86: { "hour", HOURS }, /* hours ... */
87: { "hr", HOURS }, /* abbreviated */
88: { "h", HOURS },
89: { "hours", HOURS }, /* (pluralized) */
90: { "day", DAYS }, /* days ... */
91: { "d", DAYS },
92: { "days", DAYS }, /* (pluralized) */
93: { "week", WEEKS }, /* week ... */
94: { "w", WEEKS },
95: { "weeks", WEEKS }, /* (pluralized) */
96: { "jan", JAN },
97: { "feb", FEB },
98: { "mar", MAR },
99: { "apr", APR },
100: { "may", MAY },
101: { "jun", JUN },
102: { "jul", JUL },
103: { "aug", AUG },
104: { "sep", SEP },
105: { "oct", OCT },
106: { "nov", NOV },
107: { "dec", DEC }
108: } ;
109:
110: /* File scope variables */
111:
112: static char **scp; /* scanner - pointer at arglist */
113: static char scc; /* scanner - count of remaining arguments */
114: static char *sct; /* scanner - next char pointer in current argument */
115: static int need; /* scanner - need to advance to next argument */
116:
117: static char *sc_token; /* scanner - token buffer */
118: static size_t sc_len; /* scanner - lenght of token buffer */
119: static int sc_tokid; /* scanner - token id */
120:
121: #ifndef lint
122: static char rcsid[] = "$NetBSD: parsetime.c,v 1.3 1995/03/25 18:13:36 glass Exp $";
123: #endif
124:
125: /* Local functions */
126:
127: /*
128: * parse a token, checking if it's something special to us
129: */
130: static int
131: parse_token(arg)
132: char *arg;
133: {
134: int i;
135:
136: for (i=0; i<(sizeof Specials/sizeof Specials[0]); i++)
137: if (strcasecmp(Specials[i].name, arg) == 0) {
138: return sc_tokid = Specials[i].value;
139: }
140:
141: /* not special - must be some random id */
142: return ID;
143: } /* parse_token */
144:
145:
146: /*
147: * init_scanner() sets up the scanner to eat arguments
148: */
149: static void
150: init_scanner(argc, argv)
151: int argc;
152: char **argv;
153: {
154: scp = argv;
155: scc = argc;
156: need = 1;
157: sc_len = 1;
158: while (--argc > 0)
159: sc_len += strlen(*++argv);
160:
161: sc_token = (char *) malloc(sc_len);
162: if (sc_token == NULL)
163: panic("Insufficient virtual memory");
164: } /* init_scanner */
165:
166: /*
167: * token() fetches a token from the input stream
168: */
169: static int
170: token()
171: {
172: int idx;
173:
174: while (1) {
175: memset(sc_token, 0, sc_len);
176: sc_tokid = EOF;
177: idx = 0;
178:
179: /*
180: * if we need to read another argument, walk along the argument list;
181: * when we fall off the arglist, we'll just return EOF forever
182: */
183: if (need) {
184: if (scc < 1)
185: return sc_tokid;
186: sct = *scp;
187: scp++;
188: scc--;
189: need = 0;
190: }
191: /*
192: * eat whitespace now - if we walk off the end of the argument,
193: * we'll continue, which puts us up at the top of the while loop
194: * to fetch the next argument in
195: */
196: while (isspace(*sct))
197: ++sct;
198: if (!*sct) {
199: need = 1;
200: continue;
201: }
202:
203: /*
204: * preserve the first character of the new token
205: */
206: sc_token[0] = *sct++;
207:
208: /*
209: * then see what it is
210: */
211: if (isdigit(sc_token[0])) {
212: while (isdigit(*sct))
213: sc_token[++idx] = *sct++;
214: sc_token[++idx] = 0;
215: return sc_tokid = NUMBER;
216: } else if (isalpha(sc_token[0])) {
217: while (isalpha(*sct))
218: sc_token[++idx] = *sct++;
219: sc_token[++idx] = 0;
220: return parse_token(sc_token);
221: }
222: else if (sc_token[0] == ':' || sc_token[0] == '.')
223: return sc_tokid = DOT;
224: else if (sc_token[0] == '+')
225: return sc_tokid = PLUS;
226: else if (sc_token[0] == '/')
227: return sc_tokid = SLASH;
228: else
229: return sc_tokid = JUNK;
230: } /* while (1) */
231: } /* token */
232:
233:
234: /*
235: * plonk() gives an appropriate error message if a token is incorrect
236: */
237: static void
238: plonk(tok)
239: int tok;
240: {
241: panic((tok == EOF) ? "incomplete time"
242: : "garbled time");
243: } /* plonk */
244:
245:
246: /*
247: * expect() gets a token and dies most horribly if it's not the token we want
248: */
249: static void
250: expect(desired)
251: int desired;
252: {
253: if (token() != desired)
254: plonk(sc_tokid); /* and we die here... */
255: } /* expect */
256:
257:
258: /*
259: * dateadd() adds a number of minutes to a date. It is extraordinarily
260: * stupid regarding day-of-month overflow, and will most likely not
261: * work properly
262: */
263: static void
264: dateadd(minutes, tm)
265: int minutes;
266: struct tm *tm;
267: {
268: /* increment days */
269:
270: while (minutes > 24*60) {
271: minutes -= 24*60;
272: tm->tm_mday++;
273: }
274:
275: /* increment hours */
276: while (minutes > 60) {
277: minutes -= 60;
278: tm->tm_hour++;
279: if (tm->tm_hour > 23) {
280: tm->tm_mday++;
281: tm->tm_hour = 0;
282: }
283: }
284:
285: /* increment minutes */
286: tm->tm_min += minutes;
287:
288: if (tm->tm_min > 59) {
289: tm->tm_hour++;
290: tm->tm_min -= 60;
291:
292: if (tm->tm_hour > 23) {
293: tm->tm_mday++;
294: tm->tm_hour = 0;
295: }
296: }
297: } /* dateadd */
298:
299:
300: /*
301: * plus() parses a now + time
302: *
303: * at [NOW] PLUS NUMBER [MINUTES|HOURS|DAYS|WEEKS]
304: *
305: */
306: static void
307: plus(tm)
308: struct tm *tm;
309: {
310: int delay;
311:
312: expect(NUMBER);
313:
314: delay = atoi(sc_token);
315:
316: switch (token()) {
317: case WEEKS:
318: delay *= 7;
319: case DAYS:
320: delay *= 24;
321: case HOURS:
322: delay *= 60;
323: case MINUTES:
324: dateadd(delay, tm);
325: return;
326: }
327: plonk(sc_tokid);
328: } /* plus */
329:
330:
331: /*
332: * tod() computes the time of day
333: * [NUMBER [DOT NUMBER] [AM|PM]]
334: */
335: static void
336: tod(tm)
337: struct tm *tm;
338: {
339: int hour, minute = 0;
340: int tlen;
341:
342: hour = atoi(sc_token);
343: tlen = strlen(sc_token);
344:
345: /*
346: * first pick out the time of day - if it's 4 digits, we assume
347: * a HHMM time, otherwise it's HH DOT MM time
348: */
349: if (token() == DOT) {
350: expect(NUMBER);
351: minute = atoi(sc_token);
352: token();
353: } else if (tlen == 4) {
354: minute = hour%100;
355: hour = hour/100;
356: }
357:
1.2 ! deraadt 358: if (minute > 59)
! 359: panic("garbled time");
! 360:
1.1 deraadt 361: /*
362: * check if an AM or PM specifier was given
363: */
364: if (sc_tokid == AM || sc_tokid == PM) {
365: if (hour > 12)
366: panic("garbled time");
1.2 ! deraadt 367: else if (hour == 12)
! 368: hour = 0;
1.1 deraadt 369:
370: if (sc_tokid == PM)
371: hour += 12;
372: token();
373: } else if (hour > 23)
374: panic("garbled time");
375:
376: /*
377: * if we specify an absolute time, we don't want to bump the day even
378: * if we've gone past that time - but if we're specifying a time plus
379: * a relative offset, it's okay to bump things
380: */
381: if ((sc_tokid == EOF || sc_tokid == PLUS) && tm->tm_hour > hour)
382: tm->tm_mday++;
383:
384: tm->tm_hour = hour;
385: tm->tm_min = minute;
386: } /* tod */
387:
388:
389: /*
390: * assign_date() assigns a date, wrapping to next year if needed
391: */
392: static void
393: assign_date(tm, mday, mon, year)
394: struct tm *tm;
395: long mday, mon, year;
396: {
397: if (year > 99) {
398: if (year > 1899)
399: year -= 1900;
400: else
401: panic("garbled time");
402: }
403:
404: if (year < 0 &&
405: (tm->tm_mon > mon ||(tm->tm_mon == mon && tm->tm_mday > mday)))
406: year = tm->tm_year + 1;
407:
408: tm->tm_mday = mday;
409: tm->tm_mon = mon;
410:
411: if (year >= 0)
412: tm->tm_year = year;
413: } /* assign_date */
414:
415:
416: /*
417: * month() picks apart a month specification
418: *
419: * /[<month> NUMBER [NUMBER]] \
420: * |[TOMORROW] |
421: * |NUMBER [SLASH NUMBER [SLASH NUMBER]]|
422: * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/
423: */
424: static void
425: month(tm)
426: struct tm *tm;
427: {
428: long year= (-1);
429: long mday, mon;
430: int tlen;
431:
432: switch (sc_tokid) {
433: case PLUS:
434: plus(tm);
435: break;
436:
437: case TOMORROW:
438: /* do something tomorrow */
439: tm->tm_mday ++;
440: case TODAY: /* force ourselves to stay in today - no further processing */
441: token();
442: break;
443:
444: case JAN: case FEB: case MAR: case APR: case MAY: case JUN:
445: case JUL: case AUG: case SEP: case OCT: case NOV: case DEC:
446: /*
447: * do month mday [year]
448: */
449: mon = (sc_tokid-JAN);
450: expect(NUMBER);
451: mday = atol(sc_token);
452: if (token() == NUMBER) {
453: year = atol(sc_token);
454: token();
455: }
456: assign_date(tm, mday, mon, year);
457: break;
458:
459: case NUMBER:
460: /*
461: * get numeric MMDDYY, mm/dd/yy, or dd.mm.yy
462: */
463: tlen = strlen(sc_token);
464: mon = atol(sc_token);
465: token();
466:
467: if (sc_tokid == SLASH || sc_tokid == DOT) {
468: int sep;
469:
470: sep = sc_tokid;
471: expect(NUMBER);
472: mday = atol(sc_token);
473: if (token() == sep) {
474: expect(NUMBER);
475: year = atol(sc_token);
476: token();
477: }
478:
479: /*
480: * flip months and days for european timing
481: */
482: if (sep == DOT) {
483: int x = mday;
484: mday = mon;
485: mon = x;
486: }
487: } else if (tlen == 6 || tlen == 8) {
488: if (tlen == 8) {
489: year = (mon % 10000) - 1900;
490: mon /= 10000;
491: } else {
492: year = mon % 100;
493: mon /= 100;
494: }
495: mday = mon % 100;
496: mon /= 100;
497: } else
498: panic("garbled time");
499:
500: mon--;
501: if (mon < 0 || mon > 11 || mday < 1 || mday > 31)
502: panic("garbled time");
503:
504: assign_date(tm, mday, mon, year);
505: break;
506: } /* case */
507: } /* month */
508:
509:
510: /* Global functions */
511:
512: time_t
513: parsetime(argc, argv)
514: int argc;
515: char **argv;
516: {
517: /*
518: * Do the argument parsing, die if necessary, and return the time the job
519: * should be run.
520: */
521: time_t nowtimer, runtimer;
522: struct tm nowtime, runtime;
523: int hr = 0;
524: /* this MUST be initialized to zero for midnight/noon/teatime */
525:
526: nowtimer = time(NULL);
527: nowtime = *localtime(&nowtimer);
528:
529: runtime = nowtime;
530: runtime.tm_sec = 0;
531: runtime.tm_isdst = 0;
532:
533: if (argc <= optind)
534: usage();
535:
536: init_scanner(argc-optind, argv+optind);
537:
538: switch (token()) {
539: case NOW: /* now is optional prefix for PLUS tree */
540: expect(PLUS);
541: case PLUS:
542: plus(&runtime);
543: break;
544:
545: case NUMBER:
546: tod(&runtime);
547: month(&runtime);
548: break;
549:
550: /*
551: * evil coding for TEATIME|NOON|MIDNIGHT - we've initialised
552: * hr to zero up above, then fall into this case in such a
553: * way so we add +12 +4 hours to it for teatime, +12 hours
554: * to it for noon, and nothing at all for midnight, then
555: * set our runtime to that hour before leaping into the
556: * month scanner
557: */
558: case TEATIME:
559: hr += 4;
560: case NOON:
561: hr += 12;
562: case MIDNIGHT:
563: if (runtime.tm_hour >= hr)
564: runtime.tm_mday++;
565: runtime.tm_hour = hr;
566: runtime.tm_min = 0;
567: token();
568: /* fall through to month setting */
569: default:
570: month(&runtime);
571: break;
572: } /* ugly case statement */
573: expect(EOF);
574:
575: /*
576: * adjust for daylight savings time
577: */
578: runtime.tm_isdst = -1;
579: runtimer = mktime(&runtime);
580: if (runtime.tm_isdst > 0) {
581: runtimer -= 3600;
582: runtimer = mktime(&runtime);
583: }
584:
585: if (runtimer < 0)
586: panic("garbled time");
587:
588: if (nowtimer > runtimer)
589: panic("Trying to travel back in time");
590:
591: return runtimer;
592: } /* parsetime */