parsetime.c revision 38646
150880Smarkm/*
250880Smarkm *  parsetime.c - parse time for at(1)
350880Smarkm *  Copyright (C) 1993, 1994  Thomas Koenig
450880Smarkm *
550880Smarkm *  modifications for english-language times
6147440Sru *  Copyright (C) 1993  David Parsons
7206622Suqs *
850880Smarkm * Redistribution and use in source and binary forms, with or without
950880Smarkm * modification, are permitted provided that the following conditions
1050880Smarkm * are met:
1150880Smarkm * 1. Redistributions of source code must retain the above copyright
1250880Smarkm *    notice, this list of conditions and the following disclaimer.
1350880Smarkm * 2. The name of the author(s) may not be used to endorse or promote
1450880Smarkm *    products derived from this software without specific prior written
1550880Smarkm *    permission.
1650880Smarkm *
1750880Smarkm * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
1850880Smarkm * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1950880Smarkm * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2050880Smarkm * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
2150880Smarkm * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2250880Smarkm * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2350880Smarkm * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2450880Smarkm * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2570886Sru * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2650880Smarkm * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2770886Sru *
2850880Smarkm *  at [NOW] PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS
2970886Sru *     /NUMBER [DOT NUMBER] [AM|PM]\ /[MONTH NUMBER [NUMBER]]             \
3050880Smarkm *     |NOON                       | |[TOMORROW]                          |
3170886Sru *     |MIDNIGHT                   | |[DAY OF WEEK]                       |
3270886Sru *     \TEATIME                    / |NUMBER [SLASH NUMBER [SLASH NUMBER]]|
3350880Smarkm *                                   \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/
3470886Sru */
3550880Smarkm
3670886Sru/* System Headers */
3750880Smarkm
3850880Smarkm
3950880Smarkm#include <sys/types.h>
4050880Smarkm#include <err.h>
4150880Smarkm#include <errno.h>
4250880Smarkm#include <stdio.h>
4350880Smarkm#include <stdlib.h>
4450880Smarkm#include <string.h>
4550880Smarkm#include <time.h>
4650880Smarkm#include <unistd.h>
4750880Smarkm#include <ctype.h>
4850880Smarkm#ifndef __FreeBSD__
4950880Smarkm#include <getopt.h>
5050880Smarkm#endif
5150880Smarkm
5250880Smarkm/* Local headers */
5350880Smarkm
5450880Smarkm#include "at.h"
5550880Smarkm#include "panic.h"
5650880Smarkm
57147440Sru
58147440Sru/* Structures and unions */
5950880Smarkm
6050880Smarkmenum {	/* symbols */
6150880Smarkm    MIDNIGHT, NOON, TEATIME,
6250880Smarkm    PM, AM, TOMORROW, TODAY, NOW,
6350880Smarkm    MINUTES, HOURS, DAYS, WEEKS,
6472532Sru    NUMBER, PLUS, DOT, SLASH, ID, JUNK,
6550880Smarkm    JAN, FEB, MAR, APR, MAY, JUN,
6650880Smarkm    JUL, AUG, SEP, OCT, NOV, DEC,
6750880Smarkm    SUN, MON, TUE, WED, THU, FRI, SAT
6850880Smarkm    };
6950880Smarkm
7050880Smarkm/* parse translation table - table driven parsers can be your FRIEND!
71162382Sru */
72162382Srustruct {
73162382Sru    char *name;	/* token name */
74162382Sru    int value;	/* token id */
75162382Sru    int plural;	/* is this plural? */
76162382Sru} 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 char rcsid[] = "$Id: parsetime.c,v 1.14 1998/08/08 14:02:06 alex Exp $";
147
148/* Local functions */
149
150/*
151 * parse a token, checking if it's something special to us
152 */
153static int
154parse_token(char *arg)
155{
156    int i;
157
158    for (i=0; i<(sizeof Specials/sizeof Specials[0]); i++)
159	if (strcasecmp(Specials[i].name, arg) == 0) {
160	    sc_tokplur = Specials[i].plural;
161	    return sc_tokid = Specials[i].value;
162	}
163
164    /* not special - must be some random id */
165    return ID;
166} /* parse_token */
167
168
169/*
170 * init_scanner() sets up the scanner to eat arguments
171 */
172static void
173init_scanner(int argc, char **argv)
174{
175    scp = argv;
176    scc = argc;
177    need = 1;
178    sc_len = 1;
179    while (argc-- > 0)
180	sc_len += strlen(*argv++);
181
182    sc_token = (char *) mymalloc(sc_len);
183} /* init_scanner */
184
185/*
186 * token() fetches a token from the input stream
187 */
188static int
189token()
190{
191    int idx;
192
193    while (1) {
194	memset(sc_token, 0, sc_len);
195	sc_tokid = EOF;
196	sc_tokplur = 0;
197	idx = 0;
198
199	/* if we need to read another argument, walk along the argument list;
200	 * when we fall off the arglist, we'll just return EOF forever
201	 */
202	if (need) {
203	    if (scc < 1)
204		return sc_tokid;
205	    sct = *scp;
206	    scp++;
207	    scc--;
208	    need = 0;
209	}
210	/* eat whitespace now - if we walk off the end of the argument,
211	 * we'll continue, which puts us up at the top of the while loop
212	 * to fetch the next argument in
213	 */
214	while (isspace(*sct))
215	    ++sct;
216	if (!*sct) {
217	    need = 1;
218	    continue;
219	}
220
221	/* preserve the first character of the new token
222	 */
223	sc_token[0] = *sct++;
224
225	/* then see what it is
226	 */
227	if (isdigit(sc_token[0])) {
228	    while (isdigit(*sct))
229		sc_token[++idx] = *sct++;
230	    sc_token[++idx] = 0;
231	    return sc_tokid = NUMBER;
232	}
233	else if (isalpha(sc_token[0])) {
234	    while (isalpha(*sct))
235		sc_token[++idx] = *sct++;
236	    sc_token[++idx] = 0;
237	    return parse_token(sc_token);
238	}
239	else if (sc_token[0] == ':' || sc_token[0] == '.')
240	    return sc_tokid = DOT;
241	else if (sc_token[0] == '+')
242	    return sc_tokid = PLUS;
243	else if (sc_token[0] == '/')
244	    return sc_tokid = SLASH;
245	else
246	    return sc_tokid = JUNK;
247    } /* while (1) */
248} /* token */
249
250
251/*
252 * plonk() gives an appropriate error message if a token is incorrect
253 */
254static void
255plonk(int tok)
256{
257    panic((tok == EOF) ? "incomplete time"
258		       : "garbled time");
259} /* plonk */
260
261
262/*
263 * expect() gets a token and dies most horribly if it's not the token we want
264 */
265static void
266expect(int desired)
267{
268    if (token() != desired)
269	plonk(sc_tokid);	/* and we die here... */
270} /* expect */
271
272
273/*
274 * dateadd() adds a number of minutes to a date.  It is extraordinarily
275 * stupid regarding day-of-month overflow, and will most likely not
276 * work properly
277 */
278static void
279dateadd(int minutes, struct tm *tm)
280{
281    /* increment days */
282
283    while (minutes > 24*60) {
284	minutes -= 24*60;
285	tm->tm_mday++;
286    }
287
288    /* increment hours */
289    while (minutes > 60) {
290	minutes -= 60;
291	tm->tm_hour++;
292	if (tm->tm_hour > 23) {
293	    tm->tm_mday++;
294	    tm->tm_hour = 0;
295	}
296    }
297
298    /* increment minutes */
299    tm->tm_min += minutes;
300
301    if (tm->tm_min > 59) {
302	tm->tm_hour++;
303	tm->tm_min -= 60;
304
305	if (tm->tm_hour > 23) {
306	    tm->tm_mday++;
307	    tm->tm_hour = 0;
308	}
309    }
310} /* dateadd */
311
312
313/*
314 * plus() parses a now + time
315 *
316 *  at [NOW] PLUS NUMBER [MINUTES|HOURS|DAYS|WEEKS]
317 *
318 */
319static void
320plus(struct tm *tm)
321{
322    int delay;
323    int expectplur;
324
325    expect(NUMBER);
326
327    delay = atoi(sc_token);
328    expectplur = (delay != 1) ? 1 : 0;
329
330    switch (token()) {
331    case WEEKS:
332	    delay *= 7;
333    case DAYS:
334	    delay *= 24;
335    case HOURS:
336	    delay *= 60;
337    case MINUTES:
338	    if (expectplur != sc_tokplur)
339		warnx("pluralization is wrong");
340	    dateadd(delay, tm);
341	    return;
342    }
343    plonk(sc_tokid);
344} /* plus */
345
346
347/*
348 * tod() computes the time of day
349 *     [NUMBER [DOT NUMBER] [AM|PM]]
350 */
351static void
352tod(struct tm *tm)
353{
354    int hour, minute = 0;
355    int tlen;
356
357    hour = atoi(sc_token);
358    tlen = strlen(sc_token);
359
360    /* first pick out the time of day - if it's 4 digits, we assume
361     * a HHMM time, otherwise it's HH DOT MM time
362     */
363    if (token() == DOT) {
364	expect(NUMBER);
365	minute = atoi(sc_token);
366	if (minute > 59)
367	    panic("garbled time");
368	token();
369    }
370    else if (tlen == 4) {
371	minute = hour%100;
372	if (minute > 59)
373	    panic("garbled time");
374	hour = hour/100;
375    }
376
377    /* check if an AM or PM specifier was given
378     */
379    if (sc_tokid == AM || sc_tokid == PM) {
380	if (hour > 12)
381	    panic("garbled time");
382
383	if (sc_tokid == PM) {
384	    if (hour != 12)	/* 12:xx PM is 12:xx, not 24:xx */
385			hour += 12;
386	} else {
387	    if (hour == 12)	/* 12:xx AM is 00:xx, not 12:xx */
388			hour = 0;
389	}
390	token();
391    }
392    else if (hour > 23)
393	panic("garbled time");
394
395    /* if we specify an absolute time, we don't want to bump the day even
396     * if we've gone past that time - but if we're specifying a time plus
397     * a relative offset, it's okay to bump things
398     */
399    if ((sc_tokid == EOF || sc_tokid == PLUS) && tm->tm_hour > hour) {
400	tm->tm_mday++;
401	tm->tm_wday++;
402    }
403
404    tm->tm_hour = hour;
405    tm->tm_min = minute;
406    if (tm->tm_hour == 24) {
407	tm->tm_hour = 0;
408	tm->tm_mday++;
409    }
410} /* tod */
411
412
413/*
414 * assign_date() assigns a date, wrapping to next year if needed
415 */
416static void
417assign_date(struct tm *tm, long mday, long mon, long year)
418{
419    if (year > 99) {
420	if (year > 1899)
421	    year -= 1900;
422	else
423	    panic("garbled time");
424    } else if (year != -1) {
425	struct tm *lt;
426	time_t now;
427
428	time(&now);
429	lt = localtime(&now);
430
431	/*
432	 * check if the specified year is in the next century.
433	 * allow for one year of user error as many people will
434	 * enter n - 1 at the start of year n.
435	 */
436	if (year < (lt->tm_year % 100) - 1)
437	    year += 100;
438	/* adjust for the year 2000 and beyond */
439	year += lt->tm_year - (lt->tm_year % 100);
440    }
441
442    if (year < 0 &&
443	(tm->tm_mon > mon ||(tm->tm_mon == mon && tm->tm_mday > mday)))
444	year = tm->tm_year + 1;
445
446    tm->tm_mday = mday;
447    tm->tm_mon = mon;
448
449    if (year >= 0)
450	tm->tm_year = year;
451} /* assign_date */
452
453
454/*
455 * month() picks apart a month specification
456 *
457 *  /[<month> NUMBER [NUMBER]]           \
458 *  |[TOMORROW]                          |
459 *  |[DAY OF WEEK]                       |
460 *  |NUMBER [SLASH NUMBER [SLASH NUMBER]]|
461 *  \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/
462 */
463static void
464month(struct tm *tm)
465{
466    long year= (-1);
467    long mday, wday, mon;
468    int tlen;
469
470    switch (sc_tokid) {
471    case PLUS:
472	    plus(tm);
473	    break;
474
475    case TOMORROW:
476	    /* do something tomorrow */
477	    tm->tm_mday ++;
478	    tm->tm_wday ++;
479    case TODAY:	/* force ourselves to stay in today - no further processing */
480	    token();
481	    break;
482
483    case JAN: case FEB: case MAR: case APR: case MAY: case JUN:
484    case JUL: case AUG: case SEP: case OCT: case NOV: case DEC:
485	    /* do month mday [year]
486	     */
487	    mon = (sc_tokid-JAN);
488	    expect(NUMBER);
489	    mday = atol(sc_token);
490	    if (token() == NUMBER) {
491		year = atol(sc_token);
492		token();
493	    }
494	    assign_date(tm, mday, mon, year);
495	    break;
496
497    case SUN: case MON: case TUE:
498    case WED: case THU: case FRI:
499    case SAT:
500	    /* do a particular day of the week
501	     */
502	    wday = (sc_tokid-SUN);
503
504	    mday = tm->tm_mday;
505
506	    /* if this day is < today, then roll to next week
507	     */
508	    if (wday < tm->tm_wday)
509		mday += 7 - (tm->tm_wday - wday);
510	    else
511		mday += (wday - tm->tm_wday);
512
513	    tm->tm_wday = wday;
514
515	    assign_date(tm, mday, tm->tm_mon, tm->tm_year);
516	    break;
517
518    case NUMBER:
519	    /* get numeric MMDDYY, mm/dd/yy, or dd.mm.yy
520	     */
521	    tlen = strlen(sc_token);
522	    mon = atol(sc_token);
523	    token();
524
525	    if (sc_tokid == SLASH || sc_tokid == DOT) {
526		int sep;
527
528		sep = sc_tokid;
529		expect(NUMBER);
530		mday = atol(sc_token);
531		if (token() == sep) {
532		    expect(NUMBER);
533		    year = atol(sc_token);
534		    token();
535		}
536
537		/* flip months and days for european timing
538		 */
539		if (sep == DOT) {
540		    int x = mday;
541		    mday = mon;
542		    mon = x;
543		}
544	    }
545	    else if (tlen == 6 || tlen == 8) {
546		if (tlen == 8) {
547		    year = (mon % 10000) - 1900;
548		    mon /= 10000;
549		}
550		else {
551		    year = mon % 100;
552		    mon /= 100;
553		}
554		mday = mon % 100;
555		mon /= 100;
556	    }
557	    else
558		panic("garbled time");
559
560	    mon--;
561	    if (mon < 0 || mon > 11 || mday < 1 || mday > 31)
562		panic("garbled time");
563
564	    assign_date(tm, mday, mon, year);
565	    break;
566    } /* case */
567} /* month */
568
569
570/* Global functions */
571
572time_t
573parsetime(int argc, char **argv)
574{
575/* Do the argument parsing, die if necessary, and return the time the job
576 * should be run.
577 */
578    time_t nowtimer, runtimer;
579    struct tm nowtime, runtime;
580    int hr = 0;
581    /* this MUST be initialized to zero for midnight/noon/teatime */
582
583    nowtimer = time(NULL);
584    nowtime = *localtime(&nowtimer);
585
586    runtime = nowtime;
587    runtime.tm_sec = 0;
588    runtime.tm_isdst = 0;
589
590    if (argc <= optind)
591	usage();
592
593    init_scanner(argc-optind, argv+optind);
594
595    switch (token()) {
596    case NOW:	/* now is optional prefix for PLUS tree */
597	    expect(PLUS);
598    case PLUS:
599	    plus(&runtime);
600	    break;
601
602    case NUMBER:
603	    tod(&runtime);
604	    month(&runtime);
605	    break;
606
607	    /* evil coding for TEATIME|NOON|MIDNIGHT - we've initialised
608	     * hr to zero up above, then fall into this case in such a
609	     * way so we add +12 +4 hours to it for teatime, +12 hours
610	     * to it for noon, and nothing at all for midnight, then
611	     * set our runtime to that hour before leaping into the
612	     * month scanner
613	     */
614    case TEATIME:
615	    hr += 4;
616    case NOON:
617	    hr += 12;
618    case MIDNIGHT:
619	    if (runtime.tm_hour >= hr) {
620		runtime.tm_mday++;
621		runtime.tm_wday++;
622	    }
623	    runtime.tm_hour = hr;
624	    runtime.tm_min = 0;
625	    token();
626	    /* fall through to month setting */
627    default:
628	    month(&runtime);
629	    break;
630    } /* ugly case statement */
631    expect(EOF);
632
633    /* adjust for daylight savings time
634     */
635    runtime.tm_isdst = -1;
636    runtimer = mktime(&runtime);
637    if (runtime.tm_isdst > 0) {
638	runtimer -= 3600;
639	runtimer = mktime(&runtime);
640    }
641
642    if (runtimer < 0)
643	panic("garbled time");
644
645    if (nowtimer > runtimer)
646	panic("Trying to travel back in time");
647
648    return runtimer;
649} /* parsetime */
650