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