Code

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