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