/* $NetBSD: parsetime.c,v 1.20 2019/02/16 17:56:57 kre Exp $ */ /* * parsetime.c - parse time for at(1) * Copyright (C) 1993, 1994 Thomas Koenig * * modifications for english-language times * Copyright (C) 1993 David Parsons * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. The name of the author(s) may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * at [NOW] PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS * /NUMBER [DOT NUMBER] [AM|PM]\ /[MONTH NUMBER [NUMBER]] \ * |NOON | |[TOMORROW] | * |MIDNIGHT | |[DAY OF WEEK] | * \TEATIME / |NUMBER [SLASH NUMBER [SLASH NUMBER]]| * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/ */ /* System Headers */ #include #include #include #include #include #include #include #include #include #include #include /* Local headers */ #include "at.h" #include "panic.h" #include "parsetime.h" #include "stime.h" /* Structures and unions */ typedef enum { /* symbols */ MIDNIGHT, NOON, TEATIME, PM, AM, TOMORROW, TODAY, NOW, MINUTES, HOURS, DAYS, WEEKS, MONTHS, YEARS, NUMBER, PLUS, DOT, SLASH, ID, JUNK, JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC, SUN, MON, TUE, WED, THU, FRI, SAT, TOKEOF /* EOF marker */ } tokid_t; /* * parse translation table - table driven parsers can be your FRIEND! */ static const struct { const char *name; /* token name */ tokid_t value; /* token id */ bool plural; /* is this plural? */ } Specials[] = { {"midnight", MIDNIGHT, false}, /* 00:00:00 of today or tomorrow */ {"noon", NOON, false}, /* 12:00:00 of today or tomorrow */ {"teatime", TEATIME, false}, /* 16:00:00 of today or tomorrow */ {"am", AM, false}, /* morning times for 0-12 clock */ {"pm", PM, false}, /* evening times for 0-12 clock */ {"tomorrow", TOMORROW, false}, /* execute 24 hours from time */ {"today", TODAY, false}, /* execute today - don't advance time */ {"now", NOW, false}, /* opt prefix for PLUS */ {"minute", MINUTES, false}, /* minutes multiplier */ {"min", MINUTES, false}, {"m", MINUTES, false}, {"minutes", MINUTES, true}, /* (pluralized) */ {"hour", HOURS, false}, /* hours ... */ {"hr", HOURS, false}, /* abbreviated */ {"h", HOURS, false}, {"hours", HOURS, true}, /* (pluralized) */ {"day", DAYS, false}, /* days ... */ {"d", DAYS, false}, {"days", DAYS, true}, /* (pluralized) */ {"week", WEEKS, false}, /* week ... */ {"w", WEEKS, false}, {"weeks", WEEKS, true}, /* (pluralized) */ { "month", MONTHS, 0 }, /* month ... */ { "months", MONTHS, 1 }, /* (pluralized) */ { "year", YEARS, 0 }, /* year ... */ { "years", YEARS, 1 }, /* (pluralized) */ {"jan", JAN, false}, {"feb", FEB, false}, {"mar", MAR, false}, {"apr", APR, false}, {"may", MAY, false}, {"jun", JUN, false}, {"jul", JUL, false}, {"aug", AUG, false}, {"sep", SEP, false}, {"oct", OCT, false}, {"nov", NOV, false}, {"dec", DEC, false}, {"january", JAN, false}, {"february", FEB, false}, {"march", MAR, false}, {"april", APR, false}, {"may", MAY, false}, {"june", JUN, false}, {"july", JUL, false}, {"august", AUG, false}, {"september", SEP, false}, {"october", OCT, false}, {"november", NOV, false}, {"december", DEC, false}, {"sunday", SUN, false}, {"sun", SUN, false}, {"monday", MON, false}, {"mon", MON, false}, {"tuesday", TUE, false}, {"tue", TUE, false}, {"wednesday", WED, false}, {"wed", WED, false}, {"thursday", THU, false}, {"thu", THU, false}, {"friday", FRI, false}, {"fri", FRI, false}, {"saturday", SAT, false}, {"sat", SAT, false} }; /* File scope variables */ static char **scp; /* scanner - pointer at arglist */ static char scc; /* scanner - count of remaining arguments */ static char *sct; /* scanner - next char pointer in current argument */ static bool need; /* scanner - need to advance to next argument */ static char *sc_token; /* scanner - token buffer */ static size_t sc_len; /* scanner - length of token buffer */ static tokid_t sc_tokid;/* scanner - token id */ static bool sc_tokplur; /* scanner - is token plural? */ #ifndef lint #if 0 static char rcsid[] = "$OpenBSD: parsetime.c,v 1.4 1997/03/01 23:40:10 millert Exp $"; #else __RCSID("$NetBSD: parsetime.c,v 1.20 2019/02/16 17:56:57 kre Exp $"); #endif #endif /* Local functions */ static void assign_date(struct tm *, int, int, int); static void expect(tokid_t); static void init_scanner(int, char **); static void month(struct tm *); static tokid_t parse_token(char *); static void plonk(tokid_t) __dead; static void plus(struct tm *); static void tod(struct tm *); static tokid_t token(void); /* * parse a token, checking if it's something special to us */ static tokid_t parse_token(char *arg) { size_t i; for (i=0; i < __arraycount(Specials); i++) { if (strcasecmp(Specials[i].name, arg) == 0) { sc_tokplur = Specials[i].plural; return sc_tokid = Specials[i].value; } } /* not special - must be some random id */ return ID; } /* * init_scanner() sets up the scanner to eat arguments */ static void init_scanner(int argc, char **argv) { scp = argv; scc = argc; need = true; sc_len = 1; while (argc-- > 0) sc_len += strlen(*argv++); if ((sc_token = malloc(sc_len)) == NULL) panic("Insufficient virtual memory"); } /* * token() fetches a token from the input stream */ static tokid_t token(void) { int idx; for(;;) { (void)memset(sc_token, 0, sc_len); sc_tokid = TOKEOF; sc_tokplur = false; idx = 0; /* * if we need to read another argument, walk along the * argument list; when we fall off the arglist, we'll * just return TOKEOF forever */ if (need) { if (scc < 1) return sc_tokid; sct = *scp; scp++; scc--; need = false; } /* * eat whitespace now - if we walk off the end of the argument, * we'll continue, which puts us up at the top of the while loop * to fetch the next argument in */ while (isspace((unsigned char)*sct)) ++sct; if (!*sct) { need = true; continue; } /* * preserve the first character of the new token */ sc_token[0] = *sct++; /* * then see what it is */ if (isdigit((unsigned char)sc_token[0])) { while (isdigit((unsigned char)*sct)) sc_token[++idx] = *sct++; sc_token[++idx] = 0; return sc_tokid = NUMBER; } else if (isalpha((unsigned char)sc_token[0])) { while (isalpha((unsigned char)*sct)) sc_token[++idx] = *sct++; sc_token[++idx] = 0; return parse_token(sc_token); } else if (sc_token[0] == ':' || sc_token[0] == '.') return sc_tokid = DOT; else if (sc_token[0] == '+') return sc_tokid = PLUS; else if (sc_token[0] == '/') return sc_tokid = SLASH; else return sc_tokid = JUNK; } } /* * plonk() gives an appropriate error message if a token is incorrect */ __dead static void plonk(tokid_t tok) { panic(tok == TOKEOF ? "incomplete time" : "garbled time"); } /* * expect() gets a token and dies most horribly if it's not the token we want */ static void expect(tokid_t desired) { if (token() != desired) plonk(sc_tokid); /* and we die here... */ } /* * plus() parses a now + time * * at [NOW] PLUS NUMBER [MINUTES|HOURS|DAYS|WEEKS|MONTHS|YEARS] * */ static void plus(struct tm *tm) { int delay; int expectplur; expect(NUMBER); delay = atoi(sc_token); expectplur = delay != 1; switch (token()) { case YEARS: tm->tm_year += delay; break; case MONTHS: tm->tm_mon += delay; break; case WEEKS: delay *= 7; /*FALLTHROUGH*/ case DAYS: tm->tm_mday += delay; break; case HOURS: tm->tm_hour += delay; break; case MINUTES: tm->tm_min += delay; break; default: plonk(sc_tokid); break; } if (expectplur != sc_tokplur) warnx("pluralization is wrong"); tm->tm_isdst = -1; if (mktime(tm) == -1) plonk(sc_tokid); } /* * tod() computes the time of day * [NUMBER [DOT NUMBER] [AM|PM]] */ static void tod(struct tm *tm) { int hour, minute; size_t tlen; minute = 0; hour = atoi(sc_token); tlen = strlen(sc_token); /* * first pick out the time of day - if it's 4 digits, we assume * a HHMM time, otherwise it's HH DOT MM time */ if (token() == DOT) { expect(NUMBER); minute = atoi(sc_token); (void)token(); } else if (tlen == 4) { minute = hour % 100; hour = hour / 100; } if (minute > 59) panic("garbled time"); /* * check if an AM or PM specifier was given */ if (sc_tokid == AM || sc_tokid == PM) { if (hour > 12) panic("garbled time"); if (sc_tokid == PM) { if (hour != 12) /* 12:xx PM is 12:xx, not 24:xx */ hour += 12; } else { if (hour == 12) /* 12:xx AM is 00:xx, not 12:xx */ hour = 0; } (void)token(); } else if (hour > 23) panic("garbled time"); /* * if we specify an absolute time, we don't want to bump the day even * if we've gone past that time - but if we're specifying a time plus * a relative offset, it's okay to bump things */ if ((sc_tokid == TOKEOF || sc_tokid == PLUS) && (tm->tm_hour > hour || (tm->tm_hour == hour && tm->tm_min > minute))) { tm->tm_mday++; tm->tm_wday++; } tm->tm_hour = hour; tm->tm_min = minute; } /* * assign_date() assigns a date, wrapping to next year if needed. * Accept years in 4-digit, 2-digit, or current year (-1). */ static void assign_date(struct tm *tm, int mday, int mon, int year) { if (year > 99) { /* four digit year */ if (year >= TM_YEAR_BASE) tm->tm_year = year - TM_YEAR_BASE; else panic("garbled time"); } else if (year >= 0) { /* two digit year */ tm->tm_year = conv_2dig_year(year) - TM_YEAR_BASE; } else if (year == -1) { /* year not given (use default in tm) */ /* allow for 1 year range from current date */ if (tm->tm_mon > mon || (tm->tm_mon == mon && tm->tm_mday > mday)) tm->tm_year++; } else panic("invalid year"); tm->tm_mday = mday; tm->tm_mon = mon; } /* * month() picks apart a month specification * * /[ NUMBER [NUMBER]] \ * |[TOMORROW] | * |[DAY OF WEEK] | * |NUMBER [SLASH NUMBER [SLASH NUMBER]]| * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/ */ static void month(struct tm *tm) { int year; int mday, wday, mon; size_t tlen; year = -1; mday = 0; switch (sc_tokid) { case PLUS: plus(tm); break; case TOMORROW: /* do something tomorrow */ tm->tm_mday++; tm->tm_wday++; /*FALLTHROUGH*/ case TODAY: /* force ourselves to stay in today - no further processing */ (void)token(); break; case JAN: case FEB: case MAR: case APR: case MAY: case JUN: case JUL: case AUG: case SEP: case OCT: case NOV: case DEC: /* * do month mday [year] */ mon = sc_tokid - JAN; expect(NUMBER); mday = atoi(sc_token); if (token() == NUMBER) { year = atoi(sc_token); (void)token(); } assign_date(tm, mday, mon, year); break; case SUN: case MON: case TUE: case WED: case THU: case FRI: case SAT: /* do a particular day of the week */ wday = sc_tokid - SUN; mday = tm->tm_mday; /* if this day is < today, then roll to next week */ if (wday < tm->tm_wday) mday += 7 - (tm->tm_wday - wday); else mday += (wday - tm->tm_wday); tm->tm_wday = wday; assign_date(tm, mday, tm->tm_mon, tm->tm_year + TM_YEAR_BASE); break; case NUMBER: /* * get numeric MMDDYY, mm/dd/yy, or dd.mm.yy */ tlen = strlen(sc_token); mon = atoi(sc_token); (void)token(); if (sc_tokid == SLASH || sc_tokid == DOT) { tokid_t sep; sep = sc_tokid; expect(NUMBER); mday = atoi(sc_token); if (token() == sep) { expect(NUMBER); year = atoi(sc_token); (void)token(); } /* * flip months and days for european timing */ if (sep == DOT) { int x = mday; mday = mon; mon = x; } } else if (tlen == 6 || tlen == 8) { if (tlen == 8) { year = (mon % 10000) - 1900; mon /= 10000; } else { year = mon % 100; mon /= 100; } mday = mon % 100; mon /= 100; } else panic("garbled time"); mon--; if (mon < 0 || mon > 11 || mday < 1 || mday > 31) panic("garbled time"); assign_date(tm, mday, mon, year); break; default: /* plonk(sc_tokid); */ /* XXX - die here? */ break; } } /* Global functions */ time_t parsetime(int argc, char **argv) { /* * Do the argument parsing, die if necessary, and return the * time the job should be run. */ time_t nowtimer, runtimer; struct tm nowtime, runtime; int hr = 0; /* this MUST be initialized to zero for midnight/noon/teatime */ nowtimer = time(NULL); nowtime = *localtime(&nowtimer); runtime = nowtime; runtime.tm_sec = 0; if (argc <= optind) usage(); init_scanner(argc - optind, argv + optind); switch (token()) { case NOW: if (scc < 1) return nowtimer; /* now is optional prefix for PLUS tree */ expect(PLUS); /*FALLTHROUGH*/ case PLUS: plus(&runtime); break; case NUMBER: tod(&runtime); month(&runtime); break; /* * evil coding for TEATIME|NOON|MIDNIGHT - we've initialised * hr to zero up above, then fall into this case in such a * way so we add +12 +4 hours to it for teatime, +12 hours * to it for noon, and nothing at all for midnight, then * set our runtime to that hour before leaping into the * month scanner */ case TEATIME: hr += 4; /*FALLTHROUGH*/ case NOON: hr += 12; /*FALLTHROUGH*/ case MIDNIGHT: if (runtime.tm_hour >= hr) { runtime.tm_mday++; runtime.tm_wday++; } runtime.tm_hour = hr; runtime.tm_min = 0; (void)token(); /*FALLTHROUGH*/ /* fall through to month setting */ default: month(&runtime); break; } expect(TOKEOF); /* * adjust for daylight savings time */ runtime.tm_isdst = -1; runtimer = mktime(&runtime); if (runtimer == (time_t)-1) panic("Invalid time"); if (nowtimer > runtimer) panic("Trying to travel back in time"); return runtimer; }