Code

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