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