xref: /openbsd-src/usr.bin/at/parsetime.c (revision 3e8bfcff21d087075f3de0a2d8fa31d01d9c7815)
1*3e8bfcffSnaddy /*	$OpenBSD: parsetime.c,v 1.27 2019/02/16 15:23:33 naddy Exp $	*/
2df930be7Sderaadt 
3df930be7Sderaadt /*
4df930be7Sderaadt  * parsetime.c - parse time for at(1)
55ec155afSmillert  * Copyright (C) 1993, 1994  Thomas Koenig
6df930be7Sderaadt  *
7df930be7Sderaadt  * modifications for english-language times
8df930be7Sderaadt  * Copyright (C) 1993  David Parsons
9df930be7Sderaadt  *
10df930be7Sderaadt  * Redistribution and use in source and binary forms, with or without
11df930be7Sderaadt  * modification, are permitted provided that the following conditions
12df930be7Sderaadt  * are met:
13df930be7Sderaadt  * 1. Redistributions of source code must retain the above copyright
14df930be7Sderaadt  *    notice, this list of conditions and the following disclaimer.
15df930be7Sderaadt  * 2. The name of the author(s) may not be used to endorse or promote
16df930be7Sderaadt  *    products derived from this software without specific prior written
17df930be7Sderaadt  *    permission.
18df930be7Sderaadt  *
19df930be7Sderaadt  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
20df930be7Sderaadt  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21df930be7Sderaadt  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
225ec155afSmillert  * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
23df930be7Sderaadt  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24df930be7Sderaadt  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25df930be7Sderaadt  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26faa8d6b3Skrw  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27df930be7Sderaadt  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28df930be7Sderaadt  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29df930be7Sderaadt  *
30769e1e40Smillert  *  at [NOW] PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS|MONTHS|YEARS
31df930be7Sderaadt  *     /NUMBER [DOT NUMBER] [AM|PM]\ /[MONTH NUMBER [NUMBER]]             \
32df930be7Sderaadt  *     |NOON                       | |[TOMORROW]                          |
335ec155afSmillert  *     |MIDNIGHT                   | |[DAY OF WEEK]                       |
345ec155afSmillert  *     \TEATIME                    / |NUMBER [SLASH NUMBER [SLASH NUMBER]]|
35769e1e40Smillert  *                                   \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS|MONTHS|YEARS/
36df930be7Sderaadt  */
37df930be7Sderaadt 
38df930be7Sderaadt #include <sys/types.h>
39ea5f32d2Smillert 
40ea5f32d2Smillert #include <err.h>
41df930be7Sderaadt #include <errno.h>
426eb82116Smillert #include <ctype.h>
43df930be7Sderaadt #include <stdio.h>
44df930be7Sderaadt #include <stdlib.h>
45df930be7Sderaadt #include <string.h>
46df930be7Sderaadt #include <time.h>
47df930be7Sderaadt #include <unistd.h>
48df930be7Sderaadt 
49e134e629Smillert #include "globals.h"
50df930be7Sderaadt #include "at.h"
51df930be7Sderaadt 
52df930be7Sderaadt /* Structures and unions */
53df930be7Sderaadt 
54df930be7Sderaadt enum {	/* symbols */
55df930be7Sderaadt 	MIDNIGHT, NOON, TEATIME,
56df930be7Sderaadt 	PM, AM, TOMORROW, TODAY, NOW,
57769e1e40Smillert 	MINUTES, HOURS, DAYS, WEEKS, MONTHS, YEARS,
58769e1e40Smillert 	NUMBER, NEXT, PLUS, DOT, SLASH, ID, JUNK,
59df930be7Sderaadt 	JAN, FEB, MAR, APR, MAY, JUN,
605ec155afSmillert 	JUL, AUG, SEP, OCT, NOV, DEC,
615ec155afSmillert 	SUN, MON, TUE, WED, THU, FRI, SAT
62df930be7Sderaadt };
63df930be7Sderaadt 
64df930be7Sderaadt /*
65df930be7Sderaadt  * parse translation table - table driven parsers can be your FRIEND!
66df930be7Sderaadt  */
67df930be7Sderaadt struct {
68df930be7Sderaadt 	char *name;	/* token name */
69df930be7Sderaadt 	int value;	/* token id */
705ec155afSmillert 	int plural;	/* is this plural? */
71df930be7Sderaadt } Specials[] = {
725ec155afSmillert 	{ "midnight", MIDNIGHT, 0 },	/* 00:00:00 of today or tomorrow */
735ec155afSmillert 	{ "noon", NOON, 0 },		/* 12:00:00 of today or tomorrow */
745ec155afSmillert 	{ "teatime", TEATIME, 0 },	/* 16:00:00 of today or tomorrow */
755ec155afSmillert 	{ "am", AM, 0 },		/* morning times for 0-12 clock */
765ec155afSmillert 	{ "pm", PM, 0 },		/* evening times for 0-12 clock */
775ec155afSmillert 	{ "tomorrow", TOMORROW, 0 },	/* execute 24 hours from time */
785ec155afSmillert 	{ "today", TODAY, 0 },		/* execute today - don't advance time */
795ec155afSmillert 	{ "now", NOW, 0 },		/* opt prefix for PLUS */
80769e1e40Smillert 	{ "next", NEXT, 0 },		/* opt prefix for + 1 */
81df930be7Sderaadt 
825ec155afSmillert 	{ "minute", MINUTES, 0 },	/* minutes multiplier */
835ec155afSmillert 	{ "min", MINUTES, 0 },
845ec155afSmillert 	{ "m", MINUTES, 0 },
855ec155afSmillert 	{ "minutes", MINUTES, 1 },	/* (pluralized) */
865ec155afSmillert 	{ "hour", HOURS, 0 },		/* hours ... */
875ec155afSmillert 	{ "hr", HOURS, 0 },		/* abbreviated */
885ec155afSmillert 	{ "h", HOURS, 0 },
895ec155afSmillert 	{ "hours", HOURS, 1 },		/* (pluralized) */
905ec155afSmillert 	{ "day", DAYS, 0 },		/* days ... */
915ec155afSmillert 	{ "d", DAYS, 0 },
925ec155afSmillert 	{ "days", DAYS, 1 },		/* (pluralized) */
935ec155afSmillert 	{ "week", WEEKS, 0 },		/* week ... */
945ec155afSmillert 	{ "w", WEEKS, 0 },
955ec155afSmillert 	{ "weeks", WEEKS, 1 },		/* (pluralized) */
96769e1e40Smillert 	{ "month", MONTHS, 0 },		/* month ... */
97769e1e40Smillert 	{ "mo", MONTHS, 0 },
98769e1e40Smillert 	{ "mth", MONTHS, 0 },
99769e1e40Smillert 	{ "months", MONTHS, 1 },	/* (pluralized) */
100769e1e40Smillert 	{ "year", YEARS, 0 },		/* year ... */
101769e1e40Smillert 	{ "y", YEARS, 0 },
102769e1e40Smillert 	{ "years", YEARS, 1 },		/* (pluralized) */
1035ec155afSmillert 	{ "jan", JAN, 0 },
1045ec155afSmillert 	{ "feb", FEB, 0 },
1055ec155afSmillert 	{ "mar", MAR, 0 },
1065ec155afSmillert 	{ "apr", APR, 0 },
1075ec155afSmillert 	{ "may", MAY, 0 },
1085ec155afSmillert 	{ "jun", JUN, 0 },
1095ec155afSmillert 	{ "jul", JUL, 0 },
1105ec155afSmillert 	{ "aug", AUG, 0 },
1115ec155afSmillert 	{ "sep", SEP, 0 },
1125ec155afSmillert 	{ "oct", OCT, 0 },
1135ec155afSmillert 	{ "nov", NOV, 0 },
1145ec155afSmillert 	{ "dec", DEC, 0 },
115aaf0011eSderaadt 	{ "january", JAN,0 },
116aaf0011eSderaadt 	{ "february", FEB,0 },
117aaf0011eSderaadt 	{ "march", MAR,0 },
118aaf0011eSderaadt 	{ "april", APR,0 },
119aaf0011eSderaadt 	{ "may", MAY,0 },
120aaf0011eSderaadt 	{ "june", JUN,0 },
121aaf0011eSderaadt 	{ "july", JUL,0 },
122aaf0011eSderaadt 	{ "august", AUG,0 },
123aaf0011eSderaadt 	{ "september", SEP,0 },
124aaf0011eSderaadt 	{ "october", OCT,0 },
125aaf0011eSderaadt 	{ "november", NOV,0 },
126aaf0011eSderaadt 	{ "december", DEC,0 },
1275ec155afSmillert 	{ "sunday", SUN, 0 },
1285ec155afSmillert 	{ "sun", SUN, 0 },
1295ec155afSmillert 	{ "monday", MON, 0 },
1305ec155afSmillert 	{ "mon", MON, 0 },
1315ec155afSmillert 	{ "tuesday", TUE, 0 },
1325ec155afSmillert 	{ "tue", TUE, 0 },
1335ec155afSmillert 	{ "wednesday", WED, 0 },
1345ec155afSmillert 	{ "wed", WED, 0 },
1355ec155afSmillert 	{ "thursday", THU, 0 },
1365ec155afSmillert 	{ "thu", THU, 0 },
1375ec155afSmillert 	{ "friday", FRI, 0 },
1385ec155afSmillert 	{ "fri", FRI, 0 },
1395ec155afSmillert 	{ "saturday", SAT, 0 },
1405ec155afSmillert 	{ "sat", SAT, 0 },
141df930be7Sderaadt };
142df930be7Sderaadt 
143df930be7Sderaadt static char **scp;	/* scanner - pointer at arglist */
1443e000603Scloder static int scc;		/* scanner - count of remaining arguments */
145df930be7Sderaadt static char *sct;	/* scanner - next char pointer in current argument */
146df930be7Sderaadt static int need;	/* scanner - need to advance to next argument */
147df930be7Sderaadt static char *sc_token;	/* scanner - token buffer */
148e4d25771Stodd static size_t sc_len;   /* scanner - length of token buffer */
149df930be7Sderaadt static int sc_tokid;	/* scanner - token id */
1505ec155afSmillert static int sc_tokplur;	/* scanner - is token plural? */
151df930be7Sderaadt 
152df930be7Sderaadt /*
153df930be7Sderaadt  * parse a token, checking if it's something special to us
154df930be7Sderaadt  */
155df930be7Sderaadt static int
parse_token(char * arg)156cf17aafdSmillert parse_token(char *arg)
157df930be7Sderaadt {
158df930be7Sderaadt 	int i;
159df930be7Sderaadt 
1605ec155afSmillert 	for (i=0; i < sizeof(Specials) / sizeof(Specials[0]); i++) {
161df930be7Sderaadt 		if (strcasecmp(Specials[i].name, arg) == 0) {
1625ec155afSmillert 			sc_tokplur = Specials[i].plural;
1635ec155afSmillert 		    	return (sc_tokid = Specials[i].value);
1645ec155afSmillert 		}
165df930be7Sderaadt 	}
166df930be7Sderaadt 
167df930be7Sderaadt 	/* not special - must be some random id */
1685ec155afSmillert 	return (ID);
1696eb82116Smillert }
170df930be7Sderaadt 
171df930be7Sderaadt 
172df930be7Sderaadt /*
173df930be7Sderaadt  * init_scanner() sets up the scanner to eat arguments
174df930be7Sderaadt  */
175e134e629Smillert static int
init_scanner(int argc,char ** argv)176cf17aafdSmillert init_scanner(int argc, char **argv)
177df930be7Sderaadt {
178df930be7Sderaadt 	scp = argv;
179df930be7Sderaadt 	scc = argc;
180df930be7Sderaadt 	need = 1;
181df930be7Sderaadt 	sc_len = 1;
1825ec155afSmillert 	while (argc-- > 0)
1835ec155afSmillert 		sc_len += strlen(*argv++);
184df930be7Sderaadt 
185cfff592fSderaadt 	if ((sc_token = malloc(sc_len)) == NULL) {
186ea5f32d2Smillert 		warn(NULL);
187e134e629Smillert 		return (-1);
188e134e629Smillert 	}
189e134e629Smillert 	return (0);
1906eb82116Smillert }
191df930be7Sderaadt 
192df930be7Sderaadt /*
193df930be7Sderaadt  * token() fetches a token from the input stream
194df930be7Sderaadt  */
195df930be7Sderaadt static int
token(void)196cf17aafdSmillert token(void)
197df930be7Sderaadt {
198df930be7Sderaadt 	int idx;
199df930be7Sderaadt 
200cf17aafdSmillert 	for (;;) {
201e134e629Smillert 		bzero(sc_token, sc_len);
202df930be7Sderaadt 		sc_tokid = EOF;
2035ec155afSmillert 		sc_tokplur = 0;
204df930be7Sderaadt 		idx = 0;
205df930be7Sderaadt 
206df930be7Sderaadt 		/*
2075ec155afSmillert 		 * if we need to read another argument, walk along the
2085ec155afSmillert 		 * argument list; when we fall off the arglist, we'll
2095ec155afSmillert 		 * just return EOF forever
210df930be7Sderaadt 		 */
211df930be7Sderaadt 		if (need) {
212df930be7Sderaadt 			if (scc < 1)
2135ec155afSmillert 				return (sc_tokid);
214df930be7Sderaadt 			sct = *scp;
215df930be7Sderaadt 			scp++;
216df930be7Sderaadt 			scc--;
217df930be7Sderaadt 			need = 0;
218df930be7Sderaadt 		}
219df930be7Sderaadt 		/*
220df930be7Sderaadt 		 * eat whitespace now - if we walk off the end of the argument,
221df930be7Sderaadt 		 * we'll continue, which puts us up at the top of the while loop
222df930be7Sderaadt 		 * to fetch the next argument in
223df930be7Sderaadt 		 */
2246d73225dSderaadt 		while (isspace((unsigned char)*sct))
225df930be7Sderaadt 			++sct;
226df930be7Sderaadt 		if (!*sct) {
227df930be7Sderaadt 			need = 1;
228df930be7Sderaadt 			continue;
229df930be7Sderaadt 		}
230df930be7Sderaadt 
231df930be7Sderaadt 		/*
232df930be7Sderaadt 		 * preserve the first character of the new token
233df930be7Sderaadt 		 */
234df930be7Sderaadt 		sc_token[0] = *sct++;
235df930be7Sderaadt 
236df930be7Sderaadt 		/*
237df930be7Sderaadt 		 * then see what it is
238df930be7Sderaadt 		 */
2396d73225dSderaadt 		if (isdigit((unsigned char)sc_token[0])) {
2406d73225dSderaadt 			while (isdigit((unsigned char)*sct))
241df930be7Sderaadt 				sc_token[++idx] = *sct++;
242df930be7Sderaadt 			sc_token[++idx] = 0;
2435ec155afSmillert 			return ((sc_tokid = NUMBER));
2446d73225dSderaadt 		} else if (isalpha((unsigned char)sc_token[0])) {
2456d73225dSderaadt 			while (isalpha((unsigned char)*sct))
246df930be7Sderaadt 				sc_token[++idx] = *sct++;
247df930be7Sderaadt 			sc_token[++idx] = 0;
2485ec155afSmillert 			return (parse_token(sc_token));
249df930be7Sderaadt 		}
250df930be7Sderaadt 		else if (sc_token[0] == ':' || sc_token[0] == '.')
2515ec155afSmillert 			return ((sc_tokid = DOT));
252df930be7Sderaadt 		else if (sc_token[0] == '+')
2535ec155afSmillert 			return ((sc_tokid = PLUS));
254df930be7Sderaadt 		else if (sc_token[0] == '/')
2555ec155afSmillert 			return ((sc_tokid = SLASH));
256df930be7Sderaadt 		else
2575ec155afSmillert 			return ((sc_tokid = JUNK));
2586eb82116Smillert 	}
2596eb82116Smillert }
260df930be7Sderaadt 
261df930be7Sderaadt 
262df930be7Sderaadt /*
263df930be7Sderaadt  * plonk() gives an appropriate error message if a token is incorrect
264df930be7Sderaadt  */
265df930be7Sderaadt static void
plonk(int tok)266cf17aafdSmillert plonk(int tok)
267df930be7Sderaadt {
268ea5f32d2Smillert 	warnx("%s time", (tok == EOF) ? "incomplete" : "garbled");
2696eb82116Smillert }
270df930be7Sderaadt 
271df930be7Sderaadt 
272df930be7Sderaadt /*
273e134e629Smillert  * expect() gets a token and returns -1 if it's not the token we want
274df930be7Sderaadt  */
275e134e629Smillert static int
expect(int desired)276cf17aafdSmillert expect(int desired)
277df930be7Sderaadt {
278e134e629Smillert 	if (token() != desired) {
279e134e629Smillert 		plonk(sc_tokid);
280e134e629Smillert 		return (-1);
281e134e629Smillert 	}
282e134e629Smillert 	return (0);
2836eb82116Smillert }
284df930be7Sderaadt 
285df930be7Sderaadt 
286df930be7Sderaadt /*
287df930be7Sderaadt  * dateadd() adds a number of minutes to a date.  It is extraordinarily
288df930be7Sderaadt  * stupid regarding day-of-month overflow, and will most likely not
289df930be7Sderaadt  * work properly
290df930be7Sderaadt  */
291df930be7Sderaadt static void
dateadd(int minutes,struct tm * tm)292cf17aafdSmillert dateadd(int minutes, struct tm *tm)
293df930be7Sderaadt {
294df930be7Sderaadt 	/* increment days */
295df930be7Sderaadt 
296df930be7Sderaadt 	while (minutes > 24*60) {
297df930be7Sderaadt 		minutes -= 24*60;
298df930be7Sderaadt 		tm->tm_mday++;
299df930be7Sderaadt 	}
300df930be7Sderaadt 
301df930be7Sderaadt 	/* increment hours */
302df930be7Sderaadt 	while (minutes > 60) {
303df930be7Sderaadt 		minutes -= 60;
304df930be7Sderaadt 		tm->tm_hour++;
305df930be7Sderaadt 		if (tm->tm_hour > 23) {
306df930be7Sderaadt 			tm->tm_mday++;
307df930be7Sderaadt 			tm->tm_hour = 0;
308df930be7Sderaadt 		}
309df930be7Sderaadt 	}
310df930be7Sderaadt 
311df930be7Sderaadt 	/* increment minutes */
312df930be7Sderaadt 	tm->tm_min += minutes;
313df930be7Sderaadt 
314df930be7Sderaadt 	if (tm->tm_min > 59) {
315df930be7Sderaadt 		tm->tm_hour++;
316df930be7Sderaadt 		tm->tm_min -= 60;
317df930be7Sderaadt 
318df930be7Sderaadt 		if (tm->tm_hour > 23) {
319df930be7Sderaadt 			tm->tm_mday++;
320df930be7Sderaadt 			tm->tm_hour = 0;
321df930be7Sderaadt 		}
322df930be7Sderaadt 	}
3236eb82116Smillert }
324df930be7Sderaadt 
325df930be7Sderaadt 
326df930be7Sderaadt /*
327df930be7Sderaadt  * plus() parses a now + time
328df930be7Sderaadt  *
329769e1e40Smillert  *  at [NOW] PLUS NUMBER [MINUTES|HOURS|DAYS|WEEKS|MONTHS|YEARS]
330df930be7Sderaadt  *
331df930be7Sderaadt  */
332e134e629Smillert static int
plus(struct tm * tm)333cf17aafdSmillert plus(struct tm *tm)
334df930be7Sderaadt {
335769e1e40Smillert 	int increment;
3365ec155afSmillert 	int expectplur;
337df930be7Sderaadt 
338769e1e40Smillert 	if (sc_tokid == NEXT) {
339769e1e40Smillert 		increment = 1;
340769e1e40Smillert 		expectplur = 0;
341769e1e40Smillert 	} else {
342e134e629Smillert 		if (expect(NUMBER) != 0)
343e134e629Smillert 			return (-1);
344769e1e40Smillert 		increment = atoi(sc_token);
345769e1e40Smillert 		expectplur = (increment != 1) ? 1 : 0;
346769e1e40Smillert 	}
347df930be7Sderaadt 
348df930be7Sderaadt 	switch (token()) {
349769e1e40Smillert 	case YEARS:
350769e1e40Smillert 		tm->tm_year += increment;
351769e1e40Smillert 		return (0);
352769e1e40Smillert 	case MONTHS:
353769e1e40Smillert 		tm->tm_mon += increment;
354769e1e40Smillert 		while (tm->tm_mon >= 12) {
355769e1e40Smillert 		    tm->tm_year++;
356769e1e40Smillert 		    tm->tm_mon -= 12;
357769e1e40Smillert 		}
358769e1e40Smillert 		return (0);
359df930be7Sderaadt 	case WEEKS:
360769e1e40Smillert 		increment *= 7;
3613e000603Scloder 		/* FALLTHROUGH */
362df930be7Sderaadt 	case DAYS:
363769e1e40Smillert 		increment *= 24;
3643e000603Scloder 		/* FALLTHROUGH */
365df930be7Sderaadt 	case HOURS:
366769e1e40Smillert 		increment *= 60;
3673e000603Scloder 		/* FALLTHROUGH */
368df930be7Sderaadt 	case MINUTES:
3695ec155afSmillert 		if (expectplur != sc_tokplur)
370ea5f32d2Smillert 			warnx("pluralization is wrong");
371769e1e40Smillert 		dateadd(increment, tm);
372e134e629Smillert 		return (0);
373df930be7Sderaadt 	}
3745ec155afSmillert 
375df930be7Sderaadt 	plonk(sc_tokid);
376e134e629Smillert 	return (-1);
3776eb82116Smillert }
378df930be7Sderaadt 
379df930be7Sderaadt 
380df930be7Sderaadt /*
381df930be7Sderaadt  * tod() computes the time of day
382df930be7Sderaadt  *     [NUMBER [DOT NUMBER] [AM|PM]]
383df930be7Sderaadt  */
384e134e629Smillert static int
tod(struct tm * tm)385cf17aafdSmillert tod(struct tm *tm)
386df930be7Sderaadt {
387df930be7Sderaadt 	int hour, minute = 0;
3885ec155afSmillert 	size_t tlen;
389df930be7Sderaadt 
390df930be7Sderaadt 	hour = atoi(sc_token);
391df930be7Sderaadt 	tlen = strlen(sc_token);
392df930be7Sderaadt 
393df930be7Sderaadt 	/*
394df930be7Sderaadt 	 * first pick out the time of day - if it's 4 digits, we assume
395df930be7Sderaadt 	 * a HHMM time, otherwise it's HH DOT MM time
396df930be7Sderaadt 	 */
397df930be7Sderaadt 	if (token() == DOT) {
398e134e629Smillert 		if (expect(NUMBER) != 0)
399e134e629Smillert 			return (-1);
400df930be7Sderaadt 		minute = atoi(sc_token);
4015ec155afSmillert 		if (minute > 59)
402e134e629Smillert 			goto bad;
403df930be7Sderaadt 		token();
404df930be7Sderaadt 	} else if (tlen == 4) {
405df930be7Sderaadt 		minute = hour % 100;
4067a4c58bbSderaadt 		if (minute > 59)
407e134e629Smillert 			goto bad;
4085ec155afSmillert 		hour = hour / 100;
4095ec155afSmillert 	}
4107a4c58bbSderaadt 
411df930be7Sderaadt 	/*
412df930be7Sderaadt 	 * check if an AM or PM specifier was given
413df930be7Sderaadt 	 */
414df930be7Sderaadt 	if (sc_tokid == AM || sc_tokid == PM) {
415df930be7Sderaadt 		if (hour > 12)
416e134e629Smillert 			goto bad;
417df930be7Sderaadt 
4185ec155afSmillert 		if (sc_tokid == PM) {
4195ec155afSmillert 			if (hour != 12)	/* 12:xx PM is 12:xx, not 24:xx */
420df930be7Sderaadt 				hour += 12;
4215ec155afSmillert 		} else {
4225ec155afSmillert 			if (hour == 12)	/* 12:xx AM is 00:xx, not 12:xx */
4235ec155afSmillert 				hour = 0;
4245ec155afSmillert 		}
425df930be7Sderaadt 		token();
426df930be7Sderaadt 	} else if (hour > 23)
427e134e629Smillert 		goto bad;
428df930be7Sderaadt 
429df930be7Sderaadt 	/*
430df930be7Sderaadt 	 * if we specify an absolute time, we don't want to bump the day even
431df930be7Sderaadt 	 * if we've gone past that time - but if we're specifying a time plus
432df930be7Sderaadt 	 * a relative offset, it's okay to bump things
433df930be7Sderaadt 	 */
434769e1e40Smillert 	if ((sc_tokid == EOF || sc_tokid == PLUS || sc_tokid == NEXT) &&
435*3e8bfcffSnaddy 	    (tm->tm_hour > hour ||
436*3e8bfcffSnaddy 	    (tm->tm_hour == hour && tm->tm_min > minute))) {
437df930be7Sderaadt 		tm->tm_mday++;
4385ec155afSmillert 		tm->tm_wday++;
4395ec155afSmillert 	}
440df930be7Sderaadt 
441df930be7Sderaadt 	tm->tm_hour = hour;
442df930be7Sderaadt 	tm->tm_min = minute;
4435ec155afSmillert 	if (tm->tm_hour == 24) {
4445ec155afSmillert 		tm->tm_hour = 0;
4455ec155afSmillert 		tm->tm_mday++;
4465ec155afSmillert 	}
447e134e629Smillert 	return (0);
448e134e629Smillert bad:
449ea5f32d2Smillert 	warnx("garbled time");
450e134e629Smillert 	return (-1);
4516eb82116Smillert }
452df930be7Sderaadt 
453df930be7Sderaadt 
454df930be7Sderaadt /*
455df930be7Sderaadt  * assign_date() assigns a date, wrapping to next year if needed
456df930be7Sderaadt  */
457df930be7Sderaadt static void
assign_date(struct tm * tm,int mday,int mon,int year)458cf17aafdSmillert assign_date(struct tm *tm, int mday, int mon, int year)
459df930be7Sderaadt {
4607070d708Smillert 
461a2ba2f10Salex 	/*
4627070d708Smillert 	 * Convert year into tm_year format (year - 1900).
4637070d708Smillert 	 * We may be given the year in 2 digit, 4 digit, or tm_year format.
464a2ba2f10Salex 	 */
4657070d708Smillert 	if (year != -1) {
46678badebcSmillert 		if (year >= 1900)
46778badebcSmillert 			year -= 1900;	/* convert from 4 digit year */
4687070d708Smillert 		else if (year < 100) {
4697070d708Smillert 			/* Convert to tm_year assuming current century */
4707070d708Smillert 			year += (tm->tm_year / 100) * 100;
4717070d708Smillert 
4727070d708Smillert 			if (year == tm->tm_year - 1)
4737070d708Smillert 				year++;		/* Common off by one error */
4747070d708Smillert 			else if (year < tm->tm_year)
4757070d708Smillert 				year += 100;	/* must be in next century */
4767070d708Smillert 		}
477df930be7Sderaadt 	}
478df930be7Sderaadt 
479df930be7Sderaadt 	if (year < 0 &&
480df930be7Sderaadt 	    (tm->tm_mon > mon ||(tm->tm_mon == mon && tm->tm_mday > mday)))
481df930be7Sderaadt 		year = tm->tm_year + 1;
482df930be7Sderaadt 
483df930be7Sderaadt 	tm->tm_mday = mday;
484df930be7Sderaadt 	tm->tm_mon = mon;
485df930be7Sderaadt 
486df930be7Sderaadt 	if (year >= 0)
487df930be7Sderaadt 		tm->tm_year = year;
4886eb82116Smillert }
489df930be7Sderaadt 
490df930be7Sderaadt 
491df930be7Sderaadt /*
492df930be7Sderaadt  * month() picks apart a month specification
493df930be7Sderaadt  *
494df930be7Sderaadt  *  /[<month> NUMBER [NUMBER]]           \
495df930be7Sderaadt  *  |[TOMORROW]                          |
4965ec155afSmillert  *  |[DAY OF WEEK]                       |
497df930be7Sderaadt  *  |NUMBER [SLASH NUMBER [SLASH NUMBER]]|
498769e1e40Smillert  *  \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS|MONTHS|YEARS/
499df930be7Sderaadt  */
500e134e629Smillert static int
month(struct tm * tm)501cf17aafdSmillert month(struct tm *tm)
502df930be7Sderaadt {
5035ec155afSmillert 	int year = (-1);
5045ec155afSmillert 	int mday, wday, mon;
5055ec155afSmillert 	size_t tlen;
506df930be7Sderaadt 
507df930be7Sderaadt 	switch (sc_tokid) {
508769e1e40Smillert 	case NEXT:
509df930be7Sderaadt 	case PLUS:
510e134e629Smillert 		if (plus(tm) != 0)
511e134e629Smillert 			return (-1);
512df930be7Sderaadt 		break;
513df930be7Sderaadt 
514df930be7Sderaadt 	case TOMORROW:
515df930be7Sderaadt 		/* do something tomorrow */
516df930be7Sderaadt 		tm->tm_mday++;
5175ec155afSmillert 		tm->tm_wday++;
5185ec155afSmillert 	case TODAY:
5195ec155afSmillert 		/* force ourselves to stay in today - no further processing */
520df930be7Sderaadt 		token();
521df930be7Sderaadt 		break;
522df930be7Sderaadt 
523df930be7Sderaadt 	case JAN: case FEB: case MAR: case APR: case MAY: case JUN:
524df930be7Sderaadt 	case JUL: case AUG: case SEP: case OCT: case NOV: case DEC:
525df930be7Sderaadt 		/*
526df930be7Sderaadt 		 * do month mday [year]
527df930be7Sderaadt 		 */
5285ec155afSmillert 		mon = sc_tokid - JAN;
529e134e629Smillert 		if (expect(NUMBER) != 0)
530e134e629Smillert 			return (-1);
5315ec155afSmillert 		mday = atoi(sc_token);
532df930be7Sderaadt 		if (token() == NUMBER) {
5335ec155afSmillert 			year = atoi(sc_token);
534df930be7Sderaadt 			token();
535df930be7Sderaadt 		}
536df930be7Sderaadt 		assign_date(tm, mday, mon, year);
537df930be7Sderaadt 		break;
538df930be7Sderaadt 
5395ec155afSmillert 	case SUN: case MON: case TUE:
5405ec155afSmillert 	case WED: case THU: case FRI:
5415ec155afSmillert 	case SAT:
5425ec155afSmillert 		/* do a particular day of the week */
5435ec155afSmillert 		wday = sc_tokid - SUN;
5445ec155afSmillert 
5455ec155afSmillert 		mday = tm->tm_mday;
5465ec155afSmillert 
5475ec155afSmillert 		/* if this day is < today, then roll to next week */
5485ec155afSmillert 		if (wday < tm->tm_wday)
5495ec155afSmillert 			mday += 7 - (tm->tm_wday - wday);
5505ec155afSmillert 		else
5515ec155afSmillert 			mday += (wday - tm->tm_wday);
5525ec155afSmillert 
5535ec155afSmillert 		tm->tm_wday = wday;
5545ec155afSmillert 
5555ec155afSmillert 		assign_date(tm, mday, tm->tm_mon, tm->tm_year);
5565ec155afSmillert 		break;
5575ec155afSmillert 
558df930be7Sderaadt 	case NUMBER:
559df930be7Sderaadt 		/*
560df930be7Sderaadt 		 * get numeric MMDDYY, mm/dd/yy, or dd.mm.yy
561df930be7Sderaadt 		 */
562df930be7Sderaadt 		tlen = strlen(sc_token);
5635ec155afSmillert 		mon = atoi(sc_token);
564df930be7Sderaadt 		token();
565df930be7Sderaadt 
566df930be7Sderaadt 		if (sc_tokid == SLASH || sc_tokid == DOT) {
567df930be7Sderaadt 			int sep;
568df930be7Sderaadt 
569df930be7Sderaadt 			sep = sc_tokid;
570e134e629Smillert 			if (expect(NUMBER) != 0)
571e134e629Smillert 				return (-1);
5725ec155afSmillert 			mday = atoi(sc_token);
573df930be7Sderaadt 			if (token() == sep) {
574e134e629Smillert 				if (expect(NUMBER) != 0)
575e134e629Smillert 					return (-1);
5765ec155afSmillert 				year = atoi(sc_token);
577df930be7Sderaadt 				token();
578df930be7Sderaadt 			}
579df930be7Sderaadt 
580df930be7Sderaadt 			/*
581df930be7Sderaadt 			 * flip months and days for european timing
582df930be7Sderaadt 			 */
583df930be7Sderaadt 			if (sep == DOT) {
584df930be7Sderaadt 				int x = mday;
585df930be7Sderaadt 				mday = mon;
586df930be7Sderaadt 				mon = x;
587df930be7Sderaadt 			}
588df930be7Sderaadt 		} else if (tlen == 6 || tlen == 8) {
589df930be7Sderaadt 			if (tlen == 8) {
59078badebcSmillert 				year = (mon % 10000) - 1900;
591df930be7Sderaadt 				mon /= 10000;
592df930be7Sderaadt 			} else {
593df930be7Sderaadt 				year = mon % 100;
594df930be7Sderaadt 				mon /= 100;
595df930be7Sderaadt 			}
596df930be7Sderaadt 			mday = mon % 100;
597df930be7Sderaadt 			mon /= 100;
598df930be7Sderaadt 		} else
599e134e629Smillert 			goto bad;
600df930be7Sderaadt 
601df930be7Sderaadt 		mon--;
602df930be7Sderaadt 		if (mon < 0 || mon > 11 || mday < 1 || mday > 31)
603e134e629Smillert 			goto bad;
604df930be7Sderaadt 
605df930be7Sderaadt 		assign_date(tm, mday, mon, year);
606df930be7Sderaadt 		break;
607e134e629Smillert 	}
608e134e629Smillert 	return (0);
609e134e629Smillert bad:
610ea5f32d2Smillert 	warnx("garbled time");
611e134e629Smillert 	return (-1);
6126eb82116Smillert }
613df930be7Sderaadt 
614df930be7Sderaadt 
615df930be7Sderaadt time_t
parsetime(int argc,char ** argv)616cf17aafdSmillert parsetime(int argc, char **argv)
617df930be7Sderaadt {
618df930be7Sderaadt 	/*
6195ec155afSmillert 	 * Do the argument parsing, die if necessary, and return the
6205ec155afSmillert 	 * time the job should be run.
621df930be7Sderaadt 	 */
622df930be7Sderaadt 	time_t nowtimer, runtimer;
623df930be7Sderaadt 	struct tm nowtime, runtime;
624df930be7Sderaadt 	int hr = 0;
625df930be7Sderaadt 	/* this MUST be initialized to zero for midnight/noon/teatime */
626df930be7Sderaadt 
627e134e629Smillert 	if (argc == 0)
628e134e629Smillert 		return (-1);
629e134e629Smillert 
630df930be7Sderaadt 	nowtimer = time(NULL);
631df930be7Sderaadt 	nowtime = *localtime(&nowtimer);
632df930be7Sderaadt 
633df930be7Sderaadt 	runtime = nowtime;
634df930be7Sderaadt 	runtime.tm_sec = 0;
635df930be7Sderaadt 	runtime.tm_isdst = 0;
636df930be7Sderaadt 
637e134e629Smillert 	if (init_scanner(argc, argv) == -1)
638e134e629Smillert 		return (-1);
639df930be7Sderaadt 
640df930be7Sderaadt 	switch (token()) {
641df930be7Sderaadt 	case NOW:	/* now is optional prefix for PLUS tree */
6426cb3b6d6Smillert 		token();
6436cb3b6d6Smillert 		if (sc_tokid == EOF) {
6446cb3b6d6Smillert 			runtime = nowtime;
6456cb3b6d6Smillert 			break;
6466cb3b6d6Smillert 		}
647769e1e40Smillert 		else if (sc_tokid != PLUS && sc_tokid != NEXT)
6486cb3b6d6Smillert 			plonk(sc_tokid);
649769e1e40Smillert 	case NEXT:
650df930be7Sderaadt 	case PLUS:
651e134e629Smillert 		if (plus(&runtime) != 0)
652e134e629Smillert 			return (-1);
653df930be7Sderaadt 		break;
654df930be7Sderaadt 
655df930be7Sderaadt 	case NUMBER:
656e134e629Smillert 		if (tod(&runtime) != 0 || month(&runtime) != 0)
657e134e629Smillert 			return (-1);
658df930be7Sderaadt 		break;
659df930be7Sderaadt 
660df930be7Sderaadt 		/*
661df930be7Sderaadt 		 * evil coding for TEATIME|NOON|MIDNIGHT - we've initialised
662df930be7Sderaadt 		 * hr to zero up above, then fall into this case in such a
663df930be7Sderaadt 		 * way so we add +12 +4 hours to it for teatime, +12 hours
664df930be7Sderaadt 		 * to it for noon, and nothing at all for midnight, then
665df930be7Sderaadt 		 * set our runtime to that hour before leaping into the
666df930be7Sderaadt 		 * month scanner
667df930be7Sderaadt 		 */
668df930be7Sderaadt 	case TEATIME:
669df930be7Sderaadt 		hr += 4;
6703e000603Scloder 		/* FALLTHROUGH */
671df930be7Sderaadt 	case NOON:
672df930be7Sderaadt 		hr += 12;
6733e000603Scloder 		/* FALLTHROUGH */
674df930be7Sderaadt 	case MIDNIGHT:
6755ec155afSmillert 		if (runtime.tm_hour >= hr) {
676df930be7Sderaadt 			runtime.tm_mday++;
6775ec155afSmillert 			runtime.tm_wday++;
6785ec155afSmillert 		}
679df930be7Sderaadt 		runtime.tm_hour = hr;
680df930be7Sderaadt 		runtime.tm_min = 0;
681df930be7Sderaadt 		token();
682df930be7Sderaadt 		/* fall through to month setting */
6833e000603Scloder 		/* FALLTHROUGH */
684df930be7Sderaadt 	default:
685e134e629Smillert 		if (month(&runtime) != 0)
686e134e629Smillert 			return (-1);
687df930be7Sderaadt 		break;
688df930be7Sderaadt 	} /* ugly case statement */
689e134e629Smillert 	if (expect(EOF) != 0)
690e134e629Smillert 		return (-1);
691df930be7Sderaadt 
692df930be7Sderaadt 	/*
693df930be7Sderaadt 	 * adjust for daylight savings time
694df930be7Sderaadt 	 */
695df930be7Sderaadt 	runtime.tm_isdst = -1;
696df930be7Sderaadt 	runtimer = mktime(&runtime);
697df930be7Sderaadt 
698e134e629Smillert 	if (runtimer < 0) {
699ea5f32d2Smillert 		warnx("garbled time");
700e134e629Smillert 		return (-1);
701e134e629Smillert 	}
702df930be7Sderaadt 
703e134e629Smillert 	if (nowtimer > runtimer) {
704ea5f32d2Smillert 		warnx("cannot schedule jobs in the past");
705e134e629Smillert 		return (-1);
706e134e629Smillert 	}
707df930be7Sderaadt 
7085ec155afSmillert 	return (runtimer);
7096eb82116Smillert }
710