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