fix off by 1 error
[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         snprintf(sc_token, sc_len, "%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         snprintf(sc_token, sc_len, "%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
691     switch (sc_tokid) {
692     case YESTERDAY:
693         ptv->tm.  tm_mday--;
694
695         /* FALLTRHU */
696     case TODAY:        /* force ourselves to stay in today - no further processing */
697         token();
698         break;
699     case TOMORROW:
700         ptv->tm.  tm_mday++;
701
702         token();
703         break;
704
705     case JAN:
706     case FEB:
707     case MAR:
708     case APR:
709     case MAY:
710     case JUN:
711     case JUL:
712     case AUG:
713     case SEP:
714     case OCT:
715     case NOV:
716     case DEC:
717         /* do month mday [year]
718          */
719         mon = (sc_tokid - JAN);
720         try(expect2(NUMBER, "the day of the month should follow month name"));
721         mday = atol(sc_token);
722         if (token() == NUMBER) {
723             year = atol(sc_token);
724             token();
725         } else
726             year = ptv->tm.tm_year;
727
728         try(assign_date(ptv, mday, mon, year));
729         break;
730
731     case SUN:
732     case MON:
733     case TUE:
734     case WED:
735     case THU:
736     case FRI:
737     case SAT:
738         /* do a particular day of the week
739          */
740         wday = (sc_tokid - SUN);
741         ptv->tm.  tm_mday += (
742     wday - ptv->tm.tm_wday);
743
744         token();
745         break;
746         /*
747            mday = ptv->tm.tm_mday;
748            mday += (wday - ptv->tm.tm_wday);
749            ptv->tm.tm_wday = wday;
750
751            try(assign_date(ptv, mday, ptv->tm.tm_mon, ptv->tm.tm_year));
752            break;
753          */
754
755     case NUMBER:
756         /* get numeric <sec since 1970>, MM/DD/[YY]YY, or DD.MM.[YY]YY
757          */
758         mon = atol(sc_token);
759         if (mon > 10 * 365 * 24 * 60 * 60) {
760             ptv->tm = *localtime(&mon);
761
762             token();
763             break;
764         }
765
766         if (mon > 19700101 && mon < 24000101) { /*works between 1900 and 2400 */
767             char      cmon[3], cmday[3], cyear[5];
768
769             strncpy(cyear, sc_token, 4);
770             cyear[4] = '\0';
771             year = atol(cyear);
772             strncpy(cmon, &(sc_token[4]), 2);
773             cmon[2] = '\0';
774             mon = atol(cmon);
775             strncpy(cmday, &(sc_token[6]), 2);
776             cmday[2] = '\0';
777             mday = atol(cmday);
778             token();
779         } else {
780             token();
781
782             if (mon <= 31 && (sc_tokid == SLASH || sc_tokid == DOT)) {
783                 int       sep;
784
785                 sep = sc_tokid;
786                 try(expect2(NUMBER, "there should be %s number after '%c'",
787                             sep == DOT ? "month" : "day",
788                             sep == DOT ? '.' : '/'));
789                 mday = atol(sc_token);
790                 if (token() == sep) {
791                     try(expect2
792                         (NUMBER, "there should be year number after '%c'",
793                          sep == DOT ? '.' : '/'));
794                     year = atol(sc_token);
795                     token();
796                 }
797
798                 /* flip months and days for European timing
799                  */
800                 if (sep == DOT) {
801                     long      x = mday;
802
803                     mday = mon;
804                     mon = x;
805                 }
806             }
807         }
808
809         mon--;
810         if (mon < 0 || mon > 11) {
811             panic(e("did you really mean month %d?", mon + 1));
812         }
813         if (mday < 1 || mday > 31) {
814             panic(e("I'm afraid that %d is not a valid day of the month",
815                     mday));
816         }
817         try(assign_date(ptv, mday, mon, year));
818         break;
819     }                   /* case */
820     return TIME_OK;
821 }                       /* month */
822
823
824 /* Global functions */
825
826
827 /*
828  * rrd_parsetime() is the external interface that takes tspec, parses
829  * it and puts the result in the rrd_time_value structure *ptv.
830  * It can return either absolute times (these are ensured to be
831  * correct) or relative time references that are expected to be
832  * added to some absolute time value and then normalized by
833  * mktime() The return value is either TIME_OK (aka NULL) or
834  * the pointer to the error message in the case of problems
835  */
836 char     *rrd_parsetime(
837     const char *tspec,
838     rrd_time_value_t * ptv)
839 {
840     time_t    now = time(NULL);
841     int       hr = 0;
842
843     /* this MUST be initialized to zero for midnight/noon/teatime */
844
845     Specials = VariousWords;    /* initialize special words context */
846
847     try(init_scanner(1, &tspec));
848
849     /* establish the default time reference */
850     ptv->type = ABSOLUTE_TIME;
851     ptv->offset = 0;
852     ptv->tm = *localtime(&now);
853     ptv->tm.  tm_isdst = -1;    /* mk time can figure dst by default ... */
854
855     token();
856     switch (sc_tokid) {
857     case PLUS:
858     case MINUS:
859         break;          /* jump to OFFSET-SPEC part */
860
861     case EPOCH:
862         ptv->type = RELATIVE_TO_EPOCH;
863         goto KeepItRelative;
864     case START:
865         ptv->type = RELATIVE_TO_START_TIME;
866         goto KeepItRelative;
867     case END:
868         ptv->type = RELATIVE_TO_END_TIME;
869       KeepItRelative:
870         ptv->tm.  tm_sec = 0;
871         ptv->tm.  tm_min = 0;
872         ptv->tm.  tm_hour = 0;
873         ptv->tm.  tm_mday = 0;
874         ptv->tm.  tm_mon = 0;
875         ptv->tm.  tm_year = 0;
876
877         /* FALLTHRU */
878     case NOW:
879     {
880         int       time_reference = sc_tokid;
881
882         token();
883         if (sc_tokid == PLUS || sc_tokid == MINUS)
884             break;
885         if (time_reference != NOW) {
886             panic(e("'start' or 'end' MUST be followed by +|- offset"));
887         } else if (sc_tokid != EOF) {
888             panic(e("if 'now' is followed by a token it must be +|- offset"));
889         }
890     };
891         break;
892
893         /* Only absolute time specifications below */
894     case NUMBER:
895     {
896         long      hour_sv = ptv->tm.tm_hour;
897         long      year_sv = ptv->tm.tm_year;
898
899         ptv->tm.  tm_hour = 30;
900         ptv->tm.  tm_year = 30000;
901
902         try(tod(ptv))
903             try(day(ptv))
904             if (ptv->tm.tm_hour == 30 && ptv->tm.tm_year != 30000) {
905             try(tod(ptv))
906         }
907         if (ptv->tm.tm_hour == 30) {
908             ptv->tm.  tm_hour = hour_sv;
909         }
910         if (ptv->tm.tm_year == 30000) {
911             ptv->tm.  tm_year = year_sv;
912         }
913     };
914         break;
915         /* fix month parsing */
916     case JAN:
917     case FEB:
918     case MAR:
919     case APR:
920     case MAY:
921     case JUN:
922     case JUL:
923     case AUG:
924     case SEP:
925     case OCT:
926     case NOV:
927     case DEC:
928         try(day(ptv));
929         if (sc_tokid != NUMBER)
930             break;
931         try(tod(ptv))
932             break;
933
934         /* evil coding for TEATIME|NOON|MIDNIGHT - we've initialized
935          * hr to zero up above, then fall into this case in such a
936          * way so we add +12 +4 hours to it for teatime, +12 hours
937          * to it for noon, and nothing at all for midnight, then
938          * set our rettime to that hour before leaping into the
939          * month scanner
940          */
941     case TEATIME:
942         hr += 4;
943         /* FALLTHRU */
944     case NOON:
945         hr += 12;
946         /* FALLTHRU */
947     case MIDNIGHT:
948         /* if (ptv->tm.tm_hour >= hr) {
949            ptv->tm.tm_mday++;
950            ptv->tm.tm_wday++;
951            } *//* shifting does not makes sense here ... noon is noon */
952         ptv->tm.  tm_hour = hr;
953         ptv->tm.  tm_min = 0;
954         ptv->tm.  tm_sec = 0;
955
956         token();
957         try(day(ptv));
958         break;
959     default:
960         panic(e("unparsable time: %s%s", sc_token, sct));
961         break;
962     }                   /* ugly case statement */
963
964     /*
965      * the OFFSET-SPEC part
966      *
967      * (NOTE, the sc_tokid was prefetched for us by the previous code)
968      */
969     if (sc_tokid == PLUS || sc_tokid == MINUS) {
970         Specials = TimeMultipliers; /* switch special words context */
971         while (sc_tokid == PLUS || sc_tokid == MINUS || sc_tokid == NUMBER) {
972             if (sc_tokid == NUMBER) {
973                 try(plus_minus(ptv, PREVIOUS_OP));
974             } else
975                 try(plus_minus(ptv, sc_tokid));
976             token();    /* We will get EOF eventually but that's OK, since
977                            token() will return us as many EOFs as needed */
978         }
979     }
980
981     /* now we should be at EOF */
982     if (sc_tokid != EOF) {
983         panic(e("unparsable trailing text: '...%s%s'", sc_token, sct));
984     }
985
986     if (ptv->type == ABSOLUTE_TIME)
987         if (mktime(&ptv->tm) == -1) {   /* normalize & check */
988             /* can happen for "nonexistent" times, e.g. around 3am */
989             /* when winter -> summer time correction eats a hour */
990             panic(e("the specified time is incorrect (out of range?)"));
991         }
992     EnsureMemFree();
993     return TIME_OK;
994 }                       /* rrd_parsetime */
995
996
997 int rrd_proc_start_end(
998     rrd_time_value_t * start_tv,
999     rrd_time_value_t * end_tv,
1000     time_t *start,
1001     time_t *end)
1002 {
1003     if (start_tv->type == RELATIVE_TO_END_TIME &&   /* same as the line above */
1004         end_tv->type == RELATIVE_TO_START_TIME) {
1005         rrd_set_error("the start and end times cannot be specified "
1006                       "relative to each other");
1007         return -1;
1008     }
1009
1010     if (start_tv->type == RELATIVE_TO_START_TIME) {
1011         rrd_set_error
1012             ("the start time cannot be specified relative to itself");
1013         return -1;
1014     }
1015
1016     if (end_tv->type == RELATIVE_TO_END_TIME) {
1017         rrd_set_error("the end time cannot be specified relative to itself");
1018         return -1;
1019     }
1020
1021     if (start_tv->type == RELATIVE_TO_END_TIME) {
1022         struct tm tmtmp;
1023
1024         *end = mktime(&(end_tv->tm)) + end_tv->offset;
1025         tmtmp = *localtime(end);    /* reinit end including offset */
1026         tmtmp.tm_mday += start_tv->tm.tm_mday;
1027         tmtmp.tm_mon += start_tv->tm.tm_mon;
1028         tmtmp.tm_year += start_tv->tm.tm_year;
1029
1030         *start = mktime(&tmtmp) + start_tv->offset;
1031     } else {
1032         *start = mktime(&(start_tv->tm)) + start_tv->offset;
1033     }
1034     if (end_tv->type == RELATIVE_TO_START_TIME) {
1035         struct tm tmtmp;
1036
1037         *start = mktime(&(start_tv->tm)) + start_tv->offset;
1038         tmtmp = *localtime(start);
1039         tmtmp.tm_mday += end_tv->tm.tm_mday;
1040         tmtmp.tm_mon += end_tv->tm.tm_mon;
1041         tmtmp.tm_year += end_tv->tm.tm_year;
1042
1043         *end = mktime(&tmtmp) + end_tv->offset;
1044     } else {
1045         *end = mktime(&(end_tv->tm)) + end_tv->offset;
1046     }
1047     return 0;
1048 }                       /* rrd_proc_start_end */