Home | History | Annotate | Line # | Download | only in at
parsetime.c revision 1.9
      1  1.9      tron /*	$NetBSD: parsetime.c,v 1.9 2000/01/06 00:44:09 tron Exp $	*/
      2  1.3     glass 
      3  1.1       cgd /*
      4  1.1       cgd  * parsetime.c - parse time for at(1)
      5  1.7  christos  * 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.7  christos  * 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.7  christos  *     |MIDNIGHT                   | |[DAY OF WEEK]                       |
     34  1.7  christos  *     \TEATIME                    / |NUMBER [SLASH NUMBER [SLASH NUMBER]]|
     35  1.7  christos  *                                   \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.6  christos #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.7  christos 	MIDNIGHT, NOON, TEATIME,
     61  1.7  christos 	PM, AM, TOMORROW, TODAY, NOW,
     62  1.7  christos 	MINUTES, HOURS, DAYS, WEEKS,
     63  1.7  christos 	NUMBER, PLUS, DOT, SLASH, ID, JUNK,
     64  1.7  christos 	JAN, FEB, MAR, APR, MAY, JUN,
     65  1.7  christos 	JUL, AUG, SEP, OCT, NOV, DEC,
     66  1.7  christos 	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.7  christos 	char *name;	/* token name */
     74  1.7  christos 	int value;	/* token id */
     75  1.7  christos 	int plural;	/* is this plural? */
     76  1.1       cgd } Specials[] = {
     77  1.7  christos 	{ "midnight", MIDNIGHT, 0 },	/* 00:00:00 of today or tomorrow */
     78  1.7  christos 	{ "noon", NOON, 0 },		/* 12:00:00 of today or tomorrow */
     79  1.7  christos 	{ "teatime", TEATIME, 0 },	/* 16:00:00 of today or tomorrow */
     80  1.7  christos 	{ "am", AM, 0 },		/* morning times for 0-12 clock */
     81  1.7  christos 	{ "pm", PM, 0 },		/* evening times for 0-12 clock */
     82  1.7  christos 	{ "tomorrow", TOMORROW, 0 },	/* execute 24 hours from time */
     83  1.7  christos 	{ "today", TODAY, 0 },		/* execute today - don't advance time */
     84  1.7  christos 	{ "now", NOW, 0 },		/* opt prefix for PLUS */
     85  1.7  christos 
     86  1.7  christos 	{ "minute", MINUTES, 0 },	/* minutes multiplier */
     87  1.7  christos 	{ "min", MINUTES, 0 },
     88  1.7  christos 	{ "m", MINUTES, 0 },
     89  1.7  christos 	{ "minutes", MINUTES, 1 },	/* (pluralized) */
     90  1.7  christos 	{ "hour", HOURS, 0 },		/* hours ... */
     91  1.7  christos 	{ "hr", HOURS, 0 },		/* abbreviated */
     92  1.7  christos 	{ "h", HOURS, 0 },
     93  1.7  christos 	{ "hours", HOURS, 1 },		/* (pluralized) */
     94  1.7  christos 	{ "day", DAYS, 0 },		/* days ... */
     95  1.7  christos 	{ "d", DAYS, 0 },
     96  1.7  christos 	{ "days", DAYS, 1 },		/* (pluralized) */
     97  1.7  christos 	{ "week", WEEKS, 0 },		/* week ... */
     98  1.7  christos 	{ "w", WEEKS, 0 },
     99  1.7  christos 	{ "weeks", WEEKS, 1 },		/* (pluralized) */
    100  1.7  christos 	{ "jan", JAN, 0 },
    101  1.7  christos 	{ "feb", FEB, 0 },
    102  1.7  christos 	{ "mar", MAR, 0 },
    103  1.7  christos 	{ "apr", APR, 0 },
    104  1.7  christos 	{ "may", MAY, 0 },
    105  1.7  christos 	{ "jun", JUN, 0 },
    106  1.7  christos 	{ "jul", JUL, 0 },
    107  1.7  christos 	{ "aug", AUG, 0 },
    108  1.7  christos 	{ "sep", SEP, 0 },
    109  1.7  christos 	{ "oct", OCT, 0 },
    110  1.7  christos 	{ "nov", NOV, 0 },
    111  1.7  christos 	{ "dec", DEC, 0 },
    112  1.7  christos 	{ "sunday", SUN, 0 },
    113  1.7  christos 	{ "sun", SUN, 0 },
    114  1.7  christos 	{ "monday", MON, 0 },
    115  1.7  christos 	{ "mon", MON, 0 },
    116  1.7  christos 	{ "tuesday", TUE, 0 },
    117  1.7  christos 	{ "tue", TUE, 0 },
    118  1.7  christos 	{ "wednesday", WED, 0 },
    119  1.7  christos 	{ "wed", WED, 0 },
    120  1.7  christos 	{ "thursday", THU, 0 },
    121  1.7  christos 	{ "thu", THU, 0 },
    122  1.7  christos 	{ "friday", FRI, 0 },
    123  1.7  christos 	{ "fri", FRI, 0 },
    124  1.7  christos 	{ "saturday", SAT, 0 },
    125  1.7  christos 	{ "sat", SAT, 0 },
    126  1.7  christos };
    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.7  christos static int sc_tokplur;	/* scanner - is token plural? */
    139  1.1       cgd 
    140  1.3     glass #ifndef lint
    141  1.7  christos #if 0
    142  1.7  christos static char rcsid[] = "$OpenBSD: parsetime.c,v 1.4 1997/03/01 23:40:10 millert Exp $";
    143  1.7  christos #else
    144  1.9      tron __RCSID("$NetBSD: parsetime.c,v 1.9 2000/01/06 00:44:09 tron Exp $");
    145  1.7  christos #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.7  christos 	int i;
    168  1.1       cgd 
    169  1.7  christos 	for (i=0; i < sizeof(Specials) / sizeof(Specials[0]); i++) {
    170  1.7  christos 		if (strcasecmp(Specials[i].name, arg) == 0) {
    171  1.7  christos 			sc_tokplur = Specials[i].plural;
    172  1.7  christos 		    	return (sc_tokid = Specials[i].value);
    173  1.7  christos 		}
    174  1.1       cgd 	}
    175  1.1       cgd 
    176  1.7  christos 	/* not special - must be some random id */
    177  1.7  christos 	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.7  christos 	scp = argv;
    190  1.7  christos 	scc = argc;
    191  1.7  christos 	need = 1;
    192  1.7  christos 	sc_len = 1;
    193  1.7  christos 	while (argc-- > 0)
    194  1.7  christos 		sc_len += strlen(*argv++);
    195  1.7  christos 
    196  1.7  christos 	if ((sc_token = (char *) malloc(sc_len)) == NULL)
    197  1.7  christos 		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.7  christos 	int idx;
    207  1.1       cgd 
    208  1.7  christos 	while (1) {
    209  1.7  christos 		(void)memset(sc_token, 0, sc_len);
    210  1.7  christos 		sc_tokid = EOF;
    211  1.7  christos 		sc_tokplur = 0;
    212  1.7  christos 		idx = 0;
    213  1.1       cgd 
    214  1.7  christos 		/*
    215  1.7  christos 		 * if we need to read another argument, walk along the
    216  1.7  christos 		 * argument list; when we fall off the arglist, we'll
    217  1.7  christos 		 * just return EOF forever
    218  1.7  christos 		 */
    219  1.7  christos 		if (need) {
    220  1.7  christos 			if (scc < 1)
    221  1.7  christos 				return (sc_tokid);
    222  1.7  christos 			sct = *scp;
    223  1.7  christos 			scp++;
    224  1.7  christos 			scc--;
    225  1.7  christos 			need = 0;
    226  1.7  christos 		}
    227  1.7  christos 		/*
    228  1.7  christos 		 * eat whitespace now - if we walk off the end of the argument,
    229  1.7  christos 		 * we'll continue, which puts us up at the top of the while loop
    230  1.7  christos 		 * to fetch the next argument in
    231  1.7  christos 		 */
    232  1.7  christos 		while (isspace(*sct))
    233  1.7  christos 			++sct;
    234  1.7  christos 		if (!*sct) {
    235  1.7  christos 			need = 1;
    236  1.7  christos 			continue;
    237  1.7  christos 		}
    238  1.1       cgd 
    239  1.7  christos 		/*
    240  1.7  christos 		 * preserve the first character of the new token
    241  1.7  christos 		 */
    242  1.7  christos 		sc_token[0] = *sct++;
    243  1.1       cgd 
    244  1.7  christos 		/*
    245  1.7  christos 		 * then see what it is
    246  1.7  christos 		 */
    247  1.7  christos 		if (isdigit(sc_token[0])) {
    248  1.7  christos 			while (isdigit(*sct))
    249  1.7  christos 				sc_token[++idx] = *sct++;
    250  1.7  christos 			sc_token[++idx] = 0;
    251  1.7  christos 			return ((sc_tokid = NUMBER));
    252  1.7  christos 		} else if (isalpha(sc_token[0])) {
    253  1.7  christos 			while (isalpha(*sct))
    254  1.7  christos 				sc_token[++idx] = *sct++;
    255  1.7  christos 			sc_token[++idx] = 0;
    256  1.7  christos 			return (parse_token(sc_token));
    257  1.7  christos 		}
    258  1.7  christos 		else if (sc_token[0] == ':' || sc_token[0] == '.')
    259  1.7  christos 			return ((sc_tokid = DOT));
    260  1.7  christos 		else if (sc_token[0] == '+')
    261  1.7  christos 			return ((sc_tokid = PLUS));
    262  1.7  christos 		else if (sc_token[0] == '/')
    263  1.7  christos 			return ((sc_tokid = SLASH));
    264  1.7  christos 		else
    265  1.7  christos 			return ((sc_tokid = JUNK));
    266  1.7  christos 	} /* 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.7  christos 	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.7  christos 	if (token() != desired)
    289  1.7  christos 		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.7  christos 	/* increment days */
    304  1.7  christos 
    305  1.7  christos 	while (minutes > 24*60) {
    306  1.7  christos 		minutes -= 24*60;
    307  1.7  christos 		tm->tm_mday++;
    308  1.7  christos 	}
    309  1.1       cgd 
    310  1.7  christos 	/* increment hours */
    311  1.7  christos 	while (minutes > 60) {
    312  1.7  christos 		minutes -= 60;
    313  1.7  christos 		tm->tm_hour++;
    314  1.7  christos 		if (tm->tm_hour > 23) {
    315  1.7  christos 			tm->tm_mday++;
    316  1.7  christos 			tm->tm_hour = 0;
    317  1.7  christos 		}
    318  1.7  christos 	}
    319  1.7  christos 
    320  1.7  christos 	/* increment minutes */
    321  1.7  christos 	tm->tm_min += minutes;
    322  1.7  christos 
    323  1.7  christos 	if (tm->tm_min > 59) {
    324  1.7  christos 		tm->tm_hour++;
    325  1.7  christos 		tm->tm_min -= 60;
    326  1.7  christos 
    327  1.7  christos 		if (tm->tm_hour > 23) {
    328  1.7  christos 			tm->tm_mday++;
    329  1.7  christos 			tm->tm_hour = 0;
    330  1.7  christos 		}
    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.7  christos 	int delay;
    346  1.7  christos 	int expectplur;
    347  1.1       cgd 
    348  1.7  christos 	expect(NUMBER);
    349  1.7  christos 
    350  1.7  christos 	delay = atoi(sc_token);
    351  1.7  christos 	expectplur = (delay != 1) ? 1 : 0;
    352  1.1       cgd 
    353  1.7  christos 	switch (token()) {
    354  1.7  christos 	case WEEKS:
    355  1.7  christos 		delay *= 7;
    356  1.7  christos 	case DAYS:
    357  1.7  christos 		delay *= 24;
    358  1.7  christos 	case HOURS:
    359  1.7  christos 		delay *= 60;
    360  1.7  christos 	case MINUTES:
    361  1.7  christos 		if (expectplur != sc_tokplur)
    362  1.7  christos 			(void)fprintf(stderr, "at: pluralization is wrong\n");
    363  1.7  christos 		dateadd(delay, tm);
    364  1.7  christos 		return;
    365  1.7  christos 	}
    366  1.1       cgd 
    367  1.7  christos 	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.7  christos 	int hour, minute = 0;
    380  1.7  christos 	size_t tlen;
    381  1.1       cgd 
    382  1.7  christos 	hour = atoi(sc_token);
    383  1.7  christos 	tlen = strlen(sc_token);
    384  1.7  christos 
    385  1.7  christos 	/*
    386  1.7  christos 	 * first pick out the time of day - if it's 4 digits, we assume
    387  1.7  christos 	 * a HHMM time, otherwise it's HH DOT MM time
    388  1.7  christos 	 */
    389  1.7  christos 	if (token() == DOT) {
    390  1.7  christos 		expect(NUMBER);
    391  1.7  christos 		minute = atoi(sc_token);
    392  1.7  christos 		token();
    393  1.7  christos 	} else if (tlen == 4) {
    394  1.7  christos 		minute = hour % 100;
    395  1.7  christos 		hour = hour / 100;
    396  1.7  christos 	}
    397  1.7  christos 
    398  1.7  christos 	if (minute > 59)
    399  1.7  christos 		panic("garbled time");
    400  1.1       cgd 
    401  1.7  christos 	/*
    402  1.7  christos 	 * check if an AM or PM specifier was given
    403  1.7  christos 	 */
    404  1.7  christos 	if (sc_tokid == AM || sc_tokid == PM) {
    405  1.7  christos 		if (hour > 12)
    406  1.7  christos 			panic("garbled time");
    407  1.7  christos 
    408  1.7  christos 		if (sc_tokid == PM) {
    409  1.7  christos 			if (hour != 12)	/* 12:xx PM is 12:xx, not 24:xx */
    410  1.7  christos 				hour += 12;
    411  1.7  christos 		} else {
    412  1.7  christos 			if (hour == 12)	/* 12:xx AM is 00:xx, not 12:xx */
    413  1.7  christos 				hour = 0;
    414  1.7  christos 		}
    415  1.7  christos 		token();
    416  1.7  christos 	} else if (hour > 23)
    417  1.7  christos 		panic("garbled time");
    418  1.7  christos 
    419  1.7  christos 	/*
    420  1.7  christos 	 * if we specify an absolute time, we don't want to bump the day even
    421  1.7  christos 	 * if we've gone past that time - but if we're specifying a time plus
    422  1.7  christos 	 * a relative offset, it's okay to bump things
    423  1.7  christos 	 */
    424  1.7  christos 	if ((sc_tokid == EOF || sc_tokid == PLUS) && tm->tm_hour > hour) {
    425  1.7  christos 		tm->tm_mday++;
    426  1.7  christos 		tm->tm_wday++;
    427  1.7  christos 	}
    428  1.1       cgd 
    429  1.7  christos 	tm->tm_hour = hour;
    430  1.7  christos 	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.7  christos 	if (year > 99) {
    443  1.7  christos 	    if (year >= TM_YEAR_BASE)
    444  1.7  christos 		    year -= TM_YEAR_BASE;
    445  1.7  christos 	    else
    446  1.7  christos 		    panic("garbled time");
    447  1.7  christos 	}
    448  1.7  christos 
    449  1.7  christos 	if (year >= 0) {
    450  1.8      tron 		if (year < 70)
    451  1.7  christos 			tm->tm_year = year + 2000 - TM_YEAR_BASE;
    452  1.7  christos 		else
    453  1.7  christos 			tm->tm_year = year + 1900 - TM_YEAR_BASE;
    454  1.7  christos 	}
    455  1.7  christos 	else { /* year < 0 */
    456  1.7  christos 		if (tm->tm_mon > mon ||
    457  1.7  christos 		    (tm->tm_mon == mon && tm->tm_mday > mday))
    458  1.7  christos 			tm->tm_year++;
    459  1.7  christos 	}
    460  1.1       cgd 
    461  1.7  christos 	tm->tm_mday = mday;
    462  1.7  christos 	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.7  christos  *  |[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.7  christos 	int year = (-1);
    480  1.7  christos 	int mday, wday, mon;
    481  1.7  christos 	size_t tlen;
    482  1.7  christos 
    483  1.7  christos 	mday = 0;
    484  1.7  christos 	switch (sc_tokid) {
    485  1.7  christos 	case PLUS:
    486  1.7  christos 		plus(tm);
    487  1.7  christos 		break;
    488  1.7  christos 
    489  1.7  christos 	case TOMORROW:
    490  1.7  christos 		/* do something tomorrow */
    491  1.7  christos 		tm->tm_mday++;
    492  1.7  christos 		tm->tm_wday++;
    493  1.7  christos 	case TODAY:
    494  1.7  christos 		/* force ourselves to stay in today - no further processing */
    495  1.1       cgd 		token();
    496  1.7  christos 		break;
    497  1.1       cgd 
    498  1.7  christos 	case JAN: case FEB: case MAR: case APR: case MAY: case JUN:
    499  1.7  christos 	case JUL: case AUG: case SEP: case OCT: case NOV: case DEC:
    500  1.7  christos 		/*
    501  1.7  christos 		 * do month mday [year]
    502  1.7  christos 		 */
    503  1.7  christos 		mon = sc_tokid - JAN;
    504  1.1       cgd 		expect(NUMBER);
    505  1.7  christos 		mday = atoi(sc_token);
    506  1.7  christos 		if (token() == NUMBER) {
    507  1.7  christos 			year = atoi(sc_token);
    508  1.7  christos 			token();
    509  1.1       cgd 		}
    510  1.7  christos 		assign_date(tm, mday, mon, year);
    511  1.7  christos 		break;
    512  1.7  christos 
    513  1.7  christos 	case SUN: case MON: case TUE:
    514  1.7  christos 	case WED: case THU: case FRI:
    515  1.7  christos 	case SAT:
    516  1.7  christos 		/* do a particular day of the week */
    517  1.7  christos 		wday = sc_tokid - SUN;
    518  1.7  christos 
    519  1.7  christos 		mday = tm->tm_mday;
    520  1.7  christos 
    521  1.7  christos 		/* if this day is < today, then roll to next week */
    522  1.7  christos 		if (wday < tm->tm_wday)
    523  1.7  christos 			mday += 7 - (tm->tm_wday - wday);
    524  1.7  christos 		else
    525  1.7  christos 			mday += (wday - tm->tm_wday);
    526  1.7  christos 
    527  1.7  christos 		tm->tm_wday = wday;
    528  1.1       cgd 
    529  1.9      tron 		assign_date(tm, mday, tm->tm_mon, tm->tm_year + TM_YEAR_BASE);
    530  1.7  christos 		break;
    531  1.7  christos 
    532  1.7  christos 	case NUMBER:
    533  1.1       cgd 		/*
    534  1.7  christos 		 * get numeric MMDDYY, mm/dd/yy, or dd.mm.yy
    535  1.1       cgd 		 */
    536  1.7  christos 		tlen = strlen(sc_token);
    537  1.7  christos 		mon = atoi(sc_token);
    538  1.7  christos 		token();
    539  1.1       cgd 
    540  1.7  christos 		if (sc_tokid == SLASH || sc_tokid == DOT) {
    541  1.7  christos 			int sep;
    542  1.1       cgd 
    543  1.7  christos 			sep = sc_tokid;
    544  1.7  christos 			expect(NUMBER);
    545  1.7  christos 			mday = atoi(sc_token);
    546  1.7  christos 			if (token() == sep) {
    547  1.7  christos 				expect(NUMBER);
    548  1.7  christos 				year = atoi(sc_token);
    549  1.7  christos 				token();
    550  1.7  christos 			}
    551  1.7  christos 
    552  1.7  christos 			/*
    553  1.7  christos 			 * flip months and days for european timing
    554  1.7  christos 			 */
    555  1.7  christos 			if (sep == DOT) {
    556  1.7  christos 				int x = mday;
    557  1.7  christos 				mday = mon;
    558  1.7  christos 				mon = x;
    559  1.7  christos 			}
    560  1.7  christos 		} else if (tlen == 6 || tlen == 8) {
    561  1.7  christos 			if (tlen == 8) {
    562  1.7  christos 				year = (mon % 10000) - 1900;
    563  1.7  christos 				mon /= 10000;
    564  1.7  christos 			} else {
    565  1.7  christos 				year = mon % 100;
    566  1.7  christos 				mon /= 100;
    567  1.7  christos 			}
    568  1.7  christos 			mday = mon % 100;
    569  1.7  christos 			mon /= 100;
    570  1.7  christos 		} else
    571  1.7  christos 			panic("garbled time");
    572  1.7  christos 
    573  1.7  christos 		mon--;
    574  1.7  christos 		if (mon < 0 || mon > 11 || mday < 1 || mday > 31)
    575  1.7  christos 			panic("garbled time");
    576  1.7  christos 
    577  1.7  christos 		assign_date(tm, mday, mon, year);
    578  1.7  christos 		break;
    579  1.7  christos 	} /* 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.7  christos 	/*
    591  1.7  christos 	 * Do the argument parsing, die if necessary, and return the
    592  1.7  christos 	 * time the job should be run.
    593  1.7  christos 	 */
    594  1.7  christos 	time_t nowtimer, runtimer;
    595  1.7  christos 	struct tm nowtime, runtime;
    596  1.7  christos 	int hr = 0;
    597  1.7  christos 	/* this MUST be initialized to zero for midnight/noon/teatime */
    598  1.7  christos 
    599  1.7  christos 	nowtimer = time(NULL);
    600  1.7  christos 	nowtime = *localtime(&nowtimer);
    601  1.7  christos 
    602  1.7  christos 	runtime = nowtime;
    603  1.7  christos 	runtime.tm_sec = 0;
    604  1.7  christos 	runtime.tm_isdst = 0;
    605  1.7  christos 
    606  1.7  christos 	if (argc <= optind)
    607  1.7  christos 		usage();
    608  1.7  christos 
    609  1.7  christos 	init_scanner(argc - optind, argv + optind);
    610  1.7  christos 
    611  1.7  christos 	switch (token()) {
    612  1.7  christos 	case NOW:	/* now is optional prefix for PLUS tree */
    613  1.7  christos 		expect(PLUS);
    614  1.7  christos 	case PLUS:
    615  1.7  christos 		plus(&runtime);
    616  1.7  christos 		break;
    617  1.7  christos 
    618  1.7  christos 	case NUMBER:
    619  1.7  christos 		tod(&runtime);
    620  1.7  christos 		month(&runtime);
    621  1.7  christos 		break;
    622  1.7  christos 
    623  1.7  christos 		/*
    624  1.7  christos 		 * evil coding for TEATIME|NOON|MIDNIGHT - we've initialised
    625  1.7  christos 		 * hr to zero up above, then fall into this case in such a
    626  1.7  christos 		 * way so we add +12 +4 hours to it for teatime, +12 hours
    627  1.7  christos 		 * to it for noon, and nothing at all for midnight, then
    628  1.7  christos 		 * set our runtime to that hour before leaping into the
    629  1.7  christos 		 * month scanner
    630  1.7  christos 		 */
    631  1.7  christos 	case TEATIME:
    632  1.7  christos 		hr += 4;
    633  1.7  christos 	case NOON:
    634  1.7  christos 		hr += 12;
    635  1.7  christos 	case MIDNIGHT:
    636  1.7  christos 		if (runtime.tm_hour >= hr) {
    637  1.7  christos 			runtime.tm_mday++;
    638  1.7  christos 			runtime.tm_wday++;
    639  1.7  christos 		}
    640  1.7  christos 		runtime.tm_hour = hr;
    641  1.7  christos 		runtime.tm_min = 0;
    642  1.7  christos 		token();
    643  1.7  christos 		/* fall through to month setting */
    644  1.7  christos 	default:
    645  1.7  christos 		month(&runtime);
    646  1.7  christos 		break;
    647  1.7  christos 	} /* ugly case statement */
    648  1.7  christos 	expect(EOF);
    649  1.7  christos 
    650  1.7  christos 	/*
    651  1.7  christos 	 * adjust for daylight savings time
    652  1.7  christos 	 */
    653  1.7  christos 	runtime.tm_isdst = -1;
    654  1.1       cgd 	runtimer = mktime(&runtime);
    655  1.7  christos 	if (runtime.tm_isdst > 0) {
    656  1.7  christos 		runtimer -= 3600;
    657  1.7  christos 		runtimer = mktime(&runtime);
    658  1.7  christos 	}
    659  1.1       cgd 
    660  1.7  christos 	if (runtimer < 0)
    661  1.7  christos 		panic("garbled time");
    662  1.1       cgd 
    663  1.7  christos 	if (nowtimer > runtimer)
    664  1.7  christos 		panic("Trying to travel back in time");
    665  1.1       cgd 
    666  1.7  christos 	return (runtimer);
    667  1.1       cgd } /* parsetime */
    668