71af06572a64b9d6183566c7cf52a287eec11ec4
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 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 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 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 s1,
330 s2)
331 const char *s1;
332 const char *s2;
333 {
334 const unsigned char *p1 = (const unsigned char *) s1;
335 const unsigned char *p2 = (const unsigned char *) s2;
336 unsigned char c1, c2;
338 if (p1 == p2)
339 return 0;
341 do {
342 c1 = tolower(*p1++);
343 c2 = tolower(*p2++);
344 if (c1 == '\0')
345 break;
346 }
347 while (c1 == c2);
349 return c1 - c2;
350 }
352 /*
353 * parse a token, checking if it's something special to us
354 */
355 static int parse_token(
356 char *arg)
357 {
358 int i;
360 for (i = 0; Specials[i].name != NULL; i++)
361 if (mystrcasecmp(Specials[i].name, arg) == 0)
362 return sc_tokid = Specials[i].value;
364 /* not special - must be some random id */
365 return sc_tokid = ID;
366 } /* parse_token */
370 /*
371 * init_scanner() sets up the scanner to eat arguments
372 */
373 static char *init_scanner(
374 int argc,
375 const char **argv)
376 {
377 scp = argv;
378 scc = argc;
379 need = 1;
380 sc_len = 1;
381 while (argc-- > 0)
382 sc_len += strlen(*argv++);
384 sc_token = (char *) malloc(sc_len * sizeof(char));
385 if (sc_token == NULL)
386 return "Failed to allocate memory";
387 return TIME_OK;
388 } /* init_scanner */
390 /*
391 * token() fetches a token from the input stream
392 */
393 static int token(
394 )
395 {
396 int idx;
398 while (1) {
399 memset(sc_token, '\0', sc_len);
400 sc_tokid = EOF;
401 idx = 0;
403 /* if we need to read another argument, walk along the argument list;
404 * when we fall off the arglist, we'll just return EOF forever
405 */
406 if (need) {
407 if (scc < 1)
408 return sc_tokid;
409 sct = *scp;
410 scp++;
411 scc--;
412 need = 0;
413 }
414 /* eat whitespace now - if we walk off the end of the argument,
415 * we'll continue, which puts us up at the top of the while loop
416 * to fetch the next argument in
417 */
418 while (isspace((unsigned char) *sct) || *sct == '_' || *sct == ',')
419 ++sct;
420 if (!*sct) {
421 need = 1;
422 continue;
423 }
425 /* preserve the first character of the new token
426 */
427 sc_token[0] = *sct++;
429 /* then see what it is
430 */
431 if (isdigit((unsigned char) (sc_token[0]))) {
432 while (isdigit((unsigned char) (*sct)))
433 sc_token[++idx] = *sct++;
434 sc_token[++idx] = '\0';
435 return sc_tokid = NUMBER;
436 } else if (isalpha((unsigned char) (sc_token[0]))) {
437 while (isalpha((unsigned char) (*sct)))
438 sc_token[++idx] = *sct++;
439 sc_token[++idx] = '\0';
440 return parse_token(sc_token);
441 } else
442 switch (sc_token[0]) {
443 case ':':
444 return sc_tokid = COLON;
445 case '.':
446 return sc_tokid = DOT;
447 case '+':
448 return sc_tokid = PLUS;
449 case '-':
450 return sc_tokid = MINUS;
451 case '/':
452 return sc_tokid = SLASH;
453 default:
454 /*OK, we did not make it ... */
455 sct--;
456 return sc_tokid = EOF;
457 }
458 } /* while (1) */
459 } /* token */
462 /*
463 * expect2() gets a token and complains if it's not the token we want
464 */
465 static char *expect2(
466 int desired,
467 char *complain_fmt,
468 ...)
469 {
470 va_list ap;
472 va_start(ap, complain_fmt);
473 if (token() != desired) {
474 panic(ve(complain_fmt, ap));
475 }
476 va_end(ap);
477 return TIME_OK;
479 } /* expect2 */
482 /*
483 * plus_minus() is used to parse a single NUMBER TIME-UNIT pair
484 * for the OFFSET-SPEC.
485 * It also applies those m-guessing heuristics.
486 */
487 static char *plus_minus(
488 struct rrd_time_value *ptv,
489 int doop)
490 {
491 static int op = PLUS;
492 static int prev_multiplier = -1;
493 int delta;
495 if (doop >= 0) {
496 op = doop;
497 try(expect2
498 (NUMBER, "There should be number after '%c'",
499 op == PLUS ? '+' : '-'));
500 prev_multiplier = -1; /* reset months-minutes guessing mechanics */
501 }
502 /* if doop is < 0 then we repeat the previous op
503 * with the prefetched number */
505 delta = atoi(sc_token);
507 if (token() == MONTHS_MINUTES) {
508 /* hard job to guess what does that -5m means: -5mon or -5min? */
509 switch (prev_multiplier) {
510 case DAYS:
511 case WEEKS:
512 case MONTHS:
513 case YEARS:
514 sc_tokid = MONTHS;
515 break;
517 case SECONDS:
518 case MINUTES:
519 case HOURS:
520 sc_tokid = MINUTES;
521 break;
523 default:
524 if (delta < 6) /* it may be some other value but in the context
525 * of RRD who needs less than 6 min deltas? */
526 sc_tokid = MONTHS;
527 else
528 sc_tokid = MINUTES;
529 }
530 }
531 prev_multiplier = sc_tokid;
532 switch (sc_tokid) {
533 case YEARS:
534 ptv->tm. tm_year += (
535 op == PLUS) ? delta : -delta;
537 return TIME_OK;
538 case MONTHS:
539 ptv->tm. tm_mon += (
540 op == PLUS) ? delta : -delta;
542 return TIME_OK;
543 case WEEKS:
544 delta *= 7;
545 /* FALLTHRU */
546 case DAYS:
547 ptv->tm. tm_mday += (
548 op == PLUS) ? delta : -delta;
550 return TIME_OK;
551 case HOURS:
552 ptv->offset += (op == PLUS) ? delta * 60 * 60 : -delta * 60 * 60;
553 return TIME_OK;
554 case MINUTES:
555 ptv->offset += (op == PLUS) ? delta * 60 : -delta * 60;
556 return TIME_OK;
557 case SECONDS:
558 ptv->offset += (op == PLUS) ? delta : -delta;
559 return TIME_OK;
560 default: /*default unit is seconds */
561 ptv->offset += (op == PLUS) ? delta : -delta;
562 return TIME_OK;
563 }
564 panic(e("well-known time unit expected after %d", delta));
565 /* NORETURN */
566 return TIME_OK; /* to make compiler happy :) */
567 } /* plus_minus */
570 /*
571 * tod() computes the time of day (TIME-OF-DAY-SPEC)
572 */
573 static char *tod(
574 struct rrd_time_value *ptv)
575 {
576 int hour, minute = 0;
577 int tlen;
579 /* save token status in case we must abort */
580 int scc_sv = scc;
581 const char *sct_sv = sct;
582 int sc_tokid_sv = sc_tokid;
584 tlen = strlen(sc_token);
586 /* first pick out the time of day - we assume a HH (COLON|DOT) MM time
587 */
588 if (tlen > 2) {
589 return TIME_OK;
590 }
592 hour = atoi(sc_token);
594 token();
595 if (sc_tokid == SLASH || sc_tokid == DOT) {
596 /* guess we are looking at a date */
597 scc = scc_sv;
598 sct = sct_sv;
599 sc_tokid = sc_tokid_sv;
600 sprintf(sc_token, "%d", hour);
601 return TIME_OK;
602 }
603 if (sc_tokid == COLON) {
604 try(expect2(NUMBER,
605 "Parsing HH:MM syntax, expecting MM as number, got none"));
606 minute = atoi(sc_token);
607 if (minute > 59) {
608 panic(e("parsing HH:MM syntax, got MM = %d (>59!)", minute));
609 }
610 token();
611 }
613 /* check if an AM or PM specifier was given
614 */
615 if (sc_tokid == AM || sc_tokid == PM) {
616 if (hour > 12) {
617 panic(e("there cannot be more than 12 AM or PM hours"));
618 }
619 if (sc_tokid == PM) {
620 if (hour != 12) /* 12:xx PM is 12:xx, not 24:xx */
621 hour += 12;
622 } else {
623 if (hour == 12) /* 12:xx AM is 00:xx, not 12:xx */
624 hour = 0;
625 }
626 token();
627 } else if (hour > 23) {
628 /* guess it was not a time then ... */
629 scc = scc_sv;
630 sct = sct_sv;
631 sc_tokid = sc_tokid_sv;
632 sprintf(sc_token, "%d", hour);
633 return TIME_OK;
634 }
635 ptv->tm. tm_hour = hour;
636 ptv->tm. tm_min = minute;
637 ptv->tm. tm_sec = 0;
639 if (ptv->tm.tm_hour == 24) {
640 ptv->tm. tm_hour = 0;
641 ptv->tm. tm_mday++;
642 }
643 return TIME_OK;
644 } /* tod */
647 /*
648 * assign_date() assigns a date, adjusting year as appropriate
649 */
650 static char *assign_date(
651 struct rrd_time_value *ptv,
652 long mday,
653 long mon,
654 long year)
655 {
656 if (year > 138) {
657 if (year > 1970)
658 year -= 1900;
659 else {
660 panic(e("invalid year %d (should be either 00-99 or >1900)",
661 year));
662 }
663 } else if (year >= 0 && year < 38) {
664 year += 100; /* Allow year 2000-2037 to be specified as */
665 }
666 /* 00-37 until the problem of 2038 year will */
667 /* arise for unices with 32-bit time_t :) */
668 if (year < 70) {
669 panic(e("won't handle dates before epoch (01/01/1970), sorry"));
670 }
672 ptv->tm. tm_mday = mday;
673 ptv->tm. tm_mon = mon;
674 ptv->tm. tm_year = year;
676 return TIME_OK;
677 } /* assign_date */
680 /*
681 * day() picks apart DAY-SPEC-[12]
682 */
683 static char *day(
684 struct rrd_time_value *ptv)
685 {
686 /* using time_t seems to help portability with 64bit oses */
687 time_t mday = 0, wday, mon, year = ptv->tm.tm_year;
688 int tlen;
690 switch (sc_tokid) {
691 case YESTERDAY:
692 ptv->tm. tm_mday--;
694 /* FALLTRHU */
695 case TODAY: /* force ourselves to stay in today - no further processing */
696 token();
697 break;
698 case TOMORROW:
699 ptv->tm. tm_mday++;
701 token();
702 break;
704 case JAN:
705 case FEB:
706 case MAR:
707 case APR:
708 case MAY:
709 case JUN:
710 case JUL:
711 case AUG:
712 case SEP:
713 case OCT:
714 case NOV:
715 case DEC:
716 /* do month mday [year]
717 */
718 mon = (sc_tokid - JAN);
719 try(expect2(NUMBER, "the day of the month should follow month name"));
720 mday = atol(sc_token);
721 if (token() == NUMBER) {
722 year = atol(sc_token);
723 token();
724 } else
725 year = ptv->tm.tm_year;
727 try(assign_date(ptv, mday, mon, year));
728 break;
730 case SUN:
731 case MON:
732 case TUE:
733 case WED:
734 case THU:
735 case FRI:
736 case SAT:
737 /* do a particular day of the week
738 */
739 wday = (sc_tokid - SUN);
740 ptv->tm. tm_mday += (
741 wday - ptv->tm.tm_wday);
743 token();
744 break;
745 /*
746 mday = ptv->tm.tm_mday;
747 mday += (wday - ptv->tm.tm_wday);
748 ptv->tm.tm_wday = wday;
750 try(assign_date(ptv, mday, ptv->tm.tm_mon, ptv->tm.tm_year));
751 break;
752 */
754 case NUMBER:
755 /* get numeric <sec since 1970>, MM/DD/[YY]YY, or DD.MM.[YY]YY
756 */
757 tlen = strlen(sc_token);
758 mon = atol(sc_token);
759 if (mon > 10 * 365 * 24 * 60 * 60) {
760 ptv->tm = *localtime(&mon);
762 token();
763 break;
764 }
766 if (mon > 19700101 && mon < 24000101) { /*works between 1900 and 2400 */
767 char cmon[3], cmday[3], cyear[5];
769 strncpy(cyear, sc_token, 4);
770 cyear[4] = '\0';
771 year = atol(cyear);
772 strncpy(cmon, &(sc_token[4]), 2);
773 cmon[2] = '\0';
774 mon = atol(cmon);
775 strncpy(cmday, &(sc_token[6]), 2);
776 cmday[2] = '\0';
777 mday = atol(cmday);
778 token();
779 } else {
780 token();
782 if (mon <= 31 && (sc_tokid == SLASH || sc_tokid == DOT)) {
783 int sep;
785 sep = sc_tokid;
786 try(expect2(NUMBER, "there should be %s number after '%c'",
787 sep == DOT ? "month" : "day",
788 sep == DOT ? '.' : '/'));
789 mday = atol(sc_token);
790 if (token() == sep) {
791 try(expect2
792 (NUMBER, "there should be year number after '%c'",
793 sep == DOT ? '.' : '/'));
794 year = atol(sc_token);
795 token();
796 }
798 /* flip months and days for European timing
799 */
800 if (sep == DOT) {
801 long x = mday;
803 mday = mon;
804 mon = x;
805 }
806 }
807 }
809 mon--;
810 if (mon < 0 || mon > 11) {
811 panic(e("did you really mean month %d?", mon + 1));
812 }
813 if (mday < 1 || mday > 31) {
814 panic(e("I'm afraid that %d is not a valid day of the month",
815 mday));
816 }
817 try(assign_date(ptv, mday, mon, year));
818 break;
819 } /* case */
820 return TIME_OK;
821 } /* month */
824 /* Global functions */
827 /*
828 * parsetime() is the external interface that takes tspec, parses
829 * it and puts the result in the rrd_time_value structure *ptv.
830 * It can return either absolute times (these are ensured to be
831 * correct) or relative time references that are expected to be
832 * added to some absolute time value and then normalized by
833 * mktime() The return value is either TIME_OK (aka NULL) or
834 * the pointer to the error message in the case of problems
835 */
836 char *parsetime(
837 const char *tspec,
838 struct rrd_time_value *ptv)
839 {
840 time_t now = time(NULL);
841 int hr = 0;
843 /* this MUST be initialized to zero for midnight/noon/teatime */
845 Specials = VariousWords; /* initialize special words context */
847 try(init_scanner(1, &tspec));
849 /* establish the default time reference */
850 ptv->type = ABSOLUTE_TIME;
851 ptv->offset = 0;
852 ptv->tm = *localtime(&now);
853 ptv->tm. tm_isdst = -1; /* mk time can figure this out for us ... */
855 token();
856 switch (sc_tokid) {
857 case PLUS:
858 case MINUS:
859 break; /* jump to OFFSET-SPEC part */
861 case START:
862 ptv->type = RELATIVE_TO_START_TIME;
863 goto KeepItRelative;
864 case END:
865 ptv->type = RELATIVE_TO_END_TIME;
866 KeepItRelative:
867 ptv->tm. tm_sec = 0;
868 ptv->tm. tm_min = 0;
869 ptv->tm. tm_hour = 0;
870 ptv->tm. tm_mday = 0;
871 ptv->tm. tm_mon = 0;
872 ptv->tm. tm_year = 0;
874 /* FALLTHRU */
875 case NOW:
876 {
877 int time_reference = sc_tokid;
879 token();
880 if (sc_tokid == PLUS || sc_tokid == MINUS)
881 break;
882 if (time_reference != NOW) {
883 panic(e("'start' or 'end' MUST be followed by +|- offset"));
884 } else if (sc_tokid != EOF) {
885 panic(e("if 'now' is followed by a token it must be +|- offset"));
886 }
887 };
888 break;
890 /* Only absolute time specifications below */
891 case NUMBER:
892 {
893 long hour_sv = ptv->tm.tm_hour;
894 long year_sv = ptv->tm.tm_year;
896 ptv->tm. tm_hour = 30;
897 ptv->tm. tm_year = 30000;
899 try(tod(ptv))
900 try(day(ptv))
901 if (ptv->tm.tm_hour == 30 && ptv->tm.tm_year != 30000) {
902 try(tod(ptv))
903 }
904 if (ptv->tm.tm_hour == 30) {
905 ptv->tm. tm_hour = hour_sv;
906 }
907 if (ptv->tm.tm_year == 30000) {
908 ptv->tm. tm_year = year_sv;
909 }
910 };
911 break;
912 /* fix month parsing */
913 case JAN:
914 case FEB:
915 case MAR:
916 case APR:
917 case MAY:
918 case JUN:
919 case JUL:
920 case AUG:
921 case SEP:
922 case OCT:
923 case NOV:
924 case DEC:
925 try(day(ptv));
926 if (sc_tokid != NUMBER)
927 break;
928 try(tod(ptv))
929 break;
931 /* evil coding for TEATIME|NOON|MIDNIGHT - we've initialized
932 * hr to zero up above, then fall into this case in such a
933 * way so we add +12 +4 hours to it for teatime, +12 hours
934 * to it for noon, and nothing at all for midnight, then
935 * set our rettime to that hour before leaping into the
936 * month scanner
937 */
938 case TEATIME:
939 hr += 4;
940 /* FALLTHRU */
941 case NOON:
942 hr += 12;
943 /* FALLTHRU */
944 case MIDNIGHT:
945 /* if (ptv->tm.tm_hour >= hr) {
946 ptv->tm.tm_mday++;
947 ptv->tm.tm_wday++;
948 } *//* shifting does not makes sense here ... noon is noon */
949 ptv->tm. tm_hour = hr;
950 ptv->tm. tm_min = 0;
951 ptv->tm. tm_sec = 0;
953 token();
954 try(day(ptv));
955 break;
956 default:
957 panic(e("unparsable time: %s%s", sc_token, sct));
958 break;
959 } /* ugly case statement */
961 /*
962 * the OFFSET-SPEC part
963 *
964 * (NOTE, the sc_tokid was prefetched for us by the previous code)
965 */
966 if (sc_tokid == PLUS || sc_tokid == MINUS) {
967 Specials = TimeMultipliers; /* switch special words context */
968 while (sc_tokid == PLUS || sc_tokid == MINUS || sc_tokid == NUMBER) {
969 if (sc_tokid == NUMBER) {
970 try(plus_minus(ptv, PREVIOUS_OP));
971 } else
972 try(plus_minus(ptv, sc_tokid));
973 token(); /* We will get EOF eventually but that's OK, since
974 token() will return us as many EOFs as needed */
975 }
976 }
978 /* now we should be at EOF */
979 if (sc_tokid != EOF) {
980 panic(e("unparsable trailing text: '...%s%s'", sc_token, sct));
981 }
983 ptv->tm. tm_isdst = -1; /* for mktime to guess DST status */
985 if (ptv->type == ABSOLUTE_TIME)
986 if (mktime(&ptv->tm) == -1) { /* normalize & check */
987 /* can happen for "nonexistent" times, e.g. around 3am */
988 /* when winter -> summer time correction eats a hour */
989 panic(e("the specified time is incorrect (out of range?)"));
990 }
991 EnsureMemFree();
992 return TIME_OK;
993 } /* parsetime */
996 int proc_start_end(
997 struct rrd_time_value *start_tv,
998 struct rrd_time_value *end_tv,
999 time_t *start,
1000 time_t *end)
1001 {
1002 if (start_tv->type == RELATIVE_TO_END_TIME && /* same as the line above */
1003 end_tv->type == RELATIVE_TO_START_TIME) {
1004 rrd_set_error("the start and end times cannot be specified "
1005 "relative to each other");
1006 return -1;
1007 }
1009 if (start_tv->type == RELATIVE_TO_START_TIME) {
1010 rrd_set_error
1011 ("the start time cannot be specified relative to itself");
1012 return -1;
1013 }
1015 if (end_tv->type == RELATIVE_TO_END_TIME) {
1016 rrd_set_error("the end time cannot be specified relative to itself");
1017 return -1;
1018 }
1020 if (start_tv->type == RELATIVE_TO_END_TIME) {
1021 struct tm tmtmp;
1023 *end = mktime(&(end_tv->tm)) + end_tv->offset;
1024 tmtmp = *localtime(end); /* reinit end including offset */
1025 tmtmp.tm_mday += start_tv->tm.tm_mday;
1026 tmtmp.tm_mon += start_tv->tm.tm_mon;
1027 tmtmp.tm_year += start_tv->tm.tm_year;
1029 *start = mktime(&tmtmp) + start_tv->offset;
1030 } else {
1031 *start = mktime(&(start_tv->tm)) + start_tv->offset;
1032 }
1033 if (end_tv->type == RELATIVE_TO_START_TIME) {
1034 struct tm tmtmp;
1036 *start = mktime(&(start_tv->tm)) + start_tv->offset;
1037 tmtmp = *localtime(start);
1038 tmtmp.tm_mday += end_tv->tm.tm_mday;
1039 tmtmp.tm_mon += end_tv->tm.tm_mon;
1040 tmtmp.tm_year += end_tv->tm.tm_year;
1042 *end = mktime(&tmtmp) + end_tv->offset;
1043 } else {
1044 *end = mktime(&(end_tv->tm)) + end_tv->offset;
1045 }
1046 return 0;
1047 } /* proc_start_end */