2 * parsetime.c - parse time for at(1)
3 * Copyright (C) 1993, 1994 Thomas Koenig
5 * modifications for English-language times
6 * Copyright (C) 1993 David Parsons
8 * A lot of modifications and extensions
9 * (including the new syntax being useful for RRDB)
10 * Copyright (C) 1999 Oleg Cherevko (aka Olwi Deer)
12 * severe structural damage inflicted by Tobi Oetiker in 1999
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions
17 * 1. Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
19 * 2. The name of the author(s) may not be used to endorse or promote
20 * products derived from this software without specific prior written
23 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
24 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
25 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
26 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
27 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
28 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30 * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
32 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 /* NOTE: nothing in here is thread-safe!!!! Not even the localtime
39 * The BNF-like specification of the time syntax parsed is below:
41 * As usual, [ X ] means that X is optional, { X } means that X may
42 * be either omitted or specified as many times as needed,
43 * alternatives are separated by |, brackets are used for grouping.
44 * (# marks the beginning of comment that extends to the end of line)
46 * TIME-SPECIFICATION ::= TIME-REFERENCE [ OFFSET-SPEC ] |
48 * ( START | END ) OFFSET-SPEC
50 * TIME-REFERENCE ::= NOW | TIME-OF-DAY-SPEC [ DAY-SPEC-1 ] |
51 * [ TIME-OF-DAY-SPEC ] DAY-SPEC-2
53 * TIME-OF-DAY-SPEC ::= NUMBER (':') NUMBER [am|pm] | # HH:MM
54 * 'noon' | 'midnight' | 'teatime'
56 * DAY-SPEC-1 ::= NUMBER '/' NUMBER '/' NUMBER | # MM/DD/[YY]YY
57 * NUMBER '.' NUMBER '.' NUMBER | # DD.MM.[YY]YY
58 * NUMBER # Seconds since 1970
61 * DAY-SPEC-2 ::= MONTH-NAME NUMBER [NUMBER] | # Month DD [YY]YY
62 * 'yesterday' | 'today' | 'tomorrow' |
66 * OFFSET-SPEC ::= '+'|'-' NUMBER TIME-UNIT { ['+'|'-'] NUMBER TIME-UNIT }
68 * TIME-UNIT ::= SECONDS | MINUTES | HOURS |
69 * DAYS | WEEKS | MONTHS | YEARS
73 * START ::= 'start' | 's'
76 * SECONDS ::= 'seconds' | 'second' | 'sec' | 's'
77 * MINUTES ::= 'minutes' | 'minute' | 'min' | 'm'
78 * HOURS ::= 'hours' | 'hour' | 'hr' | 'h'
79 * DAYS ::= 'days' | 'day' | 'd'
80 * WEEKS ::= 'weeks' | 'week' | 'wk' | 'w'
81 * MONTHS ::= 'months' | 'month' | 'mon' | 'm'
82 * YEARS ::= 'years' | 'year' | 'yr' | 'y'
84 * MONTH-NAME ::= 'jan' | 'january' | 'feb' | 'february' | 'mar' | 'march' |
85 * 'apr' | 'april' | 'may' | 'jun' | 'june' | 'jul' | 'july' |
86 * 'aug' | 'august' | 'sep' | 'september' | 'oct' | 'october' |
87 * 'nov' | 'november' | 'dec' | 'december'
89 * DAY-OF-WEEK ::= 'sunday' | 'sun' | 'monday' | 'mon' | 'tuesday' | 'tue' |
90 * 'wednesday' | 'wed' | 'thursday' | 'thu' | 'friday' | 'fri' |
94 * As you may note, there is an ambiguity with respect to
95 * the 'm' time unit (which can mean either minutes or months).
96 * To cope with this, code tries to read users mind :) by applying
97 * certain heuristics. There are two of them:
99 * 1. If 'm' is used in context of (i.e. right after the) years,
100 * months, weeks, or days it is assumed to mean months, while
101 * in the context of hours, minutes, and seconds it means minutes.
102 * (e.g., in -1y6m or +3w1m 'm' means 'months', while in
103 * -3h20m or +5s2m 'm' means 'minutes')
105 * 2. Out of context (i.e. right after the '+' or '-' sign) the
106 * meaning of 'm' is guessed from the number it directly follows.
107 * Currently, if the number absolute value is below 25 it is assumed
108 * that 'm' means months, otherwise it is treated as minutes.
109 * (e.g., -25m == -25 minutes, while +24m == +24 months)
117 #include "rrd_tool.h"
120 /* Structures and unions */
123 MIDNIGHT, NOON, TEATIME,
124 PM, AM, YESTERDAY, TODAY, TOMORROW, NOW, START, END,
125 SECONDS, MINUTES, HOURS, DAYS, WEEKS, MONTHS, YEARS,
127 NUMBER, PLUS, MINUS, DOT, COLON, SLASH, ID, JUNK,
128 JAN, FEB, MAR, APR, MAY, JUN,
129 JUL, AUG, SEP, OCT, NOV, DEC,
130 SUN, MON, TUE, WED, THU, FRI, SAT
133 /* the below is for plus_minus() */
134 #define PREVIOUS_OP (-1)
136 /* parse translation table - table driven parsers can be your FRIEND!
138 struct SpecialToken {
139 char *name; /* token name */
140 int value; /* token id */
142 static struct SpecialToken VariousWords[] = {
143 {"midnight", MIDNIGHT}, /* 00:00:00 of today or tomorrow */
144 {"noon", NOON}, /* 12:00:00 of today or tomorrow */
145 {"teatime", TEATIME}, /* 16:00:00 of today or tomorrow */
146 {"am", AM}, /* morning times for 0-12 clock */
147 {"pm", PM}, /* evening times for 0-12 clock */
148 {"tomorrow", TOMORROW},
149 {"yesterday", YESTERDAY},
196 {NULL, 0} /*** SENTINEL ***/
199 static struct SpecialToken TimeMultipliers[] = {
200 {"second", SECONDS}, /* seconds multiplier */
201 {"seconds", SECONDS}, /* (pluralized) */
202 {"sec", SECONDS}, /* (generic) */
203 {"s", SECONDS}, /* (short generic) */
204 {"minute", MINUTES}, /* minutes multiplier */
205 {"minutes", MINUTES}, /* (pluralized) */
206 {"min", MINUTES}, /* (generic) */
207 {"m", MONTHS_MINUTES}, /* (short generic) */
208 {"hour", HOURS}, /* hours ... */
209 {"hours", HOURS}, /* (pluralized) */
210 {"hr", HOURS}, /* (generic) */
211 {"h", HOURS}, /* (short generic) */
212 {"day", DAYS}, /* days ... */
213 {"days", DAYS}, /* (pluralized) */
214 {"d", DAYS}, /* (short generic) */
215 {"week", WEEKS}, /* week ... */
216 {"weeks", WEEKS}, /* (pluralized) */
217 {"wk", WEEKS}, /* (generic) */
218 {"w", WEEKS}, /* (short generic) */
219 {"month", MONTHS}, /* week ... */
220 {"months", MONTHS}, /* (pluralized) */
221 {"mon", MONTHS}, /* (generic) */
222 {"year", YEARS}, /* year ... */
223 {"years", YEARS}, /* (pluralized) */
224 {"yr", YEARS}, /* (generic) */
225 {"y", YEARS}, /* (short generic) */
226 {NULL, 0} /*** SENTINEL ***/
229 /* File scope variables */
231 /* context dependent list of specials for parser to recognize,
232 * required for us to be able distinguish between 'mon' as 'month'
233 * and 'mon' as 'monday'
235 static struct SpecialToken *Specials;
237 static const char **scp; /* scanner - pointer at arglist */
238 static char scc; /* scanner - count of remaining arguments */
239 static const char *sct; /* scanner - next char pointer in current argument */
240 static int need; /* scanner - need to advance to next argument */
242 static char *sc_token = NULL; /* scanner - token buffer */
243 static size_t sc_len; /* scanner - length of token buffer */
244 static int sc_tokid; /* scanner - token id */
246 /* Local functions */
247 static void EnsureMemFree(
250 static void EnsureMemFree(
260 * A hack to compensate for the lack of the C++ exceptions
262 * Every function func that might generate parsing "exception"
263 * should return TIME_OK (aka NULL) or pointer to the error message,
264 * and should be called like this: try(func(args));
266 * if the try is not successful it will reset the token pointer ...
268 * [NOTE: when try(...) is used as the only statement in the "if-true"
269 * part of the if statement that also has an "else" part it should be
270 * either enclosed in the curly braces (despite the fact that it looks
271 * like a single statement) or NOT followed by the ";"]
283 * The panic() function was used in the original code to die, we redefine
284 * it as macro to start the chain of ascending returns that in conjunction
285 * with the try(b) above will simulate a sort of "exception handling"
293 * ve() and e() are used to set the return error,
294 * the most appropriate use for these is inside panic(...)
296 #define MAX_ERR_MSG_LEN 1024
297 static char errmsg[MAX_ERR_MSG_LEN];
303 #ifdef HAVE_VSNPRINTF
304 vsnprintf(errmsg, MAX_ERR_MSG_LEN, fmt, ap);
306 vsprintf(errmsg, fmt, ap);
325 /* Compare S1 and S2, ignoring case, returning less than, equal to or
326 greater than zero if S1 is lexicographically less than,
327 equal to or greater than S2. -- copied from GNU libc*/
328 static int mystrcasecmp(
334 const unsigned char *p1 = (const unsigned char *) s1;
335 const unsigned char *p2 = (const unsigned char *) s2;
336 unsigned char c1, c2;
353 * parse a token, checking if it's something special to us
355 static int parse_token(
360 for (i = 0; Specials[i].name != NULL; i++)
361 if (mystrcasecmp(Specials[i].name, arg) == 0)
362 return sc_tokid = Specials[i].value;
364 /* not special - must be some random id */
365 return sc_tokid = ID;
371 * init_scanner() sets up the scanner to eat arguments
373 static char *init_scanner(
382 sc_len += strlen(*argv++);
384 sc_token = (char *) malloc(sc_len * sizeof(char));
385 if (sc_token == NULL)
386 return "Failed to allocate memory";
391 * token() fetches a token from the input stream
399 memset(sc_token, '\0', sc_len);
403 /* if we need to read another argument, walk along the argument list;
404 * when we fall off the arglist, we'll just return EOF forever
414 /* eat whitespace now - if we walk off the end of the argument,
415 * we'll continue, which puts us up at the top of the while loop
416 * to fetch the next argument in
418 while (isspace((unsigned char) *sct) || *sct == '_' || *sct == ',')
425 /* preserve the first character of the new token
427 sc_token[0] = *sct++;
429 /* then see what it is
431 if (isdigit((unsigned char) (sc_token[0]))) {
432 while (isdigit((unsigned char) (*sct)))
433 sc_token[++idx] = *sct++;
434 sc_token[++idx] = '\0';
435 return sc_tokid = NUMBER;
436 } else if (isalpha((unsigned char) (sc_token[0]))) {
437 while (isalpha((unsigned char) (*sct)))
438 sc_token[++idx] = *sct++;
439 sc_token[++idx] = '\0';
440 return parse_token(sc_token);
442 switch (sc_token[0]) {
444 return sc_tokid = COLON;
446 return sc_tokid = DOT;
448 return sc_tokid = PLUS;
450 return sc_tokid = MINUS;
452 return sc_tokid = SLASH;
454 /*OK, we did not make it ... */
456 return sc_tokid = EOF;
463 * expect2() gets a token and complains if it's not the token we want
465 static char *expect2(
472 va_start(ap, complain_fmt);
473 if (token() != desired) {
474 panic(ve(complain_fmt, ap));
483 * plus_minus() is used to parse a single NUMBER TIME-UNIT pair
484 * for the OFFSET-SPEC.
485 * It also applies those m-guessing heuristics.
487 static char *plus_minus(
488 struct rrd_time_value *ptv,
491 static int op = PLUS;
492 static int prev_multiplier = -1;
498 (NUMBER, "There should be number after '%c'",
499 op == PLUS ? '+' : '-'));
500 prev_multiplier = -1; /* reset months-minutes guessing mechanics */
502 /* if doop is < 0 then we repeat the previous op
503 * with the prefetched number */
505 delta = atoi(sc_token);
507 if (token() == MONTHS_MINUTES) {
508 /* hard job to guess what does that -5m means: -5mon or -5min? */
509 switch (prev_multiplier) {
524 if (delta < 6) /* it may be some other value but in the context
525 * of RRD who needs less than 6 min deltas? */
531 prev_multiplier = sc_tokid;
534 ptv->tm.tm_year += (op == PLUS) ? delta : -delta;
537 ptv->tm.tm_mon += (op == PLUS) ? delta : -delta;
543 ptv->tm.tm_mday += (op == PLUS) ? delta : -delta;
546 ptv->offset += (op == PLUS) ? delta * 60 * 60 : -delta * 60 * 60;
549 ptv->offset += (op == PLUS) ? delta * 60 : -delta * 60;
552 ptv->offset += (op == PLUS) ? delta : -delta;
554 default: /*default unit is seconds */
555 ptv->offset += (op == PLUS) ? delta : -delta;
558 panic(e("well-known time unit expected after %d", delta));
560 return TIME_OK; /* to make compiler happy :) */
565 * tod() computes the time of day (TIME-OF-DAY-SPEC)
568 struct rrd_time_value *ptv)
570 int hour, minute = 0;
573 /* save token status in case we must abort */
575 const char *sct_sv = sct;
576 int sc_tokid_sv = sc_tokid;
578 tlen = strlen(sc_token);
580 /* first pick out the time of day - we assume a HH (COLON|DOT) MM time
586 hour = atoi(sc_token);
589 if (sc_tokid == SLASH || sc_tokid == DOT) {
590 /* guess we are looking at a date */
593 sc_tokid = sc_tokid_sv;
594 sprintf(sc_token, "%d", hour);
597 if (sc_tokid == COLON) {
599 "Parsing HH:MM syntax, expecting MM as number, got none"));
600 minute = atoi(sc_token);
602 panic(e("parsing HH:MM syntax, got MM = %d (>59!)", minute));
607 /* check if an AM or PM specifier was given
609 if (sc_tokid == AM || sc_tokid == PM) {
611 panic(e("there cannot be more than 12 AM or PM hours"));
613 if (sc_tokid == PM) {
614 if (hour != 12) /* 12:xx PM is 12:xx, not 24:xx */
617 if (hour == 12) /* 12:xx AM is 00:xx, not 12:xx */
621 } else if (hour > 23) {
622 /* guess it was not a time then ... */
625 sc_tokid = sc_tokid_sv;
626 sprintf(sc_token, "%d", hour);
629 ptv->tm.tm_hour = hour;
630 ptv->tm.tm_min = minute;
632 if (ptv->tm.tm_hour == 24) {
641 * assign_date() assigns a date, adjusting year as appropriate
643 static char *assign_date(
644 struct rrd_time_value *ptv,
653 panic(e("invalid year %d (should be either 00-99 or >1900)",
656 } else if (year >= 0 && year < 38) {
657 year += 100; /* Allow year 2000-2037 to be specified as */
659 /* 00-37 until the problem of 2038 year will */
660 /* arise for unices with 32-bit time_t :) */
662 panic(e("won't handle dates before epoch (01/01/1970), sorry"));
665 ptv->tm.tm_mday = mday;
666 ptv->tm.tm_mon = mon;
667 ptv->tm.tm_year = year;
673 * day() picks apart DAY-SPEC-[12]
676 struct rrd_time_value *ptv)
678 /* using time_t seems to help portability with 64bit oses */
679 time_t mday = 0, wday, mon, year = ptv->tm.tm_year;
686 case TODAY: /* force ourselves to stay in today - no further processing */
706 /* do month mday [year]
708 mon = (sc_tokid - JAN);
709 try(expect2(NUMBER, "the day of the month should follow month name"));
710 mday = atol(sc_token);
711 if (token() == NUMBER) {
712 year = atol(sc_token);
715 year = ptv->tm.tm_year;
716 try(assign_date(ptv, mday, mon, year));
726 /* do a particular day of the week
728 wday = (sc_tokid - SUN);
729 ptv->tm.tm_mday += (wday - ptv->tm.tm_wday);
733 mday = ptv->tm.tm_mday;
734 mday += (wday - ptv->tm.tm_wday);
735 ptv->tm.tm_wday = wday;
737 try(assign_date(ptv, mday, ptv->tm.tm_mon, ptv->tm.tm_year));
742 /* get numeric <sec since 1970>, MM/DD/[YY]YY, or DD.MM.[YY]YY
744 tlen = strlen(sc_token);
745 mon = atol(sc_token);
746 if (mon > 10 * 365 * 24 * 60 * 60) {
747 ptv->tm = *localtime(&mon);
752 if (mon > 19700101 && mon < 24000101) { /*works between 1900 and 2400 */
753 char cmon[3], cmday[3], cyear[5];
755 strncpy(cyear, sc_token, 4);
758 strncpy(cmon, &(sc_token[4]), 2);
761 strncpy(cmday, &(sc_token[6]), 2);
768 if (mon <= 31 && (sc_tokid == SLASH || sc_tokid == DOT)) {
772 try(expect2(NUMBER, "there should be %s number after '%c'",
773 sep == DOT ? "month" : "day",
774 sep == DOT ? '.' : '/'));
775 mday = atol(sc_token);
776 if (token() == sep) {
778 (NUMBER, "there should be year number after '%c'",
779 sep == DOT ? '.' : '/'));
780 year = atol(sc_token);
784 /* flip months and days for European timing
796 if (mon < 0 || mon > 11) {
797 panic(e("did you really mean month %d?", mon + 1));
799 if (mday < 1 || mday > 31) {
800 panic(e("I'm afraid that %d is not a valid day of the month",
803 try(assign_date(ptv, mday, mon, year));
810 /* Global functions */
814 * parsetime() is the external interface that takes tspec, parses
815 * it and puts the result in the rrd_time_value structure *ptv.
816 * It can return either absolute times (these are ensured to be
817 * correct) or relative time references that are expected to be
818 * added to some absolute time value and then normalized by
819 * mktime() The return value is either TIME_OK (aka NULL) or
820 * the pointer to the error message in the case of problems
824 struct rrd_time_value *ptv)
826 time_t now = time(NULL);
829 /* this MUST be initialized to zero for midnight/noon/teatime */
831 Specials = VariousWords; /* initialize special words context */
833 try(init_scanner(1, &tspec));
835 /* establish the default time reference */
836 ptv->type = ABSOLUTE_TIME;
838 ptv->tm = *localtime(&now);
839 ptv->tm.tm_isdst = -1; /* mk time can figure this out for us ... */
845 break; /* jump to OFFSET-SPEC part */
848 ptv->type = RELATIVE_TO_START_TIME;
851 ptv->type = RELATIVE_TO_END_TIME;
862 int time_reference = sc_tokid;
865 if (sc_tokid == PLUS || sc_tokid == MINUS)
867 if (time_reference != NOW) {
868 panic(e("'start' or 'end' MUST be followed by +|- offset"));
869 } else if (sc_tokid != EOF) {
870 panic(e("if 'now' is followed by a token it must be +|- offset"));
875 /* Only absolute time specifications below */
878 long hour_sv = ptv->tm.tm_hour;
879 long year_sv = ptv->tm.tm_year;
881 ptv->tm.tm_hour = 30;
882 ptv->tm.tm_year = 30000;
885 if (ptv->tm.tm_hour == 30 && ptv->tm.tm_year != 30000) {
888 if (ptv->tm.tm_hour == 30) {
889 ptv->tm.tm_hour = hour_sv;
891 if (ptv->tm.tm_year == 30000) {
892 ptv->tm.tm_year = year_sv;
896 /* fix month parsing */
910 if (sc_tokid != NUMBER)
915 /* evil coding for TEATIME|NOON|MIDNIGHT - we've initialized
916 * hr to zero up above, then fall into this case in such a
917 * way so we add +12 +4 hours to it for teatime, +12 hours
918 * to it for noon, and nothing at all for midnight, then
919 * set our rettime to that hour before leaping into the
929 /* if (ptv->tm.tm_hour >= hr) {
932 } *//* shifting does not makes sense here ... noon is noon */
933 ptv->tm.tm_hour = hr;
940 panic(e("unparsable time: %s%s", sc_token, sct));
942 } /* ugly case statement */
945 * the OFFSET-SPEC part
947 * (NOTE, the sc_tokid was prefetched for us by the previous code)
949 if (sc_tokid == PLUS || sc_tokid == MINUS) {
950 Specials = TimeMultipliers; /* switch special words context */
951 while (sc_tokid == PLUS || sc_tokid == MINUS || sc_tokid == NUMBER) {
952 if (sc_tokid == NUMBER) {
953 try(plus_minus(ptv, PREVIOUS_OP));
955 try(plus_minus(ptv, sc_tokid));
956 token(); /* We will get EOF eventually but that's OK, since
957 token() will return us as many EOFs as needed */
961 /* now we should be at EOF */
962 if (sc_tokid != EOF) {
963 panic(e("unparsable trailing text: '...%s%s'", sc_token, sct));
966 ptv->tm.tm_isdst = -1; /* for mktime to guess DST status */
967 if (ptv->type == ABSOLUTE_TIME)
968 if (mktime(&ptv->tm) == -1) { /* normalize & check */
969 /* can happen for "nonexistent" times, e.g. around 3am */
970 /* when winter -> summer time correction eats a hour */
971 panic(e("the specified time is incorrect (out of range?)"));
979 struct rrd_time_value *start_tv,
980 struct rrd_time_value *end_tv,
984 if (start_tv->type == RELATIVE_TO_END_TIME && /* same as the line above */
985 end_tv->type == RELATIVE_TO_START_TIME) {
986 rrd_set_error("the start and end times cannot be specified "
987 "relative to each other");
991 if (start_tv->type == RELATIVE_TO_START_TIME) {
993 ("the start time cannot be specified relative to itself");
997 if (end_tv->type == RELATIVE_TO_END_TIME) {
998 rrd_set_error("the end time cannot be specified relative to itself");
1002 if (start_tv->type == RELATIVE_TO_END_TIME) {
1005 *end = mktime(&(end_tv->tm)) + end_tv->offset;
1006 tmtmp = *localtime(end); /* reinit end including offset */
1007 tmtmp.tm_mday += start_tv->tm.tm_mday;
1008 tmtmp.tm_mon += start_tv->tm.tm_mon;
1009 tmtmp.tm_year += start_tv->tm.tm_year;
1010 *start = mktime(&tmtmp) + start_tv->offset;
1012 *start = mktime(&(start_tv->tm)) + start_tv->offset;
1014 if (end_tv->type == RELATIVE_TO_START_TIME) {
1017 *start = mktime(&(start_tv->tm)) + start_tv->offset;
1018 tmtmp = *localtime(start);
1019 tmtmp.tm_mday += end_tv->tm.tm_mday;
1020 tmtmp.tm_mon += end_tv->tm.tm_mon;
1021 tmtmp.tm_year += end_tv->tm.tm_year;
1022 *end = mktime(&tmtmp) + end_tv->offset;
1024 *end = mktime(&(end_tv->tm)) + end_tv->offset;
1027 } /* proc_start_end */