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 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
40 /* There's no need to extend the stack, so there's no need to involve
42 #define YYSTACK_USE_ALLOCA 0
44 /* Tell Bison how much stack space is needed. 20 should be plenty for
45 this grammar, which is not right recursive. Beware setting it too
46 high, since that might cause problems on machines whose
47 implementations have lame stack-overflow checking. */
49 #define YYINITDEPTH YYMAXDEPTH
51 /* Since the code of getdate.y is not included in the Emacs executable
52 itself, there is no need to #define static in this file. Even if
53 the code were included in the Emacs executable, it probably
54 wouldn't do any harm to #undef it here; this will only cause
55 problems if we try to write to a static variable, which I don't
56 think this code needs to do. */
71 /* ISDIGIT differs from isdigit, as follows:
72 - Its arg may be any int or unsigned int; it need not be an unsigned char
74 - It's typically faster.
75 POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
76 isdigit unless it's important to use the locale's definition
77 of `digit' even when the host does not conform to POSIX. */
78 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
81 # if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
82 # define __attribute__(x)
86 #ifndef ATTRIBUTE_UNUSED
87 # define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
90 /* Shift A right by B bits portably, by dividing A by 2**B and
91 truncating towards minus infinity. A and B should be free of side
92 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
93 INT_BITS is the number of useful bits in an int. GNU code can
94 assume that INT_BITS is at least 32.
96 ISO C99 says that A >> B is implementation-defined if A < 0. Some
97 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
98 right in the usual way when A < 0, so SHR falls back on division if
99 ordinary A >> B doesn't seem to be the usual signed shift. */
103 : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
105 #define EPOCH_YEAR 1970
106 #define TM_YEAR_BASE 1900
108 #define HOUR(x) ((x) * 60)
110 /* An integer value, and the number of digits in its textual
119 /* An entry in the lexical lookup table. */
127 /* Meridian: am, pm, or 24-hour style. */
128 enum { MERam, MERpm, MER24 };
130 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
132 /* Relative times. */
135 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
145 #if HAVE_COMPOUND_LITERALS
146 # define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
148 static relative_time const RELATIVE_TIME_0;
151 /* Information passed to and from the parser. */
154 /* The input string remaining to be parsed. */
157 /* N, if this is the Nth Tuesday. */
158 long int day_ordinal;
160 /* Day of week; Sunday is 0. */
163 /* tm_isdst flag for the local zone. */
166 /* Time zone, in minutes east of UTC. */
169 /* Style used for time. */
172 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
178 struct timespec seconds; /* includes nanoseconds */
180 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
183 /* Presence or counts of nonterminals of various flavors parsed so far. */
188 size_t local_zones_seen;
193 /* Table of local time zone abbrevations, terminated by a null entry. */
194 table local_time_zone_table[3];
198 static int yylex (union YYSTYPE *, parser_control *);
199 static int yyerror (parser_control const *, char const *);
200 static long int time_zone_hhmm (textint, long int);
204 /* We want a reentrant parser, even if the TZ manipulation and the calls to
205 localtime and gmtime are not reentrant. */
207 %parse-param { parser_control *pc }
208 %lex-param { parser_control *pc }
210 /* This grammar has 20 shift/reduce conflicts. */
217 struct timespec timespec;
223 %token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
224 %token <intval> tDAY_UNIT
226 %token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
227 %token <intval> tMONTH tORDINAL tZONE
229 %token <textintval> tSNUMBER tUNUMBER
230 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
232 %type <intval> o_colon_minutes o_merid
233 %type <timespec> seconds signed_seconds unsigned_seconds
235 %type <rel> relunit relunit_snumber
248 pc->timespec_seen = true;
259 { pc->times_seen++; }
261 { pc->local_zones_seen++; }
263 { pc->zones_seen++; }
265 { pc->dates_seen++; }
269 { pc->rels_seen = true; }
278 pc->seconds.tv_sec = 0;
279 pc->seconds.tv_nsec = 0;
282 | tUNUMBER ':' tUNUMBER o_merid
285 pc->minutes = $3.value;
286 pc->seconds.tv_sec = 0;
287 pc->seconds.tv_nsec = 0;
290 | tUNUMBER ':' tUNUMBER tSNUMBER o_colon_minutes
293 pc->minutes = $3.value;
294 pc->seconds.tv_sec = 0;
295 pc->seconds.tv_nsec = 0;
296 pc->meridian = MER24;
298 pc->time_zone = time_zone_hhmm ($4, $5);
300 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
303 pc->minutes = $3.value;
307 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER o_colon_minutes
310 pc->minutes = $3.value;
312 pc->meridian = MER24;
314 pc->time_zone = time_zone_hhmm ($6, $7);
321 pc->local_isdst = $1;
322 pc->dsts_seen += (0 < $1);
327 pc->dsts_seen += (0 < $1) + 1;
333 { pc->time_zone = $1; }
334 | tZONE relunit_snumber
335 { pc->time_zone = $1;
337 pc->rel.seconds += $2.seconds;
338 pc->rel.minutes += $2.minutes;
339 pc->rel.hour += $2.hour;
340 pc->rel.day += $2.day;
341 pc->rel.month += $2.month;
342 pc->rel.year += $2.year;
343 pc->rels_seen = true; }
344 | tZONE tSNUMBER o_colon_minutes
345 { pc->time_zone = $1 + time_zone_hhmm ($2, $3); }
347 { pc->time_zone = $1 + 60; }
349 { pc->time_zone = $1 + 60; }
365 pc->day_ordinal = $1;
370 pc->day_ordinal = $1.value;
376 tUNUMBER '/' tUNUMBER
378 pc->month = $1.value;
381 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
383 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
384 otherwise as MM/DD/YY.
385 The goal in recognizing YYYY/MM/DD is solely to support legacy
386 machine-generated dates like those in an RCS log listing. If
387 you want portability, use the ISO 8601 format. */
391 pc->month = $3.value;
396 pc->month = $1.value;
401 | tUNUMBER tSNUMBER tSNUMBER
403 /* ISO 8601 format. YYYY-MM-DD. */
405 pc->month = -$2.value;
408 | tUNUMBER tMONTH tSNUMBER
410 /* e.g. 17-JUN-1992. */
413 pc->year.value = -$3.value;
414 pc->year.digits = $3.digits;
416 | tMONTH tSNUMBER tSNUMBER
418 /* e.g. JUN-17-1992. */
421 pc->year.value = -$3.value;
422 pc->year.digits = $3.digits;
429 | tMONTH tUNUMBER ',' tUNUMBER
440 | tUNUMBER tMONTH tUNUMBER
452 pc->rel.seconds -= $1.seconds;
453 pc->rel.minutes -= $1.minutes;
454 pc->rel.hour -= $1.hour;
455 pc->rel.day -= $1.day;
456 pc->rel.month -= $1.month;
457 pc->rel.year -= $1.year;
462 pc->rel.seconds += $1.seconds;
463 pc->rel.minutes += $1.minutes;
464 pc->rel.hour += $1.hour;
465 pc->rel.day += $1.day;
466 pc->rel.month += $1.month;
467 pc->rel.year += $1.year;
473 { $$ = RELATIVE_TIME_0; $$.year = $1; }
474 | tUNUMBER tYEAR_UNIT
475 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
477 { $$ = RELATIVE_TIME_0; $$.year = 1; }
478 | tORDINAL tMONTH_UNIT
479 { $$ = RELATIVE_TIME_0; $$.month = $1; }
480 | tUNUMBER tMONTH_UNIT
481 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
483 { $$ = RELATIVE_TIME_0; $$.month = 1; }
485 { $$ = RELATIVE_TIME_0; $$.day = $1 * $2; }
487 { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
489 { $$ = RELATIVE_TIME_0; $$.day = $1; }
490 | tORDINAL tHOUR_UNIT
491 { $$ = RELATIVE_TIME_0; $$.hour = $1; }
492 | tUNUMBER tHOUR_UNIT
493 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
495 { $$ = RELATIVE_TIME_0; $$.hour = 1; }
496 | tORDINAL tMINUTE_UNIT
497 { $$ = RELATIVE_TIME_0; $$.minutes = $1; }
498 | tUNUMBER tMINUTE_UNIT
499 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
501 { $$ = RELATIVE_TIME_0; $$.minutes = 1; }
503 { $$ = RELATIVE_TIME_0; $$.seconds = $1; }
505 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
506 | tSDECIMAL_NUMBER tSEC_UNIT
507 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
508 | tUDECIMAL_NUMBER tSEC_UNIT
509 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
511 { $$ = RELATIVE_TIME_0; $$.seconds = 1; }
517 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
518 | tSNUMBER tMONTH_UNIT
519 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
521 { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
522 | tSNUMBER tHOUR_UNIT
523 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
524 | tSNUMBER tMINUTE_UNIT
525 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
527 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
530 seconds: signed_seconds | unsigned_seconds;
535 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
541 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
547 if (pc->dates_seen && ! pc->year.digits
548 && ! pc->rels_seen && (pc->times_seen || 2 < $1.digits))
555 pc->day = $1.value % 100;
556 pc->month = ($1.value / 100) % 100;
557 pc->year.value = $1.value / 10000;
558 pc->year.digits = $1.digits - 4;
570 pc->hour = $1.value / 100;
571 pc->minutes = $1.value % 100;
573 pc->seconds.tv_sec = 0;
574 pc->seconds.tv_nsec = 0;
575 pc->meridian = MER24;
597 static table const meridian_table[] =
599 { "AM", tMERIDIAN, MERam },
600 { "A.M.", tMERIDIAN, MERam },
601 { "PM", tMERIDIAN, MERpm },
602 { "P.M.", tMERIDIAN, MERpm },
606 static table const dst_table[] =
611 static table const month_and_day_table[] =
613 { "JANUARY", tMONTH, 1 },
614 { "FEBRUARY", tMONTH, 2 },
615 { "MARCH", tMONTH, 3 },
616 { "APRIL", tMONTH, 4 },
617 { "MAY", tMONTH, 5 },
618 { "JUNE", tMONTH, 6 },
619 { "JULY", tMONTH, 7 },
620 { "AUGUST", tMONTH, 8 },
621 { "SEPTEMBER",tMONTH, 9 },
622 { "SEPT", tMONTH, 9 },
623 { "OCTOBER", tMONTH, 10 },
624 { "NOVEMBER", tMONTH, 11 },
625 { "DECEMBER", tMONTH, 12 },
626 { "SUNDAY", tDAY, 0 },
627 { "MONDAY", tDAY, 1 },
628 { "TUESDAY", tDAY, 2 },
630 { "WEDNESDAY",tDAY, 3 },
631 { "WEDNES", tDAY, 3 },
632 { "THURSDAY", tDAY, 4 },
634 { "THURS", tDAY, 4 },
635 { "FRIDAY", tDAY, 5 },
636 { "SATURDAY", tDAY, 6 },
640 static table const time_units_table[] =
642 { "YEAR", tYEAR_UNIT, 1 },
643 { "MONTH", tMONTH_UNIT, 1 },
644 { "FORTNIGHT",tDAY_UNIT, 14 },
645 { "WEEK", tDAY_UNIT, 7 },
646 { "DAY", tDAY_UNIT, 1 },
647 { "HOUR", tHOUR_UNIT, 1 },
648 { "MINUTE", tMINUTE_UNIT, 1 },
649 { "MIN", tMINUTE_UNIT, 1 },
650 { "SECOND", tSEC_UNIT, 1 },
651 { "SEC", tSEC_UNIT, 1 },
655 /* Assorted relative-time words. */
656 static table const relative_time_table[] =
658 { "TOMORROW", tDAY_UNIT, 1 },
659 { "YESTERDAY",tDAY_UNIT, -1 },
660 { "TODAY", tDAY_UNIT, 0 },
661 { "NOW", tDAY_UNIT, 0 },
662 { "LAST", tORDINAL, -1 },
663 { "THIS", tORDINAL, 0 },
664 { "NEXT", tORDINAL, 1 },
665 { "FIRST", tORDINAL, 1 },
666 /*{ "SECOND", tORDINAL, 2 }, */
667 { "THIRD", tORDINAL, 3 },
668 { "FOURTH", tORDINAL, 4 },
669 { "FIFTH", tORDINAL, 5 },
670 { "SIXTH", tORDINAL, 6 },
671 { "SEVENTH", tORDINAL, 7 },
672 { "EIGHTH", tORDINAL, 8 },
673 { "NINTH", tORDINAL, 9 },
674 { "TENTH", tORDINAL, 10 },
675 { "ELEVENTH", tORDINAL, 11 },
676 { "TWELFTH", tORDINAL, 12 },
681 /* The universal time zone table. These labels can be used even for
682 time stamps that would not otherwise be valid, e.g., GMT time
683 stamps in London during summer. */
684 static table const universal_time_zone_table[] =
686 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
687 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
688 { "UTC", tZONE, HOUR ( 0) },
692 /* The time zone table. This table is necessarily incomplete, as time
693 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
694 as Eastern time in Australia, not as US Eastern Standard Time.
695 You cannot rely on getdate to handle arbitrary time zone
696 abbreviations; use numeric abbreviations like `-0500' instead. */
697 static table const time_zone_table[] =
699 { "WET", tZONE, HOUR ( 0) }, /* Western European */
700 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
701 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
702 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
703 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
704 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
705 { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
706 { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
707 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
708 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
709 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
710 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
711 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
712 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
713 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
714 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
715 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
716 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
717 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
718 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
719 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
720 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
721 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
722 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
723 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
724 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
725 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
726 { "CET", tZONE, HOUR ( 1) }, /* Central European */
727 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
728 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
729 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
730 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
731 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
732 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
733 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
734 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
735 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
736 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
737 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
738 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
739 { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
740 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
741 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
742 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
743 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
744 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
745 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
749 /* Military time zone table. */
750 static table const military_table[] =
752 { "A", tZONE, -HOUR ( 1) },
753 { "B", tZONE, -HOUR ( 2) },
754 { "C", tZONE, -HOUR ( 3) },
755 { "D", tZONE, -HOUR ( 4) },
756 { "E", tZONE, -HOUR ( 5) },
757 { "F", tZONE, -HOUR ( 6) },
758 { "G", tZONE, -HOUR ( 7) },
759 { "H", tZONE, -HOUR ( 8) },
760 { "I", tZONE, -HOUR ( 9) },
761 { "K", tZONE, -HOUR (10) },
762 { "L", tZONE, -HOUR (11) },
763 { "M", tZONE, -HOUR (12) },
764 { "N", tZONE, HOUR ( 1) },
765 { "O", tZONE, HOUR ( 2) },
766 { "P", tZONE, HOUR ( 3) },
767 { "Q", tZONE, HOUR ( 4) },
768 { "R", tZONE, HOUR ( 5) },
769 { "S", tZONE, HOUR ( 6) },
770 { "T", tZONE, HOUR ( 7) },
771 { "U", tZONE, HOUR ( 8) },
772 { "V", tZONE, HOUR ( 9) },
773 { "W", tZONE, HOUR (10) },
774 { "X", tZONE, HOUR (11) },
775 { "Y", tZONE, HOUR (12) },
776 { "Z", tZONE, HOUR ( 0) },
782 /* Convert a time zone expressed as HH:MM into an integer count of
783 minutes. If MM is negative, then S is of the form HHMM and needs
784 to be picked apart; otherwise, S is of the form HH. */
787 time_zone_hhmm (textint s, long int mm)
790 return (s.value / 100) * 60 + s.value % 100;
792 return s.value * 60 + (s.negative ? -mm : mm);
796 to_hour (long int hours, int meridian)
800 default: /* Pacify GCC. */
802 return 0 <= hours && hours < 24 ? hours : -1;
804 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
806 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
811 to_year (textint textyear)
813 long int year = textyear.value;
818 /* XPG4 suggests that years 00-68 map to 2000-2068, and
819 years 69-99 map to 1969-1999. */
820 else if (textyear.digits == 2)
821 year += year < 69 ? 2000 : 1900;
827 lookup_zone (parser_control const *pc, char const *name)
831 for (tp = universal_time_zone_table; tp->name; tp++)
832 if (strcmp (name, tp->name) == 0)
835 /* Try local zone abbreviations before those in time_zone_table, as
836 the local ones are more likely to be right. */
837 for (tp = pc->local_time_zone_table; tp->name; tp++)
838 if (strcmp (name, tp->name) == 0)
841 for (tp = time_zone_table; tp->name; tp++)
842 if (strcmp (name, tp->name) == 0)
849 /* Yield the difference between *A and *B,
850 measured in seconds, ignoring leap seconds.
851 The body of this function is taken directly from the GNU C Library;
852 see src/strftime.c. */
854 tm_diff (struct tm const *a, struct tm const *b)
856 /* Compute intervening leap days correctly even if year is negative.
857 Take care to avoid int overflow in leap day calculations. */
858 int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
859 int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
860 int a100 = a4 / 25 - (a4 % 25 < 0);
861 int b100 = b4 / 25 - (b4 % 25 < 0);
862 int a400 = SHR (a100, 2);
863 int b400 = SHR (b100, 2);
864 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
865 long int ayear = a->tm_year;
866 long int years = ayear - b->tm_year;
867 long int days = (365 * years + intervening_leap_days
868 + (a->tm_yday - b->tm_yday));
869 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
870 + (a->tm_min - b->tm_min))
871 + (a->tm_sec - b->tm_sec));
873 #endif /* ! HAVE_TM_GMTOFF */
876 lookup_word (parser_control const *pc, char *word)
885 /* Make it uppercase. */
886 for (p = word; *p; p++)
888 unsigned char ch = *p;
892 for (tp = meridian_table; tp->name; tp++)
893 if (strcmp (word, tp->name) == 0)
896 /* See if we have an abbreviation for a month. */
897 wordlen = strlen (word);
898 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
900 for (tp = month_and_day_table; tp->name; tp++)
901 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
904 if ((tp = lookup_zone (pc, word)))
907 if (strcmp (word, dst_table[0].name) == 0)
910 for (tp = time_units_table; tp->name; tp++)
911 if (strcmp (word, tp->name) == 0)
914 /* Strip off any plural and try the units table again. */
915 if (word[wordlen - 1] == 'S')
917 word[wordlen - 1] = '\0';
918 for (tp = time_units_table; tp->name; tp++)
919 if (strcmp (word, tp->name) == 0)
921 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
924 for (tp = relative_time_table; tp->name; tp++)
925 if (strcmp (word, tp->name) == 0)
928 /* Military time zones. */
930 for (tp = military_table; tp->name; tp++)
931 if (word[0] == tp->name[0])
934 /* Drop out any periods and try the time zone table again. */
935 for (period_found = false, p = q = word; (*p = *q); q++)
940 if (period_found && (tp = lookup_zone (pc, word)))
947 yylex (YYSTYPE *lvalp, parser_control *pc)
954 while (c = *pc->input, isspace (c))
957 if (ISDIGIT (c) || c == '-' || c == '+')
961 unsigned long int value;
962 if (c == '-' || c == '+')
964 sign = c == '-' ? -1 : 1;
965 while (c = *++pc->input, isspace (c))
968 /* skip the '-' sign */
974 for (value = 0; ; value *= 10)
976 unsigned long int value1 = value + (c - '0');
983 if (ULONG_MAX / 10 < value)
986 if ((c == '.' || c == ',') && ISDIGIT (p[1]))
991 unsigned long int value1;
993 /* Check for overflow when converting value to time_t. */
1008 if (value != value1)
1011 /* Accumulate fraction, to ns precision. */
1014 for (digits = 2; digits <= LOG10_BILLION; digits++)
1021 /* Skip excess digits, truncating toward -Infinity. */
1023 for (; ISDIGIT (*p); p++)
1029 while (ISDIGIT (*p))
1032 /* Adjust to the timespec convention, which is that
1033 tv_nsec is always a positive offset even if tv_sec is
1043 lvalp->timespec.tv_sec = s;
1044 lvalp->timespec.tv_nsec = ns;
1046 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
1050 lvalp->textintval.negative = sign < 0;
1053 lvalp->textintval.value = - value;
1054 if (0 < lvalp->textintval.value)
1059 lvalp->textintval.value = value;
1060 if (lvalp->textintval.value < 0)
1063 lvalp->textintval.digits = p - pc->input;
1065 return sign ? tSNUMBER : tUNUMBER;
1077 if (p < buff + sizeof buff - 1)
1081 while (isalpha (c) || c == '.');
1084 tp = lookup_word (pc, buff);
1087 lvalp->intval = tp->value;
1092 return *pc->input++;
1108 /* Do nothing if the parser reports an error. */
1110 yyerror (parser_control const *pc ATTRIBUTE_UNUSED,
1111 char const *s ATTRIBUTE_UNUSED)
1116 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1117 passing it to mktime, return true if it's OK that mktime returned T.
1118 It's not OK if *TM0 has out-of-range members. */
1121 mktime_ok (struct tm const *tm0, struct tm const *tm1, time_t t)
1123 if (t == (time_t) -1)
1125 /* Guard against falsely reporting an error when parsing a time
1126 stamp that happens to equal (time_t) -1, on a host that
1127 supports such a time stamp. */
1128 tm1 = localtime (&t);
1133 return ! ((tm0->tm_sec ^ tm1->tm_sec)
1134 | (tm0->tm_min ^ tm1->tm_min)
1135 | (tm0->tm_hour ^ tm1->tm_hour)
1136 | (tm0->tm_mday ^ tm1->tm_mday)
1137 | (tm0->tm_mon ^ tm1->tm_mon)
1138 | (tm0->tm_year ^ tm1->tm_year));
1141 /* A reasonable upper bound for the size of ordinary TZ strings.
1142 Use heap allocation if TZ's length exceeds this. */
1143 enum { TZBUFSIZE = 100 };
1145 /* Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
1148 get_tz (char tzbuf[TZBUFSIZE])
1150 char *tz = getenv ("TZ");
1153 size_t tzsize = strlen (tz) + 1;
1154 tz = (tzsize <= TZBUFSIZE
1155 ? memcpy (tzbuf, tz, tzsize)
1156 : xmemdup (tz, tzsize));
1161 /* Parse a date/time string, storing the resulting time value into *RESULT.
1162 The string itself is pointed to by P. Return true if successful.
1163 P can be an incomplete or relative time specification; if so, use
1164 *NOW as the basis for the returned time. */
1166 get_date (struct timespec *result, char const *p, struct timespec const *now)
1170 struct tm const *tmp;
1174 struct timespec gettime_buffer;
1176 bool tz_was_altered = false;
1178 char tz0buf[TZBUFSIZE];
1183 gettime (&gettime_buffer);
1184 now = &gettime_buffer;
1187 Start = now->tv_sec;
1188 Start_ns = now->tv_nsec;
1190 tmp = localtime (&now->tv_sec);
1194 while (c = *p, isspace (c))
1197 if (strncmp (p, "TZ=\"", 4) == 0)
1199 char const *tzbase = p + 4;
1203 for (s = tzbase; *s; s++, tzsize++)
1207 if (! (*s == '\\' || *s == '"'))
1214 char tz1buf[TZBUFSIZE];
1215 bool large_tz = TZBUFSIZE < tzsize;
1217 tz0 = get_tz (tz0buf);
1218 z = tz1 = large_tz ? xmalloc (tzsize) : tz1buf;
1219 for (s = tzbase; *s != '"'; s++)
1220 *z++ = *(s += *s == '\\');
1222 setenv_ok = setenv ("TZ", tz1, 1) == 0;
1227 tz_was_altered = true;
1233 pc.year.value = tmp->tm_year;
1234 pc.year.value += TM_YEAR_BASE;
1236 pc.month = tmp->tm_mon + 1;
1237 pc.day = tmp->tm_mday;
1238 pc.hour = tmp->tm_hour;
1239 pc.minutes = tmp->tm_min;
1240 pc.seconds.tv_sec = tmp->tm_sec;
1241 pc.seconds.tv_nsec = Start_ns;
1242 tm.tm_isdst = tmp->tm_isdst;
1244 pc.meridian = MER24;
1245 pc.rel = RELATIVE_TIME_0;
1246 pc.timespec_seen = false;
1247 pc.rels_seen = false;
1251 pc.local_zones_seen = 0;
1255 #if HAVE_STRUCT_TM_TM_ZONE
1256 pc.local_time_zone_table[0].name = tmp->tm_zone;
1257 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1258 pc.local_time_zone_table[0].value = tmp->tm_isdst;
1259 pc.local_time_zone_table[1].name = NULL;
1261 /* Probe the names used in the next three calendar quarters, looking
1262 for a tm_isdst different from the one we already have. */
1265 for (quarter = 1; quarter <= 3; quarter++)
1267 time_t probe = Start + quarter * (90 * 24 * 60 * 60);
1268 struct tm const *probe_tm = localtime (&probe);
1269 if (probe_tm && probe_tm->tm_zone
1270 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
1273 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
1274 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1275 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
1276 pc.local_time_zone_table[2].name = NULL;
1286 extern char *tzname[];
1289 for (i = 0; i < 2; i++)
1291 pc.local_time_zone_table[i].name = tzname[i];
1292 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1293 pc.local_time_zone_table[i].value = i;
1295 pc.local_time_zone_table[i].name = NULL;
1298 pc.local_time_zone_table[0].name = NULL;
1302 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1303 && ! strcmp (pc.local_time_zone_table[0].name,
1304 pc.local_time_zone_table[1].name))
1306 /* This locale uses the same abbrevation for standard and
1307 daylight times. So if we see that abbreviation, we don't
1308 know whether it's daylight time. */
1309 pc.local_time_zone_table[0].value = -1;
1310 pc.local_time_zone_table[1].name = NULL;
1313 if (yyparse (&pc) != 0)
1316 if (pc.timespec_seen)
1317 *result = pc.seconds;
1320 if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
1321 | (pc.local_zones_seen + pc.zones_seen)))
1324 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
1325 tm.tm_mon = pc.month - 1;
1326 tm.tm_mday = pc.day;
1327 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1329 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1332 tm.tm_min = pc.minutes;
1333 tm.tm_sec = pc.seconds.tv_sec;
1337 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1338 pc.seconds.tv_nsec = 0;
1341 /* Let mktime deduce tm_isdst if we have an absolute time stamp. */
1342 if (pc.dates_seen | pc.days_seen | pc.times_seen)
1345 /* But if the input explicitly specifies local time with or without
1346 DST, give mktime that information. */
1347 if (pc.local_zones_seen)
1348 tm.tm_isdst = pc.local_isdst;
1352 Start = mktime (&tm);
1354 if (! mktime_ok (&tm0, &tm, Start))
1356 if (! pc.zones_seen)
1360 /* Guard against falsely reporting errors near the time_t
1361 boundaries when parsing times in other time zones. For
1362 example, suppose the input string "1969-12-31 23:00:00 -0100",
1363 the current time zone is 8 hours ahead of UTC, and the min
1364 time_t value is 1970-01-01 00:00:00 UTC. Then the min
1365 localtime value is 1970-01-01 08:00:00, and mktime will
1366 therefore fail on 1969-12-31 23:00:00. To work around the
1367 problem, set the time zone to 1 hour behind UTC temporarily
1368 by setting TZ="XXX1:00" and try mktime again. */
1370 long int time_zone = pc.time_zone;
1371 long int abs_time_zone = time_zone < 0 ? - time_zone : time_zone;
1372 long int abs_time_zone_hour = abs_time_zone / 60;
1373 int abs_time_zone_min = abs_time_zone % 60;
1374 char tz1buf[sizeof "XXX+0:00"
1375 + sizeof pc.time_zone * CHAR_BIT / 3];
1376 if (!tz_was_altered)
1377 tz0 = get_tz (tz0buf);
1378 sprintf (tz1buf, "XXX%s%ld:%02d", "-" + (time_zone < 0),
1379 abs_time_zone_hour, abs_time_zone_min);
1380 if (setenv ("TZ", tz1buf, 1) != 0)
1382 tz_was_altered = true;
1384 Start = mktime (&tm);
1385 if (! mktime_ok (&tm0, &tm, Start))
1390 if (pc.days_seen && ! pc.dates_seen)
1392 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1393 + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
1395 Start = mktime (&tm);
1396 if (Start == (time_t) -1)
1402 long int delta = pc.time_zone * 60;
1404 #ifdef HAVE_TM_GMTOFF
1405 delta -= tm.tm_gmtoff;
1408 struct tm const *gmt = gmtime (&t);
1411 delta -= tm_diff (&tm, gmt);
1414 if ((Start < t1) != (delta < 0))
1415 goto fail; /* time_t overflow */
1419 /* Add relative date. */
1420 if (pc.rel.year | pc.rel.month | pc.rel.day)
1422 int year = tm.tm_year + pc.rel.year;
1423 int month = tm.tm_mon + pc.rel.month;
1424 int day = tm.tm_mday + pc.rel.day;
1425 if (((year < tm.tm_year) ^ (pc.rel.year < 0))
1426 | ((month < tm.tm_mon) ^ (pc.rel.month < 0))
1427 | ((day < tm.tm_mday) ^ (pc.rel.day < 0)))
1432 tm.tm_hour = tm0.tm_hour;
1433 tm.tm_min = tm0.tm_min;
1434 tm.tm_sec = tm0.tm_sec;
1435 tm.tm_isdst = tm0.tm_isdst;
1436 Start = mktime (&tm);
1437 if (Start == (time_t) -1)
1441 /* Add relative hours, minutes, and seconds. On hosts that support
1442 leap seconds, ignore the possibility of leap seconds; e.g.,
1443 "+ 10 minutes" adds 600 seconds, even if one of them is a
1444 leap second. Typically this is not what the user wants, but it's
1445 too hard to do it the other way, because the time zone indicator
1446 must be applied before relative times, and if mktime is applied
1447 again the time zone will be lost. */
1449 long int sum_ns = pc.seconds.tv_nsec + pc.rel.ns;
1450 long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
1452 long int d1 = 60 * 60 * pc.rel.hour;
1453 time_t t1 = t0 + d1;
1454 long int d2 = 60 * pc.rel.minutes;
1455 time_t t2 = t1 + d2;
1456 long int d3 = pc.rel.seconds;
1457 time_t t3 = t2 + d3;
1458 long int d4 = (sum_ns - normalized_ns) / BILLION;
1459 time_t t4 = t3 + d4;
1461 if ((d1 / (60 * 60) ^ pc.rel.hour)
1462 | (d2 / 60 ^ pc.rel.minutes)
1463 | ((t1 < t0) ^ (d1 < 0))
1464 | ((t2 < t1) ^ (d2 < 0))
1465 | ((t3 < t2) ^ (d3 < 0))
1466 | ((t4 < t3) ^ (d4 < 0)))
1469 result->tv_sec = t4;
1470 result->tv_nsec = normalized_ns;
1480 ok &= (tz0 ? setenv ("TZ", tz0, 1) : unsetenv ("TZ")) == 0;
1489 main (int ac, char **av)
1493 printf ("Enter date, or blank line to exit.\n\t> ");
1496 buff[BUFSIZ - 1] = '\0';
1497 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1500 struct tm const *tm;
1501 if (! get_date (&d, buff, NULL))
1502 printf ("Bad format - couldn't convert.\n");
1503 else if (! (tm = localtime (&d.tv_sec)))
1505 long int sec = d.tv_sec;
1506 printf ("localtime (%ld) failed\n", sec);
1511 printf ("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1512 tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
1513 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);