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