2 /* Parse a string into an internal time stamp.
4 Copyright (C) 1999, 2000, 2002, 2003, 2004, 2005, 2006, 2007 Free Software
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>. */
20 /* Originally written by Steven M. Bellovin <smb@research.att.com> while
21 at the University of North Carolina at Chapel Hill. Later tweaked by
22 a couple of people on Usenet. Completely overhauled by Rich $alz
23 <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
25 Modified by Paul Eggert <eggert@twinsun.com> in August 1999 to do
26 the right thing about local DST. Also modified by Paul Eggert
27 <eggert@cs.ucla.edu> in February 2004 to support
28 nanosecond-resolution time stamps, and in October 2004 to support
29 TZ strings in dates. */
31 /* FIXME: Check for arithmetic overflow in all cases, not just
42 /* There's no need to extend the stack, so there's no need to involve
44 #define YYSTACK_USE_ALLOCA 0
46 /* Tell Bison how much stack space is needed. 20 should be plenty for
47 this grammar, which is not right recursive. Beware setting it too
48 high, since that might cause problems on machines whose
49 implementations have lame stack-overflow checking. */
51 #define YYINITDEPTH YYMAXDEPTH
53 /* Since the code of getdate.y is not included in the Emacs executable
54 itself, there is no need to #define static in this file. Even if
55 the code were included in the Emacs executable, it probably
56 wouldn't do any harm to #undef it here; this will only cause
57 problems if we try to write to a static variable, which I don't
58 think this code needs to do. */
72 /* ISDIGIT differs from isdigit, as follows:
73 - Its arg may be any int or unsigned int; it need not be an unsigned char
75 - It's typically faster.
76 POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
77 isdigit unless it's important to use the locale's definition
78 of `digit' even when the host does not conform to POSIX. */
79 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
82 # if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
83 # define __attribute__(x)
87 #ifndef ATTRIBUTE_UNUSED
88 # define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
91 /* Shift A right by B bits portably, by dividing A by 2**B and
92 truncating towards minus infinity. A and B should be free of side
93 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
94 INT_BITS is the number of useful bits in an int. GNU code can
95 assume that INT_BITS is at least 32.
97 ISO C99 says that A >> B is implementation-defined if A < 0. Some
98 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
99 right in the usual way when A < 0, so SHR falls back on division if
100 ordinary A >> B doesn't seem to be the usual signed shift. */
104 : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
106 #define EPOCH_YEAR 1970
107 #define TM_YEAR_BASE 1900
109 #define HOUR(x) ((x) * 60)
111 /* Lots of this code assumes time_t and time_t-like values fit into
112 long int. It also assumes that signed integer overflow silently
113 wraps around, but there's no portable way to check for that at
115 verify (TYPE_IS_INTEGER (time_t));
116 verify (LONG_MIN <= TYPE_MINIMUM (time_t) && TYPE_MAXIMUM (time_t) <= LONG_MAX);
118 /* An integer value, and the number of digits in its textual
127 /* An entry in the lexical lookup table. */
135 /* Meridian: am, pm, or 24-hour style. */
136 enum { MERam, MERpm, MER24 };
138 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
140 /* Relative times. */
143 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
153 #if HAVE_COMPOUND_LITERALS
154 # define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
156 static relative_time const RELATIVE_TIME_0;
159 /* Information passed to and from the parser. */
162 /* The input string remaining to be parsed. */
165 /* N, if this is the Nth Tuesday. */
166 long int day_ordinal;
168 /* Day of week; Sunday is 0. */
171 /* tm_isdst flag for the local zone. */
174 /* Time zone, in minutes east of UTC. */
177 /* Style used for time. */
180 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
186 struct timespec seconds; /* includes nanoseconds */
188 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
191 /* Presence or counts of nonterminals of various flavors parsed so far. */
196 size_t local_zones_seen;
201 /* Table of local time zone abbrevations, terminated by a null entry. */
202 table local_time_zone_table[3];
206 static int yylex (union YYSTYPE *, parser_control *);
207 static int yyerror (parser_control const *, char const *);
208 static long int time_zone_hhmm (textint, long int);
210 /* Extract into *PC any date and time info from a string of digits
211 of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
214 digits_to_date_time (parser_control *pc, textint text_int)
216 if (pc->dates_seen && ! pc->year.digits
217 && ! pc->rels_seen && (pc->times_seen || 2 < text_int.digits))
221 if (4 < text_int.digits)
224 pc->day = text_int.value % 100;
225 pc->month = (text_int.value / 100) % 100;
226 pc->year.value = text_int.value / 10000;
227 pc->year.digits = text_int.digits - 4;
232 if (text_int.digits <= 2)
234 pc->hour = text_int.value;
239 pc->hour = text_int.value / 100;
240 pc->minutes = text_int.value % 100;
242 pc->seconds.tv_sec = 0;
243 pc->seconds.tv_nsec = 0;
244 pc->meridian = MER24;
251 /* We want a reentrant parser, even if the TZ manipulation and the calls to
252 localtime and gmtime are not reentrant. */
254 %parse-param { parser_control *pc }
255 %lex-param { parser_control *pc }
257 /* This grammar has 20 shift/reduce conflicts. */
264 struct timespec timespec;
270 %token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
271 %token <intval> tDAY_UNIT
273 %token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
274 %token <intval> tMONTH tORDINAL tZONE
276 %token <textintval> tSNUMBER tUNUMBER
277 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
279 %type <intval> o_colon_minutes o_merid
280 %type <timespec> seconds signed_seconds unsigned_seconds
282 %type <rel> relunit relunit_snumber
295 pc->timespec_seen = true;
306 { pc->times_seen++; }
308 { pc->local_zones_seen++; }
310 { pc->zones_seen++; }
312 { pc->dates_seen++; }
316 { pc->rels_seen = true; }
326 pc->seconds.tv_sec = 0;
327 pc->seconds.tv_nsec = 0;
330 | tUNUMBER ':' tUNUMBER o_merid
333 pc->minutes = $3.value;
334 pc->seconds.tv_sec = 0;
335 pc->seconds.tv_nsec = 0;
338 | tUNUMBER ':' tUNUMBER tSNUMBER o_colon_minutes
341 pc->minutes = $3.value;
342 pc->seconds.tv_sec = 0;
343 pc->seconds.tv_nsec = 0;
344 pc->meridian = MER24;
346 pc->time_zone = time_zone_hhmm ($4, $5);
348 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
351 pc->minutes = $3.value;
355 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER o_colon_minutes
358 pc->minutes = $3.value;
360 pc->meridian = MER24;
362 pc->time_zone = time_zone_hhmm ($6, $7);
369 pc->local_isdst = $1;
370 pc->dsts_seen += (0 < $1);
375 pc->dsts_seen += (0 < $1) + 1;
381 { pc->time_zone = $1; }
382 | tZONE relunit_snumber
383 { pc->time_zone = $1;
385 pc->rel.seconds += $2.seconds;
386 pc->rel.minutes += $2.minutes;
387 pc->rel.hour += $2.hour;
388 pc->rel.day += $2.day;
389 pc->rel.month += $2.month;
390 pc->rel.year += $2.year;
391 pc->rels_seen = true; }
392 | tZONE tSNUMBER o_colon_minutes
393 { pc->time_zone = $1 + time_zone_hhmm ($2, $3); }
395 { pc->time_zone = $1 + 60; }
397 { pc->time_zone = $1 + 60; }
413 pc->day_ordinal = $1;
418 pc->day_ordinal = $1.value;
424 tUNUMBER '/' tUNUMBER
426 pc->month = $1.value;
429 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
431 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
432 otherwise as MM/DD/YY.
433 The goal in recognizing YYYY/MM/DD is solely to support legacy
434 machine-generated dates like those in an RCS log listing. If
435 you want portability, use the ISO 8601 format. */
439 pc->month = $3.value;
444 pc->month = $1.value;
449 | tUNUMBER tSNUMBER tSNUMBER
451 /* ISO 8601 format. YYYY-MM-DD. */
453 pc->month = -$2.value;
456 | tUNUMBER tMONTH tSNUMBER
458 /* e.g. 17-JUN-1992. */
461 pc->year.value = -$3.value;
462 pc->year.digits = $3.digits;
464 | tMONTH tSNUMBER tSNUMBER
466 /* e.g. JUN-17-1992. */
469 pc->year.value = -$3.value;
470 pc->year.digits = $3.digits;
477 | tMONTH tUNUMBER ',' tUNUMBER
488 | tUNUMBER tMONTH tUNUMBER
500 pc->rel.seconds -= $1.seconds;
501 pc->rel.minutes -= $1.minutes;
502 pc->rel.hour -= $1.hour;
503 pc->rel.day -= $1.day;
504 pc->rel.month -= $1.month;
505 pc->rel.year -= $1.year;
510 pc->rel.seconds += $1.seconds;
511 pc->rel.minutes += $1.minutes;
512 pc->rel.hour += $1.hour;
513 pc->rel.day += $1.day;
514 pc->rel.month += $1.month;
515 pc->rel.year += $1.year;
521 { $$ = RELATIVE_TIME_0; $$.year = $1; }
522 | tUNUMBER tYEAR_UNIT
523 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
525 { $$ = RELATIVE_TIME_0; $$.year = 1; }
526 | tORDINAL tMONTH_UNIT
527 { $$ = RELATIVE_TIME_0; $$.month = $1; }
528 | tUNUMBER tMONTH_UNIT
529 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
531 { $$ = RELATIVE_TIME_0; $$.month = 1; }
533 { $$ = RELATIVE_TIME_0; $$.day = $1 * $2; }
535 { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
537 { $$ = RELATIVE_TIME_0; $$.day = $1; }
538 | tORDINAL tHOUR_UNIT
539 { $$ = RELATIVE_TIME_0; $$.hour = $1; }
540 | tUNUMBER tHOUR_UNIT
541 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
543 { $$ = RELATIVE_TIME_0; $$.hour = 1; }
544 | tORDINAL tMINUTE_UNIT
545 { $$ = RELATIVE_TIME_0; $$.minutes = $1; }
546 | tUNUMBER tMINUTE_UNIT
547 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
549 { $$ = RELATIVE_TIME_0; $$.minutes = 1; }
551 { $$ = RELATIVE_TIME_0; $$.seconds = $1; }
553 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
554 | tSDECIMAL_NUMBER tSEC_UNIT
555 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
556 | tUDECIMAL_NUMBER tSEC_UNIT
557 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
559 { $$ = RELATIVE_TIME_0; $$.seconds = 1; }
565 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
566 | tSNUMBER tMONTH_UNIT
567 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
569 { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
570 | tSNUMBER tHOUR_UNIT
571 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
572 | tSNUMBER tMINUTE_UNIT
573 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
575 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
578 seconds: signed_seconds | unsigned_seconds;
583 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
589 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
594 { digits_to_date_time (pc, $1); }
598 tUNUMBER relunit_snumber
600 /* Hybrid all-digit and relative offset, so that we accept e.g.,
601 "YYYYMMDD +N days" as well as "YYYYMMDD N days". */
602 digits_to_date_time (pc, $1);
604 pc->rel.seconds += $2.seconds;
605 pc->rel.minutes += $2.minutes;
606 pc->rel.hour += $2.hour;
607 pc->rel.day += $2.day;
608 pc->rel.month += $2.month;
609 pc->rel.year += $2.year;
610 pc->rels_seen = true;
630 static table const meridian_table[] =
632 { "AM", tMERIDIAN, MERam },
633 { "A.M.", tMERIDIAN, MERam },
634 { "PM", tMERIDIAN, MERpm },
635 { "P.M.", tMERIDIAN, MERpm },
639 static table const dst_table[] =
644 static table const month_and_day_table[] =
646 { "JANUARY", tMONTH, 1 },
647 { "FEBRUARY", tMONTH, 2 },
648 { "MARCH", tMONTH, 3 },
649 { "APRIL", tMONTH, 4 },
650 { "MAY", tMONTH, 5 },
651 { "JUNE", tMONTH, 6 },
652 { "JULY", tMONTH, 7 },
653 { "AUGUST", tMONTH, 8 },
654 { "SEPTEMBER",tMONTH, 9 },
655 { "SEPT", tMONTH, 9 },
656 { "OCTOBER", tMONTH, 10 },
657 { "NOVEMBER", tMONTH, 11 },
658 { "DECEMBER", tMONTH, 12 },
659 { "SUNDAY", tDAY, 0 },
660 { "MONDAY", tDAY, 1 },
661 { "TUESDAY", tDAY, 2 },
663 { "WEDNESDAY",tDAY, 3 },
664 { "WEDNES", tDAY, 3 },
665 { "THURSDAY", tDAY, 4 },
667 { "THURS", tDAY, 4 },
668 { "FRIDAY", tDAY, 5 },
669 { "SATURDAY", tDAY, 6 },
673 static table const time_units_table[] =
675 { "YEAR", tYEAR_UNIT, 1 },
676 { "MONTH", tMONTH_UNIT, 1 },
677 { "FORTNIGHT",tDAY_UNIT, 14 },
678 { "WEEK", tDAY_UNIT, 7 },
679 { "DAY", tDAY_UNIT, 1 },
680 { "HOUR", tHOUR_UNIT, 1 },
681 { "MINUTE", tMINUTE_UNIT, 1 },
682 { "MIN", tMINUTE_UNIT, 1 },
683 { "SECOND", tSEC_UNIT, 1 },
684 { "SEC", tSEC_UNIT, 1 },
688 /* Assorted relative-time words. */
689 static table const relative_time_table[] =
691 { "TOMORROW", tDAY_UNIT, 1 },
692 { "YESTERDAY",tDAY_UNIT, -1 },
693 { "TODAY", tDAY_UNIT, 0 },
694 { "NOW", tDAY_UNIT, 0 },
695 { "LAST", tORDINAL, -1 },
696 { "THIS", tORDINAL, 0 },
697 { "NEXT", tORDINAL, 1 },
698 { "FIRST", tORDINAL, 1 },
699 /*{ "SECOND", tORDINAL, 2 }, */
700 { "THIRD", tORDINAL, 3 },
701 { "FOURTH", tORDINAL, 4 },
702 { "FIFTH", tORDINAL, 5 },
703 { "SIXTH", tORDINAL, 6 },
704 { "SEVENTH", tORDINAL, 7 },
705 { "EIGHTH", tORDINAL, 8 },
706 { "NINTH", tORDINAL, 9 },
707 { "TENTH", tORDINAL, 10 },
708 { "ELEVENTH", tORDINAL, 11 },
709 { "TWELFTH", tORDINAL, 12 },
714 /* The universal time zone table. These labels can be used even for
715 time stamps that would not otherwise be valid, e.g., GMT time
716 stamps in London during summer. */
717 static table const universal_time_zone_table[] =
719 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
720 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
721 { "UTC", tZONE, HOUR ( 0) },
725 /* The time zone table. This table is necessarily incomplete, as time
726 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
727 as Eastern time in Australia, not as US Eastern Standard Time.
728 You cannot rely on getdate to handle arbitrary time zone
729 abbreviations; use numeric abbreviations like `-0500' instead. */
730 static table const time_zone_table[] =
732 { "WET", tZONE, HOUR ( 0) }, /* Western European */
733 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
734 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
735 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
736 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
737 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
738 { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
739 { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
740 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
741 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
742 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
743 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
744 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
745 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
746 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
747 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
748 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
749 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
750 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
751 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
752 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
753 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
754 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
755 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
756 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
757 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
758 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
759 { "CET", tZONE, HOUR ( 1) }, /* Central European */
760 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
761 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
762 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
763 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
764 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
765 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
766 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
767 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
768 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
769 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
770 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
771 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
772 { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
773 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
774 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
775 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
776 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
777 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
778 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
782 /* Military time zone table. */
783 static table const military_table[] =
785 { "A", tZONE, -HOUR ( 1) },
786 { "B", tZONE, -HOUR ( 2) },
787 { "C", tZONE, -HOUR ( 3) },
788 { "D", tZONE, -HOUR ( 4) },
789 { "E", tZONE, -HOUR ( 5) },
790 { "F", tZONE, -HOUR ( 6) },
791 { "G", tZONE, -HOUR ( 7) },
792 { "H", tZONE, -HOUR ( 8) },
793 { "I", tZONE, -HOUR ( 9) },
794 { "K", tZONE, -HOUR (10) },
795 { "L", tZONE, -HOUR (11) },
796 { "M", tZONE, -HOUR (12) },
797 { "N", tZONE, HOUR ( 1) },
798 { "O", tZONE, HOUR ( 2) },
799 { "P", tZONE, HOUR ( 3) },
800 { "Q", tZONE, HOUR ( 4) },
801 { "R", tZONE, HOUR ( 5) },
802 { "S", tZONE, HOUR ( 6) },
803 { "T", tZONE, HOUR ( 7) },
804 { "U", tZONE, HOUR ( 8) },
805 { "V", tZONE, HOUR ( 9) },
806 { "W", tZONE, HOUR (10) },
807 { "X", tZONE, HOUR (11) },
808 { "Y", tZONE, HOUR (12) },
809 { "Z", tZONE, HOUR ( 0) },
815 /* Convert a time zone expressed as HH:MM into an integer count of
816 minutes. If MM is negative, then S is of the form HHMM and needs
817 to be picked apart; otherwise, S is of the form HH. */
820 time_zone_hhmm (textint s, long int mm)
823 return (s.value / 100) * 60 + s.value % 100;
825 return s.value * 60 + (s.negative ? -mm : mm);
829 to_hour (long int hours, int meridian)
833 default: /* Pacify GCC. */
835 return 0 <= hours && hours < 24 ? hours : -1;
837 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
839 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
844 to_year (textint textyear)
846 long int year = textyear.value;
851 /* XPG4 suggests that years 00-68 map to 2000-2068, and
852 years 69-99 map to 1969-1999. */
853 else if (textyear.digits == 2)
854 year += year < 69 ? 2000 : 1900;
860 lookup_zone (parser_control const *pc, char const *name)
864 for (tp = universal_time_zone_table; tp->name; tp++)
865 if (strcmp (name, tp->name) == 0)
868 /* Try local zone abbreviations before those in time_zone_table, as
869 the local ones are more likely to be right. */
870 for (tp = pc->local_time_zone_table; tp->name; tp++)
871 if (strcmp (name, tp->name) == 0)
874 for (tp = time_zone_table; tp->name; tp++)
875 if (strcmp (name, tp->name) == 0)
882 /* Yield the difference between *A and *B,
883 measured in seconds, ignoring leap seconds.
884 The body of this function is taken directly from the GNU C Library;
885 see src/strftime.c. */
887 tm_diff (struct tm const *a, struct tm const *b)
889 /* Compute intervening leap days correctly even if year is negative.
890 Take care to avoid int overflow in leap day calculations. */
891 int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
892 int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
893 int a100 = a4 / 25 - (a4 % 25 < 0);
894 int b100 = b4 / 25 - (b4 % 25 < 0);
895 int a400 = SHR (a100, 2);
896 int b400 = SHR (b100, 2);
897 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
898 long int ayear = a->tm_year;
899 long int years = ayear - b->tm_year;
900 long int days = (365 * years + intervening_leap_days
901 + (a->tm_yday - b->tm_yday));
902 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
903 + (a->tm_min - b->tm_min))
904 + (a->tm_sec - b->tm_sec));
906 #endif /* ! HAVE_TM_GMTOFF */
909 lookup_word (parser_control const *pc, char *word)
918 /* Make it uppercase. */
919 for (p = word; *p; p++)
921 unsigned char ch = *p;
925 for (tp = meridian_table; tp->name; tp++)
926 if (strcmp (word, tp->name) == 0)
929 /* See if we have an abbreviation for a month. */
930 wordlen = strlen (word);
931 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
933 for (tp = month_and_day_table; tp->name; tp++)
934 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
937 if ((tp = lookup_zone (pc, word)))
940 if (strcmp (word, dst_table[0].name) == 0)
943 for (tp = time_units_table; tp->name; tp++)
944 if (strcmp (word, tp->name) == 0)
947 /* Strip off any plural and try the units table again. */
948 if (word[wordlen - 1] == 'S')
950 word[wordlen - 1] = '\0';
951 for (tp = time_units_table; tp->name; tp++)
952 if (strcmp (word, tp->name) == 0)
954 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
957 for (tp = relative_time_table; tp->name; tp++)
958 if (strcmp (word, tp->name) == 0)
961 /* Military time zones. */
963 for (tp = military_table; tp->name; tp++)
964 if (word[0] == tp->name[0])
967 /* Drop out any periods and try the time zone table again. */
968 for (period_found = false, p = q = word; (*p = *q); q++)
973 if (period_found && (tp = lookup_zone (pc, word)))
980 yylex (YYSTYPE *lvalp, parser_control *pc)
987 while (c = *pc->input, isspace (c))
990 if (ISDIGIT (c) || c == '-' || c == '+')
994 unsigned long int value;
995 if (c == '-' || c == '+')
997 sign = c == '-' ? -1 : 1;
998 while (c = *++pc->input, isspace (c))
1001 /* skip the '-' sign */
1007 for (value = 0; ; value *= 10)
1009 unsigned long int value1 = value + (c - '0');
1016 if (ULONG_MAX / 10 < value)
1019 if ((c == '.' || c == ',') && ISDIGIT (p[1]))
1024 unsigned long int value1;
1026 /* Check for overflow when converting value to time_t. */
1041 if (value != value1)
1044 /* Accumulate fraction, to ns precision. */
1047 for (digits = 2; digits <= LOG10_BILLION; digits++)
1054 /* Skip excess digits, truncating toward -Infinity. */
1056 for (; ISDIGIT (*p); p++)
1062 while (ISDIGIT (*p))
1065 /* Adjust to the timespec convention, which is that
1066 tv_nsec is always a positive offset even if tv_sec is
1076 lvalp->timespec.tv_sec = s;
1077 lvalp->timespec.tv_nsec = ns;
1079 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
1083 lvalp->textintval.negative = sign < 0;
1086 lvalp->textintval.value = - value;
1087 if (0 < lvalp->textintval.value)
1092 lvalp->textintval.value = value;
1093 if (lvalp->textintval.value < 0)
1096 lvalp->textintval.digits = p - pc->input;
1098 return sign ? tSNUMBER : tUNUMBER;
1110 if (p < buff + sizeof buff - 1)
1114 while (isalpha (c) || c == '.');
1117 tp = lookup_word (pc, buff);
1120 lvalp->intval = tp->value;
1125 return *pc->input++;
1141 /* Do nothing if the parser reports an error. */
1143 yyerror (parser_control const *pc ATTRIBUTE_UNUSED,
1144 char const *s ATTRIBUTE_UNUSED)
1149 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1150 passing it to mktime, return true if it's OK that mktime returned T.
1151 It's not OK if *TM0 has out-of-range members. */
1154 mktime_ok (struct tm const *tm0, struct tm const *tm1, time_t t)
1156 if (t == (time_t) -1)
1158 /* Guard against falsely reporting an error when parsing a time
1159 stamp that happens to equal (time_t) -1, on a host that
1160 supports such a time stamp. */
1161 tm1 = localtime (&t);
1166 return ! ((tm0->tm_sec ^ tm1->tm_sec)
1167 | (tm0->tm_min ^ tm1->tm_min)
1168 | (tm0->tm_hour ^ tm1->tm_hour)
1169 | (tm0->tm_mday ^ tm1->tm_mday)
1170 | (tm0->tm_mon ^ tm1->tm_mon)
1171 | (tm0->tm_year ^ tm1->tm_year));
1174 /* A reasonable upper bound for the size of ordinary TZ strings.
1175 Use heap allocation if TZ's length exceeds this. */
1176 enum { TZBUFSIZE = 100 };
1178 /* Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
1181 get_tz (char tzbuf[TZBUFSIZE])
1183 char *tz = getenv ("TZ");
1186 size_t tzsize = strlen (tz) + 1;
1187 tz = (tzsize <= TZBUFSIZE
1188 ? memcpy (tzbuf, tz, tzsize)
1189 : xmemdup (tz, tzsize));
1194 /* Parse a date/time string, storing the resulting time value into *RESULT.
1195 The string itself is pointed to by P. Return true if successful.
1196 P can be an incomplete or relative time specification; if so, use
1197 *NOW as the basis for the returned time. */
1199 get_date (struct timespec *result, char const *p, struct timespec const *now)
1203 struct tm const *tmp;
1207 struct timespec gettime_buffer;
1209 bool tz_was_altered = false;
1211 char tz0buf[TZBUFSIZE];
1216 gettime (&gettime_buffer);
1217 now = &gettime_buffer;
1220 Start = now->tv_sec;
1221 Start_ns = now->tv_nsec;
1223 tmp = localtime (&now->tv_sec);
1227 while (c = *p, isspace (c))
1230 if (strncmp (p, "TZ=\"", 4) == 0)
1232 char const *tzbase = p + 4;
1236 for (s = tzbase; *s; s++, tzsize++)
1240 if (! (*s == '\\' || *s == '"'))
1247 char tz1buf[TZBUFSIZE];
1248 bool large_tz = TZBUFSIZE < tzsize;
1250 tz0 = get_tz (tz0buf);
1251 z = tz1 = large_tz ? xmalloc (tzsize) : tz1buf;
1252 for (s = tzbase; *s != '"'; s++)
1253 *z++ = *(s += *s == '\\');
1255 setenv_ok = setenv ("TZ", tz1, 1) == 0;
1260 tz_was_altered = true;
1265 /* As documented, be careful to treat the empty string just like
1266 a date string of "0". Without this, an empty string would be
1267 declared invalid when parsed during a DST transition. */
1272 pc.year.value = tmp->tm_year;
1273 pc.year.value += TM_YEAR_BASE;
1275 pc.month = tmp->tm_mon + 1;
1276 pc.day = tmp->tm_mday;
1277 pc.hour = tmp->tm_hour;
1278 pc.minutes = tmp->tm_min;
1279 pc.seconds.tv_sec = tmp->tm_sec;
1280 pc.seconds.tv_nsec = Start_ns;
1281 tm.tm_isdst = tmp->tm_isdst;
1283 pc.meridian = MER24;
1284 pc.rel = RELATIVE_TIME_0;
1285 pc.timespec_seen = false;
1286 pc.rels_seen = false;
1290 pc.local_zones_seen = 0;
1294 #if HAVE_STRUCT_TM_TM_ZONE
1295 pc.local_time_zone_table[0].name = tmp->tm_zone;
1296 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1297 pc.local_time_zone_table[0].value = tmp->tm_isdst;
1298 pc.local_time_zone_table[1].name = NULL;
1300 /* Probe the names used in the next three calendar quarters, looking
1301 for a tm_isdst different from the one we already have. */
1304 for (quarter = 1; quarter <= 3; quarter++)
1306 time_t probe = Start + quarter * (90 * 24 * 60 * 60);
1307 struct tm const *probe_tm = localtime (&probe);
1308 if (probe_tm && probe_tm->tm_zone
1309 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
1312 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
1313 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1314 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
1315 pc.local_time_zone_table[2].name = NULL;
1324 # if !HAVE_DECL_TZNAME
1325 extern char *tzname[];
1328 for (i = 0; i < 2; i++)
1330 pc.local_time_zone_table[i].name = tzname[i];
1331 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1332 pc.local_time_zone_table[i].value = i;
1334 pc.local_time_zone_table[i].name = NULL;
1337 pc.local_time_zone_table[0].name = NULL;
1341 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1342 && ! strcmp (pc.local_time_zone_table[0].name,
1343 pc.local_time_zone_table[1].name))
1345 /* This locale uses the same abbrevation for standard and
1346 daylight times. So if we see that abbreviation, we don't
1347 know whether it's daylight time. */
1348 pc.local_time_zone_table[0].value = -1;
1349 pc.local_time_zone_table[1].name = NULL;
1352 if (yyparse (&pc) != 0)
1355 if (pc.timespec_seen)
1356 *result = pc.seconds;
1359 if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
1360 | (pc.local_zones_seen + pc.zones_seen)))
1363 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
1364 tm.tm_mon = pc.month - 1;
1365 tm.tm_mday = pc.day;
1366 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1368 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1371 tm.tm_min = pc.minutes;
1372 tm.tm_sec = pc.seconds.tv_sec;
1376 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1377 pc.seconds.tv_nsec = 0;
1380 /* Let mktime deduce tm_isdst if we have an absolute time stamp. */
1381 if (pc.dates_seen | pc.days_seen | pc.times_seen)
1384 /* But if the input explicitly specifies local time with or without
1385 DST, give mktime that information. */
1386 if (pc.local_zones_seen)
1387 tm.tm_isdst = pc.local_isdst;
1391 Start = mktime (&tm);
1393 if (! mktime_ok (&tm0, &tm, Start))
1395 if (! pc.zones_seen)
1399 /* Guard against falsely reporting errors near the time_t
1400 boundaries when parsing times in other time zones. For
1401 example, suppose the input string "1969-12-31 23:00:00 -0100",
1402 the current time zone is 8 hours ahead of UTC, and the min
1403 time_t value is 1970-01-01 00:00:00 UTC. Then the min
1404 localtime value is 1970-01-01 08:00:00, and mktime will
1405 therefore fail on 1969-12-31 23:00:00. To work around the
1406 problem, set the time zone to 1 hour behind UTC temporarily
1407 by setting TZ="XXX1:00" and try mktime again. */
1409 long int time_zone = pc.time_zone;
1410 long int abs_time_zone = time_zone < 0 ? - time_zone : time_zone;
1411 long int abs_time_zone_hour = abs_time_zone / 60;
1412 int abs_time_zone_min = abs_time_zone % 60;
1413 char tz1buf[sizeof "XXX+0:00"
1414 + sizeof pc.time_zone * CHAR_BIT / 3];
1415 if (!tz_was_altered)
1416 tz0 = get_tz (tz0buf);
1417 sprintf (tz1buf, "XXX%s%ld:%02d", "-" + (time_zone < 0),
1418 abs_time_zone_hour, abs_time_zone_min);
1419 if (setenv ("TZ", tz1buf, 1) != 0)
1421 tz_was_altered = true;
1423 Start = mktime (&tm);
1424 if (! mktime_ok (&tm0, &tm, Start))
1429 if (pc.days_seen && ! pc.dates_seen)
1431 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1432 + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
1434 Start = mktime (&tm);
1435 if (Start == (time_t) -1)
1441 long int delta = pc.time_zone * 60;
1443 #ifdef HAVE_TM_GMTOFF
1444 delta -= tm.tm_gmtoff;
1447 struct tm const *gmt = gmtime (&t);
1450 delta -= tm_diff (&tm, gmt);
1453 if ((Start < t1) != (delta < 0))
1454 goto fail; /* time_t overflow */
1458 /* Add relative date. */
1459 if (pc.rel.year | pc.rel.month | pc.rel.day)
1461 int year = tm.tm_year + pc.rel.year;
1462 int month = tm.tm_mon + pc.rel.month;
1463 int day = tm.tm_mday + pc.rel.day;
1464 if (((year < tm.tm_year) ^ (pc.rel.year < 0))
1465 | ((month < tm.tm_mon) ^ (pc.rel.month < 0))
1466 | ((day < tm.tm_mday) ^ (pc.rel.day < 0)))
1471 tm.tm_hour = tm0.tm_hour;
1472 tm.tm_min = tm0.tm_min;
1473 tm.tm_sec = tm0.tm_sec;
1474 tm.tm_isdst = tm0.tm_isdst;
1475 Start = mktime (&tm);
1476 if (Start == (time_t) -1)
1480 /* Add relative hours, minutes, and seconds. On hosts that support
1481 leap seconds, ignore the possibility of leap seconds; e.g.,
1482 "+ 10 minutes" adds 600 seconds, even if one of them is a
1483 leap second. Typically this is not what the user wants, but it's
1484 too hard to do it the other way, because the time zone indicator
1485 must be applied before relative times, and if mktime is applied
1486 again the time zone will be lost. */
1488 long int sum_ns = pc.seconds.tv_nsec + pc.rel.ns;
1489 long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
1491 long int d1 = 60 * 60 * pc.rel.hour;
1492 time_t t1 = t0 + d1;
1493 long int d2 = 60 * pc.rel.minutes;
1494 time_t t2 = t1 + d2;
1495 long int d3 = pc.rel.seconds;
1496 time_t t3 = t2 + d3;
1497 long int d4 = (sum_ns - normalized_ns) / BILLION;
1498 time_t t4 = t3 + d4;
1500 if ((d1 / (60 * 60) ^ pc.rel.hour)
1501 | (d2 / 60 ^ pc.rel.minutes)
1502 | ((t1 < t0) ^ (d1 < 0))
1503 | ((t2 < t1) ^ (d2 < 0))
1504 | ((t3 < t2) ^ (d3 < 0))
1505 | ((t4 < t3) ^ (d4 < 0)))
1508 result->tv_sec = t4;
1509 result->tv_nsec = normalized_ns;
1519 ok &= (tz0 ? setenv ("TZ", tz0, 1) : unsetenv ("TZ")) == 0;
1528 main (int ac, char **av)
1532 printf ("Enter date, or blank line to exit.\n\t> ");
1535 buff[BUFSIZ - 1] = '\0';
1536 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1539 struct tm const *tm;
1540 if (! get_date (&d, buff, NULL))
1541 printf ("Bad format - couldn't convert.\n");
1542 else if (! (tm = localtime (&d.tv_sec)))
1544 long int sec = d.tv_sec;
1545 printf ("localtime (%ld) failed\n", sec);
1550 printf ("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1551 tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
1552 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);