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

/*
 * 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 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 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 dependant list of specials for parser to recognize,
 * required for us to be able distinguish between 'mon' as 'month'
 * and 'mon' as 'monday'
 */
static struct SpecialToken *Specials;

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 int need;	/* scanner - need to advance to next argument */

static char *sc_token=NULL;	/* scanner - token buffer */
static size_t sc_len;   /* scanner - lenght of token buffer */
static int sc_tokid;	/* scanner - token id */

static int need_to_free = 0; /* means that we need deallocating memory */

/* Local functions */

void EnsureMemFree ()
{
  if( need_to_free )
    {
    free(sc_token);
    need_to_free = 0;
    }
}

/*
 * 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 successfull 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 follwed 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 aprropriate 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 lexiographically less than,
   equal to or greater than S2.  -- copied from GNU libc*/
static int
mystrcasecmp (s1, s2)
     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, 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";
    need_to_free = 1;
    return TIME_OK;
} /* init_scanner */

/*
 * token() fetches a token from the input stream
 */
static int
token()
{
    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 */


/* 
 * expect() gets a token and complins if it's not the token we want
 */
static char *
expect(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;
    
} /* expect */


/*
 * plus_minus() is used to parse a single NUMBER TIME-UNIT pair
 *              for the OFFSET-SPEC.
 *              It allso applies those m-guessing euristics.
 */
static char *
plus_minus(struct time_value *ptv, int doop)
{
    static int op = PLUS;
    static int prev_multiplier = -1;
    int delta;

    if( doop >= 0 ) 
      {
      op = doop;
      try(expect(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(struct time_value *ptv)
{
    int hour, minute = 0;
    int tlen;
    /* save token status in  case we must abort */
    int scc_sv = scc; 
    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(expect(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(struct time_value *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(struct time_value *ptv)
{
    long 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(expect(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);
	    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*356*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(expect(NUMBER,"there should be %s number after '%c'",
			   sep == DOT ? "month" : "day", sep == DOT ? '.' : '/'));
		mday = atol(sc_token);
		if (token() == sep) {
		  try(expect(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 */


/*
 * parsetime() is the external interface that takes tspec, parses
 * it and puts the result in the 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 *
parsetime(char *tspec, struct time_value *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 this out for us ... */

    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:
	    try(tod(ptv))
	    if (sc_tokid != NUMBER) 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 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 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));
    }

    ptv->tm.tm_isdst = -1; /* for mktime to guess DST status */
    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;
} /* parsetime */


int proc_start_end (struct time_value *start_tv, 
		    struct time_value *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;
} /* proc_start_end */







