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