/*
 *  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, WEHTHER 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.
 */

/*
 * 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 6 it is assumed
 *    that 'm' means months, otherwise it is treated as minutes.
 *    (e.g., -6m == -6 minutes, while +5m == +5 months)
 *
 */

/* System Headers */

/* Local headers */
#include "mutex.h"
#include <stdarg.h>
#include <stdlib.h>
#include <ctype.h>

#include "rrd_tool.h"

/* Structures and unions */

enum {                  /* symbols */
    MIDNIGHT, NOON, TEATIME,
    PM, AM, YESTERDAY, TODAY, TOMORROW, NOW, START, END, EPOCH,
    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},
    {"epoch", EPOCH},

    {"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;
    char      *msg;

    va_start(ap, complain_fmt);
    if (token() != desired) {
    	msg = ve(complain_fmt, ap);
        va_end(ap);
        panic(msg);
    }
    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;
        snprintf(sc_token, sc_len, "%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;
        snprintf(sc_token, sc_len, "%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;
    switch (sc_tokid) {
    case YESTERDAY:
        ptv->tm.  tm_mday--;

        /* FALLTHRU */
    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
         */
        mon = atol(sc_token);
        if (mon > 10 * 365 * 24 * 60 * 60) {
            localtime_r(&mon,&(ptv->tm));

            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
 */

static mutex_t parsetime_mutex = MUTEX_INITIALIZER;

static char     *rrd_parsetime_nomt(
    const char *tspec,
    rrd_time_value_t * ptv);
    
char     *rrd_parsetime(
    const char *tspec,
    rrd_time_value_t * ptv)
{
    /* yes this code is non re-entrant ... so lets make sure we do not run
       in twice */
    mutex_lock(&parsetime_mutex);

    char *result = rrd_parsetime_nomt(tspec, ptv);

    /* ok done ... drop the mutex lock */
    mutex_unlock(&parsetime_mutex);

    return result;
}

static char     *rrd_parsetime_nomt(
    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;
    localtime_r(&now,&(ptv->tm));
    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 EPOCH:
        ptv->type = RELATIVE_TO_EPOCH;
        goto KeepItRelative;
    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_nomt */


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;
        localtime_r(end,&tmtmp);    /* 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;
        localtime_r(start,&tmtmp);
        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 */
