2 /* Parse a string into an internal time stamp.
4 Copyright (C) 1999-2000, 2002-2011 Free Software Foundation, Inc.
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>. */
19 /* Originally written by Steven M. Bellovin <smb@research.att.com> while
20 at the University of North Carolina at Chapel Hill. Later tweaked by
21 a couple of people on Usenet. Completely overhauled by Rich $alz
22 <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
24 Modified by Paul Eggert <eggert@twinsun.com> in August 1999 to do
25 the right thing about local DST. Also modified by Paul Eggert
26 <eggert@cs.ucla.edu> in February 2004 to support
27 nanosecond-resolution time stamps, and in October 2004 to support
28 TZ strings in dates. */
30 /* FIXME: Check for arithmetic overflow in all cases, not just
35 #include "parse-datetime.h"
41 /* There's no need to extend the stack, so there's no need to involve
43 #define YYSTACK_USE_ALLOCA 0
45 /* Tell Bison how much stack space is needed. 20 should be plenty for
46 this grammar, which is not right recursive. Beware setting it too
47 high, since that might cause problems on machines whose
48 implementations have lame stack-overflow checking. */
50 #define YYINITDEPTH YYMAXDEPTH
52 /* Since the code of parse-datetime.y is not included in the Emacs executable
53 itself, there is no need to #define static in this file. Even if
54 the code were included in the Emacs executable, it probably
55 wouldn't do any harm to #undef it here; this will only cause
56 problems if we try to write to a static variable, which I don't
57 think this code needs to do. */
70 /* Bison's skeleton tests _STDLIB_H, while some stdlib.h headers
71 use _STDLIB_H_ as witness. Map the latter to the one bison uses. */
72 /* FIXME: this is temporary. Remove when we have a mechanism to ensure
73 that the version we're using is fixed, too. */
79 /* ISDIGIT differs from isdigit, as follows:
80 - Its arg may be any int or unsigned int; it need not be an unsigned char
82 - It's typically faster.
83 POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
84 isdigit unless it's important to use the locale's definition
85 of `digit' even when the host does not conform to POSIX. */
86 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
88 /* Shift A right by B bits portably, by dividing A by 2**B and
89 truncating towards minus infinity. A and B should be free of side
90 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
91 INT_BITS is the number of useful bits in an int. GNU code can
92 assume that INT_BITS is at least 32.
94 ISO C99 says that A >> B is implementation-defined if A < 0. Some
95 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
96 right in the usual way when A < 0, so SHR falls back on division if
97 ordinary A >> B doesn't seem to be the usual signed shift. */
101 : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
103 #define EPOCH_YEAR 1970
104 #define TM_YEAR_BASE 1900
106 #define HOUR(x) ((x) * 60)
108 /* long_time_t is a signed integer type that contains all time_t values. */
109 verify (TYPE_IS_INTEGER (time_t));
110 #if TIME_T_FITS_IN_LONG_INT
111 typedef long int long_time_t;
113 typedef time_t long_time_t;
116 /* The attribute __pure__ was added in gcc 2.96. */
117 #undef _GL_ATTRIBUTE_PURE
118 #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 96)
119 # define _GL_ATTRIBUTE_PURE __attribute__ ((__pure__))
121 # define _GL_ATTRIBUTE_PURE /* empty */
124 /* Lots of this code assumes time_t and time_t-like values fit into
126 verify (TYPE_MINIMUM (long_time_t) <= TYPE_MINIMUM (time_t)
127 && TYPE_MAXIMUM (time_t) <= TYPE_MAXIMUM (long_time_t));
129 /* FIXME: It also assumes that signed integer overflow silently wraps around,
130 but this is not true any more with recent versions of GCC 4. */
132 /* An integer value, and the number of digits in its textual
141 /* An entry in the lexical lookup table. */
149 /* Meridian: am, pm, or 24-hour style. */
150 enum { MERam, MERpm, MER24 };
152 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
154 /* Relative times. */
157 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
167 #if HAVE_COMPOUND_LITERALS
168 # define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
170 static relative_time const RELATIVE_TIME_0;
173 /* Information passed to and from the parser. */
176 /* The input string remaining to be parsed. */
179 /* N, if this is the Nth Tuesday. */
180 long int day_ordinal;
182 /* Day of week; Sunday is 0. */
185 /* tm_isdst flag for the local zone. */
188 /* Time zone, in minutes east of UTC. */
191 /* Style used for time. */
194 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
200 struct timespec seconds; /* includes nanoseconds */
202 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
205 /* Presence or counts of nonterminals of various flavors parsed so far. */
210 size_t local_zones_seen;
215 /* Table of local time zone abbrevations, terminated by a null entry. */
216 table local_time_zone_table[3];
220 static int yylex (union YYSTYPE *, parser_control *);
221 static int yyerror (parser_control const *, char const *);
222 static long int time_zone_hhmm (parser_control *, textint, long int);
224 /* Extract into *PC any date and time info from a string of digits
225 of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
228 digits_to_date_time (parser_control *pc, textint text_int)
230 if (pc->dates_seen && ! pc->year.digits
231 && ! pc->rels_seen && (pc->times_seen || 2 < text_int.digits))
235 if (4 < text_int.digits)
238 pc->day = text_int.value % 100;
239 pc->month = (text_int.value / 100) % 100;
240 pc->year.value = text_int.value / 10000;
241 pc->year.digits = text_int.digits - 4;
246 if (text_int.digits <= 2)
248 pc->hour = text_int.value;
253 pc->hour = text_int.value / 100;
254 pc->minutes = text_int.value % 100;
256 pc->seconds.tv_sec = 0;
257 pc->seconds.tv_nsec = 0;
258 pc->meridian = MER24;
263 /* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1). */
265 apply_relative_time (parser_control *pc, relative_time rel, int factor)
267 pc->rel.ns += factor * rel.ns;
268 pc->rel.seconds += factor * rel.seconds;
269 pc->rel.minutes += factor * rel.minutes;
270 pc->rel.hour += factor * rel.hour;
271 pc->rel.day += factor * rel.day;
272 pc->rel.month += factor * rel.month;
273 pc->rel.year += factor * rel.year;
274 pc->rels_seen = true;
277 /* Set PC-> hour, minutes, seconds and nanoseconds members from arguments. */
279 set_hhmmss (parser_control *pc, long int hour, long int minutes,
280 time_t sec, long int nsec)
283 pc->minutes = minutes;
284 pc->seconds.tv_sec = sec;
285 pc->seconds.tv_nsec = nsec;
290 /* We want a reentrant parser, even if the TZ manipulation and the calls to
291 localtime and gmtime are not reentrant. */
293 %parse-param { parser_control *pc }
294 %lex-param { parser_control *pc }
296 /* This grammar has 20 shift/reduce conflicts. */
303 struct timespec timespec;
309 %token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
310 %token <intval> tDAY_UNIT tDAY_SHIFT
312 %token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
313 %token <intval> tMONTH tORDINAL tZONE
315 %token <textintval> tSNUMBER tUNUMBER
316 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
318 %type <intval> o_colon_minutes o_merid
319 %type <timespec> seconds signed_seconds unsigned_seconds
321 %type <rel> relunit relunit_snumber dayshift
334 pc->timespec_seen = true;
345 { pc->times_seen++; }
347 { pc->local_zones_seen++; }
349 { pc->zones_seen++; }
351 { pc->dates_seen++; }
362 set_hhmmss (pc, $1.value, 0, 0, 0);
365 | tUNUMBER ':' tUNUMBER o_merid
367 set_hhmmss (pc, $1.value, $3.value, 0, 0);
370 | tUNUMBER ':' tUNUMBER tSNUMBER o_colon_minutes
372 set_hhmmss (pc, $1.value, $3.value, 0, 0);
373 pc->meridian = MER24;
375 pc->time_zone = time_zone_hhmm (pc, $4, $5);
377 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
379 set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
382 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER o_colon_minutes
384 set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
385 pc->meridian = MER24;
387 pc->time_zone = time_zone_hhmm (pc, $6, $7);
394 pc->local_isdst = $1;
395 pc->dsts_seen += (0 < $1);
400 pc->dsts_seen += (0 < $1) + 1;
406 { pc->time_zone = $1; }
407 | tZONE relunit_snumber
408 { pc->time_zone = $1;
409 apply_relative_time (pc, $2, 1); }
410 | tZONE tSNUMBER o_colon_minutes
411 { pc->time_zone = $1 + time_zone_hhmm (pc, $2, $3); }
413 { pc->time_zone = $1 + 60; }
415 { pc->time_zone = $1 + 60; }
431 pc->day_ordinal = $1;
436 pc->day_ordinal = $1.value;
442 tUNUMBER '/' tUNUMBER
444 pc->month = $1.value;
447 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
449 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
450 otherwise as MM/DD/YY.
451 The goal in recognizing YYYY/MM/DD is solely to support legacy
452 machine-generated dates like those in an RCS log listing. If
453 you want portability, use the ISO 8601 format. */
457 pc->month = $3.value;
462 pc->month = $1.value;
467 | tUNUMBER tSNUMBER tSNUMBER
469 /* ISO 8601 format. YYYY-MM-DD. */
471 pc->month = -$2.value;
474 | tUNUMBER tMONTH tSNUMBER
476 /* e.g. 17-JUN-1992. */
479 pc->year.value = -$3.value;
480 pc->year.digits = $3.digits;
482 | tMONTH tSNUMBER tSNUMBER
484 /* e.g. JUN-17-1992. */
487 pc->year.value = -$3.value;
488 pc->year.digits = $3.digits;
495 | tMONTH tUNUMBER ',' tUNUMBER
506 | tUNUMBER tMONTH tUNUMBER
516 { apply_relative_time (pc, $1, -1); }
518 { apply_relative_time (pc, $1, 1); }
520 { apply_relative_time (pc, $1, 1); }
525 { $$ = RELATIVE_TIME_0; $$.year = $1; }
526 | tUNUMBER tYEAR_UNIT
527 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
529 { $$ = RELATIVE_TIME_0; $$.year = 1; }
530 | tORDINAL tMONTH_UNIT
531 { $$ = RELATIVE_TIME_0; $$.month = $1; }
532 | tUNUMBER tMONTH_UNIT
533 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
535 { $$ = RELATIVE_TIME_0; $$.month = 1; }
537 { $$ = RELATIVE_TIME_0; $$.day = $1 * $2; }
539 { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
541 { $$ = RELATIVE_TIME_0; $$.day = $1; }
542 | tORDINAL tHOUR_UNIT
543 { $$ = RELATIVE_TIME_0; $$.hour = $1; }
544 | tUNUMBER tHOUR_UNIT
545 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
547 { $$ = RELATIVE_TIME_0; $$.hour = 1; }
548 | tORDINAL tMINUTE_UNIT
549 { $$ = RELATIVE_TIME_0; $$.minutes = $1; }
550 | tUNUMBER tMINUTE_UNIT
551 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
553 { $$ = RELATIVE_TIME_0; $$.minutes = 1; }
555 { $$ = RELATIVE_TIME_0; $$.seconds = $1; }
557 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
558 | tSDECIMAL_NUMBER tSEC_UNIT
559 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
560 | tUDECIMAL_NUMBER tSEC_UNIT
561 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
563 { $$ = RELATIVE_TIME_0; $$.seconds = 1; }
569 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
570 | tSNUMBER tMONTH_UNIT
571 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
573 { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
574 | tSNUMBER tHOUR_UNIT
575 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
576 | tSNUMBER tMINUTE_UNIT
577 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
579 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
584 { $$ = RELATIVE_TIME_0; $$.day = $1; }
587 seconds: signed_seconds | unsigned_seconds;
592 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
598 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
603 { digits_to_date_time (pc, $1); }
607 tUNUMBER relunit_snumber
609 /* Hybrid all-digit and relative offset, so that we accept e.g.,
610 "YYYYMMDD +N days" as well as "YYYYMMDD N days". */
611 digits_to_date_time (pc, $1);
612 apply_relative_time (pc, $2, 1);
632 static table const meridian_table[] =
634 { "AM", tMERIDIAN, MERam },
635 { "A.M.", tMERIDIAN, MERam },
636 { "PM", tMERIDIAN, MERpm },
637 { "P.M.", tMERIDIAN, MERpm },
641 static table const dst_table[] =
646 static table const month_and_day_table[] =
648 { "JANUARY", tMONTH, 1 },
649 { "FEBRUARY", tMONTH, 2 },
650 { "MARCH", tMONTH, 3 },
651 { "APRIL", tMONTH, 4 },
652 { "MAY", tMONTH, 5 },
653 { "JUNE", tMONTH, 6 },
654 { "JULY", tMONTH, 7 },
655 { "AUGUST", tMONTH, 8 },
656 { "SEPTEMBER",tMONTH, 9 },
657 { "SEPT", tMONTH, 9 },
658 { "OCTOBER", tMONTH, 10 },
659 { "NOVEMBER", tMONTH, 11 },
660 { "DECEMBER", tMONTH, 12 },
661 { "SUNDAY", tDAY, 0 },
662 { "MONDAY", tDAY, 1 },
663 { "TUESDAY", tDAY, 2 },
665 { "WEDNESDAY",tDAY, 3 },
666 { "WEDNES", tDAY, 3 },
667 { "THURSDAY", tDAY, 4 },
669 { "THURS", tDAY, 4 },
670 { "FRIDAY", tDAY, 5 },
671 { "SATURDAY", tDAY, 6 },
675 static table const time_units_table[] =
677 { "YEAR", tYEAR_UNIT, 1 },
678 { "MONTH", tMONTH_UNIT, 1 },
679 { "FORTNIGHT",tDAY_UNIT, 14 },
680 { "WEEK", tDAY_UNIT, 7 },
681 { "DAY", tDAY_UNIT, 1 },
682 { "HOUR", tHOUR_UNIT, 1 },
683 { "MINUTE", tMINUTE_UNIT, 1 },
684 { "MIN", tMINUTE_UNIT, 1 },
685 { "SECOND", tSEC_UNIT, 1 },
686 { "SEC", tSEC_UNIT, 1 },
690 /* Assorted relative-time words. */
691 static table const relative_time_table[] =
693 { "TOMORROW", tDAY_SHIFT, 1 },
694 { "YESTERDAY",tDAY_SHIFT, -1 },
695 { "TODAY", tDAY_SHIFT, 0 },
696 { "NOW", tDAY_SHIFT, 0 },
697 { "LAST", tORDINAL, -1 },
698 { "THIS", tORDINAL, 0 },
699 { "NEXT", tORDINAL, 1 },
700 { "FIRST", tORDINAL, 1 },
701 /*{ "SECOND", tORDINAL, 2 }, */
702 { "THIRD", tORDINAL, 3 },
703 { "FOURTH", tORDINAL, 4 },
704 { "FIFTH", tORDINAL, 5 },
705 { "SIXTH", tORDINAL, 6 },
706 { "SEVENTH", tORDINAL, 7 },
707 { "EIGHTH", tORDINAL, 8 },
708 { "NINTH", tORDINAL, 9 },
709 { "TENTH", tORDINAL, 10 },
710 { "ELEVENTH", tORDINAL, 11 },
711 { "TWELFTH", tORDINAL, 12 },
716 /* The universal time zone table. These labels can be used even for
717 time stamps that would not otherwise be valid, e.g., GMT time
718 stamps in London during summer. */
719 static table const universal_time_zone_table[] =
721 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
722 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
723 { "UTC", tZONE, HOUR ( 0) },
727 /* The time zone table. This table is necessarily incomplete, as time
728 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
729 as Eastern time in Australia, not as US Eastern Standard Time.
730 You cannot rely on parse_datetime to handle arbitrary time zone
731 abbreviations; use numeric abbreviations like `-0500' instead. */
732 static table const time_zone_table[] =
734 { "WET", tZONE, HOUR ( 0) }, /* Western European */
735 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
736 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
737 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
738 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
739 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
740 { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
741 { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
742 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
743 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
744 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
745 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
746 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
747 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
748 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
749 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
750 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
751 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
752 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
753 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
754 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
755 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
756 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
757 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
758 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
759 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
760 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
761 { "CET", tZONE, HOUR ( 1) }, /* Central European */
762 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
763 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
764 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
765 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
766 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
767 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
768 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
769 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
770 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
771 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
772 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
773 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
774 { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
775 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
776 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
777 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
778 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
779 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
780 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
784 /* Military time zone table. */
785 static table const military_table[] =
787 { "A", tZONE, -HOUR ( 1) },
788 { "B", tZONE, -HOUR ( 2) },
789 { "C", tZONE, -HOUR ( 3) },
790 { "D", tZONE, -HOUR ( 4) },
791 { "E", tZONE, -HOUR ( 5) },
792 { "F", tZONE, -HOUR ( 6) },
793 { "G", tZONE, -HOUR ( 7) },
794 { "H", tZONE, -HOUR ( 8) },
795 { "I", tZONE, -HOUR ( 9) },
796 { "K", tZONE, -HOUR (10) },
797 { "L", tZONE, -HOUR (11) },
798 { "M", tZONE, -HOUR (12) },
799 { "N", tZONE, HOUR ( 1) },
800 { "O", tZONE, HOUR ( 2) },
801 { "P", tZONE, HOUR ( 3) },
802 { "Q", tZONE, HOUR ( 4) },
803 { "R", tZONE, HOUR ( 5) },
804 { "S", tZONE, HOUR ( 6) },
805 { "T", tZONE, HOUR ( 7) },
806 { "U", tZONE, HOUR ( 8) },
807 { "V", tZONE, HOUR ( 9) },
808 { "W", tZONE, HOUR (10) },
809 { "X", tZONE, HOUR (11) },
810 { "Y", tZONE, HOUR (12) },
811 { "Z", tZONE, HOUR ( 0) },
817 /* Convert a time zone expressed as HH:MM into an integer count of
818 minutes. If MM is negative, then S is of the form HHMM and needs
819 to be picked apart; otherwise, S is of the form HH. As specified in
820 http://www.opengroup.org/susv3xbd/xbd_chap08.html#tag_08_03, allow
821 only valid TZ range, and consider first two digits as hours, if no
822 minutes specified. */
825 time_zone_hhmm (parser_control *pc, textint s, long int mm)
829 /* If the length of S is 1 or 2 and no minutes are specified,
830 interpret it as a number of hours. */
831 if (s.digits <= 2 && mm < 0)
835 n_minutes = (s.value / 100) * 60 + s.value % 100;
837 n_minutes = s.value * 60 + (s.negative ? -mm : mm);
839 /* If the absolute number of minutes is larger than 24 hours,
840 arrange to reject it by incrementing pc->zones_seen. Thus,
841 we allow only values in the range UTC-24:00 to UTC+24:00. */
842 if (24 * 60 < abs (n_minutes))
849 to_hour (long int hours, int meridian)
853 default: /* Pacify GCC. */
855 return 0 <= hours && hours < 24 ? hours : -1;
857 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
859 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
864 to_year (textint textyear)
866 long int year = textyear.value;
871 /* XPG4 suggests that years 00-68 map to 2000-2068, and
872 years 69-99 map to 1969-1999. */
873 else if (textyear.digits == 2)
874 year += year < 69 ? 2000 : 1900;
879 static table const * _GL_ATTRIBUTE_PURE
880 lookup_zone (parser_control const *pc, char const *name)
884 for (tp = universal_time_zone_table; tp->name; tp++)
885 if (strcmp (name, tp->name) == 0)
888 /* Try local zone abbreviations before those in time_zone_table, as
889 the local ones are more likely to be right. */
890 for (tp = pc->local_time_zone_table; tp->name; tp++)
891 if (strcmp (name, tp->name) == 0)
894 for (tp = time_zone_table; tp->name; tp++)
895 if (strcmp (name, tp->name) == 0)
902 /* Yield the difference between *A and *B,
903 measured in seconds, ignoring leap seconds.
904 The body of this function is taken directly from the GNU C Library;
905 see src/strftime.c. */
907 tm_diff (struct tm const *a, struct tm const *b)
909 /* Compute intervening leap days correctly even if year is negative.
910 Take care to avoid int overflow in leap day calculations. */
911 int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
912 int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
913 int a100 = a4 / 25 - (a4 % 25 < 0);
914 int b100 = b4 / 25 - (b4 % 25 < 0);
915 int a400 = SHR (a100, 2);
916 int b400 = SHR (b100, 2);
917 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
918 long int ayear = a->tm_year;
919 long int years = ayear - b->tm_year;
920 long int days = (365 * years + intervening_leap_days
921 + (a->tm_yday - b->tm_yday));
922 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
923 + (a->tm_min - b->tm_min))
924 + (a->tm_sec - b->tm_sec));
926 #endif /* ! HAVE_TM_GMTOFF */
929 lookup_word (parser_control const *pc, char *word)
938 /* Make it uppercase. */
939 for (p = word; *p; p++)
941 unsigned char ch = *p;
945 for (tp = meridian_table; tp->name; tp++)
946 if (strcmp (word, tp->name) == 0)
949 /* See if we have an abbreviation for a month. */
950 wordlen = strlen (word);
951 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
953 for (tp = month_and_day_table; tp->name; tp++)
954 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
957 if ((tp = lookup_zone (pc, word)))
960 if (strcmp (word, dst_table[0].name) == 0)
963 for (tp = time_units_table; tp->name; tp++)
964 if (strcmp (word, tp->name) == 0)
967 /* Strip off any plural and try the units table again. */
968 if (word[wordlen - 1] == 'S')
970 word[wordlen - 1] = '\0';
971 for (tp = time_units_table; tp->name; tp++)
972 if (strcmp (word, tp->name) == 0)
974 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
977 for (tp = relative_time_table; tp->name; tp++)
978 if (strcmp (word, tp->name) == 0)
981 /* Military time zones. */
983 for (tp = military_table; tp->name; tp++)
984 if (word[0] == tp->name[0])
987 /* Drop out any periods and try the time zone table again. */
988 for (period_found = false, p = q = word; (*p = *q); q++)
993 if (period_found && (tp = lookup_zone (pc, word)))
1000 yylex (YYSTYPE *lvalp, parser_control *pc)
1007 while (c = *pc->input, c_isspace (c))
1010 if (ISDIGIT (c) || c == '-' || c == '+')
1014 unsigned long int value;
1015 if (c == '-' || c == '+')
1017 sign = c == '-' ? -1 : 1;
1018 while (c = *++pc->input, c_isspace (c))
1021 /* skip the '-' sign */
1027 for (value = 0; ; value *= 10)
1029 unsigned long int value1 = value + (c - '0');
1036 if (ULONG_MAX / 10 < value)
1039 if ((c == '.' || c == ',') && ISDIGIT (p[1]))
1044 unsigned long int value1;
1046 /* Check for overflow when converting value to time_t. */
1061 if (value != value1)
1064 /* Accumulate fraction, to ns precision. */
1067 for (digits = 2; digits <= LOG10_BILLION; digits++)
1074 /* Skip excess digits, truncating toward -Infinity. */
1076 for (; ISDIGIT (*p); p++)
1082 while (ISDIGIT (*p))
1085 /* Adjust to the timespec convention, which is that
1086 tv_nsec is always a positive offset even if tv_sec is
1096 lvalp->timespec.tv_sec = s;
1097 lvalp->timespec.tv_nsec = ns;
1099 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
1103 lvalp->textintval.negative = sign < 0;
1106 lvalp->textintval.value = - value;
1107 if (0 < lvalp->textintval.value)
1112 lvalp->textintval.value = value;
1113 if (lvalp->textintval.value < 0)
1116 lvalp->textintval.digits = p - pc->input;
1118 return sign ? tSNUMBER : tUNUMBER;
1130 if (p < buff + sizeof buff - 1)
1134 while (c_isalpha (c) || c == '.');
1137 tp = lookup_word (pc, buff);
1140 lvalp->intval = tp->value;
1145 return *pc->input++;
1161 /* Do nothing if the parser reports an error. */
1163 yyerror (parser_control const *pc _GL_UNUSED,
1164 char const *s _GL_UNUSED)
1169 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1170 passing it to mktime, return true if it's OK that mktime returned T.
1171 It's not OK if *TM0 has out-of-range members. */
1174 mktime_ok (struct tm const *tm0, struct tm const *tm1, time_t t)
1176 if (t == (time_t) -1)
1178 /* Guard against falsely reporting an error when parsing a time
1179 stamp that happens to equal (time_t) -1, on a host that
1180 supports such a time stamp. */
1181 tm1 = localtime (&t);
1186 return ! ((tm0->tm_sec ^ tm1->tm_sec)
1187 | (tm0->tm_min ^ tm1->tm_min)
1188 | (tm0->tm_hour ^ tm1->tm_hour)
1189 | (tm0->tm_mday ^ tm1->tm_mday)
1190 | (tm0->tm_mon ^ tm1->tm_mon)
1191 | (tm0->tm_year ^ tm1->tm_year));
1194 /* A reasonable upper bound for the size of ordinary TZ strings.
1195 Use heap allocation if TZ's length exceeds this. */
1196 enum { TZBUFSIZE = 100 };
1198 /* Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
1201 get_tz (char tzbuf[TZBUFSIZE])
1203 char *tz = getenv ("TZ");
1206 size_t tzsize = strlen (tz) + 1;
1207 tz = (tzsize <= TZBUFSIZE
1208 ? memcpy (tzbuf, tz, tzsize)
1209 : xmemdup (tz, tzsize));
1214 /* Parse a date/time string, storing the resulting time value into *RESULT.
1215 The string itself is pointed to by P. Return true if successful.
1216 P can be an incomplete or relative time specification; if so, use
1217 *NOW as the basis for the returned time. */
1219 parse_datetime (struct timespec *result, char const *p,
1220 struct timespec const *now)
1224 struct tm const *tmp;
1228 struct timespec gettime_buffer;
1230 bool tz_was_altered = false;
1232 char tz0buf[TZBUFSIZE];
1237 gettime (&gettime_buffer);
1238 now = &gettime_buffer;
1241 Start = now->tv_sec;
1242 Start_ns = now->tv_nsec;
1244 tmp = localtime (&now->tv_sec);
1248 while (c = *p, c_isspace (c))
1251 if (strncmp (p, "TZ=\"", 4) == 0)
1253 char const *tzbase = p + 4;
1257 for (s = tzbase; *s; s++, tzsize++)
1261 if (! (*s == '\\' || *s == '"'))
1268 char tz1buf[TZBUFSIZE];
1269 bool large_tz = TZBUFSIZE < tzsize;
1271 /* Free tz0, in case this is the 2nd or subsequent time through. */
1273 tz0 = get_tz (tz0buf);
1274 z = tz1 = large_tz ? xmalloc (tzsize) : tz1buf;
1275 for (s = tzbase; *s != '"'; s++)
1276 *z++ = *(s += *s == '\\');
1278 setenv_ok = setenv ("TZ", tz1, 1) == 0;
1283 tz_was_altered = true;
1288 /* As documented, be careful to treat the empty string just like
1289 a date string of "0". Without this, an empty string would be
1290 declared invalid when parsed during a DST transition. */
1295 pc.year.value = tmp->tm_year;
1296 pc.year.value += TM_YEAR_BASE;
1298 pc.month = tmp->tm_mon + 1;
1299 pc.day = tmp->tm_mday;
1300 pc.hour = tmp->tm_hour;
1301 pc.minutes = tmp->tm_min;
1302 pc.seconds.tv_sec = tmp->tm_sec;
1303 pc.seconds.tv_nsec = Start_ns;
1304 tm.tm_isdst = tmp->tm_isdst;
1306 pc.meridian = MER24;
1307 pc.rel = RELATIVE_TIME_0;
1308 pc.timespec_seen = false;
1309 pc.rels_seen = false;
1313 pc.local_zones_seen = 0;
1317 #if HAVE_STRUCT_TM_TM_ZONE
1318 pc.local_time_zone_table[0].name = tmp->tm_zone;
1319 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1320 pc.local_time_zone_table[0].value = tmp->tm_isdst;
1321 pc.local_time_zone_table[1].name = NULL;
1323 /* Probe the names used in the next three calendar quarters, looking
1324 for a tm_isdst different from the one we already have. */
1327 for (quarter = 1; quarter <= 3; quarter++)
1329 time_t probe = Start + quarter * (90 * 24 * 60 * 60);
1330 struct tm const *probe_tm = localtime (&probe);
1331 if (probe_tm && probe_tm->tm_zone
1332 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
1335 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
1336 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1337 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
1338 pc.local_time_zone_table[2].name = NULL;
1347 # if !HAVE_DECL_TZNAME
1348 extern char *tzname[];
1351 for (i = 0; i < 2; i++)
1353 pc.local_time_zone_table[i].name = tzname[i];
1354 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1355 pc.local_time_zone_table[i].value = i;
1357 pc.local_time_zone_table[i].name = NULL;
1360 pc.local_time_zone_table[0].name = NULL;
1364 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1365 && ! strcmp (pc.local_time_zone_table[0].name,
1366 pc.local_time_zone_table[1].name))
1368 /* This locale uses the same abbrevation for standard and
1369 daylight times. So if we see that abbreviation, we don't
1370 know whether it's daylight time. */
1371 pc.local_time_zone_table[0].value = -1;
1372 pc.local_time_zone_table[1].name = NULL;
1375 if (yyparse (&pc) != 0)
1378 if (pc.timespec_seen)
1379 *result = pc.seconds;
1382 if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
1383 | (pc.local_zones_seen + pc.zones_seen)))
1386 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
1387 tm.tm_mon = pc.month - 1;
1388 tm.tm_mday = pc.day;
1389 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1391 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1394 tm.tm_min = pc.minutes;
1395 tm.tm_sec = pc.seconds.tv_sec;
1399 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1400 pc.seconds.tv_nsec = 0;
1403 /* Let mktime deduce tm_isdst if we have an absolute time stamp. */
1404 if (pc.dates_seen | pc.days_seen | pc.times_seen)
1407 /* But if the input explicitly specifies local time with or without
1408 DST, give mktime that information. */
1409 if (pc.local_zones_seen)
1410 tm.tm_isdst = pc.local_isdst;
1414 Start = mktime (&tm);
1416 if (! mktime_ok (&tm0, &tm, Start))
1418 if (! pc.zones_seen)
1422 /* Guard against falsely reporting errors near the time_t
1423 boundaries when parsing times in other time zones. For
1424 example, suppose the input string "1969-12-31 23:00:00 -0100",
1425 the current time zone is 8 hours ahead of UTC, and the min
1426 time_t value is 1970-01-01 00:00:00 UTC. Then the min
1427 localtime value is 1970-01-01 08:00:00, and mktime will
1428 therefore fail on 1969-12-31 23:00:00. To work around the
1429 problem, set the time zone to 1 hour behind UTC temporarily
1430 by setting TZ="XXX1:00" and try mktime again. */
1432 long int time_zone = pc.time_zone;
1433 long int abs_time_zone = time_zone < 0 ? - time_zone : time_zone;
1434 long int abs_time_zone_hour = abs_time_zone / 60;
1435 int abs_time_zone_min = abs_time_zone % 60;
1436 char tz1buf[sizeof "XXX+0:00"
1437 + sizeof pc.time_zone * CHAR_BIT / 3];
1438 if (!tz_was_altered)
1439 tz0 = get_tz (tz0buf);
1440 sprintf (tz1buf, "XXX%s%ld:%02d", "-" + (time_zone < 0),
1441 abs_time_zone_hour, abs_time_zone_min);
1442 if (setenv ("TZ", tz1buf, 1) != 0)
1444 tz_was_altered = true;
1446 Start = mktime (&tm);
1447 if (! mktime_ok (&tm0, &tm, Start))
1452 if (pc.days_seen && ! pc.dates_seen)
1454 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1455 + 7 * (pc.day_ordinal
1456 - (0 < pc.day_ordinal
1457 && tm.tm_wday != pc.day_number)));
1459 Start = mktime (&tm);
1460 if (Start == (time_t) -1)
1464 /* Add relative date. */
1465 if (pc.rel.year | pc.rel.month | pc.rel.day)
1467 int year = tm.tm_year + pc.rel.year;
1468 int month = tm.tm_mon + pc.rel.month;
1469 int day = tm.tm_mday + pc.rel.day;
1470 if (((year < tm.tm_year) ^ (pc.rel.year < 0))
1471 | ((month < tm.tm_mon) ^ (pc.rel.month < 0))
1472 | ((day < tm.tm_mday) ^ (pc.rel.day < 0)))
1477 tm.tm_hour = tm0.tm_hour;
1478 tm.tm_min = tm0.tm_min;
1479 tm.tm_sec = tm0.tm_sec;
1480 tm.tm_isdst = tm0.tm_isdst;
1481 Start = mktime (&tm);
1482 if (Start == (time_t) -1)
1486 /* The only "output" of this if-block is an updated Start value,
1487 so this block must follow others that clobber Start. */
1490 long int delta = pc.time_zone * 60;
1492 #ifdef HAVE_TM_GMTOFF
1493 delta -= tm.tm_gmtoff;
1496 struct tm const *gmt = gmtime (&t);
1499 delta -= tm_diff (&tm, gmt);
1502 if ((Start < t1) != (delta < 0))
1503 goto fail; /* time_t overflow */
1507 /* Add relative hours, minutes, and seconds. On hosts that support
1508 leap seconds, ignore the possibility of leap seconds; e.g.,
1509 "+ 10 minutes" adds 600 seconds, even if one of them is a
1510 leap second. Typically this is not what the user wants, but it's
1511 too hard to do it the other way, because the time zone indicator
1512 must be applied before relative times, and if mktime is applied
1513 again the time zone will be lost. */
1515 long int sum_ns = pc.seconds.tv_nsec + pc.rel.ns;
1516 long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
1518 long int d1 = 60 * 60 * pc.rel.hour;
1519 time_t t1 = t0 + d1;
1520 long int d2 = 60 * pc.rel.minutes;
1521 time_t t2 = t1 + d2;
1522 long_time_t d3 = pc.rel.seconds;
1523 long_time_t t3 = t2 + d3;
1524 long int d4 = (sum_ns - normalized_ns) / BILLION;
1525 long_time_t t4 = t3 + d4;
1528 if ((d1 / (60 * 60) ^ pc.rel.hour)
1529 | (d2 / 60 ^ pc.rel.minutes)
1530 | ((t1 < t0) ^ (d1 < 0))
1531 | ((t2 < t1) ^ (d2 < 0))
1532 | ((t3 < t2) ^ (d3 < 0))
1533 | ((t4 < t3) ^ (d4 < 0))
1537 result->tv_sec = t5;
1538 result->tv_nsec = normalized_ns;
1548 ok &= (tz0 ? setenv ("TZ", tz0, 1) : unsetenv ("TZ")) == 0;
1557 main (int ac, char **av)
1561 printf ("Enter date, or blank line to exit.\n\t> ");
1564 buff[BUFSIZ - 1] = '\0';
1565 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1568 struct tm const *tm;
1569 if (! parse_datetime (&d, buff, NULL))
1570 printf ("Bad format - couldn't convert.\n");
1571 else if (! (tm = localtime (&d.tv_sec)))
1573 long int sec = d.tv_sec;
1574 printf ("localtime (%ld) failed\n", sec);
1579 printf ("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1580 tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
1581 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);