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