/* * 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 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 "rrd_tool.h" #include /* 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 dependent 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 - length 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 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 (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 */ /* * 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(struct rrd_time_value *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(struct rrd_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(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(struct rrd_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 rrd_time_value *ptv) { long mday=0, wday, mon, year = ptv->tm.tm_year; int tlen; time_t montime; 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); 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 , MM/DD/[YY]YY, or DD.MM.[YY]YY */ tlen = strlen(sc_token); mon = atol(sc_token); if (mon > 10*365*24*60*60) { montime = mon; ptv->tm=*localtime(&montime); 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 */ /* * 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 * parsetime(char *tspec, struct rrd_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); /* local time has set this right */ /* 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 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)); } /* 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 rrd_time_value *start_tv, struct rrd_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 */