parsetime.c revision 41556
1242275Sneel/*
2242275Sneel *  parsetime.c - parse time for at(1)
3242275Sneel *  Copyright (C) 1993, 1994  Thomas Koenig
4242275Sneel *
5242275Sneel *  modifications for english-language times
6242275Sneel *  Copyright (C) 1993  David Parsons
7242275Sneel *
8242275Sneel * Redistribution and use in source and binary forms, with or without
9242275Sneel * modification, are permitted provided that the following conditions
10242275Sneel * are met:
11242275Sneel * 1. Redistributions of source code must retain the above copyright
12242275Sneel *    notice, this list of conditions and the following disclaimer.
13242275Sneel * 2. The name of the author(s) may not be used to endorse or promote
14242275Sneel *    products derived from this software without specific prior written
15242275Sneel *    permission.
16242275Sneel *
17242275Sneel * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
18242275Sneel * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19242275Sneel * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20242275Sneel * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
21242275Sneel * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22242275Sneel * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23242275Sneel * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24242275Sneel * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25242275Sneel * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26242275Sneel * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27242275Sneel *
28242275Sneel *  at [NOW] PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS
29242275Sneel *     /NUMBER [DOT NUMBER] [AM|PM]\ /[MONTH NUMBER [NUMBER]]             \
30242275Sneel *     |NOON                       | |[TOMORROW]                          |
31242275Sneel *     |MIDNIGHT                   | |[DAY OF WEEK]                       |
32242275Sneel *     \TEATIME                    / |NUMBER [SLASH NUMBER [SLASH NUMBER]]|
33242275Sneel *                                   \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/
34242275Sneel */
35242275Sneel
36242275Sneel/* System Headers */
37242275Sneel
38242275Sneel
39242275Sneel#include <sys/types.h>
40242275Sneel#include <err.h>
41242275Sneel#include <errno.h>
42242275Sneel#include <stdio.h>
43242275Sneel#include <stdlib.h>
44242275Sneel#include <string.h>
45242275Sneel#include <time.h>
46242275Sneel#include <unistd.h>
47242275Sneel#include <ctype.h>
48242275Sneel#ifndef __FreeBSD__
49242275Sneel#include <getopt.h>
50242275Sneel#endif
51242275Sneel
52242275Sneel/* Local headers */
53242275Sneel
54242275Sneel#include "at.h"
55242275Sneel#include "panic.h"
56242275Sneel
57242275Sneel
58242275Sneel/* Structures and unions */
59242275Sneel
60242275Sneelenum {	/* symbols */
61242275Sneel    MIDNIGHT, NOON, TEATIME,
62242275Sneel    PM, AM, TOMORROW, TODAY, NOW,
63242275Sneel    MINUTES, HOURS, DAYS, WEEKS,
64242275Sneel    NUMBER, PLUS, DOT, SLASH, ID, JUNK,
65242275Sneel    JAN, FEB, MAR, APR, MAY, JUN,
66242275Sneel    JUL, AUG, SEP, OCT, NOV, DEC,
67242275Sneel    SUN, MON, TUE, WED, THU, FRI, SAT
68242275Sneel    };
69242275Sneel
70242275Sneel/* parse translation table - table driven parsers can be your FRIEND!
71242275Sneel */
72242275Sneelstruct {
73242275Sneel    char *name;	/* token name */
74242275Sneel    int value;	/* token id */
75242275Sneel    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