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 (void);
249 static void EnsureMemFree (void)
250 {
251 if( sc_token )
252 {
253 free(sc_token);
254 sc_token = NULL;
255 }
256 }
258 /*
259 * A hack to compensate for the lack of the C++ exceptions
260 *
261 * Every function func that might generate parsing "exception"
262 * should return TIME_OK (aka NULL) or pointer to the error message,
263 * and should be called like this: try(func(args));
264 *
265 * if the try is not successful it will reset the token pointer ...
266 *
267 * [NOTE: when try(...) is used as the only statement in the "if-true"
268 * part of the if statement that also has an "else" part it should be
269 * either enclosed in the curly braces (despite the fact that it looks
270 * like a single statement) or NOT followed by the ";"]
271 */
272 #define try(b) { \
273 char *_e; \
274 if((_e=(b))) \
275 { \
276 EnsureMemFree(); \
277 return _e; \
278 } \
279 }
281 /*
282 * The panic() function was used in the original code to die, we redefine
283 * it as macro to start the chain of ascending returns that in conjunction
284 * with the try(b) above will simulate a sort of "exception handling"
285 */
287 #define panic(e) { \
288 return (e); \
289 }
291 /*
292 * ve() and e() are used to set the return error,
293 * the most appropriate use for these is inside panic(...)
294 */
295 #define MAX_ERR_MSG_LEN 1024
296 static char errmsg[ MAX_ERR_MSG_LEN ];
298 static char *
299 ve ( char *fmt, va_list ap )
300 {
301 #ifdef HAVE_VSNPRINTF
302 vsnprintf( errmsg, MAX_ERR_MSG_LEN, fmt, ap );
303 #else
304 vsprintf( errmsg, fmt, ap );
305 #endif
306 EnsureMemFree();
307 return( errmsg );
308 }
310 static char *
311 e ( char *fmt, ... )
312 {
313 char *err;
314 va_list ap;
315 va_start( ap, fmt );
316 err = ve( fmt, ap );
317 va_end( ap );
318 return( err );
319 }
321 /* Compare S1 and S2, ignoring case, returning less than, equal to or
322 greater than zero if S1 is lexicographically less than,
323 equal to or greater than S2. -- copied from GNU libc*/
324 static int
325 mystrcasecmp (s1, s2)
326 const char *s1;
327 const char *s2;
328 {
329 const unsigned char *p1 = (const unsigned char *) s1;
330 const unsigned char *p2 = (const unsigned char *) s2;
331 unsigned char c1, c2;
333 if (p1 == p2)
334 return 0;
336 do
337 {
338 c1 = tolower (*p1++);
339 c2 = tolower (*p2++);
340 if (c1 == '\0')
341 break;
342 }
343 while (c1 == c2);
345 return c1 - c2;
346 }
348 /*
349 * parse a token, checking if it's something special to us
350 */
351 static int
352 parse_token(char *arg)
353 {
354 int i;
356 for (i=0; Specials[i].name != NULL; i++)
357 if (mystrcasecmp(Specials[i].name, arg) == 0)
358 return sc_tokid = Specials[i].value;
360 /* not special - must be some random id */
361 return sc_tokid = ID;
362 } /* parse_token */
366 /*
367 * init_scanner() sets up the scanner to eat arguments
368 */
369 static char *
370 init_scanner(int argc, const char **argv)
371 {
372 scp = argv;
373 scc = argc;
374 need = 1;
375 sc_len = 1;
376 while (argc-- > 0)
377 sc_len += strlen(*argv++);
379 sc_token = (char *) malloc(sc_len*sizeof(char));
380 if( sc_token == NULL )
381 return "Failed to allocate memory";
382 return TIME_OK;
383 } /* init_scanner */
385 /*
386 * token() fetches a token from the input stream
387 */
388 static int
389 token()
390 {
391 int idx;
393 while (1) {
394 memset(sc_token, '\0', sc_len);
395 sc_tokid = EOF;
396 idx = 0;
398 /* if we need to read another argument, walk along the argument list;
399 * when we fall off the arglist, we'll just return EOF forever
400 */
401 if (need) {
402 if (scc < 1)
403 return sc_tokid;
404 sct = *scp;
405 scp++;
406 scc--;
407 need = 0;
408 }
409 /* eat whitespace now - if we walk off the end of the argument,
410 * we'll continue, which puts us up at the top of the while loop
411 * to fetch the next argument in
412 */
413 while (isspace((unsigned char)*sct) || *sct == '_' || *sct == ',' )
414 ++sct;
415 if (!*sct) {
416 need = 1;
417 continue;
418 }
420 /* preserve the first character of the new token
421 */
422 sc_token[0] = *sct++;
424 /* then see what it is
425 */
426 if (isdigit((unsigned char)(sc_token[0]))) {
427 while (isdigit((unsigned char)(*sct)))
428 sc_token[++idx] = *sct++;
429 sc_token[++idx] = '\0';
430 return sc_tokid = NUMBER;
431 }
432 else if (isalpha((unsigned char)(sc_token[0]))) {
433 while (isalpha((unsigned char)(*sct)))
434 sc_token[++idx] = *sct++;
435 sc_token[++idx] = '\0';
436 return parse_token(sc_token);
437 }
438 else switch(sc_token[0]) {
439 case ':': return sc_tokid = COLON;
440 case '.': return sc_tokid = DOT;
441 case '+': return sc_tokid = PLUS;
442 case '-': return sc_tokid = MINUS;
443 case '/': return sc_tokid = SLASH;
444 default:
445 /*OK, we did not make it ... */
446 sct--;
447 return sc_tokid = EOF;
448 }
449 } /* while (1) */
450 } /* token */
453 /*
454 * expect2() gets a token and complains if it's not the token we want
455 */
456 static char *
457 expect2(int desired, char *complain_fmt, ...)
458 {
459 va_list ap;
460 va_start( ap, complain_fmt );
461 if (token() != desired) {
462 panic(ve( complain_fmt, ap ));
463 }
464 va_end( ap );
465 return TIME_OK;
467 } /* expect2 */
470 /*
471 * plus_minus() is used to parse a single NUMBER TIME-UNIT pair
472 * for the OFFSET-SPEC.
473 * It also applies those m-guessing heuristics.
474 */
475 static char *
476 plus_minus(struct rrd_time_value *ptv, int doop)
477 {
478 static int op = PLUS;
479 static int prev_multiplier = -1;
480 int delta;
482 if( doop >= 0 )
483 {
484 op = doop;
485 try(expect2(NUMBER,"There should be number after '%c'", op == PLUS ? '+' : '-'));
486 prev_multiplier = -1; /* reset months-minutes guessing mechanics */
487 }
488 /* if doop is < 0 then we repeat the previous op
489 * with the prefetched number */
491 delta = atoi(sc_token);
493 if( token() == MONTHS_MINUTES )
494 {
495 /* hard job to guess what does that -5m means: -5mon or -5min? */
496 switch(prev_multiplier)
497 {
498 case DAYS:
499 case WEEKS:
500 case MONTHS:
501 case YEARS:
502 sc_tokid = MONTHS;
503 break;
505 case SECONDS:
506 case MINUTES:
507 case HOURS:
508 sc_tokid = MINUTES;
509 break;
511 default:
512 if( delta < 6 ) /* it may be some other value but in the context
513 * of RRD who needs less than 6 min deltas? */
514 sc_tokid = MONTHS;
515 else
516 sc_tokid = MINUTES;
517 }
518 }
519 prev_multiplier = sc_tokid;
520 switch (sc_tokid) {
521 case YEARS:
522 ptv->tm.tm_year += (op == PLUS) ? delta : -delta;
523 return TIME_OK;
524 case MONTHS:
525 ptv->tm.tm_mon += (op == PLUS) ? delta : -delta;
526 return TIME_OK;
527 case WEEKS:
528 delta *= 7;
529 /* FALLTHRU */
530 case DAYS:
531 ptv->tm.tm_mday += (op == PLUS) ? delta : -delta;
532 return TIME_OK;
533 case HOURS:
534 ptv->offset += (op == PLUS) ? delta*60*60 : -delta*60*60;
535 return TIME_OK;
536 case MINUTES:
537 ptv->offset += (op == PLUS) ? delta*60 : -delta*60;
538 return TIME_OK;
539 case SECONDS:
540 ptv->offset += (op == PLUS) ? delta : -delta;
541 return TIME_OK;
542 default: /*default unit is seconds */
543 ptv->offset += (op == PLUS) ? delta : -delta;
544 return TIME_OK;
545 }
546 panic(e("well-known time unit expected after %d", delta));
547 /* NORETURN */
548 return TIME_OK; /* to make compiler happy :) */
549 } /* plus_minus */
552 /*
553 * tod() computes the time of day (TIME-OF-DAY-SPEC)
554 */
555 static char *
556 tod(struct rrd_time_value *ptv)
557 {
558 int hour, minute = 0;
559 int tlen;
560 /* save token status in case we must abort */
561 int scc_sv = scc;
562 const char *sct_sv = sct;
563 int sc_tokid_sv = sc_tokid;
565 tlen = strlen(sc_token);
567 /* first pick out the time of day - we assume a HH (COLON|DOT) MM time
568 */
569 if (tlen > 2) {
570 return TIME_OK;
571 }
573 hour = atoi(sc_token);
575 token();
576 if (sc_tokid == SLASH || sc_tokid == DOT) {
577 /* guess we are looking at a date */
578 scc = scc_sv;
579 sct = sct_sv;
580 sc_tokid = sc_tokid_sv;
581 sprintf (sc_token,"%d", hour);
582 return TIME_OK;
583 }
584 if (sc_tokid == COLON ) {
585 try(expect2(NUMBER,
586 "Parsing HH:MM syntax, expecting MM as number, got none"));
587 minute = atoi(sc_token);
588 if (minute > 59) {
589 panic(e("parsing HH:MM syntax, got MM = %d (>59!)", minute ));
590 }
591 token();
592 }
594 /* check if an AM or PM specifier was given
595 */
596 if (sc_tokid == AM || sc_tokid == PM) {
597 if (hour > 12) {
598 panic(e("there cannot be more than 12 AM or PM hours"));
599 }
600 if (sc_tokid == PM) {
601 if (hour != 12) /* 12:xx PM is 12:xx, not 24:xx */
602 hour += 12;
603 } else {
604 if (hour == 12) /* 12:xx AM is 00:xx, not 12:xx */
605 hour = 0;
606 }
607 token();
608 }
609 else if (hour > 23) {
610 /* guess it was not a time then ... */
611 scc = scc_sv;
612 sct = sct_sv;
613 sc_tokid = sc_tokid_sv;
614 sprintf (sc_token,"%d", hour);
615 return TIME_OK;
616 }
617 ptv->tm.tm_hour = hour;
618 ptv->tm.tm_min = minute;
619 ptv->tm.tm_sec = 0;
620 if (ptv->tm.tm_hour == 24) {
621 ptv->tm.tm_hour = 0;
622 ptv->tm.tm_mday++;
623 }
624 return TIME_OK;
625 } /* tod */
628 /*
629 * assign_date() assigns a date, adjusting year as appropriate
630 */
631 static char *
632 assign_date(struct rrd_time_value *ptv, long mday, long mon, long year)
633 {
634 if (year > 138) {
635 if (year > 1970)
636 year -= 1900;
637 else {
638 panic(e("invalid year %d (should be either 00-99 or >1900)",
639 year));
640 }
641 } else if( year >= 0 && year < 38 ) {
642 year += 100; /* Allow year 2000-2037 to be specified as */
643 } /* 00-37 until the problem of 2038 year will */
644 /* arise for unices with 32-bit time_t :) */
645 if (year < 70) {
646 panic(e("won't handle dates before epoch (01/01/1970), sorry"));
647 }
649 ptv->tm.tm_mday = mday;
650 ptv->tm.tm_mon = mon;
651 ptv->tm.tm_year = year;
652 return TIME_OK;
653 } /* assign_date */
656 /*
657 * day() picks apart DAY-SPEC-[12]
658 */
659 static char *
660 day(struct rrd_time_value *ptv)
661 {
662 /* using time_t seems to help portability with 64bit oses */
663 time_t mday=0, wday, mon, year = ptv->tm.tm_year;
664 int tlen;
666 switch (sc_tokid) {
667 case YESTERDAY:
668 ptv->tm.tm_mday--;
669 /* FALLTRHU */
670 case TODAY: /* force ourselves to stay in today - no further processing */
671 token();
672 break;
673 case TOMORROW:
674 ptv->tm.tm_mday++;
675 token();
676 break;
678 case JAN: case FEB: case MAR: case APR: case MAY: case JUN:
679 case JUL: case AUG: case SEP: case OCT: case NOV: case DEC:
680 /* do month mday [year]
681 */
682 mon = (sc_tokid-JAN);
683 try(expect2(NUMBER,
684 "the day of the month should follow month name"));
685 mday = atol(sc_token);
686 if (token() == NUMBER) {
687 year = atol(sc_token);
688 token();
689 }
690 else
691 year = ptv->tm.tm_year;
692 try(assign_date(ptv, mday, mon, year));
693 break;
695 case SUN: case MON: case TUE:
696 case WED: case THU: case FRI:
697 case SAT:
698 /* do a particular day of the week
699 */
700 wday = (sc_tokid-SUN);
701 ptv->tm.tm_mday += (wday - ptv->tm.tm_wday);
702 token();
703 break;
704 /*
705 mday = ptv->tm.tm_mday;
706 mday += (wday - ptv->tm.tm_wday);
707 ptv->tm.tm_wday = wday;
709 try(assign_date(ptv, mday, ptv->tm.tm_mon, ptv->tm.tm_year));
710 break;
711 */
713 case NUMBER:
714 /* get numeric <sec since 1970>, MM/DD/[YY]YY, or DD.MM.[YY]YY
715 */
716 tlen = strlen(sc_token);
717 mon = atol(sc_token);
718 if (mon > 10*365*24*60*60) {
719 ptv->tm=*localtime(&mon);
720 token();
721 break;
722 }
724 if (mon > 19700101 && mon < 24000101){ /*works between 1900 and 2400 */
725 char cmon[3],cmday[3],cyear[5];
726 strncpy(cyear,sc_token,4);cyear[4]='\0';
727 year = atol(cyear);
728 strncpy(cmon,&(sc_token[4]),2);cmon[2]='\0';
729 mon = atol(cmon);
730 strncpy(cmday,&(sc_token[6]),2);cmday[2]='\0';
731 mday = atol(cmday);
732 token();
733 } else {
734 token();
736 if (mon <= 31 && (sc_tokid == SLASH || sc_tokid == DOT)) {
737 int sep;
738 sep = sc_tokid;
739 try(expect2(NUMBER,"there should be %s number after '%c'",
740 sep == DOT ? "month" : "day", sep == DOT ? '.' : '/'));
741 mday = atol(sc_token);
742 if (token() == sep) {
743 try(expect2(NUMBER,"there should be year number after '%c'",
744 sep == DOT ? '.' : '/'));
745 year = atol(sc_token);
746 token();
747 }
749 /* flip months and days for European timing
750 */
751 if (sep == DOT) {
752 long x = mday;
753 mday = mon;
754 mon = x;
755 }
756 }
757 }
759 mon--;
760 if(mon < 0 || mon > 11 ) {
761 panic(e("did you really mean month %d?", mon+1));
762 }
763 if(mday < 1 || mday > 31) {
764 panic(e("I'm afraid that %d is not a valid day of the month",
765 mday));
766 }
767 try(assign_date(ptv, mday, mon, year));
768 break;
769 } /* case */
770 return TIME_OK;
771 } /* month */
774 /* Global functions */
777 /*
778 * parsetime() is the external interface that takes tspec, parses
779 * it and puts the result in the rrd_time_value structure *ptv.
780 * It can return either absolute times (these are ensured to be
781 * correct) or relative time references that are expected to be
782 * added to some absolute time value and then normalized by
783 * mktime() The return value is either TIME_OK (aka NULL) or
784 * the pointer to the error message in the case of problems
785 */
786 char *
787 parsetime(const char *tspec, struct rrd_time_value *ptv)
788 {
789 time_t now = time(NULL);
790 int hr = 0;
791 /* this MUST be initialized to zero for midnight/noon/teatime */
793 Specials = VariousWords; /* initialize special words context */
795 try(init_scanner( 1, &tspec ));
797 /* establish the default time reference */
798 ptv->type = ABSOLUTE_TIME;
799 ptv->offset = 0;
800 ptv->tm = *localtime(&now);
801 ptv->tm.tm_isdst = -1; /* mk time can figure this out for us ... */
803 token();
804 switch (sc_tokid) {
805 case PLUS:
806 case MINUS:
807 break; /* jump to OFFSET-SPEC part */
809 case START:
810 ptv->type = RELATIVE_TO_START_TIME;
811 goto KeepItRelative;
812 case END:
813 ptv->type = RELATIVE_TO_END_TIME;
814 KeepItRelative:
815 ptv->tm.tm_sec = 0;
816 ptv->tm.tm_min = 0;
817 ptv->tm.tm_hour = 0;
818 ptv->tm.tm_mday = 0;
819 ptv->tm.tm_mon = 0;
820 ptv->tm.tm_year = 0;
821 /* FALLTHRU */
822 case NOW:
823 {
824 int time_reference = sc_tokid;
825 token();
826 if( sc_tokid == PLUS || sc_tokid == MINUS )
827 break;
828 if( time_reference != NOW ) {
829 panic(e("'start' or 'end' MUST be followed by +|- offset"));
830 }
831 else
832 if( sc_tokid != EOF ) {
833 panic(e("if 'now' is followed by a token it must be +|- offset"));
834 }
835 };
836 break;
838 /* Only absolute time specifications below */
839 case NUMBER:
840 try(tod(ptv))
841 try(day(ptv))
842 break;
843 /* fix month parsing */
844 case JAN: case FEB: case MAR: case APR: case MAY: case JUN:
845 case JUL: case AUG: case SEP: case OCT: case NOV: case DEC:
846 try(day(ptv));
847 if (sc_tokid != NUMBER) break;
848 try(tod(ptv))
849 break;
851 /* evil coding for TEATIME|NOON|MIDNIGHT - we've initialized
852 * hr to zero up above, then fall into this case in such a
853 * way so we add +12 +4 hours to it for teatime, +12 hours
854 * to it for noon, and nothing at all for midnight, then
855 * set our rettime to that hour before leaping into the
856 * month scanner
857 */
858 case TEATIME:
859 hr += 4;
860 /* FALLTHRU */
861 case NOON:
862 hr += 12;
863 /* FALLTHRU */
864 case MIDNIGHT:
865 /* if (ptv->tm.tm_hour >= hr) {
866 ptv->tm.tm_mday++;
867 ptv->tm.tm_wday++;
868 } */ /* shifting does not makes sense here ... noon is noon */
869 ptv->tm.tm_hour = hr;
870 ptv->tm.tm_min = 0;
871 ptv->tm.tm_sec = 0;
872 token();
873 try(day(ptv));
874 break;
875 default:
876 panic(e("unparsable time: %s%s",sc_token,sct));
877 break;
878 } /* ugly case statement */
880 /*
881 * the OFFSET-SPEC part
882 *
883 * (NOTE, the sc_tokid was prefetched for us by the previous code)
884 */
885 if( sc_tokid == PLUS || sc_tokid == MINUS ) {
886 Specials = TimeMultipliers; /* switch special words context */
887 while( sc_tokid == PLUS || sc_tokid == MINUS ||
888 sc_tokid == NUMBER ) {
889 if( sc_tokid == NUMBER ) {
890 try(plus_minus(ptv, PREVIOUS_OP ));
891 } else
892 try(plus_minus(ptv, sc_tokid));
893 token(); /* We will get EOF eventually but that's OK, since
894 token() will return us as many EOFs as needed */
895 }
896 }
898 /* now we should be at EOF */
899 if( sc_tokid != EOF ) {
900 panic(e("unparsable trailing text: '...%s%s'", sc_token, sct));
901 }
903 ptv->tm.tm_isdst = -1; /* for mktime to guess DST status */
904 if( ptv->type == ABSOLUTE_TIME )
905 if( mktime( &ptv->tm ) == -1 ) { /* normalize & check */
906 /* can happen for "nonexistent" times, e.g. around 3am */
907 /* when winter -> summer time correction eats a hour */
908 panic(e("the specified time is incorrect (out of range?)"));
909 }
910 EnsureMemFree();
911 return TIME_OK;
912 } /* parsetime */
915 int proc_start_end (struct rrd_time_value *start_tv,
916 struct rrd_time_value *end_tv,
917 time_t *start,
918 time_t *end){
919 if (start_tv->type == RELATIVE_TO_END_TIME && /* same as the line above */
920 end_tv->type == RELATIVE_TO_START_TIME) {
921 rrd_set_error("the start and end times cannot be specified "
922 "relative to each other");
923 return -1;
924 }
926 if (start_tv->type == RELATIVE_TO_START_TIME) {
927 rrd_set_error("the start time cannot be specified relative to itself");
928 return -1;
929 }
931 if (end_tv->type == RELATIVE_TO_END_TIME) {
932 rrd_set_error("the end time cannot be specified relative to itself");
933 return -1;
934 }
936 if( start_tv->type == RELATIVE_TO_END_TIME) {
937 struct tm tmtmp;
938 *end = mktime(&(end_tv->tm)) + end_tv->offset;
939 tmtmp = *localtime(end); /* reinit end including offset */
940 tmtmp.tm_mday += start_tv->tm.tm_mday;
941 tmtmp.tm_mon += start_tv->tm.tm_mon;
942 tmtmp.tm_year += start_tv->tm.tm_year;
943 *start = mktime(&tmtmp) + start_tv->offset;
944 } else {
945 *start = mktime(&(start_tv->tm)) + start_tv->offset;
946 }
947 if (end_tv->type == RELATIVE_TO_START_TIME) {
948 struct tm tmtmp;
949 *start = mktime(&(start_tv->tm)) + start_tv->offset;
950 tmtmp = *localtime(start);
951 tmtmp.tm_mday += end_tv->tm.tm_mday;
952 tmtmp.tm_mon += end_tv->tm.tm_mon;
953 tmtmp.tm_year += end_tv->tm.tm_year;
954 *end = mktime(&tmtmp) + end_tv->offset;
955 } else {
956 *end = mktime(&(end_tv->tm)) + end_tv->offset;
957 }
958 return 0;
959 } /* proc_start_end */