* we are now creating true RGBA pngs
[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     long mday=0, wday, mon, year = ptv->tm.tm_year;
663     int tlen;
664
665     switch (sc_tokid) {
666     case YESTERDAY:
667             ptv->tm.tm_mday--;
668             /* FALLTRHU */
669     case TODAY: /* force ourselves to stay in today - no further processing */
670             token();
671             break;
672     case TOMORROW:
673             ptv->tm.tm_mday++;
674             token();
675             break;
676
677     case JAN: case FEB: case MAR: case APR: case MAY: case JUN:
678     case JUL: case AUG: case SEP: case OCT: case NOV: case DEC:
679             /* do month mday [year]
680              */
681             mon = (sc_tokid-JAN);
682             try(expect2(NUMBER,
683                 "the day of the month should follow month name"));
684             mday = atol(sc_token);
685             if (token() == NUMBER) {
686                 year = atol(sc_token);
687                 token();
688             }
689             else
690                 year = ptv->tm.tm_year;
691             try(assign_date(ptv, mday, mon, year));
692             break;
693
694     case SUN: case MON: case TUE:
695     case WED: case THU: case FRI:
696     case SAT:
697             /* do a particular day of the week
698              */
699             wday = (sc_tokid-SUN);
700             ptv->tm.tm_mday += (wday - ptv->tm.tm_wday);
701             token();
702             break;
703             /*
704             mday = ptv->tm.tm_mday;
705             mday += (wday - ptv->tm.tm_wday);
706             ptv->tm.tm_wday = wday;
707
708             try(assign_date(ptv, mday, ptv->tm.tm_mon, ptv->tm.tm_year));
709             break;
710             */
711
712     case NUMBER:
713             /* get numeric <sec since 1970>, MM/DD/[YY]YY, or DD.MM.[YY]YY
714              */
715             tlen = strlen(sc_token);
716             mon = atol(sc_token);
717             if (mon > 10*365*24*60*60) {
718                 ptv->tm=*localtime(&mon);
719                 token();
720                 break;
721             }
722
723             if (mon > 19700101 && mon < 24000101){ /*works between 1900 and 2400 */
724                 char  cmon[3],cmday[3],cyear[5];
725                 strncpy(cyear,sc_token,4);cyear[4]='\0';              
726                 year = atol(cyear);           
727                 strncpy(cmon,&(sc_token[4]),2);cmon[2]='\0';
728                 mon = atol(cmon);
729                 strncpy(cmday,&(sc_token[6]),2);cmday[2]='\0';
730                 mday = atol(cmday);
731                 token();
732             } else { 
733               token();
734               
735               if (mon <= 31 && (sc_tokid == SLASH || sc_tokid == DOT)) {
736                 int sep;                    
737                 sep = sc_tokid;
738                 try(expect2(NUMBER,"there should be %s number after '%c'",
739                            sep == DOT ? "month" : "day", sep == DOT ? '.' : '/'));
740                 mday = atol(sc_token);
741                 if (token() == sep) {
742                   try(expect2(NUMBER,"there should be year number after '%c'",
743                              sep == DOT ? '.' : '/'));
744                   year = atol(sc_token);
745                   token();
746                 }
747                 
748                 /* flip months and days for European timing
749                  */
750                 if (sep == DOT) {
751                   long x = mday;
752                   mday = mon;
753                   mon = x;
754                 }
755               }
756             }
757
758             mon--;
759             if(mon < 0 || mon > 11 ) {
760                 panic(e("did you really mean month %d?", mon+1));
761             }
762             if(mday < 1 || mday > 31) {
763                 panic(e("I'm afraid that %d is not a valid day of the month",
764                         mday));
765             }      
766             try(assign_date(ptv, mday, mon, year));
767             break;
768     } /* case */
769     return TIME_OK;
770 } /* month */
771
772
773 /* Global functions */
774
775
776 /*
777  * parsetime() is the external interface that takes tspec, parses
778  * it and puts the result in the rrd_time_value structure *ptv.
779  * It can return either absolute times (these are ensured to be
780  * correct) or relative time references that are expected to be
781  * added to some absolute time value and then normalized by
782  * mktime() The return value is either TIME_OK (aka NULL) or
783  * the pointer to the error message in the case of problems
784  */
785 char *
786 parsetime(const char *tspec, struct rrd_time_value *ptv)
787 {
788     time_t now = time(NULL);
789     int hr = 0;
790     /* this MUST be initialized to zero for midnight/noon/teatime */
791
792     Specials = VariousWords; /* initialize special words context */
793
794     try(init_scanner( 1, &tspec ));
795
796     /* establish the default time reference */
797     ptv->type = ABSOLUTE_TIME;
798     ptv->offset = 0;
799     ptv->tm = *localtime(&now);
800     ptv->tm.tm_isdst = -1; /* mk time can figure this out for us ... */
801
802     token();
803     switch (sc_tokid) {
804     case PLUS:
805     case MINUS:
806             break; /* jump to OFFSET-SPEC part */
807
808     case START:
809             ptv->type = RELATIVE_TO_START_TIME;
810             goto KeepItRelative;
811     case END:
812             ptv->type = RELATIVE_TO_END_TIME;
813          KeepItRelative:
814             ptv->tm.tm_sec  = 0;
815             ptv->tm.tm_min  = 0;
816             ptv->tm.tm_hour = 0;
817             ptv->tm.tm_mday = 0;
818             ptv->tm.tm_mon  = 0;
819             ptv->tm.tm_year = 0;
820             /* FALLTHRU */
821     case NOW:
822             {
823             int time_reference = sc_tokid;
824             token();
825             if( sc_tokid == PLUS || sc_tokid == MINUS )
826               break;
827             if( time_reference != NOW ) {
828               panic(e("'start' or 'end' MUST be followed by +|- offset"));
829             }
830             else
831               if( sc_tokid != EOF ) {
832                 panic(e("if 'now' is followed by a token it must be +|- offset"));      
833               }
834             };
835             break;
836
837     /* Only absolute time specifications below */
838     case NUMBER:
839             try(tod(ptv))
840             try(day(ptv))
841             break;
842     /* fix month parsing */
843     case JAN: case FEB: case MAR: case APR: case MAY: case JUN:
844     case JUL: case AUG: case SEP: case OCT: case NOV: case DEC:
845             try(day(ptv));
846             if (sc_tokid != NUMBER) break;
847             try(tod(ptv))
848             break;
849
850             /* evil coding for TEATIME|NOON|MIDNIGHT - we've initialized
851              * hr to zero up above, then fall into this case in such a
852              * way so we add +12 +4 hours to it for teatime, +12 hours
853              * to it for noon, and nothing at all for midnight, then
854              * set our rettime to that hour before leaping into the
855              * month scanner
856              */
857     case TEATIME:
858             hr += 4;
859             /* FALLTHRU */
860     case NOON:
861             hr += 12;
862             /* FALLTHRU */
863     case MIDNIGHT:
864             /* if (ptv->tm.tm_hour >= hr) {
865                 ptv->tm.tm_mday++;
866                 ptv->tm.tm_wday++;
867             } */ /* shifting does not makes sense here ... noon is noon */ 
868             ptv->tm.tm_hour = hr;
869             ptv->tm.tm_min = 0;
870             ptv->tm.tm_sec = 0;
871             token();
872             try(day(ptv));
873             break;
874     default:
875             panic(e("unparsable time: %s%s",sc_token,sct));
876             break;
877     } /* ugly case statement */
878
879     /*
880      * the OFFSET-SPEC part
881      *
882      * (NOTE, the sc_tokid was prefetched for us by the previous code)
883      */
884     if( sc_tokid == PLUS || sc_tokid == MINUS ) {
885         Specials = TimeMultipliers; /* switch special words context */
886         while( sc_tokid == PLUS || sc_tokid == MINUS ||
887                                sc_tokid == NUMBER ) {
888             if( sc_tokid == NUMBER ) {
889                 try(plus_minus(ptv, PREVIOUS_OP ));
890             } else
891                 try(plus_minus(ptv, sc_tokid));
892             token(); /* We will get EOF eventually but that's OK, since
893                             token() will return us as many EOFs as needed */
894         }
895     }
896
897     /* now we should be at EOF */
898     if( sc_tokid != EOF ) {
899       panic(e("unparsable trailing text: '...%s%s'", sc_token, sct));
900     }
901
902     ptv->tm.tm_isdst = -1; /* for mktime to guess DST status */
903     if( ptv->type == ABSOLUTE_TIME )
904       if( mktime( &ptv->tm ) == -1 ) { /* normalize & check */
905         /* can happen for "nonexistent" times, e.g. around 3am */
906         /* when winter -> summer time correction eats a hour */
907         panic(e("the specified time is incorrect (out of range?)"));
908       }
909     EnsureMemFree();
910     return TIME_OK;
911 } /* parsetime */
912
913
914 int proc_start_end (struct rrd_time_value *start_tv, 
915                     struct rrd_time_value *end_tv, 
916                     time_t *start, 
917                     time_t *end){
918     if (start_tv->type == RELATIVE_TO_END_TIME  && /* same as the line above */
919         end_tv->type == RELATIVE_TO_START_TIME) {
920         rrd_set_error("the start and end times cannot be specified "
921                       "relative to each other");
922         return -1;
923     }
924
925     if (start_tv->type == RELATIVE_TO_START_TIME) {
926         rrd_set_error("the start time cannot be specified relative to itself");
927         return -1;
928     }
929
930     if (end_tv->type == RELATIVE_TO_END_TIME) {
931         rrd_set_error("the end time cannot be specified relative to itself");
932         return -1;
933     }
934
935     if( start_tv->type == RELATIVE_TO_END_TIME) {
936         struct tm tmtmp;
937         *end = mktime(&(end_tv->tm)) + end_tv->offset;    
938         tmtmp = *localtime(end); /* reinit end including offset */
939         tmtmp.tm_mday += start_tv->tm.tm_mday;
940         tmtmp.tm_mon += start_tv->tm.tm_mon;
941         tmtmp.tm_year += start_tv->tm.tm_year;  
942         *start = mktime(&tmtmp) + start_tv->offset;
943     } else {
944         *start = mktime(&(start_tv->tm)) + start_tv->offset;
945     }
946     if (end_tv->type == RELATIVE_TO_START_TIME) {
947         struct tm tmtmp;
948         *start = mktime(&(start_tv->tm)) + start_tv->offset;
949         tmtmp = *localtime(start);
950         tmtmp.tm_mday += end_tv->tm.tm_mday;
951         tmtmp.tm_mon += end_tv->tm.tm_mon;
952         tmtmp.tm_year += end_tv->tm.tm_year;    
953         *end = mktime(&tmtmp) + end_tv->offset;
954     } else {
955         *end = mktime(&(end_tv->tm)) + end_tv->offset;
956     }    
957     return 0;
958 } /* proc_start_end */
959
960
961
962
963
964
965