2 /* Parse a string into an internal time stamp.
4 Copyright (C) 1999, 2000, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
5 2010 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
36 #include "parse-datetime.h"
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 parse-datetime.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. */
71 /* Bison's skeleton tests _STDLIB_H, while some stdlib.h headers
72 use _STDLIB_H_ as witness. Map the latter to the one bison uses. */
73 /* FIXME: this is temporary. Remove when we have a mechanism to ensure
74 that the version we're using is fixed, too. */
80 /* ISDIGIT differs from isdigit, as follows:
81 - Its arg may be any int or unsigned int; it need not be an unsigned char
83 - It's typically faster.
84 POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
85 isdigit unless it's important to use the locale's definition
86 of `digit' even when the host does not conform to POSIX. */
87 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
89 /* Shift A right by B bits portably, by dividing A by 2**B and
90 truncating towards minus infinity. A and B should be free of side
91 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
92 INT_BITS is the number of useful bits in an int. GNU code can
93 assume that INT_BITS is at least 32.
95 ISO C99 says that A >> B is implementation-defined if A < 0. Some
96 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
97 right in the usual way when A < 0, so SHR falls back on division if
98 ordinary A >> B doesn't seem to be the usual signed shift. */
102 : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
104 #define EPOCH_YEAR 1970
105 #define TM_YEAR_BASE 1900
107 #define HOUR(x) ((x) * 60)
109 /* long_time_t is a signed integer type that contains all time_t values. */
110 verify (TYPE_IS_INTEGER (time_t));
111 #if TIME_T_FITS_IN_LONG_INT
112 typedef long int long_time_t;
114 typedef time_t long_time_t;
117 /* Lots of this code assumes time_t and time_t-like values fit into
119 verify (TYPE_MINIMUM (long_time_t) <= TYPE_MINIMUM (time_t)
120 && TYPE_MAXIMUM (time_t) <= TYPE_MAXIMUM (long_time_t));
122 /* FIXME: It also assumes that signed integer overflow silently wraps around,
123 but this is not true any more with recent versions of GCC 4. */
125 /* An integer value, and the number of digits in its textual
134 /* An entry in the lexical lookup table. */
142 /* Meridian: am, pm, or 24-hour style. */
143 enum { MERam, MERpm, MER24 };
145 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
147 /* Relative times. */
150 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
160 #if HAVE_COMPOUND_LITERALS
161 # define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
163 static relative_time const RELATIVE_TIME_0;
166 /* Information passed to and from the parser. */
169 /* The input string remaining to be parsed. */
172 /* N, if this is the Nth Tuesday. */
173 long int day_ordinal;
175 /* Day of week; Sunday is 0. */
178 /* tm_isdst flag for the local zone. */
181 /* Time zone, in minutes east of UTC. */
184 /* Style used for time. */
187 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
193 struct timespec seconds; /* includes nanoseconds */
195 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
198 /* Presence or counts of nonterminals of various flavors parsed so far. */
203 size_t local_zones_seen;
208 /* Table of local time zone abbrevations, terminated by a null entry. */
209 table local_time_zone_table[3];
213 static int yylex (union YYSTYPE *, parser_control *);
214 static int yyerror (parser_control const *, char const *);
215 static long int time_zone_hhmm (parser_control *, textint, long int);
217 /* Extract into *PC any date and time info from a string of digits
218 of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
221 digits_to_date_time (parser_control *pc, textint text_int)
223 if (pc->dates_seen && ! pc->year.digits
224 && ! pc->rels_seen && (pc->times_seen || 2 < text_int.digits))
228 if (4 < text_int.digits)
231 pc->day = text_int.value % 100;
232 pc->month = (text_int.value / 100) % 100;
233 pc->year.value = text_int.value / 10000;
234 pc->year.digits = text_int.digits - 4;
239 if (text_int.digits <= 2)
241 pc->hour = text_int.value;
246 pc->hour = text_int.value / 100;
247 pc->minutes = text_int.value % 100;
249 pc->seconds.tv_sec = 0;
250 pc->seconds.tv_nsec = 0;
251 pc->meridian = MER24;
256 /* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1). */
258 apply_relative_time (parser_control *pc, relative_time rel, int factor)
260 pc->rel.ns += factor * rel.ns;
261 pc->rel.seconds += factor * rel.seconds;
262 pc->rel.minutes += factor * rel.minutes;
263 pc->rel.hour += factor * rel.hour;
264 pc->rel.day += factor * rel.day;
265 pc->rel.month += factor * rel.month;
266 pc->rel.year += factor * rel.year;
267 pc->rels_seen = true;
270 /* Set PC-> hour, minutes, seconds and nanoseconds members from arguments. */
272 set_hhmmss (parser_control *pc, long int hour, long int minutes,
273 time_t sec, long int nsec)
276 pc->minutes = minutes;
277 pc->seconds.tv_sec = sec;
278 pc->seconds.tv_nsec = nsec;
283 /* We want a reentrant parser, even if the TZ manipulation and the calls to
284 localtime and gmtime are not reentrant. */
286 %parse-param { parser_control *pc }
287 %lex-param { parser_control *pc }
289 /* This grammar has 20 shift/reduce conflicts. */
296 struct timespec timespec;
302 %token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
303 %token <intval> tDAY_UNIT tDAY_SHIFT
305 %token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
306 %token <intval> tMONTH tORDINAL tZONE
308 %token <textintval> tSNUMBER tUNUMBER
309 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
311 %type <intval> o_colon_minutes o_merid
312 %type <timespec> seconds signed_seconds unsigned_seconds
314 %type <rel> relunit relunit_snumber dayshift
327 pc->timespec_seen = true;
338 { pc->times_seen++; }
340 { pc->local_zones_seen++; }
342 { pc->zones_seen++; }
344 { pc->dates_seen++; }
355 set_hhmmss (pc, $1.value, 0, 0, 0);
358 | tUNUMBER ':' tUNUMBER o_merid
360 set_hhmmss (pc, $1.value, $3.value, 0, 0);
363 | tUNUMBER ':' tUNUMBER tSNUMBER o_colon_minutes
365 set_hhmmss (pc, $1.value, $3.value, 0, 0);
366 pc->meridian = MER24;
368 pc->time_zone = time_zone_hhmm (pc, $4, $5);
370 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
372 set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
375 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER o_colon_minutes
377 set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
378 pc->meridian = MER24;
380 pc->time_zone = time_zone_hhmm (pc, $6, $7);
387 pc->local_isdst = $1;
388 pc->dsts_seen += (0 < $1);
393 pc->dsts_seen += (0 < $1) + 1;
399 { pc->time_zone = $1; }
400 | tZONE relunit_snumber
401 { pc->time_zone = $1;
402 apply_relative_time (pc, $2, 1); }
403 | tZONE tSNUMBER o_colon_minutes
404 { pc->time_zone = $1 + time_zone_hhmm (pc, $2, $3); }
406 { pc->time_zone = $1 + 60; }
408 { pc->time_zone = $1 + 60; }
424 pc->day_ordinal = $1;
429 pc->day_ordinal = $1.value;
435 tUNUMBER '/' tUNUMBER
437 pc->month = $1.value;
440 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
442 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
443 otherwise as MM/DD/YY.
444 The goal in recognizing YYYY/MM/DD is solely to support legacy
445 machine-generated dates like those in an RCS log listing. If
446 you want portability, use the ISO 8601 format. */
450 pc->month = $3.value;
455 pc->month = $1.value;
460 | tUNUMBER tSNUMBER tSNUMBER
462 /* ISO 8601 format. YYYY-MM-DD. */
464 pc->month = -$2.value;
467 | tUNUMBER tMONTH tSNUMBER
469 /* e.g. 17-JUN-1992. */
472 pc->year.value = -$3.value;
473 pc->year.digits = $3.digits;
475 | tMONTH tSNUMBER tSNUMBER
477 /* e.g. JUN-17-1992. */
480 pc->year.value = -$3.value;
481 pc->year.digits = $3.digits;
488 | tMONTH tUNUMBER ',' tUNUMBER
499 | tUNUMBER tMONTH tUNUMBER
509 { apply_relative_time (pc, $1, -1); }
511 { apply_relative_time (pc, $1, 1); }
513 { apply_relative_time (pc, $1, 1); }
518 { $$ = RELATIVE_TIME_0; $$.year = $1; }
519 | tUNUMBER tYEAR_UNIT
520 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
522 { $$ = RELATIVE_TIME_0; $$.year = 1; }
523 | tORDINAL tMONTH_UNIT
524 { $$ = RELATIVE_TIME_0; $$.month = $1; }
525 | tUNUMBER tMONTH_UNIT
526 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
528 { $$ = RELATIVE_TIME_0; $$.month = 1; }
530 { $$ = RELATIVE_TIME_0; $$.day = $1 * $2; }
532 { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
534 { $$ = RELATIVE_TIME_0; $$.day = $1; }
535 | tORDINAL tHOUR_UNIT
536 { $$ = RELATIVE_TIME_0; $$.hour = $1; }
537 | tUNUMBER tHOUR_UNIT
538 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
540 { $$ = RELATIVE_TIME_0; $$.hour = 1; }
541 | tORDINAL tMINUTE_UNIT
542 { $$ = RELATIVE_TIME_0; $$.minutes = $1; }
543 | tUNUMBER tMINUTE_UNIT
544 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
546 { $$ = RELATIVE_TIME_0; $$.minutes = 1; }
548 { $$ = RELATIVE_TIME_0; $$.seconds = $1; }
550 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
551 | tSDECIMAL_NUMBER tSEC_UNIT
552 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
553 | tUDECIMAL_NUMBER tSEC_UNIT
554 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
556 { $$ = RELATIVE_TIME_0; $$.seconds = 1; }
562 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
563 | tSNUMBER tMONTH_UNIT
564 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
566 { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
567 | tSNUMBER tHOUR_UNIT
568 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
569 | tSNUMBER tMINUTE_UNIT
570 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
572 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
577 { $$ = RELATIVE_TIME_0; $$.day = $1; }
580 seconds: signed_seconds | unsigned_seconds;
585 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
591 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
596 { digits_to_date_time (pc, $1); }
600 tUNUMBER relunit_snumber
602 /* Hybrid all-digit and relative offset, so that we accept e.g.,
603 "YYYYMMDD +N days" as well as "YYYYMMDD N days". */
604 digits_to_date_time (pc, $1);
605 apply_relative_time (pc, $2, 1);
625 static table const meridian_table[] =
627 { "AM", tMERIDIAN, MERam },
628 { "A.M.", tMERIDIAN, MERam },
629 { "PM", tMERIDIAN, MERpm },
630 { "P.M.", tMERIDIAN, MERpm },
634 static table const dst_table[] =
639 static table const month_and_day_table[] =
641 { "JANUARY", tMONTH, 1 },
642 { "FEBRUARY", tMONTH, 2 },
643 { "MARCH", tMONTH, 3 },
644 { "APRIL", tMONTH, 4 },
645 { "MAY", tMONTH, 5 },
646 { "JUNE", tMONTH, 6 },
647 { "JULY", tMONTH, 7 },
648 { "AUGUST", tMONTH, 8 },
649 { "SEPTEMBER",tMONTH, 9 },
650 { "SEPT", tMONTH, 9 },
651 { "OCTOBER", tMONTH, 10 },
652 { "NOVEMBER", tMONTH, 11 },
653 { "DECEMBER", tMONTH, 12 },
654 { "SUNDAY", tDAY, 0 },
655 { "MONDAY", tDAY, 1 },
656 { "TUESDAY", tDAY, 2 },
658 { "WEDNESDAY",tDAY, 3 },
659 { "WEDNES", tDAY, 3 },
660 { "THURSDAY", tDAY, 4 },
662 { "THURS", tDAY, 4 },
663 { "FRIDAY", tDAY, 5 },
664 { "SATURDAY", tDAY, 6 },
668 static table const time_units_table[] =
670 { "YEAR", tYEAR_UNIT, 1 },
671 { "MONTH", tMONTH_UNIT, 1 },
672 { "FORTNIGHT",tDAY_UNIT, 14 },
673 { "WEEK", tDAY_UNIT, 7 },
674 { "DAY", tDAY_UNIT, 1 },
675 { "HOUR", tHOUR_UNIT, 1 },
676 { "MINUTE", tMINUTE_UNIT, 1 },
677 { "MIN", tMINUTE_UNIT, 1 },
678 { "SECOND", tSEC_UNIT, 1 },
679 { "SEC", tSEC_UNIT, 1 },
683 /* Assorted relative-time words. */
684 static table const relative_time_table[] =
686 { "TOMORROW", tDAY_SHIFT, 1 },
687 { "YESTERDAY",tDAY_SHIFT, -1 },
688 { "TODAY", tDAY_SHIFT, 0 },
689 { "NOW", tDAY_SHIFT, 0 },
690 { "LAST", tORDINAL, -1 },
691 { "THIS", tORDINAL, 0 },
692 { "NEXT", tORDINAL, 1 },
693 { "FIRST", tORDINAL, 1 },
694 /*{ "SECOND", tORDINAL, 2 }, */
695 { "THIRD", tORDINAL, 3 },
696 { "FOURTH", tORDINAL, 4 },
697 { "FIFTH", tORDINAL, 5 },
698 { "SIXTH", tORDINAL, 6 },
699 { "SEVENTH", tORDINAL, 7 },
700 { "EIGHTH", tORDINAL, 8 },
701 { "NINTH", tORDINAL, 9 },
702 { "TENTH", tORDINAL, 10 },
703 { "ELEVENTH", tORDINAL, 11 },
704 { "TWELFTH", tORDINAL, 12 },
709 /* The universal time zone table. These labels can be used even for
710 time stamps that would not otherwise be valid, e.g., GMT time
711 stamps in London during summer. */
712 static table const universal_time_zone_table[] =
714 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
715 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
716 { "UTC", tZONE, HOUR ( 0) },
720 /* The time zone table. This table is necessarily incomplete, as time
721 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
722 as Eastern time in Australia, not as US Eastern Standard Time.
723 You cannot rely on parse_datetime to handle arbitrary time zone
724 abbreviations; use numeric abbreviations like `-0500' instead. */
725 static table const time_zone_table[] =
727 { "WET", tZONE, HOUR ( 0) }, /* Western European */
728 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
729 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
730 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
731 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
732 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
733 { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
734 { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
735 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
736 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
737 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
738 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
739 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
740 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
741 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
742 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
743 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
744 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
745 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
746 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
747 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
748 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
749 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
750 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
751 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
752 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
753 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
754 { "CET", tZONE, HOUR ( 1) }, /* Central European */
755 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
756 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
757 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
758 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
759 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
760 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
761 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
762 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
763 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
764 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
765 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
766 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
767 { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
768 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
769 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
770 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
771 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
772 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
773 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
777 /* Military time zone table. */
778 static table const military_table[] =
780 { "A", tZONE, -HOUR ( 1) },
781 { "B", tZONE, -HOUR ( 2) },
782 { "C", tZONE, -HOUR ( 3) },
783 { "D", tZONE, -HOUR ( 4) },
784 { "E", tZONE, -HOUR ( 5) },
785 { "F", tZONE, -HOUR ( 6) },
786 { "G", tZONE, -HOUR ( 7) },
787 { "H", tZONE, -HOUR ( 8) },
788 { "I", tZONE, -HOUR ( 9) },
789 { "K", tZONE, -HOUR (10) },
790 { "L", tZONE, -HOUR (11) },
791 { "M", tZONE, -HOUR (12) },
792 { "N", tZONE, HOUR ( 1) },
793 { "O", tZONE, HOUR ( 2) },
794 { "P", tZONE, HOUR ( 3) },
795 { "Q", tZONE, HOUR ( 4) },
796 { "R", tZONE, HOUR ( 5) },
797 { "S", tZONE, HOUR ( 6) },
798 { "T", tZONE, HOUR ( 7) },
799 { "U", tZONE, HOUR ( 8) },
800 { "V", tZONE, HOUR ( 9) },
801 { "W", tZONE, HOUR (10) },
802 { "X", tZONE, HOUR (11) },
803 { "Y", tZONE, HOUR (12) },
804 { "Z", tZONE, HOUR ( 0) },
810 /* Convert a time zone expressed as HH:MM into an integer count of
811 minutes. If MM is negative, then S is of the form HHMM and needs
812 to be picked apart; otherwise, S is of the form HH. As specified in
813 http://www.opengroup.org/susv3xbd/xbd_chap08.html#tag_08_03, allow
814 only valid TZ range, and consider first two digits as hours, if no
815 minutes specified. */
818 time_zone_hhmm (parser_control *pc, textint s, long int mm)
822 /* If the length of S is 1 or 2 and no minutes are specified,
823 interpret it as a number of hours. */
824 if (s.digits <= 2 && mm < 0)
828 n_minutes = (s.value / 100) * 60 + s.value % 100;
830 n_minutes = s.value * 60 + (s.negative ? -mm : mm);
832 /* If the absolute number of minutes is larger than 24 hours,
833 arrange to reject it by incrementing pc->zones_seen. Thus,
834 we allow only values in the range UTC-24:00 to UTC+24:00. */
835 if (24 * 60 < abs (n_minutes))
842 to_hour (long int hours, int meridian)
846 default: /* Pacify GCC. */
848 return 0 <= hours && hours < 24 ? hours : -1;
850 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
852 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
857 to_year (textint textyear)
859 long int year = textyear.value;
864 /* XPG4 suggests that years 00-68 map to 2000-2068, and
865 years 69-99 map to 1969-1999. */
866 else if (textyear.digits == 2)
867 year += year < 69 ? 2000 : 1900;
873 lookup_zone (parser_control const *pc, char const *name)
877 for (tp = universal_time_zone_table; tp->name; tp++)
878 if (strcmp (name, tp->name) == 0)
881 /* Try local zone abbreviations before those in time_zone_table, as
882 the local ones are more likely to be right. */
883 for (tp = pc->local_time_zone_table; tp->name; tp++)
884 if (strcmp (name, tp->name) == 0)
887 for (tp = time_zone_table; tp->name; tp++)
888 if (strcmp (name, tp->name) == 0)
895 /* Yield the difference between *A and *B,
896 measured in seconds, ignoring leap seconds.
897 The body of this function is taken directly from the GNU C Library;
898 see src/strftime.c. */
900 tm_diff (struct tm const *a, struct tm const *b)
902 /* Compute intervening leap days correctly even if year is negative.
903 Take care to avoid int overflow in leap day calculations. */
904 int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
905 int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
906 int a100 = a4 / 25 - (a4 % 25 < 0);
907 int b100 = b4 / 25 - (b4 % 25 < 0);
908 int a400 = SHR (a100, 2);
909 int b400 = SHR (b100, 2);
910 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
911 long int ayear = a->tm_year;
912 long int years = ayear - b->tm_year;
913 long int days = (365 * years + intervening_leap_days
914 + (a->tm_yday - b->tm_yday));
915 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
916 + (a->tm_min - b->tm_min))
917 + (a->tm_sec - b->tm_sec));
919 #endif /* ! HAVE_TM_GMTOFF */
922 lookup_word (parser_control const *pc, char *word)
931 /* Make it uppercase. */
932 for (p = word; *p; p++)
934 unsigned char ch = *p;
938 for (tp = meridian_table; tp->name; tp++)
939 if (strcmp (word, tp->name) == 0)
942 /* See if we have an abbreviation for a month. */
943 wordlen = strlen (word);
944 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
946 for (tp = month_and_day_table; tp->name; tp++)
947 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
950 if ((tp = lookup_zone (pc, word)))
953 if (strcmp (word, dst_table[0].name) == 0)
956 for (tp = time_units_table; tp->name; tp++)
957 if (strcmp (word, tp->name) == 0)
960 /* Strip off any plural and try the units table again. */
961 if (word[wordlen - 1] == 'S')
963 word[wordlen - 1] = '\0';
964 for (tp = time_units_table; tp->name; tp++)
965 if (strcmp (word, tp->name) == 0)
967 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
970 for (tp = relative_time_table; tp->name; tp++)
971 if (strcmp (word, tp->name) == 0)
974 /* Military time zones. */
976 for (tp = military_table; tp->name; tp++)
977 if (word[0] == tp->name[0])
980 /* Drop out any periods and try the time zone table again. */
981 for (period_found = false, p = q = word; (*p = *q); q++)
986 if (period_found && (tp = lookup_zone (pc, word)))
993 yylex (YYSTYPE *lvalp, parser_control *pc)
1000 while (c = *pc->input, c_isspace (c))
1003 if (ISDIGIT (c) || c == '-' || c == '+')
1007 unsigned long int value;
1008 if (c == '-' || c == '+')
1010 sign = c == '-' ? -1 : 1;
1011 while (c = *++pc->input, c_isspace (c))
1014 /* skip the '-' sign */
1020 for (value = 0; ; value *= 10)
1022 unsigned long int value1 = value + (c - '0');
1029 if (ULONG_MAX / 10 < value)
1032 if ((c == '.' || c == ',') && ISDIGIT (p[1]))
1037 unsigned long int value1;
1039 /* Check for overflow when converting value to time_t. */
1054 if (value != value1)
1057 /* Accumulate fraction, to ns precision. */
1060 for (digits = 2; digits <= LOG10_BILLION; digits++)
1067 /* Skip excess digits, truncating toward -Infinity. */
1069 for (; ISDIGIT (*p); p++)
1075 while (ISDIGIT (*p))
1078 /* Adjust to the timespec convention, which is that
1079 tv_nsec is always a positive offset even if tv_sec is
1089 lvalp->timespec.tv_sec = s;
1090 lvalp->timespec.tv_nsec = ns;
1092 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
1096 lvalp->textintval.negative = sign < 0;
1099 lvalp->textintval.value = - value;
1100 if (0 < lvalp->textintval.value)
1105 lvalp->textintval.value = value;
1106 if (lvalp->textintval.value < 0)
1109 lvalp->textintval.digits = p - pc->input;
1111 return sign ? tSNUMBER : tUNUMBER;
1123 if (p < buff + sizeof buff - 1)
1127 while (c_isalpha (c) || c == '.');
1130 tp = lookup_word (pc, buff);
1133 lvalp->intval = tp->value;
1138 return *pc->input++;
1154 /* Do nothing if the parser reports an error. */
1156 yyerror (parser_control const *pc _GL_UNUSED,
1157 char const *s _GL_UNUSED)
1162 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1163 passing it to mktime, return true if it's OK that mktime returned T.
1164 It's not OK if *TM0 has out-of-range members. */
1167 mktime_ok (struct tm const *tm0, struct tm const *tm1, time_t t)
1169 if (t == (time_t) -1)
1171 /* Guard against falsely reporting an error when parsing a time
1172 stamp that happens to equal (time_t) -1, on a host that
1173 supports such a time stamp. */
1174 tm1 = localtime (&t);
1179 return ! ((tm0->tm_sec ^ tm1->tm_sec)
1180 | (tm0->tm_min ^ tm1->tm_min)
1181 | (tm0->tm_hour ^ tm1->tm_hour)
1182 | (tm0->tm_mday ^ tm1->tm_mday)
1183 | (tm0->tm_mon ^ tm1->tm_mon)
1184 | (tm0->tm_year ^ tm1->tm_year));
1187 /* A reasonable upper bound for the size of ordinary TZ strings.
1188 Use heap allocation if TZ's length exceeds this. */
1189 enum { TZBUFSIZE = 100 };
1191 /* Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
1194 get_tz (char tzbuf[TZBUFSIZE])
1196 char *tz = getenv ("TZ");
1199 size_t tzsize = strlen (tz) + 1;
1200 tz = (tzsize <= TZBUFSIZE
1201 ? memcpy (tzbuf, tz, tzsize)
1202 : xmemdup (tz, tzsize));
1207 /* Parse a date/time string, storing the resulting time value into *RESULT.
1208 The string itself is pointed to by P. Return true if successful.
1209 P can be an incomplete or relative time specification; if so, use
1210 *NOW as the basis for the returned time. */
1212 parse_datetime (struct timespec *result, char const *p,
1213 struct timespec const *now)
1217 struct tm const *tmp;
1221 struct timespec gettime_buffer;
1223 bool tz_was_altered = false;
1225 char tz0buf[TZBUFSIZE];
1230 gettime (&gettime_buffer);
1231 now = &gettime_buffer;
1234 Start = now->tv_sec;
1235 Start_ns = now->tv_nsec;
1237 tmp = localtime (&now->tv_sec);
1241 while (c = *p, c_isspace (c))
1244 if (strncmp (p, "TZ=\"", 4) == 0)
1246 char const *tzbase = p + 4;
1250 for (s = tzbase; *s; s++, tzsize++)
1254 if (! (*s == '\\' || *s == '"'))
1261 char tz1buf[TZBUFSIZE];
1262 bool large_tz = TZBUFSIZE < tzsize;
1264 /* Free tz0, in case this is the 2nd or subsequent time through. */
1266 tz0 = get_tz (tz0buf);
1267 z = tz1 = large_tz ? xmalloc (tzsize) : tz1buf;
1268 for (s = tzbase; *s != '"'; s++)
1269 *z++ = *(s += *s == '\\');
1271 setenv_ok = setenv ("TZ", tz1, 1) == 0;
1276 tz_was_altered = true;
1281 /* As documented, be careful to treat the empty string just like
1282 a date string of "0". Without this, an empty string would be
1283 declared invalid when parsed during a DST transition. */
1288 pc.year.value = tmp->tm_year;
1289 pc.year.value += TM_YEAR_BASE;
1291 pc.month = tmp->tm_mon + 1;
1292 pc.day = tmp->tm_mday;
1293 pc.hour = tmp->tm_hour;
1294 pc.minutes = tmp->tm_min;
1295 pc.seconds.tv_sec = tmp->tm_sec;
1296 pc.seconds.tv_nsec = Start_ns;
1297 tm.tm_isdst = tmp->tm_isdst;
1299 pc.meridian = MER24;
1300 pc.rel = RELATIVE_TIME_0;
1301 pc.timespec_seen = false;
1302 pc.rels_seen = false;
1306 pc.local_zones_seen = 0;
1310 #if HAVE_STRUCT_TM_TM_ZONE
1311 pc.local_time_zone_table[0].name = tmp->tm_zone;
1312 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1313 pc.local_time_zone_table[0].value = tmp->tm_isdst;
1314 pc.local_time_zone_table[1].name = NULL;
1316 /* Probe the names used in the next three calendar quarters, looking
1317 for a tm_isdst different from the one we already have. */
1320 for (quarter = 1; quarter <= 3; quarter++)
1322 time_t probe = Start + quarter * (90 * 24 * 60 * 60);
1323 struct tm const *probe_tm = localtime (&probe);
1324 if (probe_tm && probe_tm->tm_zone
1325 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
1328 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
1329 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1330 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
1331 pc.local_time_zone_table[2].name = NULL;
1340 # if !HAVE_DECL_TZNAME
1341 extern char *tzname[];
1344 for (i = 0; i < 2; i++)
1346 pc.local_time_zone_table[i].name = tzname[i];
1347 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1348 pc.local_time_zone_table[i].value = i;
1350 pc.local_time_zone_table[i].name = NULL;
1353 pc.local_time_zone_table[0].name = NULL;
1357 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1358 && ! strcmp (pc.local_time_zone_table[0].name,
1359 pc.local_time_zone_table[1].name))
1361 /* This locale uses the same abbrevation for standard and
1362 daylight times. So if we see that abbreviation, we don't
1363 know whether it's daylight time. */
1364 pc.local_time_zone_table[0].value = -1;
1365 pc.local_time_zone_table[1].name = NULL;
1368 if (yyparse (&pc) != 0)
1371 if (pc.timespec_seen)
1372 *result = pc.seconds;
1375 if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
1376 | (pc.local_zones_seen + pc.zones_seen)))
1379 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
1380 tm.tm_mon = pc.month - 1;
1381 tm.tm_mday = pc.day;
1382 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1384 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1387 tm.tm_min = pc.minutes;
1388 tm.tm_sec = pc.seconds.tv_sec;
1392 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1393 pc.seconds.tv_nsec = 0;
1396 /* Let mktime deduce tm_isdst if we have an absolute time stamp. */
1397 if (pc.dates_seen | pc.days_seen | pc.times_seen)
1400 /* But if the input explicitly specifies local time with or without
1401 DST, give mktime that information. */
1402 if (pc.local_zones_seen)
1403 tm.tm_isdst = pc.local_isdst;
1407 Start = mktime (&tm);
1409 if (! mktime_ok (&tm0, &tm, Start))
1411 if (! pc.zones_seen)
1415 /* Guard against falsely reporting errors near the time_t
1416 boundaries when parsing times in other time zones. For
1417 example, suppose the input string "1969-12-31 23:00:00 -0100",
1418 the current time zone is 8 hours ahead of UTC, and the min
1419 time_t value is 1970-01-01 00:00:00 UTC. Then the min
1420 localtime value is 1970-01-01 08:00:00, and mktime will
1421 therefore fail on 1969-12-31 23:00:00. To work around the
1422 problem, set the time zone to 1 hour behind UTC temporarily
1423 by setting TZ="XXX1:00" and try mktime again. */
1425 long int time_zone = pc.time_zone;
1426 long int abs_time_zone = time_zone < 0 ? - time_zone : time_zone;
1427 long int abs_time_zone_hour = abs_time_zone / 60;
1428 int abs_time_zone_min = abs_time_zone % 60;
1429 char tz1buf[sizeof "XXX+0:00"
1430 + sizeof pc.time_zone * CHAR_BIT / 3];
1431 if (!tz_was_altered)
1432 tz0 = get_tz (tz0buf);
1433 sprintf (tz1buf, "XXX%s%ld:%02d", "-" + (time_zone < 0),
1434 abs_time_zone_hour, abs_time_zone_min);
1435 if (setenv ("TZ", tz1buf, 1) != 0)
1437 tz_was_altered = true;
1439 Start = mktime (&tm);
1440 if (! mktime_ok (&tm0, &tm, Start))
1445 if (pc.days_seen && ! pc.dates_seen)
1447 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1448 + 7 * (pc.day_ordinal
1449 - (0 < pc.day_ordinal
1450 && tm.tm_wday != pc.day_number)));
1452 Start = mktime (&tm);
1453 if (Start == (time_t) -1)
1457 /* Add relative date. */
1458 if (pc.rel.year | pc.rel.month | pc.rel.day)
1460 int year = tm.tm_year + pc.rel.year;
1461 int month = tm.tm_mon + pc.rel.month;
1462 int day = tm.tm_mday + pc.rel.day;
1463 if (((year < tm.tm_year) ^ (pc.rel.year < 0))
1464 | ((month < tm.tm_mon) ^ (pc.rel.month < 0))
1465 | ((day < tm.tm_mday) ^ (pc.rel.day < 0)))
1470 tm.tm_hour = tm0.tm_hour;
1471 tm.tm_min = tm0.tm_min;
1472 tm.tm_sec = tm0.tm_sec;
1473 tm.tm_isdst = tm0.tm_isdst;
1474 Start = mktime (&tm);
1475 if (Start == (time_t) -1)
1479 /* The only "output" of this if-block is an updated Start value,
1480 so this block must follow others that clobber Start. */
1483 long int delta = pc.time_zone * 60;
1485 #ifdef HAVE_TM_GMTOFF
1486 delta -= tm.tm_gmtoff;
1489 struct tm const *gmt = gmtime (&t);
1492 delta -= tm_diff (&tm, gmt);
1495 if ((Start < t1) != (delta < 0))
1496 goto fail; /* time_t overflow */
1500 /* Add relative hours, minutes, and seconds. On hosts that support
1501 leap seconds, ignore the possibility of leap seconds; e.g.,
1502 "+ 10 minutes" adds 600 seconds, even if one of them is a
1503 leap second. Typically this is not what the user wants, but it's
1504 too hard to do it the other way, because the time zone indicator
1505 must be applied before relative times, and if mktime is applied
1506 again the time zone will be lost. */
1508 long int sum_ns = pc.seconds.tv_nsec + pc.rel.ns;
1509 long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
1511 long int d1 = 60 * 60 * pc.rel.hour;
1512 time_t t1 = t0 + d1;
1513 long int d2 = 60 * pc.rel.minutes;
1514 time_t t2 = t1 + d2;
1515 long_time_t d3 = pc.rel.seconds;
1516 long_time_t t3 = t2 + d3;
1517 long int d4 = (sum_ns - normalized_ns) / BILLION;
1518 long_time_t t4 = t3 + d4;
1521 if ((d1 / (60 * 60) ^ pc.rel.hour)
1522 | (d2 / 60 ^ pc.rel.minutes)
1523 | ((t1 < t0) ^ (d1 < 0))
1524 | ((t2 < t1) ^ (d2 < 0))
1525 | ((t3 < t2) ^ (d3 < 0))
1526 | ((t4 < t3) ^ (d4 < 0))
1530 result->tv_sec = t5;
1531 result->tv_nsec = normalized_ns;
1541 ok &= (tz0 ? setenv ("TZ", tz0, 1) : unsetenv ("TZ")) == 0;
1550 main (int ac, char **av)
1554 printf ("Enter date, or blank line to exit.\n\t> ");
1557 buff[BUFSIZ - 1] = '\0';
1558 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1561 struct tm const *tm;
1562 if (! parse_datetime (&d, buff, NULL))
1563 printf ("Bad format - couldn't convert.\n");
1564 else if (! (tm = localtime (&d.tv_sec)))
1566 long int sec = d.tv_sec;
1567 printf ("localtime (%ld) failed\n", sec);
1572 printf ("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1573 tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
1574 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);