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