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