2 /* Parse a string into an internal time stamp.
4 Copyright (C) 1999, 2000, 2002, 2003, 2004, 2005, 2006, 2007 Free Software
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>. */
20 /* Originally written by Steven M. Bellovin <smb@research.att.com> while
21 at the University of North Carolina at Chapel Hill. Later tweaked by
22 a couple of people on Usenet. Completely overhauled by Rich $alz
23 <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
25 Modified by Paul Eggert <eggert@twinsun.com> in August 1999 to do
26 the right thing about local DST. Also modified by Paul Eggert
27 <eggert@cs.ucla.edu> in February 2004 to support
28 nanosecond-resolution time stamps, and in October 2004 to support
29 TZ strings in dates. */
31 /* FIXME: Check for arithmetic overflow in all cases, not just
42 /* There's no need to extend the stack, so there's no need to involve
44 #define YYSTACK_USE_ALLOCA 0
46 /* Tell Bison how much stack space is needed. 20 should be plenty for
47 this grammar, which is not right recursive. Beware setting it too
48 high, since that might cause problems on machines whose
49 implementations have lame stack-overflow checking. */
51 #define YYINITDEPTH YYMAXDEPTH
53 /* Since the code of getdate.y is not included in the Emacs executable
54 itself, there is no need to #define static in this file. Even if
55 the code were included in the Emacs executable, it probably
56 wouldn't do any harm to #undef it here; this will only cause
57 problems if we try to write to a static variable, which I don't
58 think this code needs to do. */
73 /* ISDIGIT differs from isdigit, as follows:
74 - Its arg may be any int or unsigned int; it need not be an unsigned char
76 - It's typically faster.
77 POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
78 isdigit unless it's important to use the locale's definition
79 of `digit' even when the host does not conform to POSIX. */
80 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
83 # if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
84 # define __attribute__(x)
88 #ifndef ATTRIBUTE_UNUSED
89 # define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
92 /* Shift A right by B bits portably, by dividing A by 2**B and
93 truncating towards minus infinity. A and B should be free of side
94 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
95 INT_BITS is the number of useful bits in an int. GNU code can
96 assume that INT_BITS is at least 32.
98 ISO C99 says that A >> B is implementation-defined if A < 0. Some
99 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
100 right in the usual way when A < 0, so SHR falls back on division if
101 ordinary A >> B doesn't seem to be the usual signed shift. */
105 : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
107 #define EPOCH_YEAR 1970
108 #define TM_YEAR_BASE 1900
110 #define HOUR(x) ((x) * 60)
112 /* Lots of this code assumes time_t and time_t-like values fit into
113 long int. It also assumes that signed integer overflow silently
114 wraps around, but there's no portable way to check for that at
116 verify (TYPE_IS_INTEGER (time_t));
117 verify (LONG_MIN <= TYPE_MINIMUM (time_t) && TYPE_MAXIMUM (time_t) <= LONG_MAX);
119 /* An integer value, and the number of digits in its textual
128 /* An entry in the lexical lookup table. */
136 /* Meridian: am, pm, or 24-hour style. */
137 enum { MERam, MERpm, MER24 };
139 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
141 /* Relative times. */
144 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
154 #if HAVE_COMPOUND_LITERALS
155 # define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
157 static relative_time const RELATIVE_TIME_0;
160 /* Information passed to and from the parser. */
163 /* The input string remaining to be parsed. */
166 /* N, if this is the Nth Tuesday. */
167 long int day_ordinal;
169 /* Day of week; Sunday is 0. */
172 /* tm_isdst flag for the local zone. */
175 /* Time zone, in minutes east of UTC. */
178 /* Style used for time. */
181 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
187 struct timespec seconds; /* includes nanoseconds */
189 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
192 /* Presence or counts of nonterminals of various flavors parsed so far. */
197 size_t local_zones_seen;
202 /* Table of local time zone abbrevations, terminated by a null entry. */
203 table local_time_zone_table[3];
207 static int yylex (union YYSTYPE *, parser_control *);
208 static int yyerror (parser_control const *, char const *);
209 static long int time_zone_hhmm (textint, long int);
211 /* Extract into *PC any date and time info from a string of digits
212 of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
215 digits_to_date_time (parser_control *pc, textint text_int)
217 if (pc->dates_seen && ! pc->year.digits
218 && ! pc->rels_seen && (pc->times_seen || 2 < text_int.digits))
222 if (4 < text_int.digits)
225 pc->day = text_int.value % 100;
226 pc->month = (text_int.value / 100) % 100;
227 pc->year.value = text_int.value / 10000;
228 pc->year.digits = text_int.digits - 4;
233 if (text_int.digits <= 2)
235 pc->hour = text_int.value;
240 pc->hour = text_int.value / 100;
241 pc->minutes = text_int.value % 100;
243 pc->seconds.tv_sec = 0;
244 pc->seconds.tv_nsec = 0;
245 pc->meridian = MER24;
252 /* We want a reentrant parser, even if the TZ manipulation and the calls to
253 localtime and gmtime are not reentrant. */
255 %parse-param { parser_control *pc }
256 %lex-param { parser_control *pc }
258 /* This grammar has 20 shift/reduce conflicts. */
265 struct timespec timespec;
271 %token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
272 %token <intval> tDAY_UNIT
274 %token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
275 %token <intval> tMONTH tORDINAL tZONE
277 %token <textintval> tSNUMBER tUNUMBER
278 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
280 %type <intval> o_colon_minutes o_merid
281 %type <timespec> seconds signed_seconds unsigned_seconds
283 %type <rel> relunit relunit_snumber
296 pc->timespec_seen = true;
307 { pc->times_seen++; }
309 { pc->local_zones_seen++; }
311 { pc->zones_seen++; }
313 { pc->dates_seen++; }
317 { pc->rels_seen = true; }
327 pc->seconds.tv_sec = 0;
328 pc->seconds.tv_nsec = 0;
331 | tUNUMBER ':' tUNUMBER o_merid
334 pc->minutes = $3.value;
335 pc->seconds.tv_sec = 0;
336 pc->seconds.tv_nsec = 0;
339 | tUNUMBER ':' tUNUMBER tSNUMBER o_colon_minutes
342 pc->minutes = $3.value;
343 pc->seconds.tv_sec = 0;
344 pc->seconds.tv_nsec = 0;
345 pc->meridian = MER24;
347 pc->time_zone = time_zone_hhmm ($4, $5);
349 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
352 pc->minutes = $3.value;
356 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER o_colon_minutes
359 pc->minutes = $3.value;
361 pc->meridian = MER24;
363 pc->time_zone = time_zone_hhmm ($6, $7);
370 pc->local_isdst = $1;
371 pc->dsts_seen += (0 < $1);
376 pc->dsts_seen += (0 < $1) + 1;
382 { pc->time_zone = $1; }
383 | tZONE relunit_snumber
384 { pc->time_zone = $1;
386 pc->rel.seconds += $2.seconds;
387 pc->rel.minutes += $2.minutes;
388 pc->rel.hour += $2.hour;
389 pc->rel.day += $2.day;
390 pc->rel.month += $2.month;
391 pc->rel.year += $2.year;
392 pc->rels_seen = true; }
393 | tZONE tSNUMBER o_colon_minutes
394 { pc->time_zone = $1 + time_zone_hhmm ($2, $3); }
396 { pc->time_zone = $1 + 60; }
398 { pc->time_zone = $1 + 60; }
414 pc->day_ordinal = $1;
419 pc->day_ordinal = $1.value;
425 tUNUMBER '/' tUNUMBER
427 pc->month = $1.value;
430 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
432 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
433 otherwise as MM/DD/YY.
434 The goal in recognizing YYYY/MM/DD is solely to support legacy
435 machine-generated dates like those in an RCS log listing. If
436 you want portability, use the ISO 8601 format. */
440 pc->month = $3.value;
445 pc->month = $1.value;
450 | tUNUMBER tSNUMBER tSNUMBER
452 /* ISO 8601 format. YYYY-MM-DD. */
454 pc->month = -$2.value;
457 | tUNUMBER tMONTH tSNUMBER
459 /* e.g. 17-JUN-1992. */
462 pc->year.value = -$3.value;
463 pc->year.digits = $3.digits;
465 | tMONTH tSNUMBER tSNUMBER
467 /* e.g. JUN-17-1992. */
470 pc->year.value = -$3.value;
471 pc->year.digits = $3.digits;
478 | tMONTH tUNUMBER ',' tUNUMBER
489 | tUNUMBER tMONTH tUNUMBER
501 pc->rel.seconds -= $1.seconds;
502 pc->rel.minutes -= $1.minutes;
503 pc->rel.hour -= $1.hour;
504 pc->rel.day -= $1.day;
505 pc->rel.month -= $1.month;
506 pc->rel.year -= $1.year;
511 pc->rel.seconds += $1.seconds;
512 pc->rel.minutes += $1.minutes;
513 pc->rel.hour += $1.hour;
514 pc->rel.day += $1.day;
515 pc->rel.month += $1.month;
516 pc->rel.year += $1.year;
522 { $$ = RELATIVE_TIME_0; $$.year = $1; }
523 | tUNUMBER tYEAR_UNIT
524 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
526 { $$ = RELATIVE_TIME_0; $$.year = 1; }
527 | tORDINAL tMONTH_UNIT
528 { $$ = RELATIVE_TIME_0; $$.month = $1; }
529 | tUNUMBER tMONTH_UNIT
530 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
532 { $$ = RELATIVE_TIME_0; $$.month = 1; }
534 { $$ = RELATIVE_TIME_0; $$.day = $1 * $2; }
536 { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
538 { $$ = RELATIVE_TIME_0; $$.day = $1; }
539 | tORDINAL tHOUR_UNIT
540 { $$ = RELATIVE_TIME_0; $$.hour = $1; }
541 | tUNUMBER tHOUR_UNIT
542 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
544 { $$ = RELATIVE_TIME_0; $$.hour = 1; }
545 | tORDINAL tMINUTE_UNIT
546 { $$ = RELATIVE_TIME_0; $$.minutes = $1; }
547 | tUNUMBER tMINUTE_UNIT
548 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
550 { $$ = RELATIVE_TIME_0; $$.minutes = 1; }
552 { $$ = RELATIVE_TIME_0; $$.seconds = $1; }
554 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
555 | tSDECIMAL_NUMBER tSEC_UNIT
556 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
557 | tUDECIMAL_NUMBER tSEC_UNIT
558 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
560 { $$ = RELATIVE_TIME_0; $$.seconds = 1; }
566 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
567 | tSNUMBER tMONTH_UNIT
568 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
570 { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
571 | tSNUMBER tHOUR_UNIT
572 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
573 | tSNUMBER tMINUTE_UNIT
574 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
576 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
579 seconds: signed_seconds | unsigned_seconds;
584 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
590 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
595 { digits_to_date_time (pc, $1); }
599 tUNUMBER relunit_snumber
601 /* Hybrid all-digit and relative offset, so that we accept e.g.,
602 "YYYYMMDD +N days" as well as "YYYYMMDD N days". */
603 digits_to_date_time (pc, $1);
605 pc->rel.seconds += $2.seconds;
606 pc->rel.minutes += $2.minutes;
607 pc->rel.hour += $2.hour;
608 pc->rel.day += $2.day;
609 pc->rel.month += $2.month;
610 pc->rel.year += $2.year;
611 pc->rels_seen = true;
631 static table const meridian_table[] =
633 { "AM", tMERIDIAN, MERam },
634 { "A.M.", tMERIDIAN, MERam },
635 { "PM", tMERIDIAN, MERpm },
636 { "P.M.", tMERIDIAN, MERpm },
640 static table const dst_table[] =
645 static table const month_and_day_table[] =
647 { "JANUARY", tMONTH, 1 },
648 { "FEBRUARY", tMONTH, 2 },
649 { "MARCH", tMONTH, 3 },
650 { "APRIL", tMONTH, 4 },
651 { "MAY", tMONTH, 5 },
652 { "JUNE", tMONTH, 6 },
653 { "JULY", tMONTH, 7 },
654 { "AUGUST", tMONTH, 8 },
655 { "SEPTEMBER",tMONTH, 9 },
656 { "SEPT", tMONTH, 9 },
657 { "OCTOBER", tMONTH, 10 },
658 { "NOVEMBER", tMONTH, 11 },
659 { "DECEMBER", tMONTH, 12 },
660 { "SUNDAY", tDAY, 0 },
661 { "MONDAY", tDAY, 1 },
662 { "TUESDAY", tDAY, 2 },
664 { "WEDNESDAY",tDAY, 3 },
665 { "WEDNES", tDAY, 3 },
666 { "THURSDAY", tDAY, 4 },
668 { "THURS", tDAY, 4 },
669 { "FRIDAY", tDAY, 5 },
670 { "SATURDAY", tDAY, 6 },
674 static table const time_units_table[] =
676 { "YEAR", tYEAR_UNIT, 1 },
677 { "MONTH", tMONTH_UNIT, 1 },
678 { "FORTNIGHT",tDAY_UNIT, 14 },
679 { "WEEK", tDAY_UNIT, 7 },
680 { "DAY", tDAY_UNIT, 1 },
681 { "HOUR", tHOUR_UNIT, 1 },
682 { "MINUTE", tMINUTE_UNIT, 1 },
683 { "MIN", tMINUTE_UNIT, 1 },
684 { "SECOND", tSEC_UNIT, 1 },
685 { "SEC", tSEC_UNIT, 1 },
689 /* Assorted relative-time words. */
690 static table const relative_time_table[] =
692 { "TOMORROW", tDAY_UNIT, 1 },
693 { "YESTERDAY",tDAY_UNIT, -1 },
694 { "TODAY", tDAY_UNIT, 0 },
695 { "NOW", tDAY_UNIT, 0 },
696 { "LAST", tORDINAL, -1 },
697 { "THIS", tORDINAL, 0 },
698 { "NEXT", tORDINAL, 1 },
699 { "FIRST", tORDINAL, 1 },
700 /*{ "SECOND", tORDINAL, 2 }, */
701 { "THIRD", tORDINAL, 3 },
702 { "FOURTH", tORDINAL, 4 },
703 { "FIFTH", tORDINAL, 5 },
704 { "SIXTH", tORDINAL, 6 },
705 { "SEVENTH", tORDINAL, 7 },
706 { "EIGHTH", tORDINAL, 8 },
707 { "NINTH", tORDINAL, 9 },
708 { "TENTH", tORDINAL, 10 },
709 { "ELEVENTH", tORDINAL, 11 },
710 { "TWELFTH", tORDINAL, 12 },
715 /* The universal time zone table. These labels can be used even for
716 time stamps that would not otherwise be valid, e.g., GMT time
717 stamps in London during summer. */
718 static table const universal_time_zone_table[] =
720 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
721 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
722 { "UTC", tZONE, HOUR ( 0) },
726 /* The time zone table. This table is necessarily incomplete, as time
727 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
728 as Eastern time in Australia, not as US Eastern Standard Time.
729 You cannot rely on getdate to handle arbitrary time zone
730 abbreviations; use numeric abbreviations like `-0500' instead. */
731 static table const time_zone_table[] =
733 { "WET", tZONE, HOUR ( 0) }, /* Western European */
734 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
735 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
736 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
737 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
738 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
739 { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
740 { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
741 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
742 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
743 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
744 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
745 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
746 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
747 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
748 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
749 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
750 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
751 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
752 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
753 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
754 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
755 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
756 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
757 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
758 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
759 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
760 { "CET", tZONE, HOUR ( 1) }, /* Central European */
761 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
762 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
763 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
764 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
765 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
766 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
767 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
768 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
769 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
770 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
771 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
772 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
773 { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
774 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
775 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
776 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
777 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
778 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
779 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
783 /* Military time zone table. */
784 static table const military_table[] =
786 { "A", tZONE, -HOUR ( 1) },
787 { "B", tZONE, -HOUR ( 2) },
788 { "C", tZONE, -HOUR ( 3) },
789 { "D", tZONE, -HOUR ( 4) },
790 { "E", tZONE, -HOUR ( 5) },
791 { "F", tZONE, -HOUR ( 6) },
792 { "G", tZONE, -HOUR ( 7) },
793 { "H", tZONE, -HOUR ( 8) },
794 { "I", tZONE, -HOUR ( 9) },
795 { "K", tZONE, -HOUR (10) },
796 { "L", tZONE, -HOUR (11) },
797 { "M", tZONE, -HOUR (12) },
798 { "N", tZONE, HOUR ( 1) },
799 { "O", tZONE, HOUR ( 2) },
800 { "P", tZONE, HOUR ( 3) },
801 { "Q", tZONE, HOUR ( 4) },
802 { "R", tZONE, HOUR ( 5) },
803 { "S", tZONE, HOUR ( 6) },
804 { "T", tZONE, HOUR ( 7) },
805 { "U", tZONE, HOUR ( 8) },
806 { "V", tZONE, HOUR ( 9) },
807 { "W", tZONE, HOUR (10) },
808 { "X", tZONE, HOUR (11) },
809 { "Y", tZONE, HOUR (12) },
810 { "Z", tZONE, HOUR ( 0) },
816 /* Convert a time zone expressed as HH:MM into an integer count of
817 minutes. If MM is negative, then S is of the form HHMM and needs
818 to be picked apart; otherwise, S is of the form HH. */
821 time_zone_hhmm (textint s, long int mm)
824 return (s.value / 100) * 60 + s.value % 100;
826 return s.value * 60 + (s.negative ? -mm : mm);
830 to_hour (long int hours, int meridian)
834 default: /* Pacify GCC. */
836 return 0 <= hours && hours < 24 ? hours : -1;
838 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
840 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
845 to_year (textint textyear)
847 long int year = textyear.value;
852 /* XPG4 suggests that years 00-68 map to 2000-2068, and
853 years 69-99 map to 1969-1999. */
854 else if (textyear.digits == 2)
855 year += year < 69 ? 2000 : 1900;
861 lookup_zone (parser_control const *pc, char const *name)
865 for (tp = universal_time_zone_table; tp->name; tp++)
866 if (strcmp (name, tp->name) == 0)
869 /* Try local zone abbreviations before those in time_zone_table, as
870 the local ones are more likely to be right. */
871 for (tp = pc->local_time_zone_table; tp->name; tp++)
872 if (strcmp (name, tp->name) == 0)
875 for (tp = time_zone_table; tp->name; tp++)
876 if (strcmp (name, tp->name) == 0)
883 /* Yield the difference between *A and *B,
884 measured in seconds, ignoring leap seconds.
885 The body of this function is taken directly from the GNU C Library;
886 see src/strftime.c. */
888 tm_diff (struct tm const *a, struct tm const *b)
890 /* Compute intervening leap days correctly even if year is negative.
891 Take care to avoid int overflow in leap day calculations. */
892 int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
893 int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
894 int a100 = a4 / 25 - (a4 % 25 < 0);
895 int b100 = b4 / 25 - (b4 % 25 < 0);
896 int a400 = SHR (a100, 2);
897 int b400 = SHR (b100, 2);
898 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
899 long int ayear = a->tm_year;
900 long int years = ayear - b->tm_year;
901 long int days = (365 * years + intervening_leap_days
902 + (a->tm_yday - b->tm_yday));
903 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
904 + (a->tm_min - b->tm_min))
905 + (a->tm_sec - b->tm_sec));
907 #endif /* ! HAVE_TM_GMTOFF */
910 lookup_word (parser_control const *pc, char *word)
919 /* Make it uppercase. */
920 for (p = word; *p; p++)
922 unsigned char ch = *p;
926 for (tp = meridian_table; tp->name; tp++)
927 if (strcmp (word, tp->name) == 0)
930 /* See if we have an abbreviation for a month. */
931 wordlen = strlen (word);
932 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
934 for (tp = month_and_day_table; tp->name; tp++)
935 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
938 if ((tp = lookup_zone (pc, word)))
941 if (strcmp (word, dst_table[0].name) == 0)
944 for (tp = time_units_table; tp->name; tp++)
945 if (strcmp (word, tp->name) == 0)
948 /* Strip off any plural and try the units table again. */
949 if (word[wordlen - 1] == 'S')
951 word[wordlen - 1] = '\0';
952 for (tp = time_units_table; tp->name; tp++)
953 if (strcmp (word, tp->name) == 0)
955 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
958 for (tp = relative_time_table; tp->name; tp++)
959 if (strcmp (word, tp->name) == 0)
962 /* Military time zones. */
964 for (tp = military_table; tp->name; tp++)
965 if (word[0] == tp->name[0])
968 /* Drop out any periods and try the time zone table again. */
969 for (period_found = false, p = q = word; (*p = *q); q++)
974 if (period_found && (tp = lookup_zone (pc, word)))
981 yylex (YYSTYPE *lvalp, parser_control *pc)
988 while (c = *pc->input, isspace (c))
991 if (ISDIGIT (c) || c == '-' || c == '+')
995 unsigned long int value;
996 if (c == '-' || c == '+')
998 sign = c == '-' ? -1 : 1;
999 while (c = *++pc->input, isspace (c))
1002 /* skip the '-' sign */
1008 for (value = 0; ; value *= 10)
1010 unsigned long int value1 = value + (c - '0');
1017 if (ULONG_MAX / 10 < value)
1020 if ((c == '.' || c == ',') && ISDIGIT (p[1]))
1025 unsigned long int value1;
1027 /* Check for overflow when converting value to time_t. */
1042 if (value != value1)
1045 /* Accumulate fraction, to ns precision. */
1048 for (digits = 2; digits <= LOG10_BILLION; digits++)
1055 /* Skip excess digits, truncating toward -Infinity. */
1057 for (; ISDIGIT (*p); p++)
1063 while (ISDIGIT (*p))
1066 /* Adjust to the timespec convention, which is that
1067 tv_nsec is always a positive offset even if tv_sec is
1077 lvalp->timespec.tv_sec = s;
1078 lvalp->timespec.tv_nsec = ns;
1080 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
1084 lvalp->textintval.negative = sign < 0;
1087 lvalp->textintval.value = - value;
1088 if (0 < lvalp->textintval.value)
1093 lvalp->textintval.value = value;
1094 if (lvalp->textintval.value < 0)
1097 lvalp->textintval.digits = p - pc->input;
1099 return sign ? tSNUMBER : tUNUMBER;
1111 if (p < buff + sizeof buff - 1)
1115 while (isalpha (c) || c == '.');
1118 tp = lookup_word (pc, buff);
1121 lvalp->intval = tp->value;
1126 return *pc->input++;
1142 /* Do nothing if the parser reports an error. */
1144 yyerror (parser_control const *pc ATTRIBUTE_UNUSED,
1145 char const *s ATTRIBUTE_UNUSED)
1150 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1151 passing it to mktime, return true if it's OK that mktime returned T.
1152 It's not OK if *TM0 has out-of-range members. */
1155 mktime_ok (struct tm const *tm0, struct tm const *tm1, time_t t)
1157 if (t == (time_t) -1)
1159 /* Guard against falsely reporting an error when parsing a time
1160 stamp that happens to equal (time_t) -1, on a host that
1161 supports such a time stamp. */
1162 tm1 = localtime (&t);
1167 return ! ((tm0->tm_sec ^ tm1->tm_sec)
1168 | (tm0->tm_min ^ tm1->tm_min)
1169 | (tm0->tm_hour ^ tm1->tm_hour)
1170 | (tm0->tm_mday ^ tm1->tm_mday)
1171 | (tm0->tm_mon ^ tm1->tm_mon)
1172 | (tm0->tm_year ^ tm1->tm_year));
1175 /* A reasonable upper bound for the size of ordinary TZ strings.
1176 Use heap allocation if TZ's length exceeds this. */
1177 enum { TZBUFSIZE = 100 };
1179 /* Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
1182 get_tz (char tzbuf[TZBUFSIZE])
1184 char *tz = getenv ("TZ");
1187 size_t tzsize = strlen (tz) + 1;
1188 tz = (tzsize <= TZBUFSIZE
1189 ? memcpy (tzbuf, tz, tzsize)
1190 : xmemdup (tz, tzsize));
1195 /* Parse a date/time string, storing the resulting time value into *RESULT.
1196 The string itself is pointed to by P. Return true if successful.
1197 P can be an incomplete or relative time specification; if so, use
1198 *NOW as the basis for the returned time. */
1200 get_date (struct timespec *result, char const *p, struct timespec const *now)
1204 struct tm const *tmp;
1208 struct timespec gettime_buffer;
1210 bool tz_was_altered = false;
1212 char tz0buf[TZBUFSIZE];
1217 gettime (&gettime_buffer);
1218 now = &gettime_buffer;
1221 Start = now->tv_sec;
1222 Start_ns = now->tv_nsec;
1224 tmp = localtime (&now->tv_sec);
1228 while (c = *p, isspace (c))
1231 if (strncmp (p, "TZ=\"", 4) == 0)
1233 char const *tzbase = p + 4;
1237 for (s = tzbase; *s; s++, tzsize++)
1241 if (! (*s == '\\' || *s == '"'))
1248 char tz1buf[TZBUFSIZE];
1249 bool large_tz = TZBUFSIZE < tzsize;
1251 tz0 = get_tz (tz0buf);
1252 z = tz1 = large_tz ? xmalloc (tzsize) : tz1buf;
1253 for (s = tzbase; *s != '"'; s++)
1254 *z++ = *(s += *s == '\\');
1256 setenv_ok = setenv ("TZ", tz1, 1) == 0;
1261 tz_was_altered = true;
1266 /* As documented, be careful to treat the empty string just like
1267 a date string of "0". Without this, an empty string would be
1268 declared invalid when parsed during a DST transition. */
1273 pc.year.value = tmp->tm_year;
1274 pc.year.value += TM_YEAR_BASE;
1276 pc.month = tmp->tm_mon + 1;
1277 pc.day = tmp->tm_mday;
1278 pc.hour = tmp->tm_hour;
1279 pc.minutes = tmp->tm_min;
1280 pc.seconds.tv_sec = tmp->tm_sec;
1281 pc.seconds.tv_nsec = Start_ns;
1282 tm.tm_isdst = tmp->tm_isdst;
1284 pc.meridian = MER24;
1285 pc.rel = RELATIVE_TIME_0;
1286 pc.timespec_seen = false;
1287 pc.rels_seen = false;
1291 pc.local_zones_seen = 0;
1295 #if HAVE_STRUCT_TM_TM_ZONE
1296 pc.local_time_zone_table[0].name = tmp->tm_zone;
1297 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1298 pc.local_time_zone_table[0].value = tmp->tm_isdst;
1299 pc.local_time_zone_table[1].name = NULL;
1301 /* Probe the names used in the next three calendar quarters, looking
1302 for a tm_isdst different from the one we already have. */
1305 for (quarter = 1; quarter <= 3; quarter++)
1307 time_t probe = Start + quarter * (90 * 24 * 60 * 60);
1308 struct tm const *probe_tm = localtime (&probe);
1309 if (probe_tm && probe_tm->tm_zone
1310 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
1313 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
1314 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1315 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
1316 pc.local_time_zone_table[2].name = NULL;
1326 extern char *tzname[];
1329 for (i = 0; i < 2; i++)
1331 pc.local_time_zone_table[i].name = tzname[i];
1332 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1333 pc.local_time_zone_table[i].value = i;
1335 pc.local_time_zone_table[i].name = NULL;
1338 pc.local_time_zone_table[0].name = NULL;
1342 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1343 && ! strcmp (pc.local_time_zone_table[0].name,
1344 pc.local_time_zone_table[1].name))
1346 /* This locale uses the same abbrevation for standard and
1347 daylight times. So if we see that abbreviation, we don't
1348 know whether it's daylight time. */
1349 pc.local_time_zone_table[0].value = -1;
1350 pc.local_time_zone_table[1].name = NULL;
1353 if (yyparse (&pc) != 0)
1356 if (pc.timespec_seen)
1357 *result = pc.seconds;
1360 if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
1361 | (pc.local_zones_seen + pc.zones_seen)))
1364 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
1365 tm.tm_mon = pc.month - 1;
1366 tm.tm_mday = pc.day;
1367 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1369 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1372 tm.tm_min = pc.minutes;
1373 tm.tm_sec = pc.seconds.tv_sec;
1377 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1378 pc.seconds.tv_nsec = 0;
1381 /* Let mktime deduce tm_isdst if we have an absolute time stamp. */
1382 if (pc.dates_seen | pc.days_seen | pc.times_seen)
1385 /* But if the input explicitly specifies local time with or without
1386 DST, give mktime that information. */
1387 if (pc.local_zones_seen)
1388 tm.tm_isdst = pc.local_isdst;
1392 Start = mktime (&tm);
1394 if (! mktime_ok (&tm0, &tm, Start))
1396 if (! pc.zones_seen)
1400 /* Guard against falsely reporting errors near the time_t
1401 boundaries when parsing times in other time zones. For
1402 example, suppose the input string "1969-12-31 23:00:00 -0100",
1403 the current time zone is 8 hours ahead of UTC, and the min
1404 time_t value is 1970-01-01 00:00:00 UTC. Then the min
1405 localtime value is 1970-01-01 08:00:00, and mktime will
1406 therefore fail on 1969-12-31 23:00:00. To work around the
1407 problem, set the time zone to 1 hour behind UTC temporarily
1408 by setting TZ="XXX1:00" and try mktime again. */
1410 long int time_zone = pc.time_zone;
1411 long int abs_time_zone = time_zone < 0 ? - time_zone : time_zone;
1412 long int abs_time_zone_hour = abs_time_zone / 60;
1413 int abs_time_zone_min = abs_time_zone % 60;
1414 char tz1buf[sizeof "XXX+0:00"
1415 + sizeof pc.time_zone * CHAR_BIT / 3];
1416 if (!tz_was_altered)
1417 tz0 = get_tz (tz0buf);
1418 sprintf (tz1buf, "XXX%s%ld:%02d", "-" + (time_zone < 0),
1419 abs_time_zone_hour, abs_time_zone_min);
1420 if (setenv ("TZ", tz1buf, 1) != 0)
1422 tz_was_altered = true;
1424 Start = mktime (&tm);
1425 if (! mktime_ok (&tm0, &tm, Start))
1430 if (pc.days_seen && ! pc.dates_seen)
1432 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1433 + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
1435 Start = mktime (&tm);
1436 if (Start == (time_t) -1)
1442 long int delta = pc.time_zone * 60;
1444 #ifdef HAVE_TM_GMTOFF
1445 delta -= tm.tm_gmtoff;
1448 struct tm const *gmt = gmtime (&t);
1451 delta -= tm_diff (&tm, gmt);
1454 if ((Start < t1) != (delta < 0))
1455 goto fail; /* time_t overflow */
1459 /* Add relative date. */
1460 if (pc.rel.year | pc.rel.month | pc.rel.day)
1462 int year = tm.tm_year + pc.rel.year;
1463 int month = tm.tm_mon + pc.rel.month;
1464 int day = tm.tm_mday + pc.rel.day;
1465 if (((year < tm.tm_year) ^ (pc.rel.year < 0))
1466 | ((month < tm.tm_mon) ^ (pc.rel.month < 0))
1467 | ((day < tm.tm_mday) ^ (pc.rel.day < 0)))
1472 tm.tm_hour = tm0.tm_hour;
1473 tm.tm_min = tm0.tm_min;
1474 tm.tm_sec = tm0.tm_sec;
1475 tm.tm_isdst = tm0.tm_isdst;
1476 Start = mktime (&tm);
1477 if (Start == (time_t) -1)
1481 /* Add relative hours, minutes, and seconds. On hosts that support
1482 leap seconds, ignore the possibility of leap seconds; e.g.,
1483 "+ 10 minutes" adds 600 seconds, even if one of them is a
1484 leap second. Typically this is not what the user wants, but it's
1485 too hard to do it the other way, because the time zone indicator
1486 must be applied before relative times, and if mktime is applied
1487 again the time zone will be lost. */
1489 long int sum_ns = pc.seconds.tv_nsec + pc.rel.ns;
1490 long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
1492 long int d1 = 60 * 60 * pc.rel.hour;
1493 time_t t1 = t0 + d1;
1494 long int d2 = 60 * pc.rel.minutes;
1495 time_t t2 = t1 + d2;
1496 long int d3 = pc.rel.seconds;
1497 time_t t3 = t2 + d3;
1498 long int d4 = (sum_ns - normalized_ns) / BILLION;
1499 time_t t4 = t3 + d4;
1501 if ((d1 / (60 * 60) ^ pc.rel.hour)
1502 | (d2 / 60 ^ pc.rel.minutes)
1503 | ((t1 < t0) ^ (d1 < 0))
1504 | ((t2 < t1) ^ (d2 < 0))
1505 | ((t3 < t2) ^ (d3 < 0))
1506 | ((t4 < t3) ^ (d4 < 0)))
1509 result->tv_sec = t4;
1510 result->tv_nsec = normalized_ns;
1520 ok &= (tz0 ? setenv ("TZ", tz0, 1) : unsetenv ("TZ")) == 0;
1529 main (int ac, char **av)
1533 printf ("Enter date, or blank line to exit.\n\t> ");
1536 buff[BUFSIZ - 1] = '\0';
1537 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1540 struct tm const *tm;
1541 if (! get_date (&d, buff, NULL))
1542 printf ("Bad format - couldn't convert.\n");
1543 else if (! (tm = localtime (&d.tv_sec)))
1545 long int sec = d.tv_sec;
1546 printf ("localtime (%ld) failed\n", sec);
1551 printf ("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1552 tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
1553 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);