2 /* Parse a string into an internal time stamp.
4 Copyright (C) 1999, 2000, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009
5 Free Software Foundation, Inc.
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. */
72 /* ISDIGIT differs from isdigit, as follows:
73 - Its arg may be any int or unsigned int; it need not be an unsigned char
75 - It's typically faster.
76 POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
77 isdigit unless it's important to use the locale's definition
78 of `digit' even when the host does not conform to POSIX. */
79 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
82 # if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
83 # define __attribute__(x)
87 #ifndef ATTRIBUTE_UNUSED
88 # define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
91 /* Shift A right by B bits portably, by dividing A by 2**B and
92 truncating towards minus infinity. A and B should be free of side
93 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
94 INT_BITS is the number of useful bits in an int. GNU code can
95 assume that INT_BITS is at least 32.
97 ISO C99 says that A >> B is implementation-defined if A < 0. Some
98 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
99 right in the usual way when A < 0, so SHR falls back on division if
100 ordinary A >> B doesn't seem to be the usual signed shift. */
104 : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
106 #define EPOCH_YEAR 1970
107 #define TM_YEAR_BASE 1900
109 #define HOUR(x) ((x) * 60)
111 /* long_time_t is a signed integer type that contains all time_t values. */
112 verify (TYPE_IS_INTEGER (time_t));
113 #if TIME_T_FITS_IN_LONG_INT
114 typedef long int long_time_t;
116 typedef time_t long_time_t;
119 /* Lots of this code assumes time_t and time_t-like values fit into
121 verify (TYPE_MINIMUM (long_time_t) <= TYPE_MINIMUM (time_t)
122 && TYPE_MAXIMUM (time_t) <= TYPE_MAXIMUM (long_time_t));
124 /* FIXME: It also assumes that signed integer overflow silently wraps around,
125 but this is not true any more with recent versions of GCC 4. */
127 /* An integer value, and the number of digits in its textual
136 /* An entry in the lexical lookup table. */
144 /* Meridian: am, pm, or 24-hour style. */
145 enum { MERam, MERpm, MER24 };
147 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
149 /* Relative times. */
152 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
162 #if HAVE_COMPOUND_LITERALS
163 # define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
165 static relative_time const RELATIVE_TIME_0;
168 /* Information passed to and from the parser. */
171 /* The input string remaining to be parsed. */
174 /* N, if this is the Nth Tuesday. */
175 long int day_ordinal;
177 /* Day of week; Sunday is 0. */
180 /* tm_isdst flag for the local zone. */
183 /* Time zone, in minutes east of UTC. */
186 /* Style used for time. */
189 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
195 struct timespec seconds; /* includes nanoseconds */
197 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
200 /* Presence or counts of nonterminals of various flavors parsed so far. */
205 size_t local_zones_seen;
210 /* Table of local time zone abbrevations, terminated by a null entry. */
211 table local_time_zone_table[3];
215 static int yylex (union YYSTYPE *, parser_control *);
216 static int yyerror (parser_control const *, char const *);
217 static long int time_zone_hhmm (parser_control *, textint, long int);
219 /* Extract into *PC any date and time info from a string of digits
220 of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
223 digits_to_date_time (parser_control *pc, textint text_int)
225 if (pc->dates_seen && ! pc->year.digits
226 && ! pc->rels_seen && (pc->times_seen || 2 < text_int.digits))
230 if (4 < text_int.digits)
233 pc->day = text_int.value % 100;
234 pc->month = (text_int.value / 100) % 100;
235 pc->year.value = text_int.value / 10000;
236 pc->year.digits = text_int.digits - 4;
241 if (text_int.digits <= 2)
243 pc->hour = text_int.value;
248 pc->hour = text_int.value / 100;
249 pc->minutes = text_int.value % 100;
251 pc->seconds.tv_sec = 0;
252 pc->seconds.tv_nsec = 0;
253 pc->meridian = MER24;
258 /* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1). */
260 apply_relative_time (parser_control *pc, relative_time rel, int factor)
262 pc->rel.ns += factor * rel.ns;
263 pc->rel.seconds += factor * rel.seconds;
264 pc->rel.minutes += factor * rel.minutes;
265 pc->rel.hour += factor * rel.hour;
266 pc->rel.day += factor * rel.day;
267 pc->rel.month += factor * rel.month;
268 pc->rel.year += factor * rel.year;
269 pc->rels_seen = true;
272 /* Set PC-> hour, minutes, seconds and nanoseconds members from arguments. */
274 set_hhmmss (parser_control *pc, long int hour, long int minutes,
275 time_t sec, long int nsec)
278 pc->minutes = minutes;
279 pc->seconds.tv_sec = sec;
280 pc->seconds.tv_nsec = nsec;
285 /* We want a reentrant parser, even if the TZ manipulation and the calls to
286 localtime and gmtime are not reentrant. */
288 %parse-param { parser_control *pc }
289 %lex-param { parser_control *pc }
291 /* This grammar has 20 shift/reduce conflicts. */
298 struct timespec timespec;
304 %token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
305 %token <intval> tDAY_UNIT tDAY_SHIFT
307 %token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
308 %token <intval> tMONTH tORDINAL tZONE
310 %token <textintval> tSNUMBER tUNUMBER
311 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
313 %type <intval> o_colon_minutes o_merid
314 %type <timespec> seconds signed_seconds unsigned_seconds
316 %type <rel> relunit relunit_snumber dayshift
329 pc->timespec_seen = true;
340 { pc->times_seen++; }
342 { pc->local_zones_seen++; }
344 { pc->zones_seen++; }
346 { pc->dates_seen++; }
357 set_hhmmss (pc, $1.value, 0, 0, 0);
360 | tUNUMBER ':' tUNUMBER o_merid
362 set_hhmmss (pc, $1.value, $3.value, 0, 0);
365 | tUNUMBER ':' tUNUMBER tSNUMBER o_colon_minutes
367 set_hhmmss (pc, $1.value, $3.value, 0, 0);
368 pc->meridian = MER24;
370 pc->time_zone = time_zone_hhmm (pc, $4, $5);
372 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
374 set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
377 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER o_colon_minutes
379 set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
380 pc->meridian = MER24;
382 pc->time_zone = time_zone_hhmm (pc, $6, $7);
389 pc->local_isdst = $1;
390 pc->dsts_seen += (0 < $1);
395 pc->dsts_seen += (0 < $1) + 1;
401 { pc->time_zone = $1; }
402 | tZONE relunit_snumber
403 { pc->time_zone = $1;
404 apply_relative_time (pc, $2, 1); }
405 | tZONE tSNUMBER o_colon_minutes
406 { pc->time_zone = $1 + time_zone_hhmm (pc, $2, $3); }
408 { pc->time_zone = $1 + 60; }
410 { pc->time_zone = $1 + 60; }
426 pc->day_ordinal = $1;
431 pc->day_ordinal = $1.value;
437 tUNUMBER '/' tUNUMBER
439 pc->month = $1.value;
442 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
444 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
445 otherwise as MM/DD/YY.
446 The goal in recognizing YYYY/MM/DD is solely to support legacy
447 machine-generated dates like those in an RCS log listing. If
448 you want portability, use the ISO 8601 format. */
452 pc->month = $3.value;
457 pc->month = $1.value;
462 | tUNUMBER tSNUMBER tSNUMBER
464 /* ISO 8601 format. YYYY-MM-DD. */
466 pc->month = -$2.value;
469 | tUNUMBER tMONTH tSNUMBER
471 /* e.g. 17-JUN-1992. */
474 pc->year.value = -$3.value;
475 pc->year.digits = $3.digits;
477 | tMONTH tSNUMBER tSNUMBER
479 /* e.g. JUN-17-1992. */
482 pc->year.value = -$3.value;
483 pc->year.digits = $3.digits;
490 | tMONTH tUNUMBER ',' tUNUMBER
501 | tUNUMBER tMONTH tUNUMBER
511 { apply_relative_time (pc, $1, -1); }
513 { apply_relative_time (pc, $1, 1); }
515 { apply_relative_time (pc, $1, 1); }
520 { $$ = RELATIVE_TIME_0; $$.year = $1; }
521 | tUNUMBER tYEAR_UNIT
522 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
524 { $$ = RELATIVE_TIME_0; $$.year = 1; }
525 | tORDINAL tMONTH_UNIT
526 { $$ = RELATIVE_TIME_0; $$.month = $1; }
527 | tUNUMBER tMONTH_UNIT
528 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
530 { $$ = RELATIVE_TIME_0; $$.month = 1; }
532 { $$ = RELATIVE_TIME_0; $$.day = $1 * $2; }
534 { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
536 { $$ = RELATIVE_TIME_0; $$.day = $1; }
537 | tORDINAL tHOUR_UNIT
538 { $$ = RELATIVE_TIME_0; $$.hour = $1; }
539 | tUNUMBER tHOUR_UNIT
540 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
542 { $$ = RELATIVE_TIME_0; $$.hour = 1; }
543 | tORDINAL tMINUTE_UNIT
544 { $$ = RELATIVE_TIME_0; $$.minutes = $1; }
545 | tUNUMBER tMINUTE_UNIT
546 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
548 { $$ = RELATIVE_TIME_0; $$.minutes = 1; }
550 { $$ = RELATIVE_TIME_0; $$.seconds = $1; }
552 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
553 | tSDECIMAL_NUMBER tSEC_UNIT
554 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
555 | tUDECIMAL_NUMBER tSEC_UNIT
556 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
558 { $$ = RELATIVE_TIME_0; $$.seconds = 1; }
564 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
565 | tSNUMBER tMONTH_UNIT
566 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
568 { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
569 | tSNUMBER tHOUR_UNIT
570 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
571 | tSNUMBER tMINUTE_UNIT
572 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
574 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
579 { $$ = RELATIVE_TIME_0; $$.day = $1; }
582 seconds: signed_seconds | unsigned_seconds;
587 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
593 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
598 { digits_to_date_time (pc, $1); }
602 tUNUMBER relunit_snumber
604 /* Hybrid all-digit and relative offset, so that we accept e.g.,
605 "YYYYMMDD +N days" as well as "YYYYMMDD N days". */
606 digits_to_date_time (pc, $1);
607 apply_relative_time (pc, $2, 1);
627 static table const meridian_table[] =
629 { "AM", tMERIDIAN, MERam },
630 { "A.M.", tMERIDIAN, MERam },
631 { "PM", tMERIDIAN, MERpm },
632 { "P.M.", tMERIDIAN, MERpm },
636 static table const dst_table[] =
641 static table const month_and_day_table[] =
643 { "JANUARY", tMONTH, 1 },
644 { "FEBRUARY", tMONTH, 2 },
645 { "MARCH", tMONTH, 3 },
646 { "APRIL", tMONTH, 4 },
647 { "MAY", tMONTH, 5 },
648 { "JUNE", tMONTH, 6 },
649 { "JULY", tMONTH, 7 },
650 { "AUGUST", tMONTH, 8 },
651 { "SEPTEMBER",tMONTH, 9 },
652 { "SEPT", tMONTH, 9 },
653 { "OCTOBER", tMONTH, 10 },
654 { "NOVEMBER", tMONTH, 11 },
655 { "DECEMBER", tMONTH, 12 },
656 { "SUNDAY", tDAY, 0 },
657 { "MONDAY", tDAY, 1 },
658 { "TUESDAY", tDAY, 2 },
660 { "WEDNESDAY",tDAY, 3 },
661 { "WEDNES", tDAY, 3 },
662 { "THURSDAY", tDAY, 4 },
664 { "THURS", tDAY, 4 },
665 { "FRIDAY", tDAY, 5 },
666 { "SATURDAY", tDAY, 6 },
670 static table const time_units_table[] =
672 { "YEAR", tYEAR_UNIT, 1 },
673 { "MONTH", tMONTH_UNIT, 1 },
674 { "FORTNIGHT",tDAY_UNIT, 14 },
675 { "WEEK", tDAY_UNIT, 7 },
676 { "DAY", tDAY_UNIT, 1 },
677 { "HOUR", tHOUR_UNIT, 1 },
678 { "MINUTE", tMINUTE_UNIT, 1 },
679 { "MIN", tMINUTE_UNIT, 1 },
680 { "SECOND", tSEC_UNIT, 1 },
681 { "SEC", tSEC_UNIT, 1 },
685 /* Assorted relative-time words. */
686 static table const relative_time_table[] =
688 { "TOMORROW", tDAY_SHIFT, 1 },
689 { "YESTERDAY",tDAY_SHIFT, -1 },
690 { "TODAY", tDAY_SHIFT, 0 },
691 { "NOW", tDAY_SHIFT, 0 },
692 { "LAST", tORDINAL, -1 },
693 { "THIS", tORDINAL, 0 },
694 { "NEXT", tORDINAL, 1 },
695 { "FIRST", tORDINAL, 1 },
696 /*{ "SECOND", tORDINAL, 2 }, */
697 { "THIRD", tORDINAL, 3 },
698 { "FOURTH", tORDINAL, 4 },
699 { "FIFTH", tORDINAL, 5 },
700 { "SIXTH", tORDINAL, 6 },
701 { "SEVENTH", tORDINAL, 7 },
702 { "EIGHTH", tORDINAL, 8 },
703 { "NINTH", tORDINAL, 9 },
704 { "TENTH", tORDINAL, 10 },
705 { "ELEVENTH", tORDINAL, 11 },
706 { "TWELFTH", tORDINAL, 12 },
711 /* The universal time zone table. These labels can be used even for
712 time stamps that would not otherwise be valid, e.g., GMT time
713 stamps in London during summer. */
714 static table const universal_time_zone_table[] =
716 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
717 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
718 { "UTC", tZONE, HOUR ( 0) },
722 /* The time zone table. This table is necessarily incomplete, as time
723 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
724 as Eastern time in Australia, not as US Eastern Standard Time.
725 You cannot rely on getdate to handle arbitrary time zone
726 abbreviations; use numeric abbreviations like `-0500' instead. */
727 static table const time_zone_table[] =
729 { "WET", tZONE, HOUR ( 0) }, /* Western European */
730 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
731 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
732 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
733 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
734 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
735 { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
736 { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
737 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
738 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
739 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
740 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
741 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
742 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
743 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
744 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
745 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
746 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
747 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
748 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
749 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
750 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
751 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
752 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
753 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
754 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
755 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
756 { "CET", tZONE, HOUR ( 1) }, /* Central European */
757 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
758 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
759 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
760 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
761 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
762 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
763 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
764 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
765 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
766 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
767 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
768 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
769 { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
770 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
771 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
772 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
773 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
774 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
775 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
779 /* Military time zone table. */
780 static table const military_table[] =
782 { "A", tZONE, -HOUR ( 1) },
783 { "B", tZONE, -HOUR ( 2) },
784 { "C", tZONE, -HOUR ( 3) },
785 { "D", tZONE, -HOUR ( 4) },
786 { "E", tZONE, -HOUR ( 5) },
787 { "F", tZONE, -HOUR ( 6) },
788 { "G", tZONE, -HOUR ( 7) },
789 { "H", tZONE, -HOUR ( 8) },
790 { "I", tZONE, -HOUR ( 9) },
791 { "K", tZONE, -HOUR (10) },
792 { "L", tZONE, -HOUR (11) },
793 { "M", tZONE, -HOUR (12) },
794 { "N", tZONE, HOUR ( 1) },
795 { "O", tZONE, HOUR ( 2) },
796 { "P", tZONE, HOUR ( 3) },
797 { "Q", tZONE, HOUR ( 4) },
798 { "R", tZONE, HOUR ( 5) },
799 { "S", tZONE, HOUR ( 6) },
800 { "T", tZONE, HOUR ( 7) },
801 { "U", tZONE, HOUR ( 8) },
802 { "V", tZONE, HOUR ( 9) },
803 { "W", tZONE, HOUR (10) },
804 { "X", tZONE, HOUR (11) },
805 { "Y", tZONE, HOUR (12) },
806 { "Z", tZONE, HOUR ( 0) },
812 /* Convert a time zone expressed as HH:MM into an integer count of
813 minutes. If MM is negative, then S is of the form HHMM and needs
814 to be picked apart; otherwise, S is of the form HH. As specified in
815 http://www.opengroup.org/susv3xbd/xbd_chap08.html#tag_08_03, allow
816 only valid TZ range, and consider first two digits as hours, if no
817 minutes specified. */
820 time_zone_hhmm (parser_control *pc, textint s, long int mm)
824 /* If the length of S is 1 or 2 and no minutes are specified,
825 interpret it as a number of hours. */
826 if (s.digits <= 2 && mm < 0)
830 n_minutes = (s.value / 100) * 60 + s.value % 100;
832 n_minutes = s.value * 60 + (s.negative ? -mm : mm);
834 /* If the absolute number of minutes is larger than 24 hours,
835 arrange to reject it by incrementing pc->zones_seen. Thus,
836 we allow only values in the range UTC-24:00 to UTC+24:00. */
837 if (24 * 60 < abs (n_minutes))
844 to_hour (long int hours, int meridian)
848 default: /* Pacify GCC. */
850 return 0 <= hours && hours < 24 ? hours : -1;
852 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
854 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
859 to_year (textint textyear)
861 long int year = textyear.value;
866 /* XPG4 suggests that years 00-68 map to 2000-2068, and
867 years 69-99 map to 1969-1999. */
868 else if (textyear.digits == 2)
869 year += year < 69 ? 2000 : 1900;
875 lookup_zone (parser_control const *pc, char const *name)
879 for (tp = universal_time_zone_table; tp->name; tp++)
880 if (strcmp (name, tp->name) == 0)
883 /* Try local zone abbreviations before those in time_zone_table, as
884 the local ones are more likely to be right. */
885 for (tp = pc->local_time_zone_table; tp->name; tp++)
886 if (strcmp (name, tp->name) == 0)
889 for (tp = time_zone_table; tp->name; tp++)
890 if (strcmp (name, tp->name) == 0)
897 /* Yield the difference between *A and *B,
898 measured in seconds, ignoring leap seconds.
899 The body of this function is taken directly from the GNU C Library;
900 see src/strftime.c. */
902 tm_diff (struct tm const *a, struct tm const *b)
904 /* Compute intervening leap days correctly even if year is negative.
905 Take care to avoid int overflow in leap day calculations. */
906 int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
907 int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
908 int a100 = a4 / 25 - (a4 % 25 < 0);
909 int b100 = b4 / 25 - (b4 % 25 < 0);
910 int a400 = SHR (a100, 2);
911 int b400 = SHR (b100, 2);
912 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
913 long int ayear = a->tm_year;
914 long int years = ayear - b->tm_year;
915 long int days = (365 * years + intervening_leap_days
916 + (a->tm_yday - b->tm_yday));
917 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
918 + (a->tm_min - b->tm_min))
919 + (a->tm_sec - b->tm_sec));
921 #endif /* ! HAVE_TM_GMTOFF */
924 lookup_word (parser_control const *pc, char *word)
933 /* Make it uppercase. */
934 for (p = word; *p; p++)
936 unsigned char ch = *p;
940 for (tp = meridian_table; tp->name; tp++)
941 if (strcmp (word, tp->name) == 0)
944 /* See if we have an abbreviation for a month. */
945 wordlen = strlen (word);
946 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
948 for (tp = month_and_day_table; tp->name; tp++)
949 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
952 if ((tp = lookup_zone (pc, word)))
955 if (strcmp (word, dst_table[0].name) == 0)
958 for (tp = time_units_table; tp->name; tp++)
959 if (strcmp (word, tp->name) == 0)
962 /* Strip off any plural and try the units table again. */
963 if (word[wordlen - 1] == 'S')
965 word[wordlen - 1] = '\0';
966 for (tp = time_units_table; tp->name; tp++)
967 if (strcmp (word, tp->name) == 0)
969 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
972 for (tp = relative_time_table; tp->name; tp++)
973 if (strcmp (word, tp->name) == 0)
976 /* Military time zones. */
978 for (tp = military_table; tp->name; tp++)
979 if (word[0] == tp->name[0])
982 /* Drop out any periods and try the time zone table again. */
983 for (period_found = false, p = q = word; (*p = *q); q++)
988 if (period_found && (tp = lookup_zone (pc, word)))
995 yylex (YYSTYPE *lvalp, parser_control *pc)
1002 while (c = *pc->input, c_isspace (c))
1005 if (ISDIGIT (c) || c == '-' || c == '+')
1009 unsigned long int value;
1010 if (c == '-' || c == '+')
1012 sign = c == '-' ? -1 : 1;
1013 while (c = *++pc->input, c_isspace (c))
1016 /* skip the '-' sign */
1022 for (value = 0; ; value *= 10)
1024 unsigned long int value1 = value + (c - '0');
1031 if (ULONG_MAX / 10 < value)
1034 if ((c == '.' || c == ',') && ISDIGIT (p[1]))
1039 unsigned long int value1;
1041 /* Check for overflow when converting value to time_t. */
1056 if (value != value1)
1059 /* Accumulate fraction, to ns precision. */
1062 for (digits = 2; digits <= LOG10_BILLION; digits++)
1069 /* Skip excess digits, truncating toward -Infinity. */
1071 for (; ISDIGIT (*p); p++)
1077 while (ISDIGIT (*p))
1080 /* Adjust to the timespec convention, which is that
1081 tv_nsec is always a positive offset even if tv_sec is
1091 lvalp->timespec.tv_sec = s;
1092 lvalp->timespec.tv_nsec = ns;
1094 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
1098 lvalp->textintval.negative = sign < 0;
1101 lvalp->textintval.value = - value;
1102 if (0 < lvalp->textintval.value)
1107 lvalp->textintval.value = value;
1108 if (lvalp->textintval.value < 0)
1111 lvalp->textintval.digits = p - pc->input;
1113 return sign ? tSNUMBER : tUNUMBER;
1125 if (p < buff + sizeof buff - 1)
1129 while (c_isalpha (c) || c == '.');
1132 tp = lookup_word (pc, buff);
1135 lvalp->intval = tp->value;
1140 return *pc->input++;
1156 /* Do nothing if the parser reports an error. */
1158 yyerror (parser_control const *pc ATTRIBUTE_UNUSED,
1159 char const *s ATTRIBUTE_UNUSED)
1164 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1165 passing it to mktime, return true if it's OK that mktime returned T.
1166 It's not OK if *TM0 has out-of-range members. */
1169 mktime_ok (struct tm const *tm0, struct tm const *tm1, time_t t)
1171 if (t == (time_t) -1)
1173 /* Guard against falsely reporting an error when parsing a time
1174 stamp that happens to equal (time_t) -1, on a host that
1175 supports such a time stamp. */
1176 tm1 = localtime (&t);
1181 return ! ((tm0->tm_sec ^ tm1->tm_sec)
1182 | (tm0->tm_min ^ tm1->tm_min)
1183 | (tm0->tm_hour ^ tm1->tm_hour)
1184 | (tm0->tm_mday ^ tm1->tm_mday)
1185 | (tm0->tm_mon ^ tm1->tm_mon)
1186 | (tm0->tm_year ^ tm1->tm_year));
1189 /* A reasonable upper bound for the size of ordinary TZ strings.
1190 Use heap allocation if TZ's length exceeds this. */
1191 enum { TZBUFSIZE = 100 };
1193 /* Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
1196 get_tz (char tzbuf[TZBUFSIZE])
1198 char *tz = getenv ("TZ");
1201 size_t tzsize = strlen (tz) + 1;
1202 tz = (tzsize <= TZBUFSIZE
1203 ? memcpy (tzbuf, tz, tzsize)
1204 : xmemdup (tz, tzsize));
1209 /* Parse a date/time string, storing the resulting time value into *RESULT.
1210 The string itself is pointed to by P. Return true if successful.
1211 P can be an incomplete or relative time specification; if so, use
1212 *NOW as the basis for the returned time. */
1214 get_date (struct timespec *result, char const *p, struct timespec const *now)
1218 struct tm const *tmp;
1222 struct timespec gettime_buffer;
1224 bool tz_was_altered = false;
1226 char tz0buf[TZBUFSIZE];
1231 gettime (&gettime_buffer);
1232 now = &gettime_buffer;
1235 Start = now->tv_sec;
1236 Start_ns = now->tv_nsec;
1238 tmp = localtime (&now->tv_sec);
1242 while (c = *p, c_isspace (c))
1245 if (strncmp (p, "TZ=\"", 4) == 0)
1247 char const *tzbase = p + 4;
1251 for (s = tzbase; *s; s++, tzsize++)
1255 if (! (*s == '\\' || *s == '"'))
1262 char tz1buf[TZBUFSIZE];
1263 bool large_tz = TZBUFSIZE < tzsize;
1265 tz0 = get_tz (tz0buf);
1266 z = tz1 = large_tz ? xmalloc (tzsize) : tz1buf;
1267 for (s = tzbase; *s != '"'; s++)
1268 *z++ = *(s += *s == '\\');
1270 setenv_ok = setenv ("TZ", tz1, 1) == 0;
1275 tz_was_altered = true;
1280 /* As documented, be careful to treat the empty string just like
1281 a date string of "0". Without this, an empty string would be
1282 declared invalid when parsed during a DST transition. */
1287 pc.year.value = tmp->tm_year;
1288 pc.year.value += TM_YEAR_BASE;
1290 pc.month = tmp->tm_mon + 1;
1291 pc.day = tmp->tm_mday;
1292 pc.hour = tmp->tm_hour;
1293 pc.minutes = tmp->tm_min;
1294 pc.seconds.tv_sec = tmp->tm_sec;
1295 pc.seconds.tv_nsec = Start_ns;
1296 tm.tm_isdst = tmp->tm_isdst;
1298 pc.meridian = MER24;
1299 pc.rel = RELATIVE_TIME_0;
1300 pc.timespec_seen = false;
1301 pc.rels_seen = false;
1305 pc.local_zones_seen = 0;
1309 #if HAVE_STRUCT_TM_TM_ZONE
1310 pc.local_time_zone_table[0].name = tmp->tm_zone;
1311 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1312 pc.local_time_zone_table[0].value = tmp->tm_isdst;
1313 pc.local_time_zone_table[1].name = NULL;
1315 /* Probe the names used in the next three calendar quarters, looking
1316 for a tm_isdst different from the one we already have. */
1319 for (quarter = 1; quarter <= 3; quarter++)
1321 time_t probe = Start + quarter * (90 * 24 * 60 * 60);
1322 struct tm const *probe_tm = localtime (&probe);
1323 if (probe_tm && probe_tm->tm_zone
1324 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
1327 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
1328 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1329 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
1330 pc.local_time_zone_table[2].name = NULL;
1339 # if !HAVE_DECL_TZNAME
1340 extern char *tzname[];
1343 for (i = 0; i < 2; i++)
1345 pc.local_time_zone_table[i].name = tzname[i];
1346 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1347 pc.local_time_zone_table[i].value = i;
1349 pc.local_time_zone_table[i].name = NULL;
1352 pc.local_time_zone_table[0].name = NULL;
1356 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1357 && ! strcmp (pc.local_time_zone_table[0].name,
1358 pc.local_time_zone_table[1].name))
1360 /* This locale uses the same abbrevation for standard and
1361 daylight times. So if we see that abbreviation, we don't
1362 know whether it's daylight time. */
1363 pc.local_time_zone_table[0].value = -1;
1364 pc.local_time_zone_table[1].name = NULL;
1367 if (yyparse (&pc) != 0)
1370 if (pc.timespec_seen)
1371 *result = pc.seconds;
1374 if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
1375 | (pc.local_zones_seen + pc.zones_seen)))
1378 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
1379 tm.tm_mon = pc.month - 1;
1380 tm.tm_mday = pc.day;
1381 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1383 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1386 tm.tm_min = pc.minutes;
1387 tm.tm_sec = pc.seconds.tv_sec;
1391 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1392 pc.seconds.tv_nsec = 0;
1395 /* Let mktime deduce tm_isdst if we have an absolute time stamp. */
1396 if (pc.dates_seen | pc.days_seen | pc.times_seen)
1399 /* But if the input explicitly specifies local time with or without
1400 DST, give mktime that information. */
1401 if (pc.local_zones_seen)
1402 tm.tm_isdst = pc.local_isdst;
1406 Start = mktime (&tm);
1408 if (! mktime_ok (&tm0, &tm, Start))
1410 if (! pc.zones_seen)
1414 /* Guard against falsely reporting errors near the time_t
1415 boundaries when parsing times in other time zones. For
1416 example, suppose the input string "1969-12-31 23:00:00 -0100",
1417 the current time zone is 8 hours ahead of UTC, and the min
1418 time_t value is 1970-01-01 00:00:00 UTC. Then the min
1419 localtime value is 1970-01-01 08:00:00, and mktime will
1420 therefore fail on 1969-12-31 23:00:00. To work around the
1421 problem, set the time zone to 1 hour behind UTC temporarily
1422 by setting TZ="XXX1:00" and try mktime again. */
1424 long int time_zone = pc.time_zone;
1425 long int abs_time_zone = time_zone < 0 ? - time_zone : time_zone;
1426 long int abs_time_zone_hour = abs_time_zone / 60;
1427 int abs_time_zone_min = abs_time_zone % 60;
1428 char tz1buf[sizeof "XXX+0:00"
1429 + sizeof pc.time_zone * CHAR_BIT / 3];
1430 if (!tz_was_altered)
1431 tz0 = get_tz (tz0buf);
1432 sprintf (tz1buf, "XXX%s%ld:%02d", "-" + (time_zone < 0),
1433 abs_time_zone_hour, abs_time_zone_min);
1434 if (setenv ("TZ", tz1buf, 1) != 0)
1436 tz_was_altered = true;
1438 Start = mktime (&tm);
1439 if (! mktime_ok (&tm0, &tm, Start))
1444 if (pc.days_seen && ! pc.dates_seen)
1446 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1447 + 7 * (pc.day_ordinal
1448 - (0 < pc.day_ordinal
1449 && tm.tm_wday != pc.day_number)));
1451 Start = mktime (&tm);
1452 if (Start == (time_t) -1)
1456 /* Add relative date. */
1457 if (pc.rel.year | pc.rel.month | pc.rel.day)
1459 int year = tm.tm_year + pc.rel.year;
1460 int month = tm.tm_mon + pc.rel.month;
1461 int day = tm.tm_mday + pc.rel.day;
1462 if (((year < tm.tm_year) ^ (pc.rel.year < 0))
1463 | ((month < tm.tm_mon) ^ (pc.rel.month < 0))
1464 | ((day < tm.tm_mday) ^ (pc.rel.day < 0)))
1469 tm.tm_hour = tm0.tm_hour;
1470 tm.tm_min = tm0.tm_min;
1471 tm.tm_sec = tm0.tm_sec;
1472 tm.tm_isdst = tm0.tm_isdst;
1473 Start = mktime (&tm);
1474 if (Start == (time_t) -1)
1478 /* The only "output" of this if-block is an updated Start value,
1479 so this block must follow others that clobber Start. */
1482 long int delta = pc.time_zone * 60;
1484 #ifdef HAVE_TM_GMTOFF
1485 delta -= tm.tm_gmtoff;
1488 struct tm const *gmt = gmtime (&t);
1491 delta -= tm_diff (&tm, gmt);
1494 if ((Start < t1) != (delta < 0))
1495 goto fail; /* time_t overflow */
1499 /* Add relative hours, minutes, and seconds. On hosts that support
1500 leap seconds, ignore the possibility of leap seconds; e.g.,
1501 "+ 10 minutes" adds 600 seconds, even if one of them is a
1502 leap second. Typically this is not what the user wants, but it's
1503 too hard to do it the other way, because the time zone indicator
1504 must be applied before relative times, and if mktime is applied
1505 again the time zone will be lost. */
1507 long int sum_ns = pc.seconds.tv_nsec + pc.rel.ns;
1508 long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
1510 long int d1 = 60 * 60 * pc.rel.hour;
1511 time_t t1 = t0 + d1;
1512 long int d2 = 60 * pc.rel.minutes;
1513 time_t t2 = t1 + d2;
1514 long_time_t d3 = pc.rel.seconds;
1515 long_time_t t3 = t2 + d3;
1516 long int d4 = (sum_ns - normalized_ns) / BILLION;
1517 long_time_t t4 = t3 + d4;
1520 if ((d1 / (60 * 60) ^ pc.rel.hour)
1521 | (d2 / 60 ^ pc.rel.minutes)
1522 | ((t1 < t0) ^ (d1 < 0))
1523 | ((t2 < t1) ^ (d2 < 0))
1524 | ((t3 < t2) ^ (d3 < 0))
1525 | ((t4 < t3) ^ (d4 < 0))
1529 result->tv_sec = t5;
1530 result->tv_nsec = normalized_ns;
1540 ok &= (tz0 ? setenv ("TZ", tz0, 1) : unsetenv ("TZ")) == 0;
1549 main (int ac, char **av)
1553 printf ("Enter date, or blank line to exit.\n\t> ");
1556 buff[BUFSIZ - 1] = '\0';
1557 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1560 struct tm const *tm;
1561 if (! get_date (&d, buff, NULL))
1562 printf ("Bad format - couldn't convert.\n");
1563 else if (! (tm = localtime (&d.tv_sec)))
1565 long int sec = d.tv_sec;
1566 printf ("localtime (%ld) failed\n", sec);
1571 printf ("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1572 tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
1573 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);