2 /* Parse a string into an internal time stamp.
4 Copyright (C) 1999-2000, 2002-2015 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 /* Convert a possibly-signed character to an unsigned character. This is
117 a bit safer than casting to unsigned char, since it catches some type
118 errors that the cast doesn't. */
119 static unsigned char to_uchar (char ch) { return ch; }
121 /* Lots of this code assumes time_t and time_t-like values fit into
123 verify (TYPE_MINIMUM (long_time_t) <= TYPE_MINIMUM (time_t)
124 && TYPE_MAXIMUM (time_t) <= TYPE_MAXIMUM (long_time_t));
126 /* FIXME: It also assumes that signed integer overflow silently wraps around,
127 but this is not true any more with recent versions of GCC 4. */
129 /* An integer value, and the number of digits in its textual
138 /* An entry in the lexical lookup table. */
146 /* Meridian: am, pm, or 24-hour style. */
147 enum { MERam, MERpm, MER24 };
149 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
151 /* Relative times. */
154 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
164 #if HAVE_COMPOUND_LITERALS
165 # define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
167 static relative_time const RELATIVE_TIME_0;
170 /* Information passed to and from the parser. */
173 /* The input string remaining to be parsed. */
176 /* N, if this is the Nth Tuesday. */
177 long int day_ordinal;
179 /* Day of week; Sunday is 0. */
182 /* tm_isdst flag for the local zone. */
185 /* Time zone, in minutes east of UTC. */
188 /* Style used for time. */
191 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
197 struct timespec seconds; /* includes nanoseconds */
199 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
202 /* Presence or counts of nonterminals of various flavors parsed so far. */
207 size_t local_zones_seen;
212 /* Table of local time zone abbreviations, terminated by a null entry. */
213 table local_time_zone_table[3];
217 static int yylex (union YYSTYPE *, parser_control *);
218 static int yyerror (parser_control const *, char const *);
219 static long int time_zone_hhmm (parser_control *, textint, long int);
221 /* Extract into *PC any date and time info from a string of digits
222 of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
225 digits_to_date_time (parser_control *pc, textint text_int)
227 if (pc->dates_seen && ! pc->year.digits
228 && ! pc->rels_seen && (pc->times_seen || 2 < text_int.digits))
232 if (4 < text_int.digits)
235 pc->day = text_int.value % 100;
236 pc->month = (text_int.value / 100) % 100;
237 pc->year.value = text_int.value / 10000;
238 pc->year.digits = text_int.digits - 4;
243 if (text_int.digits <= 2)
245 pc->hour = text_int.value;
250 pc->hour = text_int.value / 100;
251 pc->minutes = text_int.value % 100;
253 pc->seconds.tv_sec = 0;
254 pc->seconds.tv_nsec = 0;
255 pc->meridian = MER24;
260 /* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1). */
262 apply_relative_time (parser_control *pc, relative_time rel, int factor)
264 pc->rel.ns += factor * rel.ns;
265 pc->rel.seconds += factor * rel.seconds;
266 pc->rel.minutes += factor * rel.minutes;
267 pc->rel.hour += factor * rel.hour;
268 pc->rel.day += factor * rel.day;
269 pc->rel.month += factor * rel.month;
270 pc->rel.year += factor * rel.year;
271 pc->rels_seen = true;
274 /* Set PC-> hour, minutes, seconds and nanoseconds members from arguments. */
276 set_hhmmss (parser_control *pc, long int hour, long int minutes,
277 time_t sec, long int nsec)
280 pc->minutes = minutes;
281 pc->seconds.tv_sec = sec;
282 pc->seconds.tv_nsec = nsec;
287 /* We want a reentrant parser, even if the TZ manipulation and the calls to
288 localtime and gmtime are not reentrant. */
290 %parse-param { parser_control *pc }
291 %lex-param { parser_control *pc }
293 /* This grammar has 31 shift/reduce conflicts. */
300 struct timespec timespec;
307 %token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
308 %token <intval> tDAY_UNIT tDAY_SHIFT
310 %token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
311 %token <intval> tMONTH tORDINAL tZONE
313 %token <textintval> tSNUMBER tUNUMBER
314 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
316 %type <intval> o_colon_minutes
317 %type <timespec> seconds signed_seconds unsigned_seconds
319 %type <rel> relunit relunit_snumber dayshift
332 pc->timespec_seen = true;
343 { pc->times_seen++; pc->dates_seen++; }
345 { pc->times_seen++; }
347 { pc->local_zones_seen++; }
349 { pc->zones_seen++; }
351 { pc->dates_seen++; }
364 iso_8601_date 'T' iso_8601_time
370 set_hhmmss (pc, $1.value, 0, 0, 0);
373 | tUNUMBER ':' tUNUMBER tMERIDIAN
375 set_hhmmss (pc, $1.value, $3.value, 0, 0);
378 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tMERIDIAN
380 set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
389 set_hhmmss (pc, $1.value, 0, 0, 0);
390 pc->meridian = MER24;
392 | tUNUMBER ':' tUNUMBER o_zone_offset
394 set_hhmmss (pc, $1.value, $3.value, 0, 0);
395 pc->meridian = MER24;
397 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_zone_offset
399 set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
400 pc->meridian = MER24;
410 tSNUMBER o_colon_minutes
413 pc->time_zone = time_zone_hhmm (pc, $1, $2);
420 pc->local_isdst = $1;
421 pc->dsts_seen += (0 < $1);
426 pc->dsts_seen += (0 < $1) + 1;
430 /* Note 'T' is a special case, as it is used as the separator in ISO
431 8601 date and time of day representation. */
434 { pc->time_zone = $1; }
436 { pc->time_zone = HOUR(7); }
437 | tZONE relunit_snumber
438 { pc->time_zone = $1;
439 apply_relative_time (pc, $2, 1); }
440 | 'T' relunit_snumber
441 { pc->time_zone = HOUR(7);
442 apply_relative_time (pc, $2, 1); }
443 | tZONE tSNUMBER o_colon_minutes
444 { pc->time_zone = $1 + time_zone_hhmm (pc, $2, $3); }
446 { pc->time_zone = $1 + 60; }
448 { pc->time_zone = $1 + 60; }
464 pc->day_ordinal = $1;
469 pc->day_ordinal = $1.value;
475 tUNUMBER '/' tUNUMBER
477 pc->month = $1.value;
480 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
482 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
483 otherwise as MM/DD/YY.
484 The goal in recognizing YYYY/MM/DD is solely to support legacy
485 machine-generated dates like those in an RCS log listing. If
486 you want portability, use the ISO 8601 format. */
490 pc->month = $3.value;
495 pc->month = $1.value;
500 | tUNUMBER tMONTH tSNUMBER
502 /* e.g. 17-JUN-1992. */
505 pc->year.value = -$3.value;
506 pc->year.digits = $3.digits;
508 | tMONTH tSNUMBER tSNUMBER
510 /* e.g. JUN-17-1992. */
513 pc->year.value = -$3.value;
514 pc->year.digits = $3.digits;
521 | tMONTH tUNUMBER ',' tUNUMBER
532 | tUNUMBER tMONTH tUNUMBER
542 tUNUMBER tSNUMBER tSNUMBER
544 /* ISO 8601 format. YYYY-MM-DD. */
546 pc->month = -$2.value;
553 { apply_relative_time (pc, $1, $2); }
555 { apply_relative_time (pc, $1, 1); }
557 { apply_relative_time (pc, $1, 1); }
562 { $$ = RELATIVE_TIME_0; $$.year = $1; }
563 | tUNUMBER tYEAR_UNIT
564 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
566 { $$ = RELATIVE_TIME_0; $$.year = 1; }
567 | tORDINAL tMONTH_UNIT
568 { $$ = RELATIVE_TIME_0; $$.month = $1; }
569 | tUNUMBER tMONTH_UNIT
570 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
572 { $$ = RELATIVE_TIME_0; $$.month = 1; }
574 { $$ = RELATIVE_TIME_0; $$.day = $1 * $2; }
576 { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
578 { $$ = RELATIVE_TIME_0; $$.day = $1; }
579 | tORDINAL tHOUR_UNIT
580 { $$ = RELATIVE_TIME_0; $$.hour = $1; }
581 | tUNUMBER tHOUR_UNIT
582 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
584 { $$ = RELATIVE_TIME_0; $$.hour = 1; }
585 | tORDINAL tMINUTE_UNIT
586 { $$ = RELATIVE_TIME_0; $$.minutes = $1; }
587 | tUNUMBER tMINUTE_UNIT
588 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
590 { $$ = RELATIVE_TIME_0; $$.minutes = 1; }
592 { $$ = RELATIVE_TIME_0; $$.seconds = $1; }
594 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
595 | tSDECIMAL_NUMBER tSEC_UNIT
596 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
597 | tUDECIMAL_NUMBER tSEC_UNIT
598 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
600 { $$ = RELATIVE_TIME_0; $$.seconds = 1; }
606 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
607 | tSNUMBER tMONTH_UNIT
608 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
610 { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
611 | tSNUMBER tHOUR_UNIT
612 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
613 | tSNUMBER tMINUTE_UNIT
614 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
616 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
621 { $$ = RELATIVE_TIME_0; $$.day = $1; }
624 seconds: signed_seconds | unsigned_seconds;
629 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
635 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
640 { digits_to_date_time (pc, $1); }
644 tUNUMBER relunit_snumber
646 /* Hybrid all-digit and relative offset, so that we accept e.g.,
647 "YYYYMMDD +N days" as well as "YYYYMMDD N days". */
648 digits_to_date_time (pc, $1);
649 apply_relative_time (pc, $2, 1);
662 static table const meridian_table[] =
664 { "AM", tMERIDIAN, MERam },
665 { "A.M.", tMERIDIAN, MERam },
666 { "PM", tMERIDIAN, MERpm },
667 { "P.M.", tMERIDIAN, MERpm },
671 static table const dst_table[] =
676 static table const month_and_day_table[] =
678 { "JANUARY", tMONTH, 1 },
679 { "FEBRUARY", tMONTH, 2 },
680 { "MARCH", tMONTH, 3 },
681 { "APRIL", tMONTH, 4 },
682 { "MAY", tMONTH, 5 },
683 { "JUNE", tMONTH, 6 },
684 { "JULY", tMONTH, 7 },
685 { "AUGUST", tMONTH, 8 },
686 { "SEPTEMBER",tMONTH, 9 },
687 { "SEPT", tMONTH, 9 },
688 { "OCTOBER", tMONTH, 10 },
689 { "NOVEMBER", tMONTH, 11 },
690 { "DECEMBER", tMONTH, 12 },
691 { "SUNDAY", tDAY, 0 },
692 { "MONDAY", tDAY, 1 },
693 { "TUESDAY", tDAY, 2 },
695 { "WEDNESDAY",tDAY, 3 },
696 { "WEDNES", tDAY, 3 },
697 { "THURSDAY", tDAY, 4 },
699 { "THURS", tDAY, 4 },
700 { "FRIDAY", tDAY, 5 },
701 { "SATURDAY", tDAY, 6 },
705 static table const time_units_table[] =
707 { "YEAR", tYEAR_UNIT, 1 },
708 { "MONTH", tMONTH_UNIT, 1 },
709 { "FORTNIGHT",tDAY_UNIT, 14 },
710 { "WEEK", tDAY_UNIT, 7 },
711 { "DAY", tDAY_UNIT, 1 },
712 { "HOUR", tHOUR_UNIT, 1 },
713 { "MINUTE", tMINUTE_UNIT, 1 },
714 { "MIN", tMINUTE_UNIT, 1 },
715 { "SECOND", tSEC_UNIT, 1 },
716 { "SEC", tSEC_UNIT, 1 },
720 /* Assorted relative-time words. */
721 static table const relative_time_table[] =
723 { "TOMORROW", tDAY_SHIFT, 1 },
724 { "YESTERDAY",tDAY_SHIFT, -1 },
725 { "TODAY", tDAY_SHIFT, 0 },
726 { "NOW", tDAY_SHIFT, 0 },
727 { "LAST", tORDINAL, -1 },
728 { "THIS", tORDINAL, 0 },
729 { "NEXT", tORDINAL, 1 },
730 { "FIRST", tORDINAL, 1 },
731 /*{ "SECOND", tORDINAL, 2 }, */
732 { "THIRD", tORDINAL, 3 },
733 { "FOURTH", tORDINAL, 4 },
734 { "FIFTH", tORDINAL, 5 },
735 { "SIXTH", tORDINAL, 6 },
736 { "SEVENTH", tORDINAL, 7 },
737 { "EIGHTH", tORDINAL, 8 },
738 { "NINTH", tORDINAL, 9 },
739 { "TENTH", tORDINAL, 10 },
740 { "ELEVENTH", tORDINAL, 11 },
741 { "TWELFTH", tORDINAL, 12 },
743 { "HENCE", tAGO, 1 },
747 /* The universal time zone table. These labels can be used even for
748 time stamps that would not otherwise be valid, e.g., GMT time
749 stamps in London during summer. */
750 static table const universal_time_zone_table[] =
752 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
753 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
754 { "UTC", tZONE, HOUR ( 0) },
758 /* The time zone table. This table is necessarily incomplete, as time
759 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
760 as Eastern time in Australia, not as US Eastern Standard Time.
761 You cannot rely on parse_datetime to handle arbitrary time zone
762 abbreviations; use numeric abbreviations like "-0500" instead. */
763 static table const time_zone_table[] =
765 { "WET", tZONE, HOUR ( 0) }, /* Western European */
766 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
767 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
768 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
769 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
770 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
771 { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
772 { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
773 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
774 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
775 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
776 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
777 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
778 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
779 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
780 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
781 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
782 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
783 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
784 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
785 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
786 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
787 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
788 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
789 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
790 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
791 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
792 { "CET", tZONE, HOUR ( 1) }, /* Central European */
793 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
794 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
795 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
796 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
797 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
798 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
799 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
800 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
801 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
802 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
803 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
804 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
805 { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
806 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
807 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
808 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
809 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
810 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
811 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
815 /* Military time zone table.
817 Note 'T' is a special case, as it is used as the separator in ISO
818 8601 date and time of day representation. */
819 static table const military_table[] =
821 { "A", tZONE, -HOUR ( 1) },
822 { "B", tZONE, -HOUR ( 2) },
823 { "C", tZONE, -HOUR ( 3) },
824 { "D", tZONE, -HOUR ( 4) },
825 { "E", tZONE, -HOUR ( 5) },
826 { "F", tZONE, -HOUR ( 6) },
827 { "G", tZONE, -HOUR ( 7) },
828 { "H", tZONE, -HOUR ( 8) },
829 { "I", tZONE, -HOUR ( 9) },
830 { "K", tZONE, -HOUR (10) },
831 { "L", tZONE, -HOUR (11) },
832 { "M", tZONE, -HOUR (12) },
833 { "N", tZONE, HOUR ( 1) },
834 { "O", tZONE, HOUR ( 2) },
835 { "P", tZONE, HOUR ( 3) },
836 { "Q", tZONE, HOUR ( 4) },
837 { "R", tZONE, HOUR ( 5) },
838 { "S", tZONE, HOUR ( 6) },
840 { "U", tZONE, HOUR ( 8) },
841 { "V", tZONE, HOUR ( 9) },
842 { "W", tZONE, HOUR (10) },
843 { "X", tZONE, HOUR (11) },
844 { "Y", tZONE, HOUR (12) },
845 { "Z", tZONE, HOUR ( 0) },
851 /* Convert a time zone expressed as HH:MM into an integer count of
852 minutes. If MM is negative, then S is of the form HHMM and needs
853 to be picked apart; otherwise, S is of the form HH. As specified in
854 http://www.opengroup.org/susv3xbd/xbd_chap08.html#tag_08_03, allow
855 only valid TZ range, and consider first two digits as hours, if no
856 minutes specified. */
859 time_zone_hhmm (parser_control *pc, textint s, long int mm)
863 /* If the length of S is 1 or 2 and no minutes are specified,
864 interpret it as a number of hours. */
865 if (s.digits <= 2 && mm < 0)
869 n_minutes = (s.value / 100) * 60 + s.value % 100;
871 n_minutes = s.value * 60 + (s.negative ? -mm : mm);
873 /* If the absolute number of minutes is larger than 24 hours,
874 arrange to reject it by incrementing pc->zones_seen. Thus,
875 we allow only values in the range UTC-24:00 to UTC+24:00. */
876 if (24 * 60 < abs (n_minutes))
883 to_hour (long int hours, int meridian)
887 default: /* Pacify GCC. */
889 return 0 <= hours && hours < 24 ? hours : -1;
891 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
893 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
898 to_year (textint textyear)
900 long int year = textyear.value;
905 /* XPG4 suggests that years 00-68 map to 2000-2068, and
906 years 69-99 map to 1969-1999. */
907 else if (textyear.digits == 2)
908 year += year < 69 ? 2000 : 1900;
913 static table const * _GL_ATTRIBUTE_PURE
914 lookup_zone (parser_control const *pc, char const *name)
918 for (tp = universal_time_zone_table; tp->name; tp++)
919 if (strcmp (name, tp->name) == 0)
922 /* Try local zone abbreviations before those in time_zone_table, as
923 the local ones are more likely to be right. */
924 for (tp = pc->local_time_zone_table; tp->name; tp++)
925 if (strcmp (name, tp->name) == 0)
928 for (tp = time_zone_table; tp->name; tp++)
929 if (strcmp (name, tp->name) == 0)
936 /* Yield the difference between *A and *B,
937 measured in seconds, ignoring leap seconds.
938 The body of this function is taken directly from the GNU C Library;
939 see src/strftime.c. */
941 tm_diff (struct tm const *a, struct tm const *b)
943 /* Compute intervening leap days correctly even if year is negative.
944 Take care to avoid int overflow in leap day calculations. */
945 int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
946 int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
947 int a100 = a4 / 25 - (a4 % 25 < 0);
948 int b100 = b4 / 25 - (b4 % 25 < 0);
949 int a400 = SHR (a100, 2);
950 int b400 = SHR (b100, 2);
951 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
952 long int ayear = a->tm_year;
953 long int years = ayear - b->tm_year;
954 long int days = (365 * years + intervening_leap_days
955 + (a->tm_yday - b->tm_yday));
956 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
957 + (a->tm_min - b->tm_min))
958 + (a->tm_sec - b->tm_sec));
960 #endif /* ! HAVE_TM_GMTOFF */
963 lookup_word (parser_control const *pc, char *word)
972 /* Make it uppercase. */
973 for (p = word; *p; p++)
975 unsigned char ch = *p;
979 for (tp = meridian_table; tp->name; tp++)
980 if (strcmp (word, tp->name) == 0)
983 /* See if we have an abbreviation for a month. */
984 wordlen = strlen (word);
985 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
987 for (tp = month_and_day_table; tp->name; tp++)
988 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
991 if ((tp = lookup_zone (pc, word)))
994 if (strcmp (word, dst_table[0].name) == 0)
997 for (tp = time_units_table; tp->name; tp++)
998 if (strcmp (word, tp->name) == 0)
1001 /* Strip off any plural and try the units table again. */
1002 if (word[wordlen - 1] == 'S')
1004 word[wordlen - 1] = '\0';
1005 for (tp = time_units_table; tp->name; tp++)
1006 if (strcmp (word, tp->name) == 0)
1008 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
1011 for (tp = relative_time_table; tp->name; tp++)
1012 if (strcmp (word, tp->name) == 0)
1015 /* Military time zones. */
1017 for (tp = military_table; tp->name; tp++)
1018 if (word[0] == tp->name[0])
1021 /* Drop out any periods and try the time zone table again. */
1022 for (period_found = false, p = q = word; (*p = *q); q++)
1024 period_found = true;
1027 if (period_found && (tp = lookup_zone (pc, word)))
1034 yylex (union YYSTYPE *lvalp, parser_control *pc)
1041 while (c = *pc->input, c_isspace (c))
1044 if (ISDIGIT (c) || c == '-' || c == '+')
1048 unsigned long int value;
1049 if (c == '-' || c == '+')
1051 sign = c == '-' ? -1 : 1;
1052 while (c = *++pc->input, c_isspace (c))
1055 /* skip the '-' sign */
1061 for (value = 0; ; value *= 10)
1063 unsigned long int value1 = value + (c - '0');
1070 if (ULONG_MAX / 10 < value)
1073 if ((c == '.' || c == ',') && ISDIGIT (p[1]))
1078 unsigned long int value1;
1080 /* Check for overflow when converting value to time_t. */
1095 if (value != value1)
1098 /* Accumulate fraction, to ns precision. */
1101 for (digits = 2; digits <= LOG10_BILLION; digits++)
1108 /* Skip excess digits, truncating toward -Infinity. */
1110 for (; ISDIGIT (*p); p++)
1116 while (ISDIGIT (*p))
1119 /* Adjust to the timespec convention, which is that
1120 tv_nsec is always a positive offset even if tv_sec is
1130 lvalp->timespec.tv_sec = s;
1131 lvalp->timespec.tv_nsec = ns;
1133 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
1137 lvalp->textintval.negative = sign < 0;
1140 lvalp->textintval.value = - value;
1141 if (0 < lvalp->textintval.value)
1146 lvalp->textintval.value = value;
1147 if (lvalp->textintval.value < 0)
1150 lvalp->textintval.digits = p - pc->input;
1152 return sign ? tSNUMBER : tUNUMBER;
1164 if (p < buff + sizeof buff - 1)
1168 while (c_isalpha (c) || c == '.');
1171 tp = lookup_word (pc, buff);
1174 lvalp->intval = tp->value;
1179 return to_uchar (*pc->input++);
1196 /* Do nothing if the parser reports an error. */
1198 yyerror (parser_control const *pc _GL_UNUSED,
1199 char const *s _GL_UNUSED)
1204 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1205 passing it to mktime, return true if it's OK that mktime returned T.
1206 It's not OK if *TM0 has out-of-range members. */
1209 mktime_ok (struct tm const *tm0, struct tm const *tm1, time_t t)
1211 if (t == (time_t) -1)
1213 /* Guard against falsely reporting an error when parsing a time
1214 stamp that happens to equal (time_t) -1, on a host that
1215 supports such a time stamp. */
1216 tm1 = localtime (&t);
1221 return ! ((tm0->tm_sec ^ tm1->tm_sec)
1222 | (tm0->tm_min ^ tm1->tm_min)
1223 | (tm0->tm_hour ^ tm1->tm_hour)
1224 | (tm0->tm_mday ^ tm1->tm_mday)
1225 | (tm0->tm_mon ^ tm1->tm_mon)
1226 | (tm0->tm_year ^ tm1->tm_year));
1229 /* A reasonable upper bound for the size of ordinary TZ strings.
1230 Use heap allocation if TZ's length exceeds this. */
1231 enum { TZBUFSIZE = 100 };
1233 /* Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
1236 get_tz (char tzbuf[TZBUFSIZE])
1238 char *tz = getenv ("TZ");
1241 size_t tzsize = strlen (tz) + 1;
1242 tz = (tzsize <= TZBUFSIZE
1243 ? memcpy (tzbuf, tz, tzsize)
1244 : xmemdup (tz, tzsize));
1249 /* Parse a date/time string, storing the resulting time value into *RESULT.
1250 The string itself is pointed to by P. Return true if successful.
1251 P can be an incomplete or relative time specification; if so, use
1252 *NOW as the basis for the returned time. */
1254 parse_datetime (struct timespec *result, char const *p,
1255 struct timespec const *now)
1259 struct tm const *tmp;
1263 struct timespec gettime_buffer;
1265 bool tz_was_altered = false;
1267 char tz0buf[TZBUFSIZE];
1272 gettime (&gettime_buffer);
1273 now = &gettime_buffer;
1276 Start = now->tv_sec;
1277 Start_ns = now->tv_nsec;
1279 tmp = localtime (&now->tv_sec);
1283 while (c = *p, c_isspace (c))
1286 if (strncmp (p, "TZ=\"", 4) == 0)
1288 char const *tzbase = p + 4;
1292 for (s = tzbase; *s; s++, tzsize++)
1296 if (! (*s == '\\' || *s == '"'))
1303 char tz1buf[TZBUFSIZE];
1304 bool large_tz = TZBUFSIZE < tzsize;
1306 tz0 = get_tz (tz0buf);
1307 z = tz1 = large_tz ? xmalloc (tzsize) : tz1buf;
1308 for (s = tzbase; *s != '"'; s++)
1309 *z++ = *(s += *s == '\\');
1311 setenv_ok = setenv ("TZ", tz1, 1) == 0;
1316 tz_was_altered = true;
1319 while (c = *p, c_isspace (c))
1326 /* As documented, be careful to treat the empty string just like
1327 a date string of "0". Without this, an empty string would be
1328 declared invalid when parsed during a DST transition. */
1333 pc.year.value = tmp->tm_year;
1334 pc.year.value += TM_YEAR_BASE;
1336 pc.month = tmp->tm_mon + 1;
1337 pc.day = tmp->tm_mday;
1338 pc.hour = tmp->tm_hour;
1339 pc.minutes = tmp->tm_min;
1340 pc.seconds.tv_sec = tmp->tm_sec;
1341 pc.seconds.tv_nsec = Start_ns;
1342 tm.tm_isdst = tmp->tm_isdst;
1344 pc.meridian = MER24;
1345 pc.rel = RELATIVE_TIME_0;
1346 pc.timespec_seen = false;
1347 pc.rels_seen = false;
1351 pc.local_zones_seen = 0;
1355 #if HAVE_STRUCT_TM_TM_ZONE
1356 pc.local_time_zone_table[0].name = tmp->tm_zone;
1357 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1358 pc.local_time_zone_table[0].value = tmp->tm_isdst;
1359 pc.local_time_zone_table[1].name = NULL;
1361 /* Probe the names used in the next three calendar quarters, looking
1362 for a tm_isdst different from the one we already have. */
1365 for (quarter = 1; quarter <= 3; quarter++)
1367 time_t probe = Start + quarter * (90 * 24 * 60 * 60);
1368 struct tm const *probe_tm = localtime (&probe);
1369 if (probe_tm && probe_tm->tm_zone
1370 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
1373 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
1374 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1375 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
1376 pc.local_time_zone_table[2].name = NULL;
1385 # if !HAVE_DECL_TZNAME
1386 extern char *tzname[];
1389 for (i = 0; i < 2; i++)
1391 pc.local_time_zone_table[i].name = tzname[i];
1392 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1393 pc.local_time_zone_table[i].value = i;
1395 pc.local_time_zone_table[i].name = NULL;
1398 pc.local_time_zone_table[0].name = NULL;
1402 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1403 && ! strcmp (pc.local_time_zone_table[0].name,
1404 pc.local_time_zone_table[1].name))
1406 /* This locale uses the same abbreviation for standard and
1407 daylight times. So if we see that abbreviation, we don't
1408 know whether it's daylight time. */
1409 pc.local_time_zone_table[0].value = -1;
1410 pc.local_time_zone_table[1].name = NULL;
1413 if (yyparse (&pc) != 0)
1416 if (pc.timespec_seen)
1417 *result = pc.seconds;
1420 if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
1421 | (pc.local_zones_seen + pc.zones_seen)))
1424 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
1425 tm.tm_mon = pc.month - 1;
1426 tm.tm_mday = pc.day;
1427 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1429 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1432 tm.tm_min = pc.minutes;
1433 tm.tm_sec = pc.seconds.tv_sec;
1437 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1438 pc.seconds.tv_nsec = 0;
1441 /* Let mktime deduce tm_isdst if we have an absolute time stamp. */
1442 if (pc.dates_seen | pc.days_seen | pc.times_seen)
1445 /* But if the input explicitly specifies local time with or without
1446 DST, give mktime that information. */
1447 if (pc.local_zones_seen)
1448 tm.tm_isdst = pc.local_isdst;
1452 Start = mktime (&tm);
1454 if (! mktime_ok (&tm0, &tm, Start))
1456 if (! pc.zones_seen)
1460 /* Guard against falsely reporting errors near the time_t
1461 boundaries when parsing times in other time zones. For
1462 example, suppose the input string "1969-12-31 23:00:00 -0100",
1463 the current time zone is 8 hours ahead of UTC, and the min
1464 time_t value is 1970-01-01 00:00:00 UTC. Then the min
1465 localtime value is 1970-01-01 08:00:00, and mktime will
1466 therefore fail on 1969-12-31 23:00:00. To work around the
1467 problem, set the time zone to 1 hour behind UTC temporarily
1468 by setting TZ="XXX1:00" and try mktime again. */
1470 long int time_zone = pc.time_zone;
1471 long int abs_time_zone = time_zone < 0 ? - time_zone : time_zone;
1472 long int abs_time_zone_hour = abs_time_zone / 60;
1473 int abs_time_zone_min = abs_time_zone % 60;
1474 char tz1buf[sizeof "XXX+0:00"
1475 + sizeof pc.time_zone * CHAR_BIT / 3];
1476 if (!tz_was_altered)
1477 tz0 = get_tz (tz0buf);
1478 sprintf (tz1buf, "XXX%s%ld:%02d", &"-"[time_zone < 0],
1479 abs_time_zone_hour, abs_time_zone_min);
1480 if (setenv ("TZ", tz1buf, 1) != 0)
1482 tz_was_altered = true;
1484 Start = mktime (&tm);
1485 if (! mktime_ok (&tm0, &tm, Start))
1490 if (pc.days_seen && ! pc.dates_seen)
1492 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1493 + 7 * (pc.day_ordinal
1494 - (0 < pc.day_ordinal
1495 && tm.tm_wday != pc.day_number)));
1497 Start = mktime (&tm);
1498 if (Start == (time_t) -1)
1502 /* Add relative date. */
1503 if (pc.rel.year | pc.rel.month | pc.rel.day)
1505 int year = tm.tm_year + pc.rel.year;
1506 int month = tm.tm_mon + pc.rel.month;
1507 int day = tm.tm_mday + pc.rel.day;
1508 if (((year < tm.tm_year) ^ (pc.rel.year < 0))
1509 | ((month < tm.tm_mon) ^ (pc.rel.month < 0))
1510 | ((day < tm.tm_mday) ^ (pc.rel.day < 0)))
1515 tm.tm_hour = tm0.tm_hour;
1516 tm.tm_min = tm0.tm_min;
1517 tm.tm_sec = tm0.tm_sec;
1518 tm.tm_isdst = tm0.tm_isdst;
1519 Start = mktime (&tm);
1520 if (Start == (time_t) -1)
1524 /* The only "output" of this if-block is an updated Start value,
1525 so this block must follow others that clobber Start. */
1528 long int delta = pc.time_zone * 60;
1530 #ifdef HAVE_TM_GMTOFF
1531 delta -= tm.tm_gmtoff;
1534 struct tm const *gmt = gmtime (&t);
1537 delta -= tm_diff (&tm, gmt);
1540 if ((Start < t1) != (delta < 0))
1541 goto fail; /* time_t overflow */
1545 /* Add relative hours, minutes, and seconds. On hosts that support
1546 leap seconds, ignore the possibility of leap seconds; e.g.,
1547 "+ 10 minutes" adds 600 seconds, even if one of them is a
1548 leap second. Typically this is not what the user wants, but it's
1549 too hard to do it the other way, because the time zone indicator
1550 must be applied before relative times, and if mktime is applied
1551 again the time zone will be lost. */
1553 long int sum_ns = pc.seconds.tv_nsec + pc.rel.ns;
1554 long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
1556 long int d1 = 60 * 60 * pc.rel.hour;
1557 time_t t1 = t0 + d1;
1558 long int d2 = 60 * pc.rel.minutes;
1559 time_t t2 = t1 + d2;
1560 long_time_t d3 = pc.rel.seconds;
1561 long_time_t t3 = t2 + d3;
1562 long int d4 = (sum_ns - normalized_ns) / BILLION;
1563 long_time_t t4 = t3 + d4;
1566 if ((d1 / (60 * 60) ^ pc.rel.hour)
1567 | (d2 / 60 ^ pc.rel.minutes)
1568 | ((t1 < t0) ^ (d1 < 0))
1569 | ((t2 < t1) ^ (d2 < 0))
1570 | ((t3 < t2) ^ (d3 < 0))
1571 | ((t4 < t3) ^ (d4 < 0))
1575 result->tv_sec = t5;
1576 result->tv_nsec = normalized_ns;
1586 ok &= (tz0 ? setenv ("TZ", tz0, 1) : unsetenv ("TZ")) == 0;
1595 main (int ac, char **av)
1599 printf ("Enter date, or blank line to exit.\n\t> ");
1602 buff[BUFSIZ - 1] = '\0';
1603 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1606 struct tm const *tm;
1607 if (! parse_datetime (&d, buff, NULL))
1608 printf ("Bad format - couldn't convert.\n");
1609 else if (! (tm = localtime (&d.tv_sec)))
1611 long int sec = d.tv_sec;
1612 printf ("localtime (%ld) failed\n", sec);
1617 printf ("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1618 tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
1619 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);