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