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