2 /* Parse a string into an internal time stamp.
3 Copyright (C) 1999, 2000, 2002, 2003, 2004 Free Software Foundation, Inc.
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2, or (at your option)
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software Foundation,
17 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
19 /* Originally written by Steven M. Bellovin <smb@research.att.com> while
20 at the University of North Carolina at Chapel Hill. Later tweaked by
21 a couple of people on Usenet. Completely overhauled by Rich $alz
22 <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
24 Modified by Paul Eggert <eggert@twinsun.com> in August 1999 to do
25 the right thing about local DST, and in February 2004 to support
26 nanosecond-resolution time stamps. Unlike previous versions, this
27 version is reentrant. */
29 /* FIXME: Check for arithmetic overflow in all cases, not just
32 FIXME: The current code uses 'int' to count seconds; it should use
33 something like 'intmax_t' to support time stamps that don't fit in
44 /* Since the code of getdate.y is not included in the Emacs executable
45 itself, there is no need to #define static in this file. Even if
46 the code were included in the Emacs executable, it probably
47 wouldn't do any harm to #undef it here; this will only cause
48 problems if we try to write to a static variable, which I don't
49 think this code needs to do. */
57 #if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII)
58 # define IN_CTYPE_DOMAIN(c) 1
60 # define IN_CTYPE_DOMAIN(c) isascii (c)
63 #define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c))
64 #define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c))
65 #define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c))
66 #define ISDIGIT_LOCALE(c) (IN_CTYPE_DOMAIN (c) && isdigit (c))
68 /* ISDIGIT differs from ISDIGIT_LOCALE, as follows:
69 - Its arg may be any int or unsigned int; it need not be an unsigned char.
70 - It's guaranteed to evaluate its argument exactly once.
71 - It's typically faster.
72 POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
73 ISDIGIT_LOCALE unless it's important to use the locale's definition
74 of `digit' even when the host does not conform to POSIX. */
75 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
79 #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
80 # define __attribute__(x)
83 #ifndef ATTRIBUTE_UNUSED
84 # define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
87 #define EPOCH_YEAR 1970
88 #define TM_YEAR_BASE 1900
90 #define HOUR(x) ((x) * 60)
92 /* An integer value, and the number of digits in its textual
100 /* An entry in the lexical lookup table. */
108 /* Meridian: am, pm, or 24-hour style. */
109 enum { MERam, MERpm, MER24 };
111 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
113 /* Information passed to and from the parser. */
116 /* The input string remaining to be parsed. */
119 /* N, if this is the Nth Tuesday. */
120 long int day_ordinal;
122 /* Day of week; Sunday is 0. */
125 /* tm_isdst flag for the local zone. */
128 /* Time zone, in minutes east of UTC. */
131 /* Style used for time. */
134 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
140 struct timespec seconds; /* includes nanoseconds */
142 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
147 long int rel_minutes;
148 long int rel_seconds;
151 /* Counts of nonterminals of various flavors parsed so far. */
155 size_t local_zones_seen;
160 /* Table of local time zone abbrevations, terminated by a null entry. */
161 table local_time_zone_table[3];
165 static int yylex (union YYSTYPE *, parser_control *);
166 static int yyerror (parser_control *, char *);
170 /* We want a reentrant parser. */
172 %parse-param { parser_control *pc }
173 %lex-param { parser_control *pc }
175 /* This grammar has 13 shift/reduce conflicts. */
182 struct timespec timespec;
187 %token <intval> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN
188 %token <intval> tMINUTE_UNIT tMONTH tMONTH_UNIT tSEC_UNIT tYEAR_UNIT tZONE
190 %token <textintval> tSNUMBER tUNUMBER
191 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
193 %type <intval> o_merid
194 %type <timespec> seconds signed_seconds unsigned_seconds
207 pc->timespec_seen = true;
218 { pc->times_seen++; }
220 { pc->local_zones_seen++; }
222 { pc->zones_seen++; }
224 { pc->dates_seen++; }
237 pc->seconds.tv_sec = 0;
238 pc->seconds.tv_nsec = 0;
241 | tUNUMBER ':' tUNUMBER o_merid
244 pc->minutes = $3.value;
245 pc->seconds.tv_sec = 0;
246 pc->seconds.tv_nsec = 0;
249 | tUNUMBER ':' tUNUMBER tSNUMBER
252 pc->minutes = $3.value;
253 pc->seconds.tv_sec = 0;
254 pc->seconds.tv_nsec = 0;
255 pc->meridian = MER24;
257 pc->time_zone = $4.value % 100 + ($4.value / 100) * 60;
259 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
262 pc->minutes = $3.value;
266 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER
269 pc->minutes = $3.value;
271 pc->meridian = MER24;
273 pc->time_zone = $6.value % 100 + ($6.value / 100) * 60;
279 { pc->local_isdst = $1; }
281 { pc->local_isdst = $1 < 0 ? 1 : $1 + 1; }
286 { pc->time_zone = $1; }
288 { pc->time_zone = $1 + 60; }
290 { pc->time_zone = $1 + 60; }
306 pc->day_ordinal = $1.value;
312 tUNUMBER '/' tUNUMBER
314 pc->month = $1.value;
317 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
319 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
320 otherwise as MM/DD/YY.
321 The goal in recognizing YYYY/MM/DD is solely to support legacy
322 machine-generated dates like those in an RCS log listing. If
323 you want portability, use the ISO 8601 format. */
327 pc->month = $3.value;
332 pc->month = $1.value;
337 | tUNUMBER tSNUMBER tSNUMBER
339 /* ISO 8601 format. YYYY-MM-DD. */
341 pc->month = -$2.value;
344 | tUNUMBER tMONTH tSNUMBER
346 /* e.g. 17-JUN-1992. */
349 pc->year.value = -$3.value;
350 pc->year.digits = $3.digits;
352 | tMONTH tSNUMBER tSNUMBER
354 /* e.g. JUN-17-1992. */
357 pc->year.value = -$3.value;
358 pc->year.digits = $3.digits;
365 | tMONTH tUNUMBER ',' tUNUMBER
376 | tUNUMBER tMONTH tUNUMBER
387 pc->rel_ns = -pc->rel_ns;
388 pc->rel_seconds = -pc->rel_seconds;
389 pc->rel_minutes = -pc->rel_minutes;
390 pc->rel_hour = -pc->rel_hour;
391 pc->rel_day = -pc->rel_day;
392 pc->rel_month = -pc->rel_month;
393 pc->rel_year = -pc->rel_year;
400 { pc->rel_year += $1.value * $2; }
401 | tSNUMBER tYEAR_UNIT
402 { pc->rel_year += $1.value * $2; }
404 { pc->rel_year += $1; }
405 | tUNUMBER tMONTH_UNIT
406 { pc->rel_month += $1.value * $2; }
407 | tSNUMBER tMONTH_UNIT
408 { pc->rel_month += $1.value * $2; }
410 { pc->rel_month += $1; }
412 { pc->rel_day += $1.value * $2; }
414 { pc->rel_day += $1.value * $2; }
416 { pc->rel_day += $1; }
417 | tUNUMBER tHOUR_UNIT
418 { pc->rel_hour += $1.value * $2; }
419 | tSNUMBER tHOUR_UNIT
420 { pc->rel_hour += $1.value * $2; }
422 { pc->rel_hour += $1; }
423 | tUNUMBER tMINUTE_UNIT
424 { pc->rel_minutes += $1.value * $2; }
425 | tSNUMBER tMINUTE_UNIT
426 { pc->rel_minutes += $1.value * $2; }
428 { pc->rel_minutes += $1; }
430 { pc->rel_seconds += $1.value * $2; }
432 { pc->rel_seconds += $1.value * $2; }
433 | tSDECIMAL_NUMBER tSEC_UNIT
434 { pc->rel_seconds += $1.tv_sec * $2; pc->rel_ns += $1.tv_nsec * $2; }
435 | tUDECIMAL_NUMBER tSEC_UNIT
436 { pc->rel_seconds += $1.tv_sec * $2; pc->rel_ns += $1.tv_nsec * $2; }
438 { pc->rel_seconds += $1; }
441 seconds: signed_seconds | unsigned_seconds;
446 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
452 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
459 && ! pc->rels_seen && (pc->times_seen || 2 < $1.digits))
466 pc->day = $1.value % 100;
467 pc->month = ($1.value / 100) % 100;
468 pc->year.value = $1.value / 10000;
469 pc->year.digits = $1.digits - 4;
481 pc->hour = $1.value / 100;
482 pc->minutes = $1.value % 100;
484 pc->seconds.tv_sec = 0;
485 pc->seconds.tv_nsec = 0;
486 pc->meridian = MER24;
501 static table const meridian_table[] =
503 { "AM", tMERIDIAN, MERam },
504 { "A.M.", tMERIDIAN, MERam },
505 { "PM", tMERIDIAN, MERpm },
506 { "P.M.", tMERIDIAN, MERpm },
510 static table const dst_table[] =
515 static table const month_and_day_table[] =
517 { "JANUARY", tMONTH, 1 },
518 { "FEBRUARY", tMONTH, 2 },
519 { "MARCH", tMONTH, 3 },
520 { "APRIL", tMONTH, 4 },
521 { "MAY", tMONTH, 5 },
522 { "JUNE", tMONTH, 6 },
523 { "JULY", tMONTH, 7 },
524 { "AUGUST", tMONTH, 8 },
525 { "SEPTEMBER",tMONTH, 9 },
526 { "SEPT", tMONTH, 9 },
527 { "OCTOBER", tMONTH, 10 },
528 { "NOVEMBER", tMONTH, 11 },
529 { "DECEMBER", tMONTH, 12 },
530 { "SUNDAY", tDAY, 0 },
531 { "MONDAY", tDAY, 1 },
532 { "TUESDAY", tDAY, 2 },
534 { "WEDNESDAY",tDAY, 3 },
535 { "WEDNES", tDAY, 3 },
536 { "THURSDAY", tDAY, 4 },
538 { "THURS", tDAY, 4 },
539 { "FRIDAY", tDAY, 5 },
540 { "SATURDAY", tDAY, 6 },
544 static table const time_units_table[] =
546 { "YEAR", tYEAR_UNIT, 1 },
547 { "MONTH", tMONTH_UNIT, 1 },
548 { "FORTNIGHT",tDAY_UNIT, 14 },
549 { "WEEK", tDAY_UNIT, 7 },
550 { "DAY", tDAY_UNIT, 1 },
551 { "HOUR", tHOUR_UNIT, 1 },
552 { "MINUTE", tMINUTE_UNIT, 1 },
553 { "MIN", tMINUTE_UNIT, 1 },
554 { "SECOND", tSEC_UNIT, 1 },
555 { "SEC", tSEC_UNIT, 1 },
559 /* Assorted relative-time words. */
560 static table const relative_time_table[] =
562 { "TOMORROW", tDAY_UNIT, 1 },
563 { "YESTERDAY",tDAY_UNIT, -1 },
564 { "TODAY", tDAY_UNIT, 0 },
565 { "NOW", tDAY_UNIT, 0 },
566 { "LAST", tUNUMBER, -1 },
567 { "THIS", tUNUMBER, 0 },
568 { "NEXT", tUNUMBER, 1 },
569 { "FIRST", tUNUMBER, 1 },
570 /*{ "SECOND", tUNUMBER, 2 }, */
571 { "THIRD", tUNUMBER, 3 },
572 { "FOURTH", tUNUMBER, 4 },
573 { "FIFTH", tUNUMBER, 5 },
574 { "SIXTH", tUNUMBER, 6 },
575 { "SEVENTH", tUNUMBER, 7 },
576 { "EIGHTH", tUNUMBER, 8 },
577 { "NINTH", tUNUMBER, 9 },
578 { "TENTH", tUNUMBER, 10 },
579 { "ELEVENTH", tUNUMBER, 11 },
580 { "TWELFTH", tUNUMBER, 12 },
585 /* The time zone table. This table is necessarily incomplete, as time
586 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
587 as Eastern time in Australia, not as US Eastern Standard Time.
588 You cannot rely on getdate to handle arbitrary time zone
589 abbreviations; use numeric abbreviations like `-0500' instead. */
590 static table const time_zone_table[] =
592 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
593 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
594 { "UTC", tZONE, HOUR ( 0) },
595 { "WET", tZONE, HOUR ( 0) }, /* Western European */
596 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
597 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
598 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
599 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
600 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
601 { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
602 { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
603 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
604 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
605 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
606 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
607 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
608 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
609 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
610 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
611 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
612 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
613 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
614 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
615 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
616 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
617 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
618 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
619 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
620 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
621 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
622 { "CET", tZONE, HOUR ( 1) }, /* Central European */
623 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
624 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
625 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
626 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
627 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
628 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
629 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
630 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
631 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
632 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
633 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
634 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
635 { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
636 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
637 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
638 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
639 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
640 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
641 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
645 /* Military time zone table. */
646 static table const military_table[] =
648 { "A", tZONE, -HOUR ( 1) },
649 { "B", tZONE, -HOUR ( 2) },
650 { "C", tZONE, -HOUR ( 3) },
651 { "D", tZONE, -HOUR ( 4) },
652 { "E", tZONE, -HOUR ( 5) },
653 { "F", tZONE, -HOUR ( 6) },
654 { "G", tZONE, -HOUR ( 7) },
655 { "H", tZONE, -HOUR ( 8) },
656 { "I", tZONE, -HOUR ( 9) },
657 { "K", tZONE, -HOUR (10) },
658 { "L", tZONE, -HOUR (11) },
659 { "M", tZONE, -HOUR (12) },
660 { "N", tZONE, HOUR ( 1) },
661 { "O", tZONE, HOUR ( 2) },
662 { "P", tZONE, HOUR ( 3) },
663 { "Q", tZONE, HOUR ( 4) },
664 { "R", tZONE, HOUR ( 5) },
665 { "S", tZONE, HOUR ( 6) },
666 { "T", tZONE, HOUR ( 7) },
667 { "U", tZONE, HOUR ( 8) },
668 { "V", tZONE, HOUR ( 9) },
669 { "W", tZONE, HOUR (10) },
670 { "X", tZONE, HOUR (11) },
671 { "Y", tZONE, HOUR (12) },
672 { "Z", tZONE, HOUR ( 0) },
679 to_hour (long int hours, int meridian)
683 default: /* Pacify GCC. */
685 return 0 <= hours && hours < 24 ? hours : -1;
687 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
689 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
694 to_year (textint textyear)
696 long int year = textyear.value;
701 /* XPG4 suggests that years 00-68 map to 2000-2068, and
702 years 69-99 map to 1969-1999. */
703 else if (textyear.digits == 2)
704 year += year < 69 ? 2000 : 1900;
710 lookup_zone (parser_control const *pc, char const *name)
714 /* Try local zone abbreviations first; they're more likely to be right. */
715 for (tp = pc->local_time_zone_table; tp->name; tp++)
716 if (strcmp (name, tp->name) == 0)
719 for (tp = time_zone_table; tp->name; tp++)
720 if (strcmp (name, tp->name) == 0)
727 /* Yield the difference between *A and *B,
728 measured in seconds, ignoring leap seconds.
729 The body of this function is taken directly from the GNU C Library;
730 see src/strftime.c. */
732 tm_diff (struct tm const *a, struct tm const *b)
734 /* Compute intervening leap days correctly even if year is negative.
735 Take care to avoid int overflow in leap day calculations. */
736 int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3);
737 int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3);
738 int a100 = a4 / 25 - (a4 % 25 < 0);
739 int b100 = b4 / 25 - (b4 % 25 < 0);
740 int a400 = a100 >> 2;
741 int b400 = b100 >> 2;
742 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
743 long int ayear = a->tm_year;
744 long int years = ayear - b->tm_year;
745 long int days = (365 * years + intervening_leap_days
746 + (a->tm_yday - b->tm_yday));
747 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
748 + (a->tm_min - b->tm_min))
749 + (a->tm_sec - b->tm_sec));
751 #endif /* ! HAVE_TM_GMTOFF */
754 lookup_word (parser_control const *pc, char *word)
763 /* Make it uppercase. */
764 for (p = word; *p; p++)
766 unsigned char ch = *p;
771 for (tp = meridian_table; tp->name; tp++)
772 if (strcmp (word, tp->name) == 0)
775 /* See if we have an abbreviation for a month. */
776 wordlen = strlen (word);
777 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
779 for (tp = month_and_day_table; tp->name; tp++)
780 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
783 if ((tp = lookup_zone (pc, word)))
786 if (strcmp (word, dst_table[0].name) == 0)
789 for (tp = time_units_table; tp->name; tp++)
790 if (strcmp (word, tp->name) == 0)
793 /* Strip off any plural and try the units table again. */
794 if (word[wordlen - 1] == 'S')
796 word[wordlen - 1] = '\0';
797 for (tp = time_units_table; tp->name; tp++)
798 if (strcmp (word, tp->name) == 0)
800 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
803 for (tp = relative_time_table; tp->name; tp++)
804 if (strcmp (word, tp->name) == 0)
807 /* Military time zones. */
809 for (tp = military_table; tp->name; tp++)
810 if (word[0] == tp->name[0])
813 /* Drop out any periods and try the time zone table again. */
814 for (period_found = false, p = q = word; (*p = *q); q++)
819 if (period_found && (tp = lookup_zone (pc, word)))
826 yylex (YYSTYPE *lvalp, parser_control *pc)
833 while (c = *pc->input, ISSPACE (c))
836 if (ISDIGIT (c) || c == '-' || c == '+')
840 unsigned long int value;
841 if (c == '-' || c == '+')
843 sign = c == '-' ? -1 : 1;
844 while (c = *++pc->input, ISSPACE (c))
847 /* skip the '-' sign */
853 for (value = 0; ; value *= 10)
855 unsigned long int value1 = value + (c - '0');
862 if (ULONG_MAX / 10 < value)
865 if ((c == '.' || c == ',') && ISDIGIT (p[1]))
870 unsigned long int value1;
872 /* Check for overflow when converting value to time_t. */
890 /* Accumulate fraction, to ns precision. */
893 for (digits = 2; digits <= LOG10_BILLION; digits++)
900 /* Skip excess digits, truncating toward -Infinity. */
902 for (; ISDIGIT (*p); p++)
911 /* Adjust to the timespec convention, which is that
912 tv_nsec is always a positive offset even if tv_sec is
922 lvalp->timespec.tv_sec = s;
923 lvalp->timespec.tv_nsec = ns;
925 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
931 lvalp->textintval.value = - value;
932 if (0 < lvalp->textintval.value)
937 lvalp->textintval.value = value;
938 if (lvalp->textintval.value < 0)
941 lvalp->textintval.digits = p - pc->input;
943 return sign ? tSNUMBER : tUNUMBER;
955 if (p < buff + sizeof buff - 1)
959 while (ISALPHA (c) || c == '.');
962 tp = lookup_word (pc, buff);
965 lvalp->intval = tp->value;
986 /* Do nothing if the parser reports an error. */
988 yyerror (parser_control *pc ATTRIBUTE_UNUSED, char *s ATTRIBUTE_UNUSED)
993 /* Parse a date/time string, storing the resulting time value into *RESULT.
994 The string itself is pointed to by P. Return true if successful.
995 P can be an incomplete or relative time specification; if so, use
996 *NOW as the basis for the returned time. */
998 get_date (struct timespec *result, char const *p, struct timespec const *now)
1002 struct tm const *tmp;
1006 struct timespec gettime_buffer;
1010 if (gettime (&gettime_buffer) != 0)
1012 now = &gettime_buffer;
1015 Start = now->tv_sec;
1016 Start_ns = now->tv_nsec;
1018 tmp = localtime (&now->tv_sec);
1023 pc.year.value = tmp->tm_year;
1024 pc.year.value += TM_YEAR_BASE;
1026 pc.month = tmp->tm_mon + 1;
1027 pc.day = tmp->tm_mday;
1028 pc.hour = tmp->tm_hour;
1029 pc.minutes = tmp->tm_min;
1030 pc.seconds.tv_sec = tmp->tm_sec;
1031 pc.seconds.tv_nsec = Start_ns;
1032 tm.tm_isdst = tmp->tm_isdst;
1034 pc.meridian = MER24;
1042 pc.timespec_seen = false;
1047 pc.local_zones_seen = 0;
1050 #if HAVE_STRUCT_TM_TM_ZONE
1051 pc.local_time_zone_table[0].name = tmp->tm_zone;
1052 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1053 pc.local_time_zone_table[0].value = tmp->tm_isdst;
1054 pc.local_time_zone_table[1].name = NULL;
1056 /* Probe the names used in the next three calendar quarters, looking
1057 for a tm_isdst different from the one we already have. */
1060 for (quarter = 1; quarter <= 3; quarter++)
1062 time_t probe = Start + quarter * (90 * 24 * 60 * 60);
1063 struct tm const *probe_tm = localtime (&probe);
1064 if (probe_tm && probe_tm->tm_zone
1065 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
1068 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
1069 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1070 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
1071 pc.local_time_zone_table[2].name = NULL;
1081 extern char *tzname[];
1084 for (i = 0; i < 2; i++)
1086 pc.local_time_zone_table[i].name = tzname[i];
1087 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1088 pc.local_time_zone_table[i].value = i;
1090 pc.local_time_zone_table[i].name = NULL;
1093 pc.local_time_zone_table[0].name = NULL;
1097 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1098 && ! strcmp (pc.local_time_zone_table[0].name,
1099 pc.local_time_zone_table[1].name))
1101 /* This locale uses the same abbrevation for standard and
1102 daylight times. So if we see that abbreviation, we don't
1103 know whether it's daylight time. */
1104 pc.local_time_zone_table[0].value = -1;
1105 pc.local_time_zone_table[1].name = NULL;
1108 if (yyparse (&pc) != 0)
1111 if (pc.timespec_seen)
1113 *result = pc.seconds;
1117 if (1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen
1118 || 1 < (pc.local_zones_seen + pc.zones_seen)
1119 || (pc.local_zones_seen && 1 < pc.local_isdst))
1122 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE + pc.rel_year;
1123 tm.tm_mon = pc.month - 1 + pc.rel_month;
1124 tm.tm_mday = pc.day + pc.rel_day;
1125 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1127 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1130 tm.tm_min = pc.minutes;
1131 tm.tm_sec = pc.seconds.tv_sec;
1135 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1136 pc.seconds.tv_nsec = 0;
1139 /* Let mktime deduce tm_isdst if we have an absolute time stamp,
1140 or if the relative time stamp mentions days, months, or years. */
1141 if (pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day
1142 | pc.rel_month | pc.rel_year)
1145 /* But if the input explicitly specifies local time with or without
1146 DST, give mktime that information. */
1147 if (pc.local_zones_seen)
1148 tm.tm_isdst = pc.local_isdst;
1152 Start = mktime (&tm);
1154 if (Start == (time_t) -1)
1157 /* Guard against falsely reporting errors near the time_t boundaries
1158 when parsing times in other time zones. For example, if the min
1159 time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
1160 of UTC, then the min localtime value is 1970-01-01 08:00:00; if
1161 we apply mktime to 1970-01-01 00:00:00 we will get an error, so
1162 we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
1163 zone by 24 hours to compensate. This algorithm assumes that
1164 there is no DST transition within a day of the time_t boundaries. */
1168 if (tm.tm_year <= EPOCH_YEAR - TM_YEAR_BASE)
1171 pc.time_zone += 24 * 60;
1176 pc.time_zone -= 24 * 60;
1178 Start = mktime (&tm);
1181 if (Start == (time_t) -1)
1185 if (pc.days_seen && ! pc.dates_seen)
1187 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1188 + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
1190 Start = mktime (&tm);
1191 if (Start == (time_t) -1)
1197 long int delta = pc.time_zone * 60;
1199 #ifdef HAVE_TM_GMTOFF
1200 delta -= tm.tm_gmtoff;
1203 struct tm const *gmt = gmtime (&t);
1206 delta -= tm_diff (&tm, gmt);
1209 if ((Start < t1) != (delta < 0))
1210 return false; /* time_t overflow */
1214 /* Add relative hours, minutes, and seconds. Ignore leap seconds;
1215 i.e. "+ 10 minutes" means 600 seconds, even if one of them is a
1216 leap second. Typically this is not what the user wants, but it's
1217 too hard to do it the other way, because the time zone indicator
1218 must be applied before relative times, and if mktime is applied
1219 again the time zone will be lost. */
1221 long int sum_ns = pc.seconds.tv_nsec + pc.rel_ns;
1222 long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
1224 long int d1 = 60 * 60 * pc.rel_hour;
1225 time_t t1 = t0 + d1;
1226 long int d2 = 60 * pc.rel_minutes;
1227 time_t t2 = t1 + d2;
1228 long int d3 = pc.rel_seconds;
1229 time_t t3 = t2 + d3;
1230 long int d4 = (sum_ns - normalized_ns) / BILLION;
1231 time_t t4 = t3 + d4;
1233 if ((d1 / (60 * 60) ^ pc.rel_hour)
1234 | (d2 / 60 ^ pc.rel_minutes)
1235 | ((t1 < t0) ^ (d1 < 0))
1236 | ((t2 < t1) ^ (d2 < 0))
1237 | ((t3 < t2) ^ (d3 < 0))
1238 | ((t4 < t3) ^ (d4 < 0)))
1241 result->tv_sec = t4;
1242 result->tv_nsec = normalized_ns;
1252 main (int ac, char **av)
1256 printf ("Enter date, or blank line to exit.\n\t> ");
1259 buff[BUFSIZ - 1] = '\0';
1260 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1263 struct tm const *tm;
1264 if (! get_date (&d, buff, NULL))
1265 printf ("Bad format - couldn't convert.\n");
1266 else if (! (tm = localtime (&d.tv_sec)))
1268 long int sec = d.tv_sec;
1269 printf ("localtime (%ld) failed\n", sec);
1274 printf ("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1275 tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
1276 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);