508546a23ba1ae7c9f2fa56fee93ffb4c5125e5a
[rrdtool.git] / src / parsetime.c
1 /*  
2  *  parsetime.c - parse time for at(1)
3  *  Copyright (C) 1993, 1994  Thomas Koenig
4  *
5  *  modifications for English-language times
6  *  Copyright (C) 1993  David Parsons
7  *
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)
11  *
12  *  severe structural damage inflicted by Tobi Oetiker in 1999
13  *
14  * Redistribution and use in source and binary forms, with or without
15  * modification, are permitted provided that the following conditions
16  * are met:
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
21  *    permission.
22  *
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.
33  */
34
35 /* NOTE: nothing in here is thread-safe!!!! Not even the localtime
36    calls ... */
37
38 /*
39  * The BNF-like specification of the time syntax parsed is below:
40  *                                                               
41  * As usual, [ X ] means that X is optional, { X } means that X may
42  * be either omitted or specified as many times as needed,
43  * alternatives are separated by |, brackets are used for grouping.
44  * (# marks the beginning of comment that extends to the end of line)
45  *
46  * TIME-SPECIFICATION ::= TIME-REFERENCE [ OFFSET-SPEC ] |
47  *                                         OFFSET-SPEC   |
48  *                         ( START | END ) OFFSET-SPEC 
49  *
50  * TIME-REFERENCE ::= NOW | TIME-OF-DAY-SPEC [ DAY-SPEC-1 ] |
51  *                        [ TIME-OF-DAY-SPEC ] DAY-SPEC-2
52  *
53  * TIME-OF-DAY-SPEC ::= NUMBER (':') NUMBER [am|pm] | # HH:MM
54  *                     'noon' | 'midnight' | 'teatime'
55  *
56  * DAY-SPEC-1 ::= NUMBER '/' NUMBER '/' NUMBER |  # MM/DD/[YY]YY
57  *                NUMBER '.' NUMBER '.' NUMBER |  # DD.MM.[YY]YY
58  *                NUMBER                          # Seconds since 1970
59  *                NUMBER                          # YYYYMMDD
60  *
61  * DAY-SPEC-2 ::= MONTH-NAME NUMBER [NUMBER] |    # Month DD [YY]YY
62  *                'yesterday' | 'today' | 'tomorrow' |
63  *                DAY-OF-WEEK
64  *
65  *
66  * OFFSET-SPEC ::= '+'|'-' NUMBER TIME-UNIT { ['+'|'-'] NUMBER TIME-UNIT }
67  *
68  * TIME-UNIT ::= SECONDS | MINUTES | HOURS |
69  *               DAYS | WEEKS | MONTHS | YEARS
70  *
71  * NOW ::= 'now' | 'n'
72  *
73  * START ::= 'start' | 's'
74  * END   ::= 'end' | 'e'
75  *
76  * SECONDS ::= 'seconds' | 'second' | 'sec' | 's'
77  * MINUTES ::= 'minutes' | 'minute' | 'min' | 'm'
78  * HOURS   ::= 'hours' | 'hour' | 'hr' | 'h'
79  * DAYS    ::= 'days' | 'day' | 'd'
80  * WEEKS   ::= 'weeks' | 'week' | 'wk' | 'w'
81  * MONTHS  ::= 'months' | 'month' | 'mon' | 'm'
82  * YEARS   ::= 'years' | 'year' | 'yr' | 'y'
83  *
84  * MONTH-NAME ::= 'jan' | 'january' | 'feb' | 'february' | 'mar' | 'march' |
85  *                'apr' | 'april' | 'may' | 'jun' | 'june' | 'jul' | 'july' |
86  *                'aug' | 'august' | 'sep' | 'september' | 'oct' | 'october' |
87  *                'nov' | 'november' | 'dec' | 'december'
88  *
89  * DAY-OF-WEEK ::= 'sunday' | 'sun' | 'monday' | 'mon' | 'tuesday' | 'tue' |
90  *                 'wednesday' | 'wed' | 'thursday' | 'thu' | 'friday' | 'fri' |
91  *                 'saturday' | 'sat'
92  *
93  *
94  * As you may note, there is an ambiguity with respect to
95  * the 'm' time unit (which can mean either minutes or months).
96  * To cope with this, code tries to read users mind :) by applying
97  * certain heuristics. There are two of them:
98  *
99  * 1. If 'm' is used in context of (i.e. right after the) years,
100  *    months, weeks, or days it is assumed to mean months, while
101  *    in the context of hours, minutes, and seconds it means minutes.
102  *    (e.g., in -1y6m or +3w1m 'm' means 'months', while in
103  *    -3h20m or +5s2m 'm' means 'minutes')
104  *
105  * 2. Out of context (i.e. right after the '+' or '-' sign) the
106  *    meaning of 'm' is guessed from the number it directly follows.
107  *    Currently, if the number absolute value is below 25 it is assumed
108  *    that 'm' means months, otherwise it is treated as minutes.
109  *    (e.g., -25m == -25 minutes, while +24m == +24 months)
110  *
111  */
112
113 /* System Headers */
114
115 /* Local headers */
116
117 #include "rrd_tool.h"
118 #include <stdarg.h>
119
120 /* Structures and unions */
121
122 enum {                  /* symbols */
123     MIDNIGHT, NOON, TEATIME,
124     PM, AM, YESTERDAY, TODAY, TOMORROW, NOW, START, END,
125     SECONDS, MINUTES, HOURS, DAYS, WEEKS, MONTHS, YEARS,
126     MONTHS_MINUTES,
127     NUMBER, PLUS, MINUS, DOT, COLON, SLASH, ID, JUNK,
128     JAN, FEB, MAR, APR, MAY, JUN,
129     JUL, AUG, SEP, OCT, NOV, DEC,
130     SUN, MON, TUE, WED, THU, FRI, SAT
131 };
132
133 /* the below is for plus_minus() */
134 #define PREVIOUS_OP     (-1)
135
136 /* parse translation table - table driven parsers can be your FRIEND!
137  */
138 struct SpecialToken {
139     char     *name;     /* token name */
140     int       value;    /* token id */
141 };
142 static struct SpecialToken VariousWords[] = {
143     {"midnight", MIDNIGHT}, /* 00:00:00 of today or tomorrow */
144     {"noon", NOON},     /* 12:00:00 of today or tomorrow */
145     {"teatime", TEATIME},   /* 16:00:00 of today or tomorrow */
146     {"am", AM},         /* morning times for 0-12 clock */
147     {"pm", PM},         /* evening times for 0-12 clock */
148     {"tomorrow", TOMORROW},
149     {"yesterday", YESTERDAY},
150     {"today", TODAY},
151     {"now", NOW},
152     {"n", NOW},
153     {"start", START},
154     {"s", START},
155     {"end", END},
156     {"e", END},
157
158     {"jan", JAN},
159     {"feb", FEB},
160     {"mar", MAR},
161     {"apr", APR},
162     {"may", MAY},
163     {"jun", JUN},
164     {"jul", JUL},
165     {"aug", AUG},
166     {"sep", SEP},
167     {"oct", OCT},
168     {"nov", NOV},
169     {"dec", DEC},
170     {"january", JAN},
171     {"february", FEB},
172     {"march", MAR},
173     {"april", APR},
174     {"may", MAY},
175     {"june", JUN},
176     {"july", JUL},
177     {"august", AUG},
178     {"september", SEP},
179     {"october", OCT},
180     {"november", NOV},
181     {"december", DEC},
182     {"sunday", SUN},
183     {"sun", SUN},
184     {"monday", MON},
185     {"mon", MON},
186     {"tuesday", TUE},
187     {"tue", TUE},
188     {"wednesday", WED},
189     {"wed", WED},
190     {"thursday", THU},
191     {"thu", THU},
192     {"friday", FRI},
193     {"fri", FRI},
194     {"saturday", SAT},
195     {"sat", SAT},
196     {NULL, 0}           /*** SENTINEL ***/
197 };
198
199 static struct SpecialToken TimeMultipliers[] = {
200     {"second", SECONDS},    /* seconds multiplier */
201     {"seconds", SECONDS},   /* (pluralized) */
202     {"sec", SECONDS},   /* (generic) */
203     {"s", SECONDS},     /* (short generic) */
204     {"minute", MINUTES},    /* minutes multiplier */
205     {"minutes", MINUTES},   /* (pluralized) */
206     {"min", MINUTES},   /* (generic) */
207     {"m", MONTHS_MINUTES},  /* (short generic) */
208     {"hour", HOURS},    /* hours ... */
209     {"hours", HOURS},   /* (pluralized) */
210     {"hr", HOURS},      /* (generic) */
211     {"h", HOURS},       /* (short generic) */
212     {"day", DAYS},      /* days ... */
213     {"days", DAYS},     /* (pluralized) */
214     {"d", DAYS},        /* (short generic) */
215     {"week", WEEKS},    /* week ... */
216     {"weeks", WEEKS},   /* (pluralized) */
217     {"wk", WEEKS},      /* (generic) */
218     {"w", WEEKS},       /* (short generic) */
219     {"month", MONTHS},  /* week ... */
220     {"months", MONTHS}, /* (pluralized) */
221     {"mon", MONTHS},    /* (generic) */
222     {"year", YEARS},    /* year ... */
223     {"years", YEARS},   /* (pluralized) */
224     {"yr", YEARS},      /* (generic) */
225     {"y", YEARS},       /* (short generic) */
226     {NULL, 0}           /*** SENTINEL ***/
227 };
228
229 /* File scope variables */
230
231 /* context dependent list of specials for parser to recognize,
232  * required for us to be able distinguish between 'mon' as 'month'
233  * and 'mon' as 'monday'
234  */
235 static struct SpecialToken *Specials;
236
237 static const char **scp;    /* scanner - pointer at arglist */
238 static char scc;        /* scanner - count of remaining arguments */
239 static const char *sct; /* scanner - next char pointer in current argument */
240 static int need;        /* scanner - need to advance to next argument */
241
242 static char *sc_token = NULL;   /* scanner - token buffer */
243 static size_t sc_len;   /* scanner - length of token buffer */
244 static int sc_tokid;    /* scanner - token id */
245
246 /* Local functions */
247 static void EnsureMemFree(
248     void);
249
250 static void EnsureMemFree(
251     void)
252 {
253     if (sc_token) {
254         free(sc_token);
255         sc_token = NULL;
256     }
257 }
258
259 /*
260  * A hack to compensate for the lack of the C++ exceptions
261  *
262  * Every function func that might generate parsing "exception"
263  * should return TIME_OK (aka NULL) or pointer to the error message,
264  * and should be called like this: try(func(args));
265  *
266  * if the try is not successful it will reset the token pointer ...
267  *
268  * [NOTE: when try(...) is used as the only statement in the "if-true"
269  *  part of the if statement that also has an "else" part it should be
270  *  either enclosed in the curly braces (despite the fact that it looks
271  *  like a single statement) or NOT followed by the ";"]
272  */
273 #define try(b)          { \
274                         char *_e; \
275                         if((_e=(b))) \
276                           { \
277                           EnsureMemFree(); \
278                           return _e; \
279                           } \
280                         }
281
282 /*
283  * The panic() function was used in the original code to die, we redefine
284  * it as macro to start the chain of ascending returns that in conjunction
285  * with the try(b) above will simulate a sort of "exception handling"
286  */
287
288 #define panic(e)        { \
289                         return (e); \
290                         }
291
292 /*
293  * ve() and e() are used to set the return error,
294  * the most appropriate use for these is inside panic(...) 
295  */
296 #define MAX_ERR_MSG_LEN 1024
297 static char errmsg[MAX_ERR_MSG_LEN];
298
299 static char *ve(
300     char *fmt,
301     va_list ap)
302 {
303 #ifdef HAVE_VSNPRINTF
304     vsnprintf(errmsg, MAX_ERR_MSG_LEN, fmt, ap);
305 #else
306     vsprintf(errmsg, fmt, ap);
307 #endif
308     EnsureMemFree();
309     return (errmsg);
310 }
311
312 static char *e(
313     char *fmt,
314     ...)
315 {
316     char     *err;
317     va_list   ap;
318
319     va_start(ap, fmt);
320     err = ve(fmt, ap);
321     va_end(ap);
322     return (err);
323 }
324
325 /* Compare S1 and S2, ignoring case, returning less than, equal to or
326    greater than zero if S1 is lexicographically less than,
327    equal to or greater than S2.  -- copied from GNU libc*/
328 static int mystrcasecmp(
329     s1,
330     s2)
331     const char *s1;
332     const char *s2;
333 {
334     const unsigned char *p1 = (const unsigned char *) s1;
335     const unsigned char *p2 = (const unsigned char *) s2;
336     unsigned char c1, c2;
337
338     if (p1 == p2)
339         return 0;
340
341     do {
342         c1 = tolower(*p1++);
343         c2 = tolower(*p2++);
344         if (c1 == '\0')
345             break;
346     }
347     while (c1 == c2);
348
349     return c1 - c2;
350 }
351
352 /*
353  * parse a token, checking if it's something special to us
354  */
355 static int parse_token(
356     char *arg)
357 {
358     int       i;
359
360     for (i = 0; Specials[i].name != NULL; i++)
361         if (mystrcasecmp(Specials[i].name, arg) == 0)
362             return sc_tokid = Specials[i].value;
363
364     /* not special - must be some random id */
365     return sc_tokid = ID;
366 }                       /* parse_token */
367
368
369
370 /*
371  * init_scanner() sets up the scanner to eat arguments
372  */
373 static char *init_scanner(
374     int argc,
375     const char **argv)
376 {
377     scp = argv;
378     scc = argc;
379     need = 1;
380     sc_len = 1;
381     while (argc-- > 0)
382         sc_len += strlen(*argv++);
383
384     sc_token = (char *) malloc(sc_len * sizeof(char));
385     if (sc_token == NULL)
386         return "Failed to allocate memory";
387     return TIME_OK;
388 }                       /* init_scanner */
389
390 /*
391  * token() fetches a token from the input stream
392  */
393 static int token(
394     )
395 {
396     int       idx;
397
398     while (1) {
399         memset(sc_token, '\0', sc_len);
400         sc_tokid = EOF;
401         idx = 0;
402
403         /* if we need to read another argument, walk along the argument list;
404          * when we fall off the arglist, we'll just return EOF forever
405          */
406         if (need) {
407             if (scc < 1)
408                 return sc_tokid;
409             sct = *scp;
410             scp++;
411             scc--;
412             need = 0;
413         }
414         /* eat whitespace now - if we walk off the end of the argument,
415          * we'll continue, which puts us up at the top of the while loop
416          * to fetch the next argument in
417          */
418         while (isspace((unsigned char) *sct) || *sct == '_' || *sct == ',')
419             ++sct;
420         if (!*sct) {
421             need = 1;
422             continue;
423         }
424
425         /* preserve the first character of the new token
426          */
427         sc_token[0] = *sct++;
428
429         /* then see what it is
430          */
431         if (isdigit((unsigned char) (sc_token[0]))) {
432             while (isdigit((unsigned char) (*sct)))
433                 sc_token[++idx] = *sct++;
434             sc_token[++idx] = '\0';
435             return sc_tokid = NUMBER;
436         } else if (isalpha((unsigned char) (sc_token[0]))) {
437             while (isalpha((unsigned char) (*sct)))
438                 sc_token[++idx] = *sct++;
439             sc_token[++idx] = '\0';
440             return parse_token(sc_token);
441         } else
442             switch (sc_token[0]) {
443             case ':':
444                 return sc_tokid = COLON;
445             case '.':
446                 return sc_tokid = DOT;
447             case '+':
448                 return sc_tokid = PLUS;
449             case '-':
450                 return sc_tokid = MINUS;
451             case '/':
452                 return sc_tokid = SLASH;
453             default:
454                 /*OK, we did not make it ... */
455                 sct--;
456                 return sc_tokid = EOF;
457             }
458     }                   /* while (1) */
459 }                       /* token */
460
461
462 /* 
463  * expect2() gets a token and complains if it's not the token we want
464  */
465 static char *expect2(
466     int desired,
467     char *complain_fmt,
468     ...)
469 {
470     va_list   ap;
471
472     va_start(ap, complain_fmt);
473     if (token() != desired) {
474         panic(ve(complain_fmt, ap));
475     }
476     va_end(ap);
477     return TIME_OK;
478
479 }                       /* expect2 */
480
481
482 /*
483  * plus_minus() is used to parse a single NUMBER TIME-UNIT pair
484  *              for the OFFSET-SPEC.
485  *              It also applies those m-guessing heuristics.
486  */
487 static char *plus_minus(
488     struct rrd_time_value *ptv,
489     int doop)
490 {
491     static int op = PLUS;
492     static int prev_multiplier = -1;
493     int       delta;
494
495     if (doop >= 0) {
496         op = doop;
497         try(expect2
498             (NUMBER, "There should be number after '%c'",
499              op == PLUS ? '+' : '-'));
500         prev_multiplier = -1;   /* reset months-minutes guessing mechanics */
501     }
502     /* if doop is < 0 then we repeat the previous op
503      * with the prefetched number */
504
505     delta = atoi(sc_token);
506
507     if (token() == MONTHS_MINUTES) {
508         /* hard job to guess what does that -5m means: -5mon or -5min? */
509         switch (prev_multiplier) {
510         case DAYS:
511         case WEEKS:
512         case MONTHS:
513         case YEARS:
514             sc_tokid = MONTHS;
515             break;
516
517         case SECONDS:
518         case MINUTES:
519         case HOURS:
520             sc_tokid = MINUTES;
521             break;
522
523         default:
524             if (delta < 6)  /* it may be some other value but in the context
525                              * of RRD who needs less than 6 min deltas? */
526                 sc_tokid = MONTHS;
527             else
528                 sc_tokid = MINUTES;
529         }
530     }
531     prev_multiplier = sc_tokid;
532     switch (sc_tokid) {
533     case YEARS:
534         ptv->tm.tm_year += (op == PLUS) ? delta : -delta;
535         return TIME_OK;
536     case MONTHS:
537         ptv->tm.tm_mon += (op == PLUS) ? delta : -delta;
538         return TIME_OK;
539     case WEEKS:
540         delta *= 7;
541         /* FALLTHRU */
542     case DAYS:
543         ptv->tm.tm_mday += (op == PLUS) ? delta : -delta;
544         return TIME_OK;
545     case HOURS:
546         ptv->offset += (op == PLUS) ? delta * 60 * 60 : -delta * 60 * 60;
547         return TIME_OK;
548     case MINUTES:
549         ptv->offset += (op == PLUS) ? delta * 60 : -delta * 60;
550         return TIME_OK;
551     case SECONDS:
552         ptv->offset += (op == PLUS) ? delta : -delta;
553         return TIME_OK;
554     default:           /*default unit is seconds */
555         ptv->offset += (op == PLUS) ? delta : -delta;
556         return TIME_OK;
557     }
558     panic(e("well-known time unit expected after %d", delta));
559     /* NORETURN */
560     return TIME_OK;     /* to make compiler happy :) */
561 }                       /* plus_minus */
562
563
564 /*
565  * tod() computes the time of day (TIME-OF-DAY-SPEC)
566  */
567 static char *tod(
568     struct rrd_time_value *ptv)
569 {
570     int       hour, minute = 0;
571     int       tlen;
572
573     /* save token status in  case we must abort */
574     int       scc_sv = scc;
575     const char *sct_sv = sct;
576     int       sc_tokid_sv = sc_tokid;
577
578     tlen = strlen(sc_token);
579
580     /* first pick out the time of day - we assume a HH (COLON|DOT) MM time
581      */
582     if (tlen > 2) {
583         return TIME_OK;
584     }
585
586     hour = atoi(sc_token);
587
588     token();
589     if (sc_tokid == SLASH || sc_tokid == DOT) {
590         /* guess we are looking at a date */
591         scc = scc_sv;
592         sct = sct_sv;
593         sc_tokid = sc_tokid_sv;
594         sprintf(sc_token, "%d", hour);
595         return TIME_OK;
596     }
597     if (sc_tokid == COLON) {
598         try(expect2(NUMBER,
599                     "Parsing HH:MM syntax, expecting MM as number, got none"));
600         minute = atoi(sc_token);
601         if (minute > 59) {
602             panic(e("parsing HH:MM syntax, got MM = %d (>59!)", minute));
603         }
604         token();
605     }
606
607     /* check if an AM or PM specifier was given
608      */
609     if (sc_tokid == AM || sc_tokid == PM) {
610         if (hour > 12) {
611             panic(e("there cannot be more than 12 AM or PM hours"));
612         }
613         if (sc_tokid == PM) {
614             if (hour != 12) /* 12:xx PM is 12:xx, not 24:xx */
615                 hour += 12;
616         } else {
617             if (hour == 12) /* 12:xx AM is 00:xx, not 12:xx */
618                 hour = 0;
619         }
620         token();
621     } else if (hour > 23) {
622         /* guess it was not a time then ... */
623         scc = scc_sv;
624         sct = sct_sv;
625         sc_tokid = sc_tokid_sv;
626         sprintf(sc_token, "%d", hour);
627         return TIME_OK;
628     }
629     ptv->tm.tm_hour = hour;
630     ptv->tm.tm_min = minute;
631     ptv->tm.tm_sec = 0;
632     if (ptv->tm.tm_hour == 24) {
633         ptv->tm.tm_hour = 0;
634         ptv->tm.tm_mday++;
635     }
636     return TIME_OK;
637 }                       /* tod */
638
639
640 /*
641  * assign_date() assigns a date, adjusting year as appropriate
642  */
643 static char *assign_date(
644     struct rrd_time_value *ptv,
645     long mday,
646     long mon,
647     long year)
648 {
649     if (year > 138) {
650         if (year > 1970)
651             year -= 1900;
652         else {
653             panic(e("invalid year %d (should be either 00-99 or >1900)",
654                     year));
655         }
656     } else if (year >= 0 && year < 38) {
657         year += 100;    /* Allow year 2000-2037 to be specified as   */
658     }
659     /* 00-37 until the problem of 2038 year will */
660     /* arise for unices with 32-bit time_t :)    */
661     if (year < 70) {
662         panic(e("won't handle dates before epoch (01/01/1970), sorry"));
663     }
664
665     ptv->tm.tm_mday = mday;
666     ptv->tm.tm_mon = mon;
667     ptv->tm.tm_year = year;
668     return TIME_OK;
669 }                       /* assign_date */
670
671
672 /* 
673  * day() picks apart DAY-SPEC-[12]
674  */
675 static char *day(
676     struct rrd_time_value *ptv)
677 {
678     /* using time_t seems to help portability with 64bit oses */
679     time_t    mday = 0, wday, mon, year = ptv->tm.tm_year;
680     int       tlen;
681
682     switch (sc_tokid) {
683     case YESTERDAY:
684         ptv->tm.tm_mday--;
685         /* FALLTRHU */
686     case TODAY:        /* force ourselves to stay in today - no further processing */
687         token();
688         break;
689     case TOMORROW:
690         ptv->tm.tm_mday++;
691         token();
692         break;
693
694     case JAN:
695     case FEB:
696     case MAR:
697     case APR:
698     case MAY:
699     case JUN:
700     case JUL:
701     case AUG:
702     case SEP:
703     case OCT:
704     case NOV:
705     case DEC:
706         /* do month mday [year]
707          */
708         mon = (sc_tokid - JAN);
709         try(expect2(NUMBER, "the day of the month should follow month name"));
710         mday = atol(sc_token);
711         if (token() == NUMBER) {
712             year = atol(sc_token);
713             token();
714         } else
715             year = ptv->tm.tm_year;
716         try(assign_date(ptv, mday, mon, year));
717         break;
718
719     case SUN:
720     case MON:
721     case TUE:
722     case WED:
723     case THU:
724     case FRI:
725     case SAT:
726         /* do a particular day of the week
727          */
728         wday = (sc_tokid - SUN);
729         ptv->tm.tm_mday += (wday - ptv->tm.tm_wday);
730         token();
731         break;
732         /*
733            mday = ptv->tm.tm_mday;
734            mday += (wday - ptv->tm.tm_wday);
735            ptv->tm.tm_wday = wday;
736
737            try(assign_date(ptv, mday, ptv->tm.tm_mon, ptv->tm.tm_year));
738            break;
739          */
740
741     case NUMBER:
742         /* get numeric <sec since 1970>, MM/DD/[YY]YY, or DD.MM.[YY]YY
743          */
744         tlen = strlen(sc_token);
745         mon = atol(sc_token);
746         if (mon > 10 * 365 * 24 * 60 * 60) {
747             ptv->tm = *localtime(&mon);
748             token();
749             break;
750         }
751
752         if (mon > 19700101 && mon < 24000101) { /*works between 1900 and 2400 */
753             char      cmon[3], cmday[3], cyear[5];
754
755             strncpy(cyear, sc_token, 4);
756             cyear[4] = '\0';
757             year = atol(cyear);
758             strncpy(cmon, &(sc_token[4]), 2);
759             cmon[2] = '\0';
760             mon = atol(cmon);
761             strncpy(cmday, &(sc_token[6]), 2);
762             cmday[2] = '\0';
763             mday = atol(cmday);
764             token();
765         } else {
766             token();
767
768             if (mon <= 31 && (sc_tokid == SLASH || sc_tokid == DOT)) {
769                 int       sep;
770
771                 sep = sc_tokid;
772                 try(expect2(NUMBER, "there should be %s number after '%c'",
773                             sep == DOT ? "month" : "day",
774                             sep == DOT ? '.' : '/'));
775                 mday = atol(sc_token);
776                 if (token() == sep) {
777                     try(expect2
778                         (NUMBER, "there should be year number after '%c'",
779                          sep == DOT ? '.' : '/'));
780                     year = atol(sc_token);
781                     token();
782                 }
783
784                 /* flip months and days for European timing
785                  */
786                 if (sep == DOT) {
787                     long      x = mday;
788
789                     mday = mon;
790                     mon = x;
791                 }
792             }
793         }
794
795         mon--;
796         if (mon < 0 || mon > 11) {
797             panic(e("did you really mean month %d?", mon + 1));
798         }
799         if (mday < 1 || mday > 31) {
800             panic(e("I'm afraid that %d is not a valid day of the month",
801                     mday));
802         }
803         try(assign_date(ptv, mday, mon, year));
804         break;
805     }                   /* case */
806     return TIME_OK;
807 }                       /* month */
808
809
810 /* Global functions */
811
812
813 /*
814  * parsetime() is the external interface that takes tspec, parses
815  * it and puts the result in the rrd_time_value structure *ptv.
816  * It can return either absolute times (these are ensured to be
817  * correct) or relative time references that are expected to be
818  * added to some absolute time value and then normalized by
819  * mktime() The return value is either TIME_OK (aka NULL) or
820  * the pointer to the error message in the case of problems
821  */
822 char     *parsetime(
823     const char *tspec,
824     struct rrd_time_value *ptv)
825 {
826     time_t    now = time(NULL);
827     int       hr = 0;
828
829     /* this MUST be initialized to zero for midnight/noon/teatime */
830
831     Specials = VariousWords;    /* initialize special words context */
832
833     try(init_scanner(1, &tspec));
834
835     /* establish the default time reference */
836     ptv->type = ABSOLUTE_TIME;
837     ptv->offset = 0;
838     ptv->tm = *localtime(&now);
839     ptv->tm.tm_isdst = -1;  /* mk time can figure this out for us ... */
840
841     token();
842     switch (sc_tokid) {
843     case PLUS:
844     case MINUS:
845         break;          /* jump to OFFSET-SPEC part */
846
847     case START:
848         ptv->type = RELATIVE_TO_START_TIME;
849         goto KeepItRelative;
850     case END:
851         ptv->type = RELATIVE_TO_END_TIME;
852       KeepItRelative:
853         ptv->tm.tm_sec = 0;
854         ptv->tm.tm_min = 0;
855         ptv->tm.tm_hour = 0;
856         ptv->tm.tm_mday = 0;
857         ptv->tm.tm_mon = 0;
858         ptv->tm.tm_year = 0;
859         /* FALLTHRU */
860     case NOW:
861     {
862         int       time_reference = sc_tokid;
863
864         token();
865         if (sc_tokid == PLUS || sc_tokid == MINUS)
866             break;
867         if (time_reference != NOW) {
868             panic(e("'start' or 'end' MUST be followed by +|- offset"));
869         } else if (sc_tokid != EOF) {
870             panic(e("if 'now' is followed by a token it must be +|- offset"));
871         }
872     };
873         break;
874
875         /* Only absolute time specifications below */
876     case NUMBER:
877     {
878         long      hour_sv = ptv->tm.tm_hour;
879         long      year_sv = ptv->tm.tm_year;
880
881         ptv->tm.tm_hour = 30;
882         ptv->tm.tm_year = 30000;
883         try(tod(ptv))
884             try(day(ptv))
885             if (ptv->tm.tm_hour == 30 && ptv->tm.tm_year != 30000) {
886             try(tod(ptv))
887         }
888         if (ptv->tm.tm_hour == 30) {
889             ptv->tm.tm_hour = hour_sv;
890         }
891         if (ptv->tm.tm_year == 30000) {
892             ptv->tm.tm_year = year_sv;
893         }
894     };
895         break;
896         /* fix month parsing */
897     case JAN:
898     case FEB:
899     case MAR:
900     case APR:
901     case MAY:
902     case JUN:
903     case JUL:
904     case AUG:
905     case SEP:
906     case OCT:
907     case NOV:
908     case DEC:
909         try(day(ptv));
910         if (sc_tokid != NUMBER)
911             break;
912         try(tod(ptv))
913             break;
914
915         /* evil coding for TEATIME|NOON|MIDNIGHT - we've initialized
916          * hr to zero up above, then fall into this case in such a
917          * way so we add +12 +4 hours to it for teatime, +12 hours
918          * to it for noon, and nothing at all for midnight, then
919          * set our rettime to that hour before leaping into the
920          * month scanner
921          */
922     case TEATIME:
923         hr += 4;
924         /* FALLTHRU */
925     case NOON:
926         hr += 12;
927         /* FALLTHRU */
928     case MIDNIGHT:
929         /* if (ptv->tm.tm_hour >= hr) {
930            ptv->tm.tm_mday++;
931            ptv->tm.tm_wday++;
932            } *//* shifting does not makes sense here ... noon is noon */
933         ptv->tm.tm_hour = hr;
934         ptv->tm.tm_min = 0;
935         ptv->tm.tm_sec = 0;
936         token();
937         try(day(ptv));
938         break;
939     default:
940         panic(e("unparsable time: %s%s", sc_token, sct));
941         break;
942     }                   /* ugly case statement */
943
944     /*
945      * the OFFSET-SPEC part
946      *
947      * (NOTE, the sc_tokid was prefetched for us by the previous code)
948      */
949     if (sc_tokid == PLUS || sc_tokid == MINUS) {
950         Specials = TimeMultipliers; /* switch special words context */
951         while (sc_tokid == PLUS || sc_tokid == MINUS || sc_tokid == NUMBER) {
952             if (sc_tokid == NUMBER) {
953                 try(plus_minus(ptv, PREVIOUS_OP));
954             } else
955                 try(plus_minus(ptv, sc_tokid));
956             token();    /* We will get EOF eventually but that's OK, since
957                            token() will return us as many EOFs as needed */
958         }
959     }
960
961     /* now we should be at EOF */
962     if (sc_tokid != EOF) {
963         panic(e("unparsable trailing text: '...%s%s'", sc_token, sct));
964     }
965
966     ptv->tm.tm_isdst = -1;  /* for mktime to guess DST status */
967     if (ptv->type == ABSOLUTE_TIME)
968         if (mktime(&ptv->tm) == -1) {   /* normalize & check */
969             /* can happen for "nonexistent" times, e.g. around 3am */
970             /* when winter -> summer time correction eats a hour */
971             panic(e("the specified time is incorrect (out of range?)"));
972         }
973     EnsureMemFree();
974     return TIME_OK;
975 }                       /* parsetime */
976
977
978 int proc_start_end(
979     struct rrd_time_value *start_tv,
980     struct rrd_time_value *end_tv,
981     time_t *start,
982     time_t *end)
983 {
984     if (start_tv->type == RELATIVE_TO_END_TIME &&   /* same as the line above */
985         end_tv->type == RELATIVE_TO_START_TIME) {
986         rrd_set_error("the start and end times cannot be specified "
987                       "relative to each other");
988         return -1;
989     }
990
991     if (start_tv->type == RELATIVE_TO_START_TIME) {
992         rrd_set_error
993             ("the start time cannot be specified relative to itself");
994         return -1;
995     }
996
997     if (end_tv->type == RELATIVE_TO_END_TIME) {
998         rrd_set_error("the end time cannot be specified relative to itself");
999         return -1;
1000     }
1001
1002     if (start_tv->type == RELATIVE_TO_END_TIME) {
1003         struct tm tmtmp;
1004
1005         *end = mktime(&(end_tv->tm)) + end_tv->offset;
1006         tmtmp = *localtime(end);    /* reinit end including offset */
1007         tmtmp.tm_mday += start_tv->tm.tm_mday;
1008         tmtmp.tm_mon += start_tv->tm.tm_mon;
1009         tmtmp.tm_year += start_tv->tm.tm_year;
1010         *start = mktime(&tmtmp) + start_tv->offset;
1011     } else {
1012         *start = mktime(&(start_tv->tm)) + start_tv->offset;
1013     }
1014     if (end_tv->type == RELATIVE_TO_START_TIME) {
1015         struct tm tmtmp;
1016
1017         *start = mktime(&(start_tv->tm)) + start_tv->offset;
1018         tmtmp = *localtime(start);
1019         tmtmp.tm_mday += end_tv->tm.tm_mday;
1020         tmtmp.tm_mon += end_tv->tm.tm_mon;
1021         tmtmp.tm_year += end_tv->tm.tm_year;
1022         *end = mktime(&tmtmp) + end_tv->offset;
1023     } else {
1024         *end = mktime(&(end_tv->tm)) + end_tv->offset;
1025     }
1026     return 0;
1027 }                       /* proc_start_end */