--- /dev/null
+/*
+ * rrd_parsetime.c - parse time for at(1)
+ * Copyright (C) 1993, 1994 Thomas Koenig
+ *
+ * modifications for English-language times
+ * Copyright (C) 1993 David Parsons
+ *
+ * A lot of modifications and extensions
+ * (including the new syntax being useful for RRDB)
+ * Copyright (C) 1999 Oleg Cherevko (aka Olwi Deer)
+ *
+ * severe structural damage inflicted by Tobi Oetiker in 1999
+ *
+ * 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.
+ */
+
+/* NOTE: nothing in here is thread-safe!!!! Not even the localtime
+ calls ... */
+
+/*
+ * The BNF-like specification of the time syntax parsed is below:
+ *
+ * As usual, [ X ] means that X is optional, { X } means that X may
+ * be either omitted or specified as many times as needed,
+ * alternatives are separated by |, brackets are used for grouping.
+ * (# marks the beginning of comment that extends to the end of line)
+ *
+ * TIME-SPECIFICATION ::= TIME-REFERENCE [ OFFSET-SPEC ] |
+ * OFFSET-SPEC |
+ * ( START | END ) OFFSET-SPEC
+ *
+ * TIME-REFERENCE ::= NOW | TIME-OF-DAY-SPEC [ DAY-SPEC-1 ] |
+ * [ TIME-OF-DAY-SPEC ] DAY-SPEC-2
+ *
+ * TIME-OF-DAY-SPEC ::= NUMBER (':') NUMBER [am|pm] | # HH:MM
+ * 'noon' | 'midnight' | 'teatime'
+ *
+ * DAY-SPEC-1 ::= NUMBER '/' NUMBER '/' NUMBER | # MM/DD/[YY]YY
+ * NUMBER '.' NUMBER '.' NUMBER | # DD.MM.[YY]YY
+ * NUMBER # Seconds since 1970
+ * NUMBER # YYYYMMDD
+ *
+ * DAY-SPEC-2 ::= MONTH-NAME NUMBER [NUMBER] | # Month DD [YY]YY
+ * 'yesterday' | 'today' | 'tomorrow' |
+ * DAY-OF-WEEK
+ *
+ *
+ * OFFSET-SPEC ::= '+'|'-' NUMBER TIME-UNIT { ['+'|'-'] NUMBER TIME-UNIT }
+ *
+ * TIME-UNIT ::= SECONDS | MINUTES | HOURS |
+ * DAYS | WEEKS | MONTHS | YEARS
+ *
+ * NOW ::= 'now' | 'n'
+ *
+ * START ::= 'start' | 's'
+ * END ::= 'end' | 'e'
+ *
+ * SECONDS ::= 'seconds' | 'second' | 'sec' | 's'
+ * MINUTES ::= 'minutes' | 'minute' | 'min' | 'm'
+ * HOURS ::= 'hours' | 'hour' | 'hr' | 'h'
+ * DAYS ::= 'days' | 'day' | 'd'
+ * WEEKS ::= 'weeks' | 'week' | 'wk' | 'w'
+ * MONTHS ::= 'months' | 'month' | 'mon' | 'm'
+ * YEARS ::= 'years' | 'year' | 'yr' | 'y'
+ *
+ * MONTH-NAME ::= 'jan' | 'january' | 'feb' | 'february' | 'mar' | 'march' |
+ * 'apr' | 'april' | 'may' | 'jun' | 'june' | 'jul' | 'july' |
+ * 'aug' | 'august' | 'sep' | 'september' | 'oct' | 'october' |
+ * 'nov' | 'november' | 'dec' | 'december'
+ *
+ * DAY-OF-WEEK ::= 'sunday' | 'sun' | 'monday' | 'mon' | 'tuesday' | 'tue' |
+ * 'wednesday' | 'wed' | 'thursday' | 'thu' | 'friday' | 'fri' |
+ * 'saturday' | 'sat'
+ *
+ *
+ * As you may note, there is an ambiguity with respect to
+ * the 'm' time unit (which can mean either minutes or months).
+ * To cope with this, code tries to read users mind :) by applying
+ * certain heuristics. There are two of them:
+ *
+ * 1. If 'm' is used in context of (i.e. right after the) years,
+ * months, weeks, or days it is assumed to mean months, while
+ * in the context of hours, minutes, and seconds it means minutes.
+ * (e.g., in -1y6m or +3w1m 'm' means 'months', while in
+ * -3h20m or +5s2m 'm' means 'minutes')
+ *
+ * 2. Out of context (i.e. right after the '+' or '-' sign) the
+ * meaning of 'm' is guessed from the number it directly follows.
+ * Currently, if the number absolute value is below 25 it is assumed
+ * that 'm' means months, otherwise it is treated as minutes.
+ * (e.g., -25m == -25 minutes, while +24m == +24 months)
+ *
+ */
+
+/* System Headers */
+
+/* Local headers */
+
+#include "rrd_tool.h"
+#include <stdarg.h>
+
+/* Structures and unions */
+
+enum { /* symbols */
+ MIDNIGHT, NOON, TEATIME,
+ PM, AM, YESTERDAY, TODAY, TOMORROW, NOW, START, END,
+ SECONDS, MINUTES, HOURS, DAYS, WEEKS, MONTHS, YEARS,
+ MONTHS_MINUTES,
+ NUMBER, PLUS, MINUS, DOT, COLON, SLASH, ID, JUNK,
+ JAN, FEB, MAR, APR, MAY, JUN,
+ JUL, AUG, SEP, OCT, NOV, DEC,
+ SUN, MON, TUE, WED, THU, FRI, SAT
+};
+
+/* the below is for plus_minus() */
+#define PREVIOUS_OP (-1)
+
+/* parse translation table - table driven parsers can be your FRIEND!
+ */
+struct SpecialToken {
+ char *name; /* token name */
+ int value; /* token id */
+};
+static const struct SpecialToken VariousWords[] = {
+ {"midnight", MIDNIGHT}, /* 00:00:00 of today or tomorrow */
+ {"noon", NOON}, /* 12:00:00 of today or tomorrow */
+ {"teatime", TEATIME}, /* 16:00:00 of today or tomorrow */
+ {"am", AM}, /* morning times for 0-12 clock */
+ {"pm", PM}, /* evening times for 0-12 clock */
+ {"tomorrow", TOMORROW},
+ {"yesterday", YESTERDAY},
+ {"today", TODAY},
+ {"now", NOW},
+ {"n", NOW},
+ {"start", START},
+ {"s", START},
+ {"end", END},
+ {"e", END},
+
+ {"jan", JAN},
+ {"feb", FEB},
+ {"mar", MAR},
+ {"apr", APR},
+ {"may", MAY},
+ {"jun", JUN},
+ {"jul", JUL},
+ {"aug", AUG},
+ {"sep", SEP},
+ {"oct", OCT},
+ {"nov", NOV},
+ {"dec", DEC},
+ {"january", JAN},
+ {"february", FEB},
+ {"march", MAR},
+ {"april", APR},
+ {"may", MAY},
+ {"june", JUN},
+ {"july", JUL},
+ {"august", AUG},
+ {"september", SEP},
+ {"october", OCT},
+ {"november", NOV},
+ {"december", DEC},
+ {"sunday", SUN},
+ {"sun", SUN},
+ {"monday", MON},
+ {"mon", MON},
+ {"tuesday", TUE},
+ {"tue", TUE},
+ {"wednesday", WED},
+ {"wed", WED},
+ {"thursday", THU},
+ {"thu", THU},
+ {"friday", FRI},
+ {"fri", FRI},
+ {"saturday", SAT},
+ {"sat", SAT},
+ {NULL, 0} /*** SENTINEL ***/
+};
+
+static const struct SpecialToken TimeMultipliers[] = {
+ {"second", SECONDS}, /* seconds multiplier */
+ {"seconds", SECONDS}, /* (pluralized) */
+ {"sec", SECONDS}, /* (generic) */
+ {"s", SECONDS}, /* (short generic) */
+ {"minute", MINUTES}, /* minutes multiplier */
+ {"minutes", MINUTES}, /* (pluralized) */
+ {"min", MINUTES}, /* (generic) */
+ {"m", MONTHS_MINUTES}, /* (short generic) */
+ {"hour", HOURS}, /* hours ... */
+ {"hours", HOURS}, /* (pluralized) */
+ {"hr", HOURS}, /* (generic) */
+ {"h", HOURS}, /* (short generic) */
+ {"day", DAYS}, /* days ... */
+ {"days", DAYS}, /* (pluralized) */
+ {"d", DAYS}, /* (short generic) */
+ {"week", WEEKS}, /* week ... */
+ {"weeks", WEEKS}, /* (pluralized) */
+ {"wk", WEEKS}, /* (generic) */
+ {"w", WEEKS}, /* (short generic) */
+ {"month", MONTHS}, /* week ... */
+ {"months", MONTHS}, /* (pluralized) */
+ {"mon", MONTHS}, /* (generic) */
+ {"year", YEARS}, /* year ... */
+ {"years", YEARS}, /* (pluralized) */
+ {"yr", YEARS}, /* (generic) */
+ {"y", YEARS}, /* (short generic) */
+ {NULL, 0} /*** SENTINEL ***/
+};
+
+/* File scope variables */
+
+/* context dependent list of specials for parser to recognize,
+ * required for us to be able distinguish between 'mon' as 'month'
+ * and 'mon' as 'monday'
+ */
+static const struct SpecialToken *Specials;
+
+static const char **scp; /* scanner - pointer at arglist */
+static char scc; /* scanner - count of remaining arguments */
+static const char *sct; /* scanner - next char pointer in current argument */
+static int need; /* scanner - need to advance to next argument */
+
+static char *sc_token = NULL; /* scanner - token buffer */
+static size_t sc_len; /* scanner - length of token buffer */
+static int sc_tokid; /* scanner - token id */
+
+/* Local functions */
+static void EnsureMemFree(
+ void);
+
+static void EnsureMemFree(
+ void)
+{
+ if (sc_token) {
+ free(sc_token);
+ sc_token = NULL;
+ }
+}
+
+/*
+ * A hack to compensate for the lack of the C++ exceptions
+ *
+ * Every function func that might generate parsing "exception"
+ * should return TIME_OK (aka NULL) or pointer to the error message,
+ * and should be called like this: try(func(args));
+ *
+ * if the try is not successful it will reset the token pointer ...
+ *
+ * [NOTE: when try(...) is used as the only statement in the "if-true"
+ * part of the if statement that also has an "else" part it should be
+ * either enclosed in the curly braces (despite the fact that it looks
+ * like a single statement) or NOT followed by the ";"]
+ */
+#define try(b) { \
+ char *_e; \
+ if((_e=(b))) \
+ { \
+ EnsureMemFree(); \
+ return _e; \
+ } \
+ }
+
+/*
+ * The panic() function was used in the original code to die, we redefine
+ * it as macro to start the chain of ascending returns that in conjunction
+ * with the try(b) above will simulate a sort of "exception handling"
+ */
+
+#define panic(e) { \
+ return (e); \
+ }
+
+/*
+ * ve() and e() are used to set the return error,
+ * the most appropriate use for these is inside panic(...)
+ */
+#define MAX_ERR_MSG_LEN 1024
+static char errmsg[MAX_ERR_MSG_LEN];
+
+static char *ve(
+ char *fmt,
+ va_list ap)
+{
+#ifdef HAVE_VSNPRINTF
+ vsnprintf(errmsg, MAX_ERR_MSG_LEN, fmt, ap);
+#else
+ vsprintf(errmsg, fmt, ap);
+#endif
+ EnsureMemFree();
+ return (errmsg);
+}
+
+static char *e(
+ char *fmt,
+ ...)
+{
+ char *err;
+ va_list ap;
+
+ va_start(ap, fmt);
+ err = ve(fmt, ap);
+ va_end(ap);
+ return (err);
+}
+
+/* Compare S1 and S2, ignoring case, returning less than, equal to or
+ greater than zero if S1 is lexicographically less than,
+ equal to or greater than S2. -- copied from GNU libc*/
+static int mystrcasecmp(
+ const char *s1,
+ const char *s2)
+{
+ const unsigned char *p1 = (const unsigned char *) s1;
+ const unsigned char *p2 = (const unsigned char *) s2;
+ unsigned char c1, c2;
+
+ if (p1 == p2)
+ return 0;
+
+ do {
+ c1 = tolower(*p1++);
+ c2 = tolower(*p2++);
+ if (c1 == '\0')
+ break;
+ }
+ while (c1 == c2);
+
+ return c1 - c2;
+}
+
+/*
+ * parse a token, checking if it's something special to us
+ */
+static int parse_token(
+ char *arg)
+{
+ int i;
+
+ for (i = 0; Specials[i].name != NULL; i++)
+ if (mystrcasecmp(Specials[i].name, arg) == 0)
+ return sc_tokid = Specials[i].value;
+
+ /* not special - must be some random id */
+ return sc_tokid = ID;
+} /* parse_token */
+
+
+
+/*
+ * init_scanner() sets up the scanner to eat arguments
+ */
+static char *init_scanner(
+ int argc,
+ const char **argv)
+{
+ scp = argv;
+ scc = argc;
+ need = 1;
+ sc_len = 1;
+ while (argc-- > 0)
+ sc_len += strlen(*argv++);
+
+ sc_token = (char *) malloc(sc_len * sizeof(char));
+ if (sc_token == NULL)
+ return "Failed to allocate memory";
+ return TIME_OK;
+} /* init_scanner */
+
+/*
+ * token() fetches a token from the input stream
+ */
+static int token(
+ void)
+{
+ int idx;
+
+ while (1) {
+ memset(sc_token, '\0', sc_len);
+ sc_tokid = EOF;
+ idx = 0;
+
+ /* if we need to read another argument, walk along the argument list;
+ * when we fall off the arglist, we'll just return EOF forever
+ */
+ if (need) {
+ if (scc < 1)
+ return sc_tokid;
+ sct = *scp;
+ scp++;
+ scc--;
+ need = 0;
+ }
+ /* 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 == '_' || *sct == ',')
+ ++sct;
+ if (!*sct) {
+ need = 1;
+ 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
+ switch (sc_token[0]) {
+ case ':':
+ return sc_tokid = COLON;
+ case '.':
+ return sc_tokid = DOT;
+ case '+':
+ return sc_tokid = PLUS;
+ case '-':
+ return sc_tokid = MINUS;
+ case '/':
+ return sc_tokid = SLASH;
+ default:
+ /*OK, we did not make it ... */
+ sct--;
+ return sc_tokid = EOF;
+ }
+ } /* while (1) */
+} /* token */
+
+
+/*
+ * expect2() gets a token and complains if it's not the token we want
+ */
+static char *expect2(
+ int desired,
+ char *complain_fmt,
+ ...)
+{
+ va_list ap;
+
+ va_start(ap, complain_fmt);
+ if (token() != desired) {
+ panic(ve(complain_fmt, ap));
+ }
+ va_end(ap);
+ return TIME_OK;
+
+} /* expect2 */
+
+
+/*
+ * plus_minus() is used to parse a single NUMBER TIME-UNIT pair
+ * for the OFFSET-SPEC.
+ * It also applies those m-guessing heuristics.
+ */
+static char *plus_minus(
+ rrd_time_value_t *ptv,
+ int doop)
+{
+ static int op = PLUS;
+ static int prev_multiplier = -1;
+ int delta;
+
+ if (doop >= 0) {
+ op = doop;
+ try(expect2
+ (NUMBER, "There should be number after '%c'",
+ op == PLUS ? '+' : '-'));
+ prev_multiplier = -1; /* reset months-minutes guessing mechanics */
+ }
+ /* if doop is < 0 then we repeat the previous op
+ * with the prefetched number */
+
+ delta = atoi(sc_token);
+
+ if (token() == MONTHS_MINUTES) {
+ /* hard job to guess what does that -5m means: -5mon or -5min? */
+ switch (prev_multiplier) {
+ case DAYS:
+ case WEEKS:
+ case MONTHS:
+ case YEARS:
+ sc_tokid = MONTHS;
+ break;
+
+ case SECONDS:
+ case MINUTES:
+ case HOURS:
+ sc_tokid = MINUTES;
+ break;
+
+ default:
+ if (delta < 6) /* it may be some other value but in the context
+ * of RRD who needs less than 6 min deltas? */
+ sc_tokid = MONTHS;
+ else
+ sc_tokid = MINUTES;
+ }
+ }
+ prev_multiplier = sc_tokid;
+ switch (sc_tokid) {
+ case YEARS:
+ ptv->tm. tm_year += (
+ op == PLUS) ? delta : -delta;
+
+ return TIME_OK;
+ case MONTHS:
+ ptv->tm. tm_mon += (
+ op == PLUS) ? delta : -delta;
+
+ return TIME_OK;
+ case WEEKS:
+ delta *= 7;
+ /* FALLTHRU */
+ case DAYS:
+ ptv->tm. tm_mday += (
+ op == PLUS) ? delta : -delta;
+
+ return TIME_OK;
+ case HOURS:
+ ptv->offset += (op == PLUS) ? delta * 60 * 60 : -delta * 60 * 60;
+ return TIME_OK;
+ case MINUTES:
+ ptv->offset += (op == PLUS) ? delta * 60 : -delta * 60;
+ return TIME_OK;
+ case SECONDS:
+ ptv->offset += (op == PLUS) ? delta : -delta;
+ return TIME_OK;
+ default: /*default unit is seconds */
+ ptv->offset += (op == PLUS) ? delta : -delta;
+ return TIME_OK;
+ }
+ panic(e("well-known time unit expected after %d", delta));
+ /* NORETURN */
+ return TIME_OK; /* to make compiler happy :) */
+} /* plus_minus */
+
+
+/*
+ * tod() computes the time of day (TIME-OF-DAY-SPEC)
+ */
+static char *tod(
+ rrd_time_value_t *ptv)
+{
+ int hour, minute = 0;
+ int tlen;
+
+ /* save token status in case we must abort */
+ int scc_sv = scc;
+ const char *sct_sv = sct;
+ int sc_tokid_sv = sc_tokid;
+
+ tlen = strlen(sc_token);
+
+ /* first pick out the time of day - we assume a HH (COLON|DOT) MM time
+ */
+ if (tlen > 2) {
+ return TIME_OK;
+ }
+
+ hour = atoi(sc_token);
+
+ token();
+ if (sc_tokid == SLASH || sc_tokid == DOT) {
+ /* guess we are looking at a date */
+ scc = scc_sv;
+ sct = sct_sv;
+ sc_tokid = sc_tokid_sv;
+ sprintf(sc_token, "%d", hour);
+ return TIME_OK;
+ }
+ if (sc_tokid == COLON) {
+ try(expect2(NUMBER,
+ "Parsing HH:MM syntax, expecting MM as number, got none"));
+ minute = atoi(sc_token);
+ if (minute > 59) {
+ panic(e("parsing HH:MM syntax, got MM = %d (>59!)", minute));
+ }
+ token();
+ }
+
+ /* check if an AM or PM specifier was given
+ */
+ if (sc_tokid == AM || sc_tokid == PM) {
+ if (hour > 12) {
+ panic(e("there cannot be more than 12 AM or PM hours"));
+ }
+ 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;
+ }
+ token();
+ } else if (hour > 23) {
+ /* guess it was not a time then ... */
+ scc = scc_sv;
+ sct = sct_sv;
+ sc_tokid = sc_tokid_sv;
+ sprintf(sc_token, "%d", hour);
+ return TIME_OK;
+ }
+ ptv->tm. tm_hour = hour;
+ ptv->tm. tm_min = minute;
+ ptv->tm. tm_sec = 0;
+
+ if (ptv->tm.tm_hour == 24) {
+ ptv->tm. tm_hour = 0;
+ ptv->tm. tm_mday++;
+ }
+ return TIME_OK;
+} /* tod */
+
+
+/*
+ * assign_date() assigns a date, adjusting year as appropriate
+ */
+static char *assign_date(
+ rrd_time_value_t *ptv,
+ long mday,
+ long mon,
+ long year)
+{
+ if (year > 138) {
+ if (year > 1970)
+ year -= 1900;
+ else {
+ panic(e("invalid year %d (should be either 00-99 or >1900)",
+ year));
+ }
+ } else if (year >= 0 && year < 38) {
+ year += 100; /* Allow year 2000-2037 to be specified as */
+ }
+ /* 00-37 until the problem of 2038 year will */
+ /* arise for unices with 32-bit time_t :) */
+ if (year < 70) {
+ panic(e("won't handle dates before epoch (01/01/1970), sorry"));
+ }
+
+ ptv->tm. tm_mday = mday;
+ ptv->tm. tm_mon = mon;
+ ptv->tm. tm_year = year;
+
+ return TIME_OK;
+} /* assign_date */
+
+
+/*
+ * day() picks apart DAY-SPEC-[12]
+ */
+static char *day(
+ rrd_time_value_t *ptv)
+{
+ /* using time_t seems to help portability with 64bit oses */
+ time_t mday = 0, wday, mon, year = ptv->tm.tm_year;
+ int tlen;
+
+ switch (sc_tokid) {
+ case YESTERDAY:
+ ptv->tm. tm_mday--;
+
+ /* FALLTRHU */
+ case TODAY: /* force ourselves to stay in today - no further processing */
+ token();
+ break;
+ case TOMORROW:
+ ptv->tm. tm_mday++;
+
+ 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);
+ try(expect2(NUMBER, "the day of the month should follow month name"));
+ mday = atol(sc_token);
+ if (token() == NUMBER) {
+ year = atol(sc_token);
+ token();
+ } else
+ year = ptv->tm.tm_year;
+
+ try(assign_date(ptv, 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);
+ ptv->tm. tm_mday += (
+ wday - ptv->tm.tm_wday);
+
+ token();
+ break;
+ /*
+ mday = ptv->tm.tm_mday;
+ mday += (wday - ptv->tm.tm_wday);
+ ptv->tm.tm_wday = wday;
+
+ try(assign_date(ptv, mday, ptv->tm.tm_mon, ptv->tm.tm_year));
+ break;
+ */
+
+ case NUMBER:
+ /* get numeric <sec since 1970>, MM/DD/[YY]YY, or DD.MM.[YY]YY
+ */
+ tlen = strlen(sc_token);
+ mon = atol(sc_token);
+ if (mon > 10 * 365 * 24 * 60 * 60) {
+ ptv->tm = *localtime(&mon);
+
+ token();
+ break;
+ }
+
+ if (mon > 19700101 && mon < 24000101) { /*works between 1900 and 2400 */
+ char cmon[3], cmday[3], cyear[5];
+
+ strncpy(cyear, sc_token, 4);
+ cyear[4] = '\0';
+ year = atol(cyear);
+ strncpy(cmon, &(sc_token[4]), 2);
+ cmon[2] = '\0';
+ mon = atol(cmon);
+ strncpy(cmday, &(sc_token[6]), 2);
+ cmday[2] = '\0';
+ mday = atol(cmday);
+ token();
+ } else {
+ token();
+
+ if (mon <= 31 && (sc_tokid == SLASH || sc_tokid == DOT)) {
+ int sep;
+
+ sep = sc_tokid;
+ try(expect2(NUMBER, "there should be %s number after '%c'",
+ sep == DOT ? "month" : "day",
+ sep == DOT ? '.' : '/'));
+ mday = atol(sc_token);
+ if (token() == sep) {
+ try(expect2
+ (NUMBER, "there should be year number after '%c'",
+ sep == DOT ? '.' : '/'));
+ year = atol(sc_token);
+ token();
+ }
+
+ /* flip months and days for European timing
+ */
+ if (sep == DOT) {
+ long x = mday;
+
+ mday = mon;
+ mon = x;
+ }
+ }
+ }
+
+ mon--;
+ if (mon < 0 || mon > 11) {
+ panic(e("did you really mean month %d?", mon + 1));
+ }
+ if (mday < 1 || mday > 31) {
+ panic(e("I'm afraid that %d is not a valid day of the month",
+ mday));
+ }
+ try(assign_date(ptv, mday, mon, year));
+ break;
+ } /* case */
+ return TIME_OK;
+} /* month */
+
+
+/* Global functions */
+
+
+/*
+ * rrd_parsetime() is the external interface that takes tspec, parses
+ * it and puts the result in the rrd_time_value structure *ptv.
+ * It can return either absolute times (these are ensured to be
+ * correct) or relative time references that are expected to be
+ * added to some absolute time value and then normalized by
+ * mktime() The return value is either TIME_OK (aka NULL) or
+ * the pointer to the error message in the case of problems
+ */
+char *rrd_parsetime(
+ const char *tspec,
+ rrd_time_value_t *ptv)
+{
+ time_t now = time(NULL);
+ int hr = 0;
+
+ /* this MUST be initialized to zero for midnight/noon/teatime */
+
+ Specials = VariousWords; /* initialize special words context */
+
+ try(init_scanner(1, &tspec));
+
+ /* establish the default time reference */
+ ptv->type = ABSOLUTE_TIME;
+ ptv->offset = 0;
+ ptv->tm = *localtime(&now);
+ ptv->tm. tm_isdst = -1; /* mk time can figure dst by default ... */
+
+ token();
+ switch (sc_tokid) {
+ case PLUS:
+ case MINUS:
+ break; /* jump to OFFSET-SPEC part */
+
+ case START:
+ ptv->type = RELATIVE_TO_START_TIME;
+ goto KeepItRelative;
+ case END:
+ ptv->type = RELATIVE_TO_END_TIME;
+ KeepItRelative:
+ ptv->tm. tm_sec = 0;
+ ptv->tm. tm_min = 0;
+ ptv->tm. tm_hour = 0;
+ ptv->tm. tm_mday = 0;
+ ptv->tm. tm_mon = 0;
+ ptv->tm. tm_year = 0;
+
+ /* FALLTHRU */
+ case NOW:
+ {
+ int time_reference = sc_tokid;
+
+ token();
+ if (sc_tokid == PLUS || sc_tokid == MINUS)
+ break;
+ if (time_reference != NOW) {
+ panic(e("'start' or 'end' MUST be followed by +|- offset"));
+ } else if (sc_tokid != EOF) {
+ panic(e("if 'now' is followed by a token it must be +|- offset"));
+ }
+ };
+ break;
+
+ /* Only absolute time specifications below */
+ case NUMBER:
+ {
+ long hour_sv = ptv->tm.tm_hour;
+ long year_sv = ptv->tm.tm_year;
+
+ ptv->tm. tm_hour = 30;
+ ptv->tm. tm_year = 30000;
+
+ try(tod(ptv))
+ try(day(ptv))
+ if (ptv->tm.tm_hour == 30 && ptv->tm.tm_year != 30000) {
+ try(tod(ptv))
+ }
+ if (ptv->tm.tm_hour == 30) {
+ ptv->tm. tm_hour = hour_sv;
+ }
+ if (ptv->tm.tm_year == 30000) {
+ ptv->tm. tm_year = year_sv;
+ }
+ };
+ break;
+ /* fix month parsing */
+ case JAN:
+ case FEB:
+ case MAR:
+ case APR:
+ case MAY:
+ case JUN:
+ case JUL:
+ case AUG:
+ case SEP:
+ case OCT:
+ case NOV:
+ case DEC:
+ try(day(ptv));
+ if (sc_tokid != NUMBER)
+ break;
+ try(tod(ptv))
+ break;
+
+ /* evil coding for TEATIME|NOON|MIDNIGHT - we've initialized
+ * 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 rettime to that hour before leaping into the
+ * month scanner
+ */
+ case TEATIME:
+ hr += 4;
+ /* FALLTHRU */
+ case NOON:
+ hr += 12;
+ /* FALLTHRU */
+ case MIDNIGHT:
+ /* if (ptv->tm.tm_hour >= hr) {
+ ptv->tm.tm_mday++;
+ ptv->tm.tm_wday++;
+ } *//* shifting does not makes sense here ... noon is noon */
+ ptv->tm. tm_hour = hr;
+ ptv->tm. tm_min = 0;
+ ptv->tm. tm_sec = 0;
+
+ token();
+ try(day(ptv));
+ break;
+ default:
+ panic(e("unparsable time: %s%s", sc_token, sct));
+ break;
+ } /* ugly case statement */
+
+ /*
+ * the OFFSET-SPEC part
+ *
+ * (NOTE, the sc_tokid was prefetched for us by the previous code)
+ */
+ if (sc_tokid == PLUS || sc_tokid == MINUS) {
+ Specials = TimeMultipliers; /* switch special words context */
+ while (sc_tokid == PLUS || sc_tokid == MINUS || sc_tokid == NUMBER) {
+ if (sc_tokid == NUMBER) {
+ try(plus_minus(ptv, PREVIOUS_OP));
+ } else
+ try(plus_minus(ptv, sc_tokid));
+ token(); /* We will get EOF eventually but that's OK, since
+ token() will return us as many EOFs as needed */
+ }
+ }
+
+ /* now we should be at EOF */
+ if (sc_tokid != EOF) {
+ panic(e("unparsable trailing text: '...%s%s'", sc_token, sct));
+ }
+
+ if (ptv->type == ABSOLUTE_TIME)
+ if (mktime(&ptv->tm) == -1) { /* normalize & check */
+ /* can happen for "nonexistent" times, e.g. around 3am */
+ /* when winter -> summer time correction eats a hour */
+ panic(e("the specified time is incorrect (out of range?)"));
+ }
+ EnsureMemFree();
+ return TIME_OK;
+} /* rrd_parsetime */
+
+
+int rrd_proc_start_end(
+ rrd_time_value_t *start_tv,
+ rrd_time_value_t *end_tv,
+ time_t *start,
+ time_t *end)
+{
+ if (start_tv->type == RELATIVE_TO_END_TIME && /* same as the line above */
+ end_tv->type == RELATIVE_TO_START_TIME) {
+ rrd_set_error("the start and end times cannot be specified "
+ "relative to each other");
+ return -1;
+ }
+
+ if (start_tv->type == RELATIVE_TO_START_TIME) {
+ rrd_set_error
+ ("the start time cannot be specified relative to itself");
+ return -1;
+ }
+
+ if (end_tv->type == RELATIVE_TO_END_TIME) {
+ rrd_set_error("the end time cannot be specified relative to itself");
+ return -1;
+ }
+
+ if (start_tv->type == RELATIVE_TO_END_TIME) {
+ struct tm tmtmp;
+
+ *end = mktime(&(end_tv->tm)) + end_tv->offset;
+ tmtmp = *localtime(end); /* reinit end including offset */
+ tmtmp.tm_mday += start_tv->tm.tm_mday;
+ tmtmp.tm_mon += start_tv->tm.tm_mon;
+ tmtmp.tm_year += start_tv->tm.tm_year;
+
+ *start = mktime(&tmtmp) + start_tv->offset;
+ } else {
+ *start = mktime(&(start_tv->tm)) + start_tv->offset;
+ }
+ if (end_tv->type == RELATIVE_TO_START_TIME) {
+ struct tm tmtmp;
+
+ *start = mktime(&(start_tv->tm)) + start_tv->offset;
+ tmtmp = *localtime(start);
+ tmtmp.tm_mday += end_tv->tm.tm_mday;
+ tmtmp.tm_mon += end_tv->tm.tm_mon;
+ tmtmp.tm_year += end_tv->tm.tm_year;
+
+ *end = mktime(&tmtmp) + end_tv->offset;
+ } else {
+ *end = mktime(&(end_tv->tm)) + end_tv->offset;
+ }
+ return 0;
+} /* rrd_proc_start_end */