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