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