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