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