Code

Added rrd_graph_helper
[rrdtool-all.git] / program / 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  */
35 /*
36  * The BNF-like specification of the time syntax parsed is below:
37  *                                                               
38  * As usual, [ X ] means that X is optional, { X } means that X may
39  * be either omitted or specified as many times as needed,
40  * alternatives are separated by |, brackets are used for grouping.
41  * (# marks the beginning of comment that extends to the end of line)
42  *
43  * TIME-SPECIFICATION ::= TIME-REFERENCE [ OFFSET-SPEC ] |
44  *                                         OFFSET-SPEC   |
45  *                         ( START | END ) OFFSET-SPEC 
46  *
47  * TIME-REFERENCE ::= NOW | TIME-OF-DAY-SPEC [ DAY-SPEC-1 ] |
48  *                        [ TIME-OF-DAY-SPEC ] DAY-SPEC-2
49  *
50  * TIME-OF-DAY-SPEC ::= NUMBER (':') NUMBER [am|pm] | # HH:MM
51  *                     'noon' | 'midnight' | 'teatime'
52  *
53  * DAY-SPEC-1 ::= NUMBER '/' NUMBER '/' NUMBER |  # MM/DD/[YY]YY
54  *                NUMBER '.' NUMBER '.' NUMBER |  # DD.MM.[YY]YY
55  *                NUMBER                          # Seconds since 1970
56  *                NUMBER                          # YYYYMMDD
57  *
58  * DAY-SPEC-2 ::= MONTH-NAME NUMBER [NUMBER] |    # Month DD [YY]YY
59  *                'yesterday' | 'today' | 'tomorrow' |
60  *                DAY-OF-WEEK
61  *
62  *
63  * OFFSET-SPEC ::= '+'|'-' NUMBER TIME-UNIT { ['+'|'-'] NUMBER TIME-UNIT }
64  *
65  * TIME-UNIT ::= SECONDS | MINUTES | HOURS |
66  *               DAYS | WEEKS | MONTHS | YEARS
67  *
68  * NOW ::= 'now' | 'n'
69  *
70  * START ::= 'start' | 's'
71  * END   ::= 'end' | 'e'
72  *
73  * SECONDS ::= 'seconds' | 'second' | 'sec' | 's'
74  * MINUTES ::= 'minutes' | 'minute' | 'min' | 'm'
75  * HOURS   ::= 'hours' | 'hour' | 'hr' | 'h'
76  * DAYS    ::= 'days' | 'day' | 'd'
77  * WEEKS   ::= 'weeks' | 'week' | 'wk' | 'w'
78  * MONTHS  ::= 'months' | 'month' | 'mon' | 'm'
79  * YEARS   ::= 'years' | 'year' | 'yr' | 'y'
80  *
81  * MONTH-NAME ::= 'jan' | 'january' | 'feb' | 'february' | 'mar' | 'march' |
82  *                'apr' | 'april' | 'may' | 'jun' | 'june' | 'jul' | 'july' |
83  *                'aug' | 'august' | 'sep' | 'september' | 'oct' | 'october' |
84  *                'nov' | 'november' | 'dec' | 'december'
85  *
86  * DAY-OF-WEEK ::= 'sunday' | 'sun' | 'monday' | 'mon' | 'tuesday' | 'tue' |
87  *                 'wednesday' | 'wed' | 'thursday' | 'thu' | 'friday' | 'fri' |
88  *                 'saturday' | 'sat'
89  *
90  *
91  * As you may note, there is an ambiguity with respect to
92  * the 'm' time unit (which can mean either minutes or months).
93  * To cope with this, code tries to read users mind :) by applying
94  * certain heuristics. There are two of them:
95  *
96  * 1. If 'm' is used in context of (i.e. right after the) years,
97  *    months, weeks, or days it is assumed to mean months, while
98  *    in the context of hours, minutes, and seconds it means minutes.
99  *    (e.g., in -1y6m or +3w1m 'm' means 'months', while in
100  *    -3h20m or +5s2m 'm' means 'minutes')
101  *
102  * 2. Out of context (i.e. right after the '+' or '-' sign) the
103  *    meaning of 'm' is guessed from the number it directly follows.
104  *    Currently, if the number absolute value is below 25 it is assumed
105  *    that 'm' means months, otherwise it is treated as minutes.
106  *    (e.g., -25m == -25 minutes, while +24m == +24 months)
107  *
108  */
110 /* System Headers */
112 /* Local headers */
114 #include "rrd_tool.h"
115 #include <stdarg.h>
117 /* Structures and unions */
119 enum {  /* symbols */
120     MIDNIGHT, NOON, TEATIME,
121     PM, AM, YESTERDAY, TODAY, TOMORROW, NOW, START, END,
122     SECONDS, MINUTES, HOURS, DAYS, WEEKS, MONTHS, YEARS,
123     MONTHS_MINUTES,
124     NUMBER, PLUS, MINUS, DOT, COLON, SLASH, ID, JUNK,
125     JAN, FEB, MAR, APR, MAY, JUN,
126     JUL, AUG, SEP, OCT, NOV, DEC,
127     SUN, MON, TUE, WED, THU, FRI, SAT
128     };
130 /* the below is for plus_minus() */
131 #define PREVIOUS_OP     (-1)
133 /* parse translation table - table driven parsers can be your FRIEND!
134  */
135 struct SpecialToken {
136     char *name; /* token name */
137     int value;  /* token id */
138 };
139 static struct SpecialToken VariousWords[] = {
140     { "midnight", MIDNIGHT },   /* 00:00:00 of today or tomorrow */
141     { "noon", NOON },           /* 12:00:00 of today or tomorrow */
142     { "teatime", TEATIME },     /* 16:00:00 of today or tomorrow */
143     { "am", AM },               /* morning times for 0-12 clock */
144     { "pm", PM },               /* evening times for 0-12 clock */
145     { "tomorrow", TOMORROW },
146     { "yesterday", YESTERDAY },
147     { "today", TODAY },
148     { "now", NOW },
149     { "n", NOW },
150     { "start", START },
151     { "s", START },     
152     { "end", END },
153     { "e", END },
155     { "jan", JAN },
156     { "feb", FEB },
157     { "mar", MAR },
158     { "apr", APR },
159     { "may", MAY },
160     { "jun", JUN },
161     { "jul", JUL },
162     { "aug", AUG },
163     { "sep", SEP },
164     { "oct", OCT },
165     { "nov", NOV },
166     { "dec", DEC },
167     { "january", JAN },
168     { "february", FEB },
169     { "march", MAR },
170     { "april", APR },
171     { "may", MAY },
172     { "june", JUN },
173     { "july", JUL },
174     { "august", AUG },
175     { "september", SEP },
176     { "october", OCT },
177     { "november", NOV },
178     { "december", DEC },
179     { "sunday", SUN },
180     { "sun", SUN },
181     { "monday", MON },
182     { "mon", MON },
183     { "tuesday", TUE },
184     { "tue", TUE },
185     { "wednesday", WED },
186     { "wed", WED },
187     { "thursday", THU },
188     { "thu", THU },
189     { "friday", FRI },
190     { "fri", FRI },
191     { "saturday", SAT },
192     { "sat", SAT },
193     { NULL, 0 }                 /*** SENTINEL ***/
194 };
196 static struct SpecialToken TimeMultipliers[] = {
197     { "second", SECONDS },      /* seconds multiplier */
198     { "seconds", SECONDS },     /* (pluralized) */
199     { "sec", SECONDS },         /* (generic) */
200     { "s", SECONDS },           /* (short generic) */
201     { "minute", MINUTES },      /* minutes multiplier */
202     { "minutes", MINUTES },     /* (pluralized) */
203     { "min", MINUTES },         /* (generic) */
204     { "m", MONTHS_MINUTES },    /* (short generic) */
205     { "hour", HOURS },          /* hours ... */
206     { "hours", HOURS },         /* (pluralized) */
207     { "hr", HOURS },            /* (generic) */
208     { "h", HOURS },             /* (short generic) */
209     { "day", DAYS },            /* days ... */
210     { "days", DAYS },           /* (pluralized) */
211     { "d", DAYS },              /* (short generic) */
212     { "week", WEEKS },          /* week ... */
213     { "weeks", WEEKS },         /* (pluralized) */
214     { "wk", WEEKS },            /* (generic) */
215     { "w", WEEKS },             /* (short generic) */
216     { "month", MONTHS },        /* week ... */
217     { "months", MONTHS },       /* (pluralized) */
218     { "mon", MONTHS },          /* (generic) */
219     { "year", YEARS },          /* year ... */
220     { "years", YEARS },         /* (pluralized) */
221     { "yr", YEARS },            /* (generic) */
222     { "y", YEARS },             /* (short generic) */
223     { NULL, 0 }                 /*** SENTINEL ***/
224 };
226 /* File scope variables */
228 /* context dependant list of specials for parser to recognize,
229  * required for us to be able distinguish between 'mon' as 'month'
230  * and 'mon' as 'monday'
231  */
232 static struct SpecialToken *Specials;
234 static char **scp;      /* scanner - pointer at arglist */
235 static char scc;        /* scanner - count of remaining arguments */
236 static char *sct;       /* scanner - next char pointer in current argument */
237 static int need;        /* scanner - need to advance to next argument */
239 static char *sc_token=NULL;     /* scanner - token buffer */
240 static size_t sc_len;   /* scanner - lenght of token buffer */
241 static int sc_tokid;    /* scanner - token id */
243 static int need_to_free = 0; /* means that we need deallocating memory */
245 /* Local functions */
247 void EnsureMemFree ()
249   if( need_to_free )
250     {
251     free(sc_token);
252     need_to_free = 0;
253     }
256 /*
257  * A hack to compensate for the lack of the C++ exceptions
258  *
259  * Every function func that might generate parsing "exception"
260  * should return TIME_OK (aka NULL) or pointer to the error message,
261  * and should be called like this: try(func(args));
262  *
263  * if the try is not successfull it will reset the token pointer ...
264  *
265  * [NOTE: when try(...) is used as the only statement in the "if-true"
266  *  part of the if statement that also has an "else" part it should be
267  *  either enclosed in the curly braces (despite the fact that it looks
268  *  like a single statement) or NOT follwed by the ";"]
269  */
270 #define try(b)          { \
271                         char *_e; \
272                         if((_e=(b))) \
273                           { \
274                           EnsureMemFree(); \
275                           return _e; \
276                           } \
277                         }
279 /*
280  * The panic() function was used in the original code to die, we redefine
281  * it as macro to start the chain of ascending returns that in conjunction
282  * with the try(b) above will simulate a sort of "exception handling"
283  */
285 #define panic(e)        { \
286                         return (e); \
287                         }
289 /*
290  * ve() and e() are used to set the return error,
291  * the most aprropriate use for these is inside panic(...) 
292  */
293 #define MAX_ERR_MSG_LEN 1024
294 static char errmsg[ MAX_ERR_MSG_LEN ];
296 static char *
297 ve ( char *fmt, va_list ap )
299 #ifdef HAVE_VSNPRINTF
300   vsnprintf( errmsg, MAX_ERR_MSG_LEN, fmt, ap );
301 #else
302   vsprintf( errmsg, fmt, ap );
303 #endif
304   EnsureMemFree();
305   return( errmsg );
308 static char *
309 e ( char *fmt, ... )
311   char *err;
312   va_list ap;
313   va_start( ap, fmt );
314   err = ve( fmt, ap );
315   va_end( ap );
316   return( err );
319 /* Compare S1 and S2, ignoring case, returning less than, equal to or
320    greater than zero if S1 is lexiographically less than,
321    equal to or greater than S2.  -- copied from GNU libc*/
322 static int
323 mystrcasecmp (s1, s2)
324      const char *s1;
325      const char *s2;
327   const unsigned char *p1 = (const unsigned char *) s1;
328   const unsigned char *p2 = (const unsigned char *) s2;
329   unsigned char c1, c2;
331   if (p1 == p2)
332     return 0;
334   do
335     {
336       c1 = tolower (*p1++);
337       c2 = tolower (*p2++);
338       if (c1 == '\0')
339         break;
340     }
341   while (c1 == c2);
343   return c1 - c2;
346 /*
347  * parse a token, checking if it's something special to us
348  */
349 static int
350 parse_token(char *arg)
352     int i;
354     for (i=0; Specials[i].name != NULL; i++)
355         if (mystrcasecmp(Specials[i].name, arg) == 0)
356             return sc_tokid = Specials[i].value;
358     /* not special - must be some random id */
359     return sc_tokid = ID;
360 } /* parse_token */
364 /*
365  * init_scanner() sets up the scanner to eat arguments
366  */
367 static char *
368 init_scanner(int argc, char **argv)
370     scp = argv;
371     scc = argc;
372     need = 1;
373     sc_len = 1;
374     while (argc-- > 0)
375         sc_len += strlen(*argv++);
377     sc_token = (char *) malloc(sc_len*sizeof(char));
378     if( sc_token == NULL )
379       return "Failed to allocate memory";
380     need_to_free = 1;
381     return TIME_OK;
382 } /* init_scanner */
384 /*
385  * token() fetches a token from the input stream
386  */
387 static int
388 token()
390     int idx;
392     while (1) {
393         memset(sc_token, '\0', sc_len);
394         sc_tokid = EOF;
395         idx = 0;
397         /* if we need to read another argument, walk along the argument list;
398          * when we fall off the arglist, we'll just return EOF forever
399          */
400         if (need) {
401             if (scc < 1)
402                 return sc_tokid;
403             sct = *scp;
404             scp++;
405             scc--;
406             need = 0;
407         }
408         /* eat whitespace now - if we walk off the end of the argument,
409          * we'll continue, which puts us up at the top of the while loop
410          * to fetch the next argument in
411          */
412         while (isspace((unsigned char)*sct) || *sct == '_' || *sct == ',' )
413             ++sct;
414         if (!*sct) {
415             need = 1;
416             continue;
417         }
419         /* preserve the first character of the new token
420          */
421         sc_token[0] = *sct++;
423         /* then see what it is
424          */
425         if (isdigit((unsigned char)(sc_token[0]))) {
426             while (isdigit((unsigned char)(*sct)))
427                 sc_token[++idx] = *sct++;
428             sc_token[++idx] = '\0';
429             return sc_tokid = NUMBER;
430         }
431         else if (isalpha((unsigned char)(sc_token[0]))) {
432             while (isalpha((unsigned char)(*sct)))
433                 sc_token[++idx] = *sct++;
434             sc_token[++idx] = '\0';
435             return parse_token(sc_token);
436         }
437         else switch(sc_token[0]) {
438             case ':': return sc_tokid = COLON;
439             case '.': return sc_tokid = DOT;
440             case '+': return sc_tokid = PLUS;
441             case '-': return sc_tokid = MINUS;
442             case '/': return sc_tokid = SLASH;
443         default:
444         /*OK, we did not make it ... */
445             sct--;
446             return sc_tokid = EOF;
447         }
448     } /* while (1) */
449 } /* token */
452 /* 
453  * expect() gets a token and complins if it's not the token we want
454  */
455 static char *
456 expect(int desired, char *complain_fmt, ...)
458     va_list ap;
459     va_start( ap, complain_fmt );
460     if (token() != desired) {
461         panic(ve( complain_fmt, ap ));
462     }
463     va_end( ap );
464     return TIME_OK;
465     
466 } /* expect */
469 /*
470  * plus_minus() is used to parse a single NUMBER TIME-UNIT pair
471  *              for the OFFSET-SPEC.
472  *              It allso applies those m-guessing euristics.
473  */
474 static char *
475 plus_minus(struct time_value *ptv, int doop)
477     static int op = PLUS;
478     static int prev_multiplier = -1;
479     int delta;
481     if( doop >= 0 ) 
482       {
483       op = doop;
484       try(expect(NUMBER,"There should be number after '%c'", op == PLUS ? '+' : '-'));
485       prev_multiplier = -1; /* reset months-minutes guessing mechanics */
486       }
487     /* if doop is < 0 then we repeat the previous op
488      * with the prefetched number */
490     delta = atoi(sc_token);
492     if( token() == MONTHS_MINUTES )
493       {
494       /* hard job to guess what does that -5m means: -5mon or -5min? */
495       switch(prev_multiplier)
496         {
497         case DAYS:
498         case WEEKS:
499         case MONTHS:
500         case YEARS:
501              sc_tokid = MONTHS;
502              break;
504         case SECONDS:
505         case MINUTES:
506         case HOURS:
507              sc_tokid = MINUTES;
508              break;
510         default:
511              if( delta < 6 ) /* it may be some other value but in the context
512                                * of RRD who needs less than 6 min deltas? */
513                sc_tokid = MONTHS;
514              else
515                sc_tokid = MINUTES;
516         }
517       }
518     prev_multiplier = sc_tokid;
519     switch (sc_tokid) {
520     case YEARS:
521             ptv->tm.tm_year += (op == PLUS) ? delta : -delta;
522             return TIME_OK;
523     case MONTHS:
524             ptv->tm.tm_mon += (op == PLUS) ? delta : -delta;
525             return TIME_OK;
526     case WEEKS:
527             delta *= 7;
528             /* FALLTHRU */
529     case DAYS:
530             ptv->tm.tm_mday += (op == PLUS) ? delta : -delta;
531             return TIME_OK;
532     case HOURS:
533             ptv->offset += (op == PLUS) ? delta*60*60 : -delta*60*60;
534             return TIME_OK;
535     case MINUTES:
536             ptv->offset += (op == PLUS) ? delta*60 : -delta*60;
537             return TIME_OK;
538     case SECONDS:
539             ptv->offset += (op == PLUS) ? delta : -delta;
540             return TIME_OK;
541     default: /*default unit is seconds */
542         ptv->offset += (op == PLUS) ? delta : -delta;
543         return TIME_OK;
544     }
545     panic(e("well-known time unit expected after %d", delta));
546     /* NORETURN */
547     return TIME_OK; /* to make compiler happy :) */
548 } /* plus_minus */
551 /*
552  * tod() computes the time of day (TIME-OF-DAY-SPEC)
553  */
554 static char *
555 tod(struct time_value *ptv)
557     int hour, minute = 0;
558     int tlen;
559     /* save token status in  case we must abort */
560     int scc_sv = scc; 
561     char *sct_sv = sct; 
562     int sc_tokid_sv = sc_tokid;
564     tlen = strlen(sc_token);
565     
566     /* first pick out the time of day - we assume a HH (COLON|DOT) MM time
567      */    
568     if (tlen > 2) {
569       return TIME_OK;
570     }
571     
572     hour = atoi(sc_token);
574     token();
575     if (sc_tokid == SLASH || sc_tokid == DOT) {
576       /* guess we are looking at a date */
577       scc = scc_sv;
578       sct = sct_sv;
579       sc_tokid = sc_tokid_sv;
580       sprintf (sc_token,"%d", hour);
581       return TIME_OK;
582     }
583     if (sc_tokid == COLON ) {
584         try(expect(NUMBER,
585             "Parsing HH:MM syntax, expecting MM as number, got none"));
586         minute = atoi(sc_token);
587         if (minute > 59) {
588             panic(e("parsing HH:MM syntax, got MM = %d (>59!)", minute ));
589         }
590         token();
591     }
593     /* check if an AM or PM specifier was given
594      */
595     if (sc_tokid == AM || sc_tokid == PM) {
596         if (hour > 12) {
597             panic(e("there cannot be more than 12 AM or PM hours"));
598         }
599         if (sc_tokid == PM) {
600             if (hour != 12)     /* 12:xx PM is 12:xx, not 24:xx */
601                         hour += 12;
602         } else {
603             if (hour == 12)     /* 12:xx AM is 00:xx, not 12:xx */
604                         hour = 0;
605         }
606         token();
607     } 
608     else if (hour > 23) {
609       /* guess it was not a time then ... */
610       scc = scc_sv;
611       sct = sct_sv;
612       sc_tokid = sc_tokid_sv;
613       sprintf (sc_token,"%d", hour);
614       return TIME_OK;
615     }
616     ptv->tm.tm_hour = hour;
617     ptv->tm.tm_min = minute;
618     ptv->tm.tm_sec = 0;
619     if (ptv->tm.tm_hour == 24) {
620         ptv->tm.tm_hour = 0;
621         ptv->tm.tm_mday++;
622     }
623   return TIME_OK;
624 } /* tod */
627 /*
628  * assign_date() assigns a date, adjusting year as appropriate
629  */
630 static char *
631 assign_date(struct time_value *ptv, long mday, long mon, long year)
633     if (year > 138) {
634         if (year > 1970)
635             year -= 1900;
636         else {
637             panic(e("invalid year %d (should be either 00-99 or >1900)",
638                     year));
639         }
640     } else if( year >= 0 && year < 38 ) {
641         year += 100;         /* Allow year 2000-2037 to be specified as   */
642     }                        /* 00-37 until the problem of 2038 year will */
643                              /* arise for unices with 32-bit time_t :)    */
644     if (year < 70) {
645       panic(e("won't handle dates before epoch (01/01/1970), sorry"));
646     }
648     ptv->tm.tm_mday = mday;
649     ptv->tm.tm_mon = mon;
650     ptv->tm.tm_year = year;
651   return TIME_OK;
652 } /* assign_date */
655 /* 
656  * day() picks apart DAY-SPEC-[12]
657  */
658 static char *
659 day(struct time_value *ptv)
661     long mday=0, wday, mon, year = ptv->tm.tm_year;
662     int tlen;
664     switch (sc_tokid) {
665     case YESTERDAY:
666             ptv->tm.tm_mday--;
667             /* FALLTRHU */
668     case TODAY: /* force ourselves to stay in today - no further processing */
669             token();
670             break;
671     case TOMORROW:
672             ptv->tm.tm_mday++;
673             token();
674             break;
676     case JAN: case FEB: case MAR: case APR: case MAY: case JUN:
677     case JUL: case AUG: case SEP: case OCT: case NOV: case DEC:
678             /* do month mday [year]
679              */
680             mon = (sc_tokid-JAN);
681             try(expect(NUMBER,
682                 "the day of the month should follow month name"));
683             mday = atol(sc_token);
684             if (token() == NUMBER) {
685                 year = atol(sc_token);
686                 token();
687             }
688             else
689                 year = ptv->tm.tm_year;
690             try(assign_date(ptv, mday, mon, year));
691             break;
693     case SUN: case MON: case TUE:
694     case WED: case THU: case FRI:
695     case SAT:
696             /* do a particular day of the week
697              */
698             wday = (sc_tokid-SUN);
699             ptv->tm.tm_mday += (wday - ptv->tm.tm_wday);
700             break;
701             /*
702             mday = ptv->tm.tm_mday;
703             mday += (wday - ptv->tm.tm_wday);
704             ptv->tm.tm_wday = wday;
706             try(assign_date(ptv, mday, ptv->tm.tm_mon, ptv->tm.tm_year));
707             break;
708             */
710     case NUMBER:
711             /* get numeric <sec since 1970>, MM/DD/[YY]YY, or DD.MM.[YY]YY
712              */
713             tlen = strlen(sc_token);
714             mon = atol(sc_token);
715             if (mon > 10*356*24*60*60) {
716                 ptv->tm=*localtime(&mon);
717                 token();
718                 break;
719             }
721             if (mon > 19700101 && mon < 24000101){ /*works between 1900 and 2400 */
722                 char  cmon[3],cmday[3],cyear[5];
723                 strncpy(cyear,sc_token,4);cyear[4]='\0';              
724                 year = atol(cyear);           
725                 strncpy(cmon,&(sc_token[4]),2);cmon[2]='\0';
726                 mon = atol(cmon);
727                 strncpy(cmday,&(sc_token[6]),2);cmday[2]='\0';
728                 mday = atol(cmday);
729                 token();
730             } else { 
731               token();
732               
733               if (mon <= 31 && (sc_tokid == SLASH || sc_tokid == DOT)) {
734                 int sep;                    
735                 sep = sc_tokid;
736                 try(expect(NUMBER,"there should be %s number after '%c'",
737                            sep == DOT ? "month" : "day", sep == DOT ? '.' : '/'));
738                 mday = atol(sc_token);
739                 if (token() == sep) {
740                   try(expect(NUMBER,"there should be year number after '%c'",
741                              sep == DOT ? '.' : '/'));
742                   year = atol(sc_token);
743                   token();
744                 }
745                 
746                 /* flip months and days for european timing
747                  */
748                 if (sep == DOT) {
749                   long x = mday;
750                   mday = mon;
751                   mon = x;
752                 }
753               }
754             }
756             mon--;
757             if(mon < 0 || mon > 11 ) {
758                 panic(e("did you really mean month %d?", mon+1));
759             }
760             if(mday < 1 || mday > 31) {
761                 panic(e("I'm afraid that %d is not a valid day of the month",
762                         mday));
763             }      
764             try(assign_date(ptv, mday, mon, year));
765             break;
766     } /* case */
767     return TIME_OK;
768 } /* month */
771 /* Global functions */
774 /*
775  * parsetime() is the external interface that takes tspec, parses
776  * it and puts the result in the time_value structure *ptv.
777  * It can return either absolute times (these are ensured to be
778  * correct) or relative time references that are expected to be
779  * added to some absolute time value and then normalized by
780  * mktime() The return value is either TIME_OK (aka NULL) or
781  * the pointer to the error message in the case of problems
782  */
783 char *
784 parsetime(char *tspec, struct time_value *ptv)
786     time_t now = time(NULL);
787     int hr = 0;
788     /* this MUST be initialized to zero for midnight/noon/teatime */
790     Specials = VariousWords; /* initialize special words context */
792     try(init_scanner( 1, &tspec ));
794     /* establish the default time reference */
795     ptv->type = ABSOLUTE_TIME;
796     ptv->offset = 0;
797     ptv->tm = *localtime(&now);
798     ptv->tm.tm_isdst = -1; /* mk time can figure this out for us ... */
800     token();
801     switch (sc_tokid) {
802     case PLUS:
803     case MINUS:
804             break; /* jump to OFFSET-SPEC part */
806     case START:
807             ptv->type = RELATIVE_TO_START_TIME;
808             goto KeepItRelative;
809     case END:
810             ptv->type = RELATIVE_TO_END_TIME;
811          KeepItRelative:
812             ptv->tm.tm_sec  = 0;
813             ptv->tm.tm_min  = 0;
814             ptv->tm.tm_hour = 0;
815             ptv->tm.tm_mday = 0;
816             ptv->tm.tm_mon  = 0;
817             ptv->tm.tm_year = 0;
818             /* FALLTHRU */
819     case NOW:
820             {
821             int time_reference = sc_tokid;
822             token();
823             if( sc_tokid == PLUS || sc_tokid == MINUS )
824               break;
825             if( time_reference != NOW ) {
826               panic(e("'start' or 'end' MUST be followed by +|- offset"));
827             }
828             else
829               if( sc_tokid != EOF ) {
830                 panic(e("if 'now' is followed by a token it must be +|- offset"));      
831               }
832             };
833             break;
835     /* Only absolute time specifications below */
836     case NUMBER:
837             try(tod(ptv))
838             if (sc_tokid != NUMBER) break; 
839     /* fix month parsing */
840     case JAN: case FEB: case MAR: case APR: case MAY: case JUN:
841     case JUL: case AUG: case SEP: case OCT: case NOV: case DEC:
842             try(day(ptv));
843             if (sc_tokid != NUMBER) break;
844             try(tod(ptv))
845             break;
847             /* evil coding for TEATIME|NOON|MIDNIGHT - we've initialised
848              * hr to zero up above, then fall into this case in such a
849              * way so we add +12 +4 hours to it for teatime, +12 hours
850              * to it for noon, and nothing at all for midnight, then
851              * set our rettime to that hour before leaping into the
852              * month scanner
853              */
854     case TEATIME:
855             hr += 4;
856             /* FALLTHRU */
857     case NOON:
858             hr += 12;
859             /* FALLTHRU */
860     case MIDNIGHT:
861             /* if (ptv->tm.tm_hour >= hr) {
862                 ptv->tm.tm_mday++;
863                 ptv->tm.tm_wday++;
864             } */ /* shifting does not makes sense here ... noon is noon */ 
865             ptv->tm.tm_hour = hr;
866             ptv->tm.tm_min = 0;
867             ptv->tm.tm_sec = 0;
868             token();
869             try(day(ptv));
870             break;
871     default:
872             panic(e("unparsable time: %s%s",sc_token,sct));
873             break;
874     } /* ugly case statement */
876     /*
877      * the OFFSET-SPEC part
878      *
879      * (NOTE, the sc_tokid was prefetched for us by the previous code)
880      */
881     if( sc_tokid == PLUS || sc_tokid == MINUS ) {
882         Specials = TimeMultipliers; /* switch special words context */
883         while( sc_tokid == PLUS || sc_tokid == MINUS ||
884                                sc_tokid == NUMBER ) {
885             if( sc_tokid == NUMBER ) {
886                 try(plus_minus(ptv, PREVIOUS_OP ));
887             } else
888                 try(plus_minus(ptv, sc_tokid));
889             token(); /* We will get EOF eventually but that's OK, since
890                             token() will return us as many EOFs as needed */
891         }
892     }
894     /* now we should be at EOF */
895     if( sc_tokid != EOF ) {
896       panic(e("unparsable trailing text: '...%s%s'", sc_token, sct));
897     }
899     ptv->tm.tm_isdst = -1; /* for mktime to guess DST status */
900     if( ptv->type == ABSOLUTE_TIME )
901       if( mktime( &ptv->tm ) == -1 ) { /* normalize & check */
902         /* can happen for "nonexistent" times, e.g. around 3am */
903         /* when winter -> summer time correction eats a hour */
904         panic(e("the specified time is incorrect (out of range?)"));
905       }
906     EnsureMemFree();
907     return TIME_OK;
908 } /* parsetime */
911 int proc_start_end (struct time_value *start_tv, 
912                     struct time_value *end_tv, 
913                     time_t *start, 
914                     time_t *end){
915     if (start_tv->type == RELATIVE_TO_END_TIME  && /* same as the line above */
916         end_tv->type == RELATIVE_TO_START_TIME) {
917         rrd_set_error("the start and end times cannot be specified "
918                       "relative to each other");
919         return -1;
920     }
922     if (start_tv->type == RELATIVE_TO_START_TIME) {
923         rrd_set_error("the start time cannot be specified relative to itself");
924         return -1;
925     }
927     if (end_tv->type == RELATIVE_TO_END_TIME) {
928         rrd_set_error("the end time cannot be specified relative to itself");
929         return -1;
930     }
932     if( start_tv->type == RELATIVE_TO_END_TIME) {
933         struct tm tmtmp;
934         *end = mktime(&(end_tv->tm)) + end_tv->offset;    
935         tmtmp = *localtime(end); /* reinit end including offset */
936         tmtmp.tm_mday += start_tv->tm.tm_mday;
937         tmtmp.tm_mon += start_tv->tm.tm_mon;
938         tmtmp.tm_year += start_tv->tm.tm_year;  
939         *start = mktime(&tmtmp) + start_tv->offset;
940     } else {
941         *start = mktime(&(start_tv->tm)) + start_tv->offset;
942     }
943     if (end_tv->type == RELATIVE_TO_START_TIME) {
944         struct tm tmtmp;
945         *start = mktime(&(start_tv->tm)) + start_tv->offset;
946         tmtmp = *localtime(start);
947         tmtmp.tm_mday += end_tv->tm.tm_mday;
948         tmtmp.tm_mon += end_tv->tm.tm_mon;
949         tmtmp.tm_year += end_tv->tm.tm_year;    
950         *end = mktime(&tmtmp) + end_tv->offset;
951     } else {
952         *end = mktime(&(end_tv->tm)) + end_tv->offset;
953     }    
954     return 0;
955 } /* proc_start_end */