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