2
* parsetime.c - parse time for at(1)
3
* Copyright (C) 1993, 1994 Thomas Koenig
5
* modifications for english-language times
6
* Copyright (C) 1993 David Parsons
8
* A lot of modifications and extensions
9
* (including the new syntax being useful for RRDB)
10
* Copyright (C) 1999 Oleg Cherevko (aka Olwi Deer)
12
* severe structural damage inflicted by Tobi Oetiker in 1999
14
* Redistribution and use in source and binary forms, with or without
15
* modification, are permitted provided that the following conditions
17
* 1. Redistributions of source code must retain the above copyright
18
* notice, this list of conditions and the following disclaimer.
19
* 2. The name of the author(s) may not be used to endorse or promote
20
* products derived from this software without specific prior written
23
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
24
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
25
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
26
* IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
27
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
28
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30
* THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
32
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36
* The BNF-like specification of the time syntax parsed is below:
38
* As usual, [ X ] means that X is optional, { X } means that X may
39
* be either omitted or specified as many times as needed,
40
* alternatives are separated by |, brackets are used for grouping.
41
* (# marks the beginning of comment that extends to the end of line)
43
* TIME-SPECIFICATION ::= TIME-REFERENCE [ OFFSET-SPEC ] |
45
* ( START | END ) OFFSET-SPEC
47
* TIME-REFERENCE ::= NOW | TIME-OF-DAY-SPEC [ DAY-SPEC-1 ] |
48
* [ TIME-OF-DAY-SPEC ] DAY-SPEC-2
50
* TIME-OF-DAY-SPEC ::= NUMBER (':') NUMBER [am|pm] | # HH:MM
51
* 'noon' | 'midnight' | 'teatime'
53
* DAY-SPEC-1 ::= NUMBER '/' NUMBER '/' NUMBER | # MM/DD/[YY]YY
54
* NUMBER '.' NUMBER '.' NUMBER | # DD.MM.[YY]YY
55
* NUMBER # Seconds since 1970
58
* DAY-SPEC-2 ::= MONTH-NAME NUMBER [NUMBER] | # Month DD [YY]YY
59
* 'yesterday' | 'today' | 'tomorrow' |
63
* OFFSET-SPEC ::= '+'|'-' NUMBER TIME-UNIT { ['+'|'-'] NUMBER TIME-UNIT }
65
* TIME-UNIT ::= SECONDS | MINUTES | HOURS |
66
* DAYS | WEEKS | MONTHS | YEARS
70
* START ::= 'start' | 's'
73
* SECONDS ::= 'seconds' | 'second' | 'sec' | 's'
74
* MINUTES ::= 'minutes' | 'minute' | 'min' | 'm'
75
* HOURS ::= 'hours' | 'hour' | 'hr' | 'h'
76
* DAYS ::= 'days' | 'day' | 'd'
77
* WEEKS ::= 'weeks' | 'week' | 'wk' | 'w'
78
* MONTHS ::= 'months' | 'month' | 'mon' | 'm'
79
* YEARS ::= 'years' | 'year' | 'yr' | 'y'
81
* MONTH-NAME ::= 'jan' | 'january' | 'feb' | 'february' | 'mar' | 'march' |
82
* 'apr' | 'april' | 'may' | 'jun' | 'june' | 'jul' | 'july' |
83
* 'aug' | 'august' | 'sep' | 'september' | 'oct' | 'october' |
84
* 'nov' | 'november' | 'dec' | 'december'
86
* DAY-OF-WEEK ::= 'sunday' | 'sun' | 'monday' | 'mon' | 'tuesday' | 'tue' |
87
* 'wednesday' | 'wed' | 'thursday' | 'thu' | 'friday' | 'fri' |
91
* As you may note, there is an ambiguity with respect to
92
* the 'm' time unit (which can mean either minutes or months).
93
* To cope with this, code tries to read users mind :) by applying
94
* certain heuristics. There are two of them:
96
* 1. If 'm' is used in context of (i.e. right after the) years,
97
* months, weeks, or days it is assumed to mean months, while
98
* in the context of hours, minutes, and seconds it means minutes.
99
* (e.g., in -1y6m or +3w1m 'm' means 'months', while in
100
* -3h20m or +5s2m 'm' means 'minutes')
102
* 2. Out of context (i.e. right after the '+' or '-' sign) the
103
* meaning of 'm' is guessed from the number it directly follows.
104
* Currently, if the number absolute value is below 25 it is assumed
105
* that 'm' means months, otherwise it is treated as minutes.
106
* (e.g., -25m == -25 minutes, while +24m == +24 months)
114
#include "rrd_tool.h"
117
/* Structures and unions */
120
MIDNIGHT, NOON, TEATIME,
121
PM, AM, YESTERDAY, TODAY, TOMORROW, NOW, START, END,
122
SECONDS, MINUTES, HOURS, DAYS, WEEKS, MONTHS, YEARS,
124
NUMBER, PLUS, MINUS, DOT, COLON, SLASH, ID, JUNK,
125
JAN, FEB, MAR, APR, MAY, JUN,
126
JUL, AUG, SEP, OCT, NOV, DEC,
127
SUN, MON, TUE, WED, THU, FRI, SAT
130
/* the below is for plus_minus() */
131
#define PREVIOUS_OP (-1)
133
/* parse translation table - table driven parsers can be your FRIEND!
135
struct SpecialToken {
136
char *name; /* token name */
137
int value; /* token id */
139
static struct SpecialToken VariousWords[] = {
140
{ "midnight", MIDNIGHT }, /* 00:00:00 of today or tomorrow */
141
{ "noon", NOON }, /* 12:00:00 of today or tomorrow */
142
{ "teatime", TEATIME }, /* 16:00:00 of today or tomorrow */
143
{ "am", AM }, /* morning times for 0-12 clock */
144
{ "pm", PM }, /* evening times for 0-12 clock */
145
{ "tomorrow", TOMORROW },
146
{ "yesterday", YESTERDAY },
175
{ "september", SEP },
185
{ "wednesday", WED },
193
{ NULL, 0 } /*** SENTINEL ***/
196
static struct SpecialToken TimeMultipliers[] = {
197
{ "second", SECONDS }, /* seconds multiplier */
198
{ "seconds", SECONDS }, /* (pluralized) */
199
{ "sec", SECONDS }, /* (generic) */
200
{ "s", SECONDS }, /* (short generic) */
201
{ "minute", MINUTES }, /* minutes multiplier */
202
{ "minutes", MINUTES }, /* (pluralized) */
203
{ "min", MINUTES }, /* (generic) */
204
{ "m", MONTHS_MINUTES }, /* (short generic) */
205
{ "hour", HOURS }, /* hours ... */
206
{ "hours", HOURS }, /* (pluralized) */
207
{ "hr", HOURS }, /* (generic) */
208
{ "h", HOURS }, /* (short generic) */
209
{ "day", DAYS }, /* days ... */
210
{ "days", DAYS }, /* (pluralized) */
211
{ "d", DAYS }, /* (short generic) */
212
{ "week", WEEKS }, /* week ... */
213
{ "weeks", WEEKS }, /* (pluralized) */
214
{ "wk", WEEKS }, /* (generic) */
215
{ "w", WEEKS }, /* (short generic) */
216
{ "month", MONTHS }, /* week ... */
217
{ "months", MONTHS }, /* (pluralized) */
218
{ "mon", MONTHS }, /* (generic) */
219
{ "year", YEARS }, /* year ... */
220
{ "years", YEARS }, /* (pluralized) */
221
{ "yr", YEARS }, /* (generic) */
222
{ "y", YEARS }, /* (short generic) */
223
{ NULL, 0 } /*** SENTINEL ***/
226
/* File scope variables */
228
/* context dependant list of specials for parser to recognize,
229
* required for us to be able distinguish between 'mon' as 'month'
230
* and 'mon' as 'monday'
232
static struct SpecialToken *Specials;
234
static char **scp; /* scanner - pointer at arglist */
235
static char scc; /* scanner - count of remaining arguments */
236
static char *sct; /* scanner - next char pointer in current argument */
237
static int need; /* scanner - need to advance to next argument */
239
static char *sc_token=NULL; /* scanner - token buffer */
240
static size_t sc_len; /* scanner - lenght of token buffer */
241
static int sc_tokid; /* scanner - token id */
243
static int need_to_free = 0; /* means that we need deallocating memory */
245
/* Local functions */
247
void EnsureMemFree ()
257
* A hack to compensate for the lack of the C++ exceptions
259
* Every function func that might generate parsing "exception"
260
* should return TIME_OK (aka NULL) or pointer to the error message,
261
* and should be called like this: try(func(args));
263
* if the try is not successfull it will reset the token pointer ...
265
* [NOTE: when try(...) is used as the only statement in the "if-true"
266
* part of the if statement that also has an "else" part it should be
267
* either enclosed in the curly braces (despite the fact that it looks
268
* like a single statement) or NOT follwed by the ";"]
280
* The panic() function was used in the original code to die, we redefine
281
* it as macro to start the chain of ascending returns that in conjunction
282
* with the try(b) above will simulate a sort of "exception handling"
290
* ve() and e() are used to set the return error,
291
* the most aprropriate use for these is inside panic(...)
293
#define MAX_ERR_MSG_LEN 1024
294
static char errmsg[ MAX_ERR_MSG_LEN ];
297
ve ( char *fmt, va_list ap )
299
#ifdef HAVE_VSNPRINTF
300
vsnprintf( errmsg, MAX_ERR_MSG_LEN, fmt, ap );
302
vsprintf( errmsg, fmt, ap );
319
/* Compare S1 and S2, ignoring case, returning less than, equal to or
320
greater than zero if S1 is lexiographically less than,
321
equal to or greater than S2. -- copied from GNU libc*/
323
mystrcasecmp (s1, s2)
327
const unsigned char *p1 = (const unsigned char *) s1;
328
const unsigned char *p2 = (const unsigned char *) s2;
329
unsigned char c1, c2;
336
c1 = tolower (*p1++);
337
c2 = tolower (*p2++);
347
* parse a token, checking if it's something special to us
350
parse_token(char *arg)
354
for (i=0; Specials[i].name != NULL; i++)
355
if (mystrcasecmp(Specials[i].name, arg) == 0)
356
return sc_tokid = Specials[i].value;
358
/* not special - must be some random id */
359
return sc_tokid = ID;
365
* init_scanner() sets up the scanner to eat arguments
368
init_scanner(int argc, char **argv)
375
sc_len += strlen(*argv++);
377
sc_token = (char *) malloc(sc_len*sizeof(char));
378
if( sc_token == NULL )
379
return "Failed to allocate memory";
385
* token() fetches a token from the input stream
393
memset(sc_token, '\0', sc_len);
397
/* if we need to read another argument, walk along the argument list;
398
* when we fall off the arglist, we'll just return EOF forever
408
/* eat whitespace now - if we walk off the end of the argument,
409
* we'll continue, which puts us up at the top of the while loop
410
* to fetch the next argument in
412
while (isspace((unsigned char)*sct) || *sct == '_' || *sct == ',' )
419
/* preserve the first character of the new token
421
sc_token[0] = *sct++;
423
/* then see what it is
425
if (isdigit((unsigned char)(sc_token[0]))) {
426
while (isdigit((unsigned char)(*sct)))
427
sc_token[++idx] = *sct++;
428
sc_token[++idx] = '\0';
429
return sc_tokid = NUMBER;
431
else if (isalpha((unsigned char)(sc_token[0]))) {
432
while (isalpha((unsigned char)(*sct)))
433
sc_token[++idx] = *sct++;
434
sc_token[++idx] = '\0';
435
return parse_token(sc_token);
437
else switch(sc_token[0]) {
438
case ':': return sc_tokid = COLON;
439
case '.': return sc_tokid = DOT;
440
case '+': return sc_tokid = PLUS;
441
case '-': return sc_tokid = MINUS;
442
case '/': return sc_tokid = SLASH;
444
/*OK, we did not make it ... */
446
return sc_tokid = EOF;
453
* expect2() gets a token and complins if it's not the token we want
456
expect2(int desired, char *complain_fmt, ...)
459
va_start( ap, complain_fmt );
460
if (token() != desired) {
461
panic(ve( complain_fmt, ap ));
470
* plus_minus() is used to parse a single NUMBER TIME-UNIT pair
471
* for the OFFSET-SPEC.
472
* It allso applies those m-guessing euristics.
475
plus_minus(struct time_value *ptv, int doop)
477
static int op = PLUS;
478
static int prev_multiplier = -1;
484
try(expect2(NUMBER,"There should be number after '%c'", op == PLUS ? '+' : '-'));
485
prev_multiplier = -1; /* reset months-minutes guessing mechanics */
487
/* if doop is < 0 then we repeat the previous op
488
* with the prefetched number */
490
delta = atoi(sc_token);
492
if( token() == MONTHS_MINUTES )
494
/* hard job to guess what does that -5m means: -5mon or -5min? */
495
switch(prev_multiplier)
511
if( delta < 6 ) /* it may be some other value but in the context
512
* of RRD who needs less than 6 min deltas? */
518
prev_multiplier = sc_tokid;
521
ptv->tm.tm_year += (op == PLUS) ? delta : -delta;
524
ptv->tm.tm_mon += (op == PLUS) ? delta : -delta;
530
ptv->tm.tm_mday += (op == PLUS) ? delta : -delta;
533
ptv->offset += (op == PLUS) ? delta*60*60 : -delta*60*60;
536
ptv->offset += (op == PLUS) ? delta*60 : -delta*60;
539
ptv->offset += (op == PLUS) ? delta : -delta;
541
default: /*default unit is seconds */
542
ptv->offset += (op == PLUS) ? delta : -delta;
545
panic(e("well-known time unit expected after %d", delta));
547
return TIME_OK; /* to make compiler happy :) */
552
* tod() computes the time of day (TIME-OF-DAY-SPEC)
555
tod(struct time_value *ptv)
557
int hour, minute = 0;
559
/* save token status in case we must abort */
562
int sc_tokid_sv = sc_tokid;
564
tlen = strlen(sc_token);
566
/* first pick out the time of day - we assume a HH (COLON|DOT) MM time
572
hour = atoi(sc_token);
575
if (sc_tokid == SLASH || sc_tokid == DOT) {
576
/* guess we are looking at a date */
579
sc_tokid = sc_tokid_sv;
580
sprintf (sc_token,"%d", hour);
583
if (sc_tokid == COLON ) {
585
"Parsing HH:MM syntax, expecting MM as number, got none"));
586
minute = atoi(sc_token);
588
panic(e("parsing HH:MM syntax, got MM = %d (>59!)", minute ));
593
/* check if an AM or PM specifier was given
595
if (sc_tokid == AM || sc_tokid == PM) {
597
panic(e("there cannot be more than 12 AM or PM hours"));
599
if (sc_tokid == PM) {
600
if (hour != 12) /* 12:xx PM is 12:xx, not 24:xx */
603
if (hour == 12) /* 12:xx AM is 00:xx, not 12:xx */
608
else if (hour > 23) {
609
/* guess it was not a time then ... */
612
sc_tokid = sc_tokid_sv;
613
sprintf (sc_token,"%d", hour);
616
ptv->tm.tm_hour = hour;
617
ptv->tm.tm_min = minute;
619
if (ptv->tm.tm_hour == 24) {
628
* assign_date() assigns a date, adjusting year as appropriate
631
assign_date(struct time_value *ptv, long mday, long mon, long year)
637
panic(e("invalid year %d (should be either 00-99 or >1900)",
640
} else if( year >= 0 && year < 38 ) {
641
year += 100; /* Allow year 2000-2037 to be specified as */
642
} /* 00-37 until the problem of 2038 year will */
643
/* arise for unices with 32-bit time_t :) */
645
panic(e("won't handle dates before epoch (01/01/1970), sorry"));
648
ptv->tm.tm_mday = mday;
649
ptv->tm.tm_mon = mon;
650
ptv->tm.tm_year = year;
656
* day() picks apart DAY-SPEC-[12]
659
day(struct time_value *ptv)
661
long mday=0, wday, mon, year = ptv->tm.tm_year;
668
case TODAY: /* force ourselves to stay in today - no further processing */
676
case JAN: case FEB: case MAR: case APR: case MAY: case JUN:
677
case JUL: case AUG: case SEP: case OCT: case NOV: case DEC:
678
/* do month mday [year]
680
mon = (sc_tokid-JAN);
682
"the day of the month should follow month name"));
683
mday = atol(sc_token);
684
if (token() == NUMBER) {
685
year = atol(sc_token);
689
year = ptv->tm.tm_year;
690
try(assign_date(ptv, mday, mon, year));
693
case SUN: case MON: case TUE:
694
case WED: case THU: case FRI:
696
/* do a particular day of the week
698
wday = (sc_tokid-SUN);
699
ptv->tm.tm_mday += (wday - ptv->tm.tm_wday);
702
mday = ptv->tm.tm_mday;
703
mday += (wday - ptv->tm.tm_wday);
704
ptv->tm.tm_wday = wday;
706
try(assign_date(ptv, mday, ptv->tm.tm_mon, ptv->tm.tm_year));
711
/* get numeric <sec since 1970>, MM/DD/[YY]YY, or DD.MM.[YY]YY
713
tlen = strlen(sc_token);
714
mon = atol(sc_token);
715
if (mon > 10*356*24*60*60) {
716
ptv->tm=*localtime(&mon);
721
if (mon > 19700101 && mon < 24000101){ /*works between 1900 and 2400 */
722
char cmon[3],cmday[3],cyear[5];
723
strncpy(cyear,sc_token,4);cyear[4]='\0';
725
strncpy(cmon,&(sc_token[4]),2);cmon[2]='\0';
727
strncpy(cmday,&(sc_token[6]),2);cmday[2]='\0';
733
if (mon <= 31 && (sc_tokid == SLASH || sc_tokid == DOT)) {
736
try(expect2(NUMBER,"there should be %s number after '%c'",
737
sep == DOT ? "month" : "day", sep == DOT ? '.' : '/'));
738
mday = atol(sc_token);
739
if (token() == sep) {
740
try(expect2(NUMBER,"there should be year number after '%c'",
741
sep == DOT ? '.' : '/'));
742
year = atol(sc_token);
746
/* flip months and days for european timing
757
if(mon < 0 || mon > 11 ) {
758
panic(e("did you really mean month %d?", mon+1));
760
if(mday < 1 || mday > 31) {
761
panic(e("I'm afraid that %d is not a valid day of the month",
764
try(assign_date(ptv, mday, mon, year));
771
/* Global functions */
775
* parsetime() is the external interface that takes tspec, parses
776
* it and puts the result in the time_value structure *ptv.
777
* It can return either absolute times (these are ensured to be
778
* correct) or relative time references that are expected to be
779
* added to some absolute time value and then normalized by
780
* mktime() The return value is either TIME_OK (aka NULL) or
781
* the pointer to the error message in the case of problems
784
parsetime(char *tspec, struct time_value *ptv)
786
time_t now = time(NULL);
788
/* this MUST be initialized to zero for midnight/noon/teatime */
790
Specials = VariousWords; /* initialize special words context */
792
try(init_scanner( 1, &tspec ));
794
/* establish the default time reference */
795
ptv->type = ABSOLUTE_TIME;
797
ptv->tm = *localtime(&now);
798
ptv->tm.tm_isdst = -1; /* mk time can figure this out for us ... */
804
break; /* jump to OFFSET-SPEC part */
807
ptv->type = RELATIVE_TO_START_TIME;
810
ptv->type = RELATIVE_TO_END_TIME;
821
int time_reference = sc_tokid;
823
if( sc_tokid == PLUS || sc_tokid == MINUS )
825
if( time_reference != NOW ) {
826
panic(e("'start' or 'end' MUST be followed by +|- offset"));
829
if( sc_tokid != EOF ) {
830
panic(e("if 'now' is followed by a token it must be +|- offset"));
835
/* Only absolute time specifications below */
838
if (sc_tokid != NUMBER) break;
839
/* fix month parsing */
840
case JAN: case FEB: case MAR: case APR: case MAY: case JUN:
841
case JUL: case AUG: case SEP: case OCT: case NOV: case DEC:
843
if (sc_tokid != NUMBER) break;
847
/* evil coding for TEATIME|NOON|MIDNIGHT - we've initialised
848
* hr to zero up above, then fall into this case in such a
849
* way so we add +12 +4 hours to it for teatime, +12 hours
850
* to it for noon, and nothing at all for midnight, then
851
* set our rettime to that hour before leaping into the
861
/* if (ptv->tm.tm_hour >= hr) {
864
} */ /* shifting does not makes sense here ... noon is noon */
865
ptv->tm.tm_hour = hr;
872
panic(e("unparsable time: %s%s",sc_token,sct));
874
} /* ugly case statement */
877
* the OFFSET-SPEC part
879
* (NOTE, the sc_tokid was prefetched for us by the previous code)
881
if( sc_tokid == PLUS || sc_tokid == MINUS ) {
882
Specials = TimeMultipliers; /* switch special words context */
883
while( sc_tokid == PLUS || sc_tokid == MINUS ||
884
sc_tokid == NUMBER ) {
885
if( sc_tokid == NUMBER ) {
886
try(plus_minus(ptv, PREVIOUS_OP ));
888
try(plus_minus(ptv, sc_tokid));
889
token(); /* We will get EOF eventually but that's OK, since
890
token() will return us as many EOFs as needed */
894
/* now we should be at EOF */
895
if( sc_tokid != EOF ) {
896
panic(e("unparsable trailing text: '...%s%s'", sc_token, sct));
899
ptv->tm.tm_isdst = -1; /* for mktime to guess DST status */
900
if( ptv->type == ABSOLUTE_TIME )
901
if( mktime( &ptv->tm ) == -1 ) { /* normalize & check */
902
/* can happen for "nonexistent" times, e.g. around 3am */
903
/* when winter -> summer time correction eats a hour */
904
panic(e("the specified time is incorrect (out of range?)"));
911
int proc_start_end (struct time_value *start_tv,
912
struct time_value *end_tv,
915
if (start_tv->type == RELATIVE_TO_END_TIME && /* same as the line above */
916
end_tv->type == RELATIVE_TO_START_TIME) {
917
rrd_set_error("the start and end times cannot be specified "
918
"relative to each other");
922
if (start_tv->type == RELATIVE_TO_START_TIME) {
923
rrd_set_error("the start time cannot be specified relative to itself");
927
if (end_tv->type == RELATIVE_TO_END_TIME) {
928
rrd_set_error("the end time cannot be specified relative to itself");
932
if( start_tv->type == RELATIVE_TO_END_TIME) {
934
*end = mktime(&(end_tv->tm)) + end_tv->offset;
935
tmtmp = *localtime(end); /* reinit end including offset */
936
tmtmp.tm_mday += start_tv->tm.tm_mday;
937
tmtmp.tm_mon += start_tv->tm.tm_mon;
938
tmtmp.tm_year += start_tv->tm.tm_year;
939
*start = mktime(&tmtmp) + start_tv->offset;
941
*start = mktime(&(start_tv->tm)) + start_tv->offset;
943
if (end_tv->type == RELATIVE_TO_START_TIME) {
945
*start = mktime(&(start_tv->tm)) + start_tv->offset;
946
tmtmp = *localtime(start);
947
tmtmp.tm_mday += end_tv->tm.tm_mday;
948
tmtmp.tm_mon += end_tv->tm.tm_mon;
949
tmtmp.tm_year += end_tv->tm.tm_year;
950
*end = mktime(&tmtmp) + end_tv->offset;
952
*end = mktime(&(end_tv->tm)) + end_tv->offset;
955
} /* proc_start_end */