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