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