parsetime.c revision 50477
1234285Sdim/*
2234285Sdim *  parsetime.c - parse time for at(1)
3234285Sdim *  Copyright (C) 1993, 1994  Thomas Koenig
4234285Sdim *
5234285Sdim *  modifications for english-language times
6234285Sdim *  Copyright (C) 1993  David Parsons
7234285Sdim *
8234285Sdim * Redistribution and use in source and binary forms, with or without
9234285Sdim * modification, are permitted provided that the following conditions
10234285Sdim * are met:
11234285Sdim * 1. Redistributions of source code must retain the above copyright
12234285Sdim *    notice, this list of conditions and the following disclaimer.
13234285Sdim * 2. The name of the author(s) may not be used to endorse or promote
14234285Sdim *    products derived from this software without specific prior written
15234285Sdim *    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, MONTHS, YEARS,
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    { "month", MONTHS,0 },	/* month ... */
95    { "months", MONTHS,1 },	/* (pluralized) */
96    { "year", YEARS,0 },	/* year ... */
97    { "years", YEARS,1 },	/* (pluralized) */
98    { "jan", JAN,0 },
99    { "feb", FEB,0 },
100    { "mar", MAR,0 },
101    { "apr", APR,0 },
102    { "may", MAY,0 },
103    { "jun", JUN,0 },
104    { "jul", JUL,0 },
105    { "aug", AUG,0 },
106    { "sep", SEP,0 },
107    { "oct", OCT,0 },
108    { "nov", NOV,0 },
109    { "dec", DEC,0 },
110    { "january", JAN,0 },
111    { "february", FEB,0 },
112    { "march", MAR,0 },
113    { "april", APR,0 },
114    { "may", MAY,0 },
115    { "june", JUN,0 },
116    { "july", JUL,0 },
117    { "august", AUG,0 },
118    { "september", SEP,0 },
119    { "october", OCT,0 },
120    { "november", NOV,0 },
121    { "december", DEC,0 },
122    { "sunday", SUN, 0 },
123    { "sun", SUN, 0 },
124    { "monday", MON, 0 },
125    { "mon", MON, 0 },
126    { "tuesday", TUE, 0 },
127    { "tue", TUE, 0 },
128    { "wednesday", WED, 0 },
129    { "wed", WED, 0 },
130    { "thursday", THU, 0 },
131    { "thu", THU, 0 },
132    { "friday", FRI, 0 },
133    { "fri", FRI, 0 },
134    { "saturday", SAT, 0 },
135    { "sat", SAT, 0 },
136} ;
137
138/* File scope variables */
139
140static char **scp;	/* scanner - pointer at arglist */
141static char scc;	/* scanner - count of remaining arguments */
142static char *sct;	/* scanner - next char pointer in current argument */
143static int need;	/* scanner - need to advance to next argument */
144
145static char *sc_token;	/* scanner - token buffer */
146static size_t sc_len;   /* scanner - lenght of token buffer */
147static int sc_tokid;	/* scanner - token id */
148static int sc_tokplur;	/* scanner - is token plural? */
149
150static const char rcsid[] =
151  "$FreeBSD: head/usr.bin/at/parsetime.c 50477 1999-08-28 01:08:13Z peter $";
152
153/* Local functions */
154
155/*
156 * parse a token, checking if it's something special to us
157 */
158static int
159parse_token(char *arg)
160{
161    int i;
162
163    for (i=0; i<(sizeof Specials/sizeof Specials[0]); i++)
164	if (strcasecmp(Specials[i].name, arg) == 0) {
165	    sc_tokplur = Specials[i].plural;
166	    return sc_tokid = Specials[i].value;
167	}
168
169    /* not special - must be some random id */
170    return ID;
171} /* parse_token */
172
173
174/*
175 * init_scanner() sets up the scanner to eat arguments
176 */
177static void
178init_scanner(int argc, char **argv)
179{
180    scp = argv;
181    scc = argc;
182    need = 1;
183    sc_len = 1;
184    while (argc-- > 0)
185	sc_len += strlen(*argv++);
186
187    sc_token = (char *) mymalloc(sc_len);
188} /* init_scanner */
189
190/*
191 * token() fetches a token from the input stream
192 */
193static int
194token()
195{
196    int idx;
197
198    while (1) {
199	memset(sc_token, 0, sc_len);
200	sc_tokid = EOF;
201	sc_tokplur = 0;
202	idx = 0;
203
204	/* if we need to read another argument, walk along the argument list;
205	 * when we fall off the arglist, we'll just return EOF forever
206	 */
207	if (need) {
208	    if (scc < 1)
209		return sc_tokid;
210	    sct = *scp;
211	    scp++;
212	    scc--;
213	    need = 0;
214	}
215	/* eat whitespace now - if we walk off the end of the argument,
216	 * we'll continue, which puts us up at the top of the while loop
217	 * to fetch the next argument in
218	 */
219	while (isspace(*sct))
220	    ++sct;
221	if (!*sct) {
222	    need = 1;
223	    continue;
224	}
225
226	/* preserve the first character of the new token
227	 */
228	sc_token[0] = *sct++;
229
230	/* then see what it is
231	 */
232	if (isdigit(sc_token[0])) {
233	    while (isdigit(*sct))
234		sc_token[++idx] = *sct++;
235	    sc_token[++idx] = 0;
236	    return sc_tokid = NUMBER;
237	}
238	else if (isalpha(sc_token[0])) {
239	    while (isalpha(*sct))
240		sc_token[++idx] = *sct++;
241	    sc_token[++idx] = 0;
242	    return parse_token(sc_token);
243	}
244	else if (sc_token[0] == ':' || sc_token[0] == '.')
245	    return sc_tokid = DOT;
246	else if (sc_token[0] == '+')
247	    return sc_tokid = PLUS;
248	else if (sc_token[0] == '/')
249	    return sc_tokid = SLASH;
250	else
251	    return sc_tokid = JUNK;
252    } /* while (1) */
253} /* token */
254
255
256/*
257 * plonk() gives an appropriate error message if a token is incorrect
258 */
259static void
260plonk(int tok)
261{
262    panic((tok == EOF) ? "incomplete time"
263		       : "garbled time");
264} /* plonk */
265
266
267/*
268 * expect() gets a token and dies most horribly if it's not the token we want
269 */
270static void
271expect(int desired)
272{
273    if (token() != desired)
274	plonk(sc_tokid);	/* and we die here... */
275} /* expect */
276
277
278/*
279 * plus() parses a now + time
280 *
281 *  at [NOW] PLUS NUMBER [MINUTES|HOURS|DAYS|WEEKS|MONTHS|YEARS]
282 *
283 */
284
285static void
286plus(struct tm *tm)
287{
288    int delay;
289    int expectplur;
290
291    expect(NUMBER);
292
293    delay = atoi(sc_token);
294    expectplur = (delay != 1) ? 1 : 0;
295
296    switch (token()) {
297    case YEARS:
298	    tm->tm_year += delay;
299	    break;
300    case MONTHS:
301	    tm->tm_mon += delay;
302	    break;
303    case WEEKS:
304	    delay *= 7;
305    case DAYS:
306	    tm->tm_mday += delay;
307	    break;
308    case HOURS:
309	    tm->tm_hour += delay;
310	    break;
311    case MINUTES:
312	    tm->tm_min += delay;
313	    break;
314    default:
315    	    plonk(sc_tokid);
316	    break;
317    }
318
319    if (expectplur != sc_tokplur)
320	warnx("pluralization is wrong");
321
322    tm->tm_isdst = -1;
323    if (mktime(tm) < 0)
324	plonk(sc_tokid);
325
326} /* plus */
327
328
329/*
330 * tod() computes the time of day
331 *     [NUMBER [DOT NUMBER] [AM|PM]]
332 */
333static void
334tod(struct tm *tm)
335{
336    int hour, minute = 0;
337    int tlen;
338
339    hour = atoi(sc_token);
340    tlen = strlen(sc_token);
341
342    /* first pick out the time of day - if it's 4 digits, we assume
343     * a HHMM time, otherwise it's HH DOT MM time
344     */
345    if (token() == DOT) {
346	expect(NUMBER);
347	minute = atoi(sc_token);
348	if (minute > 59)
349	    panic("garbled time");
350	token();
351    }
352    else if (tlen == 4) {
353	minute = hour%100;
354	if (minute > 59)
355	    panic("garbled time");
356	hour = hour/100;
357    }
358
359    /* check if an AM or PM specifier was given
360     */
361    if (sc_tokid == AM || sc_tokid == PM) {
362	if (hour > 12)
363	    panic("garbled time");
364
365	if (sc_tokid == PM) {
366	    if (hour != 12)	/* 12:xx PM is 12:xx, not 24:xx */
367			hour += 12;
368	} else {
369	    if (hour == 12)	/* 12:xx AM is 00:xx, not 12:xx */
370			hour = 0;
371	}
372	token();
373    }
374    else if (hour > 23)
375	panic("garbled time");
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	tm->tm_wday++;
384    }
385
386    tm->tm_hour = hour;
387    tm->tm_min = minute;
388    if (tm->tm_hour == 24) {
389	tm->tm_hour = 0;
390	tm->tm_mday++;
391    }
392} /* tod */
393
394
395/*
396 * assign_date() assigns a date, wrapping to next year if needed
397 */
398static void
399assign_date(struct tm *tm, long mday, long mon, long year)
400{
401    if (year > 99) {
402	if (year > 1899)
403	    year -= 1900;
404	else
405	    panic("garbled time");
406    } else if (year != -1) {
407	struct tm *lt;
408	time_t now;
409
410	time(&now);
411	lt = localtime(&now);
412
413	/*
414	 * check if the specified year is in the next century.
415	 * allow for one year of user error as many people will
416	 * enter n - 1 at the start of year n.
417	 */
418	if (year < (lt->tm_year % 100) - 1)
419	    year += 100;
420	/* adjust for the year 2000 and beyond */
421	year += lt->tm_year - (lt->tm_year % 100);
422    }
423
424    if (year < 0 &&
425	(tm->tm_mon > mon ||(tm->tm_mon == mon && tm->tm_mday > mday)))
426	year = tm->tm_year + 1;
427
428    tm->tm_mday = mday;
429    tm->tm_mon = mon;
430
431    if (year >= 0)
432	tm->tm_year = year;
433} /* assign_date */
434
435
436/*
437 * month() picks apart a month specification
438 *
439 *  /[<month> NUMBER [NUMBER]]           \
440 *  |[TOMORROW]                          |
441 *  |[DAY OF WEEK]                       |
442 *  |NUMBER [SLASH NUMBER [SLASH NUMBER]]|
443 *  \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/
444 */
445static void
446month(struct tm *tm)
447{
448    long year= (-1);
449    long mday = 0, wday, mon;
450    int tlen;
451
452    switch (sc_tokid) {
453    case PLUS:
454	    plus(tm);
455	    break;
456
457    case TOMORROW:
458	    /* do something tomorrow */
459	    tm->tm_mday ++;
460	    tm->tm_wday ++;
461    case TODAY:	/* force ourselves to stay in today - no further processing */
462	    token();
463	    break;
464
465    case JAN: case FEB: case MAR: case APR: case MAY: case JUN:
466    case JUL: case AUG: case SEP: case OCT: case NOV: case DEC:
467	    /* do month mday [year]
468	     */
469	    mon = (sc_tokid-JAN);
470	    expect(NUMBER);
471	    mday = atol(sc_token);
472	    if (token() == NUMBER) {
473		year = atol(sc_token);
474		token();
475	    }
476	    assign_date(tm, mday, mon, year);
477	    break;
478
479    case SUN: case MON: case TUE:
480    case WED: case THU: case FRI:
481    case SAT:
482	    /* do a particular day of the week
483	     */
484	    wday = (sc_tokid-SUN);
485
486	    mday = tm->tm_mday;
487
488	    /* if this day is < today, then roll to next week
489	     */
490	    if (wday < tm->tm_wday)
491		mday += 7 - (tm->tm_wday - wday);
492	    else
493		mday += (wday - tm->tm_wday);
494
495	    tm->tm_wday = wday;
496
497	    assign_date(tm, mday, tm->tm_mon, tm->tm_year);
498	    break;
499
500    case NUMBER:
501	    /* get numeric MMDDYY, mm/dd/yy, or dd.mm.yy
502	     */
503	    tlen = strlen(sc_token);
504	    mon = atol(sc_token);
505	    token();
506
507	    if (sc_tokid == SLASH || sc_tokid == DOT) {
508		int sep;
509
510		sep = sc_tokid;
511		expect(NUMBER);
512		mday = atol(sc_token);
513		if (token() == sep) {
514		    expect(NUMBER);
515		    year = atol(sc_token);
516		    token();
517		}
518
519		/* flip months and days for european timing
520		 */
521		if (sep == DOT) {
522		    int x = mday;
523		    mday = mon;
524		    mon = x;
525		}
526	    }
527	    else if (tlen == 6 || tlen == 8) {
528		if (tlen == 8) {
529		    year = (mon % 10000) - 1900;
530		    mon /= 10000;
531		}
532		else {
533		    year = mon % 100;
534		    mon /= 100;
535		}
536		mday = mon % 100;
537		mon /= 100;
538	    }
539	    else
540		panic("garbled time");
541
542	    mon--;
543	    if (mon < 0 || mon > 11 || mday < 1 || mday > 31)
544		panic("garbled time");
545
546	    assign_date(tm, mday, mon, year);
547	    break;
548    } /* case */
549} /* month */
550
551
552/* Global functions */
553
554time_t
555parsetime(int argc, char **argv)
556{
557/* Do the argument parsing, die if necessary, and return the time the job
558 * should be run.
559 */
560    time_t nowtimer, runtimer;
561    struct tm nowtime, runtime;
562    int hr = 0;
563    /* this MUST be initialized to zero for midnight/noon/teatime */
564
565    nowtimer = time(NULL);
566    nowtime = *localtime(&nowtimer);
567
568    runtime = nowtime;
569    runtime.tm_sec = 0;
570    runtime.tm_isdst = 0;
571
572    if (argc <= optind)
573	usage();
574
575    init_scanner(argc-optind, argv+optind);
576
577    switch (token()) {
578    case NOW:	/* now is optional prefix for PLUS tree */
579	    expect(PLUS);
580    case PLUS:
581	    plus(&runtime);
582	    break;
583
584    case NUMBER:
585	    tod(&runtime);
586	    month(&runtime);
587	    break;
588
589	    /* evil coding for TEATIME|NOON|MIDNIGHT - we've initialised
590	     * hr to zero up above, then fall into this case in such a
591	     * way so we add +12 +4 hours to it for teatime, +12 hours
592	     * to it for noon, and nothing at all for midnight, then
593	     * set our runtime to that hour before leaping into the
594	     * month scanner
595	     */
596    case TEATIME:
597	    hr += 4;
598    case NOON:
599	    hr += 12;
600    case MIDNIGHT:
601	    if (runtime.tm_hour >= hr) {
602		runtime.tm_mday++;
603		runtime.tm_wday++;
604	    }
605	    runtime.tm_hour = hr;
606	    runtime.tm_min = 0;
607	    token();
608	    /* fall through to month setting */
609    default:
610	    month(&runtime);
611	    break;
612    } /* ugly case statement */
613    expect(EOF);
614
615    /* adjust for daylight savings time
616     */
617    runtime.tm_isdst = -1;
618    runtimer = mktime(&runtime);
619    if (runtime.tm_isdst > 0) {
620	runtimer -= 3600;
621	runtimer = mktime(&runtime);
622    }
623
624    if (runtimer < 0)
625	panic("garbled time");
626
627    if (nowtimer > runtimer)
628	panic("Trying to travel back in time");
629
630    return runtimer;
631} /* parsetime */
632