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 */
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 */
110 /* System Headers */
112 /* Local headers */
114 #include "rrd_tool.h"
115 #include <stdarg.h>
117 /* Structures and unions */
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 };
130 /* the below is for plus_minus() */
131 #define PREVIOUS_OP (-1)
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 },
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 };
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 };
226 /* File scope variables */
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;
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 */
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 */
243 static int need_to_free = 0; /* means that we need deallocating memory */
245 /* Local functions */
247 void EnsureMemFree ()
248 {
249 if( need_to_free )
250 {
251 free(sc_token);
252 need_to_free = 0;
253 }
254 }
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 }
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 */
285 #define panic(e) { \
286 return (e); \
287 }
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 ];
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 }
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 }
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;
331 if (p1 == p2)
332 return 0;
334 do
335 {
336 c1 = tolower (*p1++);
337 c2 = tolower (*p2++);
338 if (c1 == '\0')
339 break;
340 }
341 while (c1 == c2);
343 return c1 - c2;
344 }
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;
354 for (i=0; Specials[i].name != NULL; i++)
355 if (mystrcasecmp(Specials[i].name, arg) == 0)
356 return sc_tokid = Specials[i].value;
358 /* not special - must be some random id */
359 return sc_tokid = ID;
360 } /* parse_token */
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++);
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 */
384 /*
385 * token() fetches a token from the input stream
386 */
387 static int
388 token()
389 {
390 int idx;
392 while (1) {
393 memset(sc_token, '\0', sc_len);
394 sc_tokid = EOF;
395 idx = 0;
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 }
419 /* preserve the first character of the new token
420 */
421 sc_token[0] = *sct++;
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 */
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;
466 } /* expect */
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;
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 */
490 delta = atoi(sc_token);
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;
504 case SECONDS:
505 case MINUTES:
506 case HOURS:
507 sc_tokid = MINUTES;
508 break;
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 */
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;
564 tlen = strlen(sc_token);
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 }
572 hour = atoi(sc_token);
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 }
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 */
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 }
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 */
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;
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;
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;
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;
707 try(assign_date(ptv, mday, ptv->tm.tm_mon, ptv->tm.tm_year));
708 break;
709 */
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 }
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();
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 }
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 }
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 */
772 /* Global functions */
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 */
791 Specials = VariousWords; /* initialize special words context */
793 try(init_scanner( 1, &tspec ));
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 ... */
801 token();
802 switch (sc_tokid) {
803 case PLUS:
804 case MINUS:
805 break; /* jump to OFFSET-SPEC part */
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;
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;
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 */
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 }
896 /* now we should be at EOF */
897 if( sc_tokid != EOF ) {
898 panic(e("unparsable trailing text: '...%s%s'", sc_token, sct));
899 }
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 */
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 }
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 }
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 }
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 */