2 /* Parse a string into an internal time stamp.
4 Copyright (C) 1999, 2000, 2002, 2003, 2004, 2005, 2006 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 2, or (at your option)
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, write to the Free Software Foundation,
19 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
21 /* Originally written by Steven M. Bellovin <smb@research.att.com> while
22 at the University of North Carolina at Chapel Hill. Later tweaked by
23 a couple of people on Usenet. Completely overhauled by Rich $alz
24 <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
26 Modified by Paul Eggert <eggert@twinsun.com> in August 1999 to do
27 the right thing about local DST. Also modified by Paul Eggert
28 <eggert@cs.ucla.edu> in February 2004 to support
29 nanosecond-resolution time stamps, and in October 2004 to support
30 TZ strings in dates. */
32 /* FIXME: Check for arithmetic overflow in all cases, not just
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 getdate.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. */
71 #if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII)
72 # define IN_CTYPE_DOMAIN(c) 1
74 # define IN_CTYPE_DOMAIN(c) isascii (c)
77 #define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c))
78 #define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c))
79 #define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c))
81 /* ISDIGIT differs from isdigit, as follows:
82 - Its arg may be any int or unsigned int; it need not be an unsigned char.
83 - It's guaranteed to evaluate its argument exactly once.
84 - It's typically faster.
85 POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
86 isdigit unless it's important to use the locale's definition
87 of `digit' even when the host does not conform to POSIX. */
88 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
90 #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
91 # define __attribute__(x)
94 #ifndef ATTRIBUTE_UNUSED
95 # define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
98 /* Shift A right by B bits portably, by dividing A by 2**B and
99 truncating towards minus infinity. A and B should be free of side
100 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
101 INT_BITS is the number of useful bits in an int. GNU code can
102 assume that INT_BITS is at least 32.
104 ISO C99 says that A >> B is implementation-defined if A < 0. Some
105 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
106 right in the usual way when A < 0, so SHR falls back on division if
107 ordinary A >> B doesn't seem to be the usual signed shift. */
111 : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
113 #define EPOCH_YEAR 1970
114 #define TM_YEAR_BASE 1900
116 #define HOUR(x) ((x) * 60)
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 *, char *);
208 static long int time_zone_hhmm (textint, long int);
212 /* We want a reentrant parser, even if the TZ manipulation and the calls to
213 localtime and gmtime are not reentrant. */
215 %parse-param { parser_control *pc }
216 %lex-param { parser_control *pc }
218 /* This grammar has 20 shift/reduce conflicts. */
225 struct timespec timespec;
231 %token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
232 %token <intval> tDAY_UNIT
234 %token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
235 %token <intval> tMONTH tORDINAL tZONE
237 %token <textintval> tSNUMBER tUNUMBER
238 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
240 %type <intval> o_colon_minutes o_merid
241 %type <timespec> seconds signed_seconds unsigned_seconds
243 %type <rel> relunit relunit_snumber
256 pc->timespec_seen = true;
267 { pc->times_seen++; }
269 { pc->local_zones_seen++; }
271 { pc->zones_seen++; }
273 { pc->dates_seen++; }
277 { pc->rels_seen = true; }
286 pc->seconds.tv_sec = 0;
287 pc->seconds.tv_nsec = 0;
290 | tUNUMBER ':' tUNUMBER o_merid
293 pc->minutes = $3.value;
294 pc->seconds.tv_sec = 0;
295 pc->seconds.tv_nsec = 0;
298 | tUNUMBER ':' tUNUMBER tSNUMBER o_colon_minutes
301 pc->minutes = $3.value;
302 pc->seconds.tv_sec = 0;
303 pc->seconds.tv_nsec = 0;
304 pc->meridian = MER24;
306 pc->time_zone = time_zone_hhmm ($4, $5);
308 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
311 pc->minutes = $3.value;
315 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER o_colon_minutes
318 pc->minutes = $3.value;
320 pc->meridian = MER24;
322 pc->time_zone = time_zone_hhmm ($6, $7);
329 pc->local_isdst = $1;
330 pc->dsts_seen += (0 < $1);
335 pc->dsts_seen += (0 < $1) + 1;
341 { pc->time_zone = $1; }
342 | tZONE relunit_snumber
343 { pc->time_zone = $1;
345 pc->rel.seconds += $2.seconds;
346 pc->rel.minutes += $2.minutes;
347 pc->rel.hour += $2.hour;
348 pc->rel.day += $2.day;
349 pc->rel.month += $2.month;
350 pc->rel.year += $2.year;
351 pc->rels_seen = true; }
352 | tZONE tSNUMBER o_colon_minutes
353 { pc->time_zone = $1 + time_zone_hhmm ($2, $3); }
355 { pc->time_zone = $1 + 60; }
357 { pc->time_zone = $1 + 60; }
373 pc->day_ordinal = $1;
378 pc->day_ordinal = $1.value;
384 tUNUMBER '/' tUNUMBER
386 pc->month = $1.value;
389 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
391 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
392 otherwise as MM/DD/YY.
393 The goal in recognizing YYYY/MM/DD is solely to support legacy
394 machine-generated dates like those in an RCS log listing. If
395 you want portability, use the ISO 8601 format. */
399 pc->month = $3.value;
404 pc->month = $1.value;
409 | tUNUMBER tSNUMBER tSNUMBER
411 /* ISO 8601 format. YYYY-MM-DD. */
413 pc->month = -$2.value;
416 | tUNUMBER tMONTH tSNUMBER
418 /* e.g. 17-JUN-1992. */
421 pc->year.value = -$3.value;
422 pc->year.digits = $3.digits;
424 | tMONTH tSNUMBER tSNUMBER
426 /* e.g. JUN-17-1992. */
429 pc->year.value = -$3.value;
430 pc->year.digits = $3.digits;
437 | tMONTH tUNUMBER ',' tUNUMBER
448 | tUNUMBER tMONTH tUNUMBER
460 pc->rel.seconds -= $1.seconds;
461 pc->rel.minutes -= $1.minutes;
462 pc->rel.hour -= $1.hour;
463 pc->rel.day -= $1.day;
464 pc->rel.month -= $1.month;
465 pc->rel.year -= $1.year;
470 pc->rel.seconds += $1.seconds;
471 pc->rel.minutes += $1.minutes;
472 pc->rel.hour += $1.hour;
473 pc->rel.day += $1.day;
474 pc->rel.month += $1.month;
475 pc->rel.year += $1.year;
481 { $$ = RELATIVE_TIME_0; $$.year = $1; }
482 | tUNUMBER tYEAR_UNIT
483 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
485 { $$ = RELATIVE_TIME_0; $$.year = 1; }
486 | tORDINAL tMONTH_UNIT
487 { $$ = RELATIVE_TIME_0; $$.month = $1; }
488 | tUNUMBER tMONTH_UNIT
489 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
491 { $$ = RELATIVE_TIME_0; $$.month = 1; }
493 { $$ = RELATIVE_TIME_0; $$.day = $1 * $2; }
495 { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
497 { $$ = RELATIVE_TIME_0; $$.day = $1; }
498 | tORDINAL tHOUR_UNIT
499 { $$ = RELATIVE_TIME_0; $$.hour = $1; }
500 | tUNUMBER tHOUR_UNIT
501 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
503 { $$ = RELATIVE_TIME_0; $$.hour = 1; }
504 | tORDINAL tMINUTE_UNIT
505 { $$ = RELATIVE_TIME_0; $$.minutes = $1; }
506 | tUNUMBER tMINUTE_UNIT
507 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
509 { $$ = RELATIVE_TIME_0; $$.minutes = 1; }
511 { $$ = RELATIVE_TIME_0; $$.seconds = $1; }
513 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
514 | tSDECIMAL_NUMBER tSEC_UNIT
515 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
516 | tUDECIMAL_NUMBER tSEC_UNIT
517 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
519 { $$ = RELATIVE_TIME_0; $$.seconds = 1; }
525 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
526 | tSNUMBER tMONTH_UNIT
527 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
529 { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
530 | tSNUMBER tHOUR_UNIT
531 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
532 | tSNUMBER tMINUTE_UNIT
533 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
535 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
538 seconds: signed_seconds | unsigned_seconds;
543 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
549 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
555 if (pc->dates_seen && ! pc->year.digits
556 && ! pc->rels_seen && (pc->times_seen || 2 < $1.digits))
563 pc->day = $1.value % 100;
564 pc->month = ($1.value / 100) % 100;
565 pc->year.value = $1.value / 10000;
566 pc->year.digits = $1.digits - 4;
578 pc->hour = $1.value / 100;
579 pc->minutes = $1.value % 100;
581 pc->seconds.tv_sec = 0;
582 pc->seconds.tv_nsec = 0;
583 pc->meridian = MER24;
605 static table const meridian_table[] =
607 { "AM", tMERIDIAN, MERam },
608 { "A.M.", tMERIDIAN, MERam },
609 { "PM", tMERIDIAN, MERpm },
610 { "P.M.", tMERIDIAN, MERpm },
614 static table const dst_table[] =
619 static table const month_and_day_table[] =
621 { "JANUARY", tMONTH, 1 },
622 { "FEBRUARY", tMONTH, 2 },
623 { "MARCH", tMONTH, 3 },
624 { "APRIL", tMONTH, 4 },
625 { "MAY", tMONTH, 5 },
626 { "JUNE", tMONTH, 6 },
627 { "JULY", tMONTH, 7 },
628 { "AUGUST", tMONTH, 8 },
629 { "SEPTEMBER",tMONTH, 9 },
630 { "SEPT", tMONTH, 9 },
631 { "OCTOBER", tMONTH, 10 },
632 { "NOVEMBER", tMONTH, 11 },
633 { "DECEMBER", tMONTH, 12 },
634 { "SUNDAY", tDAY, 0 },
635 { "MONDAY", tDAY, 1 },
636 { "TUESDAY", tDAY, 2 },
638 { "WEDNESDAY",tDAY, 3 },
639 { "WEDNES", tDAY, 3 },
640 { "THURSDAY", tDAY, 4 },
642 { "THURS", tDAY, 4 },
643 { "FRIDAY", tDAY, 5 },
644 { "SATURDAY", tDAY, 6 },
648 static table const time_units_table[] =
650 { "YEAR", tYEAR_UNIT, 1 },
651 { "MONTH", tMONTH_UNIT, 1 },
652 { "FORTNIGHT",tDAY_UNIT, 14 },
653 { "WEEK", tDAY_UNIT, 7 },
654 { "DAY", tDAY_UNIT, 1 },
655 { "HOUR", tHOUR_UNIT, 1 },
656 { "MINUTE", tMINUTE_UNIT, 1 },
657 { "MIN", tMINUTE_UNIT, 1 },
658 { "SECOND", tSEC_UNIT, 1 },
659 { "SEC", tSEC_UNIT, 1 },
663 /* Assorted relative-time words. */
664 static table const relative_time_table[] =
666 { "TOMORROW", tDAY_UNIT, 1 },
667 { "YESTERDAY",tDAY_UNIT, -1 },
668 { "TODAY", tDAY_UNIT, 0 },
669 { "NOW", tDAY_UNIT, 0 },
670 { "LAST", tORDINAL, -1 },
671 { "THIS", tORDINAL, 0 },
672 { "NEXT", tORDINAL, 1 },
673 { "FIRST", tORDINAL, 1 },
674 /*{ "SECOND", tORDINAL, 2 }, */
675 { "THIRD", tORDINAL, 3 },
676 { "FOURTH", tORDINAL, 4 },
677 { "FIFTH", tORDINAL, 5 },
678 { "SIXTH", tORDINAL, 6 },
679 { "SEVENTH", tORDINAL, 7 },
680 { "EIGHTH", tORDINAL, 8 },
681 { "NINTH", tORDINAL, 9 },
682 { "TENTH", tORDINAL, 10 },
683 { "ELEVENTH", tORDINAL, 11 },
684 { "TWELFTH", tORDINAL, 12 },
689 /* The universal time zone table. These labels can be used even for
690 time stamps that would not otherwise be valid, e.g., GMT time
691 stamps in London during summer. */
692 static table const universal_time_zone_table[] =
694 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
695 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
696 { "UTC", tZONE, HOUR ( 0) },
700 /* The time zone table. This table is necessarily incomplete, as time
701 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
702 as Eastern time in Australia, not as US Eastern Standard Time.
703 You cannot rely on getdate to handle arbitrary time zone
704 abbreviations; use numeric abbreviations like `-0500' instead. */
705 static table const time_zone_table[] =
707 { "WET", tZONE, HOUR ( 0) }, /* Western European */
708 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
709 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
710 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
711 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
712 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
713 { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
714 { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
715 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
716 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
717 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
718 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
719 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
720 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
721 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
722 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
723 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
724 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
725 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
726 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
727 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
728 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
729 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
730 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
731 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
732 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
733 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
734 { "CET", tZONE, HOUR ( 1) }, /* Central European */
735 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
736 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
737 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
738 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
739 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
740 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
741 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
742 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
743 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
744 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
745 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
746 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
747 { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
748 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
749 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
750 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
751 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
752 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
753 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
757 /* Military time zone table. */
758 static table const military_table[] =
760 { "A", tZONE, -HOUR ( 1) },
761 { "B", tZONE, -HOUR ( 2) },
762 { "C", tZONE, -HOUR ( 3) },
763 { "D", tZONE, -HOUR ( 4) },
764 { "E", tZONE, -HOUR ( 5) },
765 { "F", tZONE, -HOUR ( 6) },
766 { "G", tZONE, -HOUR ( 7) },
767 { "H", tZONE, -HOUR ( 8) },
768 { "I", tZONE, -HOUR ( 9) },
769 { "K", tZONE, -HOUR (10) },
770 { "L", tZONE, -HOUR (11) },
771 { "M", tZONE, -HOUR (12) },
772 { "N", tZONE, HOUR ( 1) },
773 { "O", tZONE, HOUR ( 2) },
774 { "P", tZONE, HOUR ( 3) },
775 { "Q", tZONE, HOUR ( 4) },
776 { "R", tZONE, HOUR ( 5) },
777 { "S", tZONE, HOUR ( 6) },
778 { "T", tZONE, HOUR ( 7) },
779 { "U", tZONE, HOUR ( 8) },
780 { "V", tZONE, HOUR ( 9) },
781 { "W", tZONE, HOUR (10) },
782 { "X", tZONE, HOUR (11) },
783 { "Y", tZONE, HOUR (12) },
784 { "Z", tZONE, HOUR ( 0) },
790 /* Convert a time zone expressed as HH:MM into an integer count of
791 minutes. If MM is negative, then S is of the form HHMM and needs
792 to be picked apart; otherwise, S is of the form HH. */
795 time_zone_hhmm (textint s, long int mm)
798 return (s.value / 100) * 60 + s.value % 100;
800 return s.value * 60 + (s.negative ? -mm : mm);
804 to_hour (long int hours, int meridian)
808 default: /* Pacify GCC. */
810 return 0 <= hours && hours < 24 ? hours : -1;
812 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
814 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
819 to_year (textint textyear)
821 long int year = textyear.value;
826 /* XPG4 suggests that years 00-68 map to 2000-2068, and
827 years 69-99 map to 1969-1999. */
828 else if (textyear.digits == 2)
829 year += year < 69 ? 2000 : 1900;
835 lookup_zone (parser_control const *pc, char const *name)
839 for (tp = universal_time_zone_table; tp->name; tp++)
840 if (strcmp (name, tp->name) == 0)
843 /* Try local zone abbreviations before those in time_zone_table, as
844 the local ones are more likely to be right. */
845 for (tp = pc->local_time_zone_table; tp->name; tp++)
846 if (strcmp (name, tp->name) == 0)
849 for (tp = time_zone_table; tp->name; tp++)
850 if (strcmp (name, tp->name) == 0)
857 /* Yield the difference between *A and *B,
858 measured in seconds, ignoring leap seconds.
859 The body of this function is taken directly from the GNU C Library;
860 see src/strftime.c. */
862 tm_diff (struct tm const *a, struct tm const *b)
864 /* Compute intervening leap days correctly even if year is negative.
865 Take care to avoid int overflow in leap day calculations. */
866 int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
867 int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
868 int a100 = a4 / 25 - (a4 % 25 < 0);
869 int b100 = b4 / 25 - (b4 % 25 < 0);
870 int a400 = SHR (a100, 2);
871 int b400 = SHR (b100, 2);
872 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
873 long int ayear = a->tm_year;
874 long int years = ayear - b->tm_year;
875 long int days = (365 * years + intervening_leap_days
876 + (a->tm_yday - b->tm_yday));
877 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
878 + (a->tm_min - b->tm_min))
879 + (a->tm_sec - b->tm_sec));
881 #endif /* ! HAVE_TM_GMTOFF */
884 lookup_word (parser_control const *pc, char *word)
893 /* Make it uppercase. */
894 for (p = word; *p; p++)
896 unsigned char ch = *p;
901 for (tp = meridian_table; tp->name; tp++)
902 if (strcmp (word, tp->name) == 0)
905 /* See if we have an abbreviation for a month. */
906 wordlen = strlen (word);
907 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
909 for (tp = month_and_day_table; tp->name; tp++)
910 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
913 if ((tp = lookup_zone (pc, word)))
916 if (strcmp (word, dst_table[0].name) == 0)
919 for (tp = time_units_table; tp->name; tp++)
920 if (strcmp (word, tp->name) == 0)
923 /* Strip off any plural and try the units table again. */
924 if (word[wordlen - 1] == 'S')
926 word[wordlen - 1] = '\0';
927 for (tp = time_units_table; tp->name; tp++)
928 if (strcmp (word, tp->name) == 0)
930 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
933 for (tp = relative_time_table; tp->name; tp++)
934 if (strcmp (word, tp->name) == 0)
937 /* Military time zones. */
939 for (tp = military_table; tp->name; tp++)
940 if (word[0] == tp->name[0])
943 /* Drop out any periods and try the time zone table again. */
944 for (period_found = false, p = q = word; (*p = *q); q++)
949 if (period_found && (tp = lookup_zone (pc, word)))
956 yylex (YYSTYPE *lvalp, parser_control *pc)
963 while (c = *pc->input, ISSPACE (c))
966 if (ISDIGIT (c) || c == '-' || c == '+')
970 unsigned long int value;
971 if (c == '-' || c == '+')
973 sign = c == '-' ? -1 : 1;
974 while (c = *++pc->input, ISSPACE (c))
977 /* skip the '-' sign */
983 for (value = 0; ; value *= 10)
985 unsigned long int value1 = value + (c - '0');
992 if (ULONG_MAX / 10 < value)
995 if ((c == '.' || c == ',') && ISDIGIT (p[1]))
1000 unsigned long int value1;
1002 /* Check for overflow when converting value to time_t. */
1017 if (value != value1)
1020 /* Accumulate fraction, to ns precision. */
1023 for (digits = 2; digits <= LOG10_BILLION; digits++)
1030 /* Skip excess digits, truncating toward -Infinity. */
1032 for (; ISDIGIT (*p); p++)
1038 while (ISDIGIT (*p))
1041 /* Adjust to the timespec convention, which is that
1042 tv_nsec is always a positive offset even if tv_sec is
1052 lvalp->timespec.tv_sec = s;
1053 lvalp->timespec.tv_nsec = ns;
1055 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
1059 lvalp->textintval.negative = sign < 0;
1062 lvalp->textintval.value = - value;
1063 if (0 < lvalp->textintval.value)
1068 lvalp->textintval.value = value;
1069 if (lvalp->textintval.value < 0)
1072 lvalp->textintval.digits = p - pc->input;
1074 return sign ? tSNUMBER : tUNUMBER;
1086 if (p < buff + sizeof buff - 1)
1090 while (ISALPHA (c) || c == '.');
1093 tp = lookup_word (pc, buff);
1096 lvalp->intval = tp->value;
1101 return *pc->input++;
1117 /* Do nothing if the parser reports an error. */
1119 yyerror (parser_control *pc ATTRIBUTE_UNUSED, char *s ATTRIBUTE_UNUSED)
1124 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1125 passing it to mktime, return true if it's OK that mktime returned T.
1126 It's not OK if *TM0 has out-of-range members. */
1129 mktime_ok (struct tm const *tm0, struct tm const *tm1, time_t t)
1131 if (t == (time_t) -1)
1133 /* Guard against falsely reporting an error when parsing a time
1134 stamp that happens to equal (time_t) -1, on a host that
1135 supports such a time stamp. */
1136 tm1 = localtime (&t);
1141 return ! ((tm0->tm_sec ^ tm1->tm_sec)
1142 | (tm0->tm_min ^ tm1->tm_min)
1143 | (tm0->tm_hour ^ tm1->tm_hour)
1144 | (tm0->tm_mday ^ tm1->tm_mday)
1145 | (tm0->tm_mon ^ tm1->tm_mon)
1146 | (tm0->tm_year ^ tm1->tm_year));
1149 /* A reasonable upper bound for the size of ordinary TZ strings.
1150 Use heap allocation if TZ's length exceeds this. */
1151 enum { TZBUFSIZE = 100 };
1153 /* Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
1156 get_tz (char tzbuf[TZBUFSIZE])
1158 char *tz = getenv ("TZ");
1161 size_t tzsize = strlen (tz) + 1;
1162 tz = (tzsize <= TZBUFSIZE
1163 ? memcpy (tzbuf, tz, tzsize)
1164 : xmemdup (tz, tzsize));
1169 /* Parse a date/time string, storing the resulting time value into *RESULT.
1170 The string itself is pointed to by P. Return true if successful.
1171 P can be an incomplete or relative time specification; if so, use
1172 *NOW as the basis for the returned time. */
1174 get_date (struct timespec *result, char const *p, struct timespec const *now)
1178 struct tm const *tmp;
1182 struct timespec gettime_buffer;
1184 bool tz_was_altered = false;
1186 char tz0buf[TZBUFSIZE];
1191 gettime (&gettime_buffer);
1192 now = &gettime_buffer;
1195 Start = now->tv_sec;
1196 Start_ns = now->tv_nsec;
1198 tmp = localtime (&now->tv_sec);
1202 while (c = *p, ISSPACE (c))
1205 if (strncmp (p, "TZ=\"", 4) == 0)
1207 char const *tzbase = p + 4;
1211 for (s = tzbase; *s; s++, tzsize++)
1215 if (! (*s == '\\' || *s == '"'))
1222 char tz1buf[TZBUFSIZE];
1223 bool large_tz = TZBUFSIZE < tzsize;
1225 tz0 = get_tz (tz0buf);
1226 z = tz1 = large_tz ? xmalloc (tzsize) : tz1buf;
1227 for (s = tzbase; *s != '"'; s++)
1228 *z++ = *(s += *s == '\\');
1230 setenv_ok = setenv ("TZ", tz1, 1) == 0;
1235 tz_was_altered = true;
1241 pc.year.value = tmp->tm_year;
1242 pc.year.value += TM_YEAR_BASE;
1244 pc.month = tmp->tm_mon + 1;
1245 pc.day = tmp->tm_mday;
1246 pc.hour = tmp->tm_hour;
1247 pc.minutes = tmp->tm_min;
1248 pc.seconds.tv_sec = tmp->tm_sec;
1249 pc.seconds.tv_nsec = Start_ns;
1250 tm.tm_isdst = tmp->tm_isdst;
1252 pc.meridian = MER24;
1253 pc.rel = RELATIVE_TIME_0;
1254 pc.timespec_seen = false;
1255 pc.rels_seen = false;
1259 pc.local_zones_seen = 0;
1263 #if HAVE_STRUCT_TM_TM_ZONE
1264 pc.local_time_zone_table[0].name = tmp->tm_zone;
1265 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1266 pc.local_time_zone_table[0].value = tmp->tm_isdst;
1267 pc.local_time_zone_table[1].name = NULL;
1269 /* Probe the names used in the next three calendar quarters, looking
1270 for a tm_isdst different from the one we already have. */
1273 for (quarter = 1; quarter <= 3; quarter++)
1275 time_t probe = Start + quarter * (90 * 24 * 60 * 60);
1276 struct tm const *probe_tm = localtime (&probe);
1277 if (probe_tm && probe_tm->tm_zone
1278 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
1281 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
1282 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1283 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
1284 pc.local_time_zone_table[2].name = NULL;
1294 extern char *tzname[];
1297 for (i = 0; i < 2; i++)
1299 pc.local_time_zone_table[i].name = tzname[i];
1300 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1301 pc.local_time_zone_table[i].value = i;
1303 pc.local_time_zone_table[i].name = NULL;
1306 pc.local_time_zone_table[0].name = NULL;
1310 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1311 && ! strcmp (pc.local_time_zone_table[0].name,
1312 pc.local_time_zone_table[1].name))
1314 /* This locale uses the same abbrevation for standard and
1315 daylight times. So if we see that abbreviation, we don't
1316 know whether it's daylight time. */
1317 pc.local_time_zone_table[0].value = -1;
1318 pc.local_time_zone_table[1].name = NULL;
1321 if (yyparse (&pc) != 0)
1324 if (pc.timespec_seen)
1325 *result = pc.seconds;
1328 if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
1329 | (pc.local_zones_seen + pc.zones_seen)))
1332 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
1333 tm.tm_mon = pc.month - 1;
1334 tm.tm_mday = pc.day;
1335 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1337 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1340 tm.tm_min = pc.minutes;
1341 tm.tm_sec = pc.seconds.tv_sec;
1345 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1346 pc.seconds.tv_nsec = 0;
1349 /* Let mktime deduce tm_isdst if we have an absolute time stamp. */
1350 if (pc.dates_seen | pc.days_seen | pc.times_seen)
1353 /* But if the input explicitly specifies local time with or without
1354 DST, give mktime that information. */
1355 if (pc.local_zones_seen)
1356 tm.tm_isdst = pc.local_isdst;
1360 Start = mktime (&tm);
1362 if (! mktime_ok (&tm0, &tm, Start))
1364 if (! pc.zones_seen)
1368 /* Guard against falsely reporting errors near the time_t
1369 boundaries when parsing times in other time zones. For
1370 example, suppose the input string "1969-12-31 23:00:00 -0100",
1371 the current time zone is 8 hours ahead of UTC, and the min
1372 time_t value is 1970-01-01 00:00:00 UTC. Then the min
1373 localtime value is 1970-01-01 08:00:00, and mktime will
1374 therefore fail on 1969-12-31 23:00:00. To work around the
1375 problem, set the time zone to 1 hour behind UTC temporarily
1376 by setting TZ="XXX1:00" and try mktime again. */
1378 long int time_zone = pc.time_zone;
1379 long int abs_time_zone = time_zone < 0 ? - time_zone : time_zone;
1380 long int abs_time_zone_hour = abs_time_zone / 60;
1381 int abs_time_zone_min = abs_time_zone % 60;
1382 char tz1buf[sizeof "XXX+0:00"
1383 + sizeof pc.time_zone * CHAR_BIT / 3];
1384 if (!tz_was_altered)
1385 tz0 = get_tz (tz0buf);
1386 sprintf (tz1buf, "XXX%s%ld:%02d", "-" + (time_zone < 0),
1387 abs_time_zone_hour, abs_time_zone_min);
1388 if (setenv ("TZ", tz1buf, 1) != 0)
1390 tz_was_altered = true;
1392 Start = mktime (&tm);
1393 if (! mktime_ok (&tm0, &tm, Start))
1398 if (pc.days_seen && ! pc.dates_seen)
1400 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1401 + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
1403 Start = mktime (&tm);
1404 if (Start == (time_t) -1)
1410 long int delta = pc.time_zone * 60;
1412 #ifdef HAVE_TM_GMTOFF
1413 delta -= tm.tm_gmtoff;
1416 struct tm const *gmt = gmtime (&t);
1419 delta -= tm_diff (&tm, gmt);
1422 if ((Start < t1) != (delta < 0))
1423 goto fail; /* time_t overflow */
1427 /* Add relative date. */
1428 if (pc.rel.year | pc.rel.month | pc.rel.day)
1430 int year = tm.tm_year + pc.rel.year;
1431 int month = tm.tm_mon + pc.rel.month;
1432 int day = tm.tm_mday + pc.rel.day;
1433 if (((year < tm.tm_year) ^ (pc.rel.year < 0))
1434 | ((month < tm.tm_mon) ^ (pc.rel.month < 0))
1435 | ((day < tm.tm_mday) ^ (pc.rel.day < 0)))
1440 tm.tm_hour = tm0.tm_hour;
1441 tm.tm_min = tm0.tm_min;
1442 tm.tm_sec = tm0.tm_sec;
1443 tm.tm_isdst = tm0.tm_isdst;
1444 Start = mktime (&tm);
1445 if (Start == (time_t) -1)
1449 /* Add relative hours, minutes, and seconds. On hosts that support
1450 leap seconds, ignore the possibility of leap seconds; e.g.,
1451 "+ 10 minutes" adds 600 seconds, even if one of them is a
1452 leap second. Typically this is not what the user wants, but it's
1453 too hard to do it the other way, because the time zone indicator
1454 must be applied before relative times, and if mktime is applied
1455 again the time zone will be lost. */
1457 long int sum_ns = pc.seconds.tv_nsec + pc.rel.ns;
1458 long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
1460 long int d1 = 60 * 60 * pc.rel.hour;
1461 time_t t1 = t0 + d1;
1462 long int d2 = 60 * pc.rel.minutes;
1463 time_t t2 = t1 + d2;
1464 long int d3 = pc.rel.seconds;
1465 time_t t3 = t2 + d3;
1466 long int d4 = (sum_ns - normalized_ns) / BILLION;
1467 time_t t4 = t3 + d4;
1469 if ((d1 / (60 * 60) ^ pc.rel.hour)
1470 | (d2 / 60 ^ pc.rel.minutes)
1471 | ((t1 < t0) ^ (d1 < 0))
1472 | ((t2 < t1) ^ (d2 < 0))
1473 | ((t3 < t2) ^ (d3 < 0))
1474 | ((t4 < t3) ^ (d4 < 0)))
1477 result->tv_sec = t4;
1478 result->tv_nsec = normalized_ns;
1488 ok &= (tz0 ? setenv ("TZ", tz0, 1) : unsetenv ("TZ")) == 0;
1497 main (int ac, char **av)
1501 printf ("Enter date, or blank line to exit.\n\t> ");
1504 buff[BUFSIZ - 1] = '\0';
1505 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1508 struct tm const *tm;
1509 if (! get_date (&d, buff, NULL))
1510 printf ("Bad format - couldn't convert.\n");
1511 else if (! (tm = localtime (&d.tv_sec)))
1513 long int sec = d.tv_sec;
1514 printf ("localtime (%ld) failed\n", sec);
1519 printf ("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1520 tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
1521 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);