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. */
72 /* ISDIGIT differs from isdigit, as follows:
73 - Its arg may be any int or unsigned int; it need not be an unsigned char.
74 - It's guaranteed to evaluate its argument exactly once.
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 /* An integer value, and the number of digits in its textual
120 /* An entry in the lexical lookup table. */
128 /* Meridian: am, pm, or 24-hour style. */
129 enum { MERam, MERpm, MER24 };
131 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
133 /* Relative times. */
136 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
146 #if HAVE_COMPOUND_LITERALS
147 # define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
149 static relative_time const RELATIVE_TIME_0;
152 /* Information passed to and from the parser. */
155 /* The input string remaining to be parsed. */
158 /* N, if this is the Nth Tuesday. */
159 long int day_ordinal;
161 /* Day of week; Sunday is 0. */
164 /* tm_isdst flag for the local zone. */
167 /* Time zone, in minutes east of UTC. */
170 /* Style used for time. */
173 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
179 struct timespec seconds; /* includes nanoseconds */
181 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
184 /* Presence or counts of nonterminals of various flavors parsed so far. */
189 size_t local_zones_seen;
194 /* Table of local time zone abbrevations, terminated by a null entry. */
195 table local_time_zone_table[3];
199 static int yylex (union YYSTYPE *, parser_control *);
200 static int yyerror (parser_control *, char *);
201 static long int time_zone_hhmm (textint, long int);
205 /* We want a reentrant parser, even if the TZ manipulation and the calls to
206 localtime and gmtime are not reentrant. */
208 %parse-param { parser_control *pc }
209 %lex-param { parser_control *pc }
211 /* This grammar has 20 shift/reduce conflicts. */
218 struct timespec timespec;
224 %token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
225 %token <intval> tDAY_UNIT
227 %token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
228 %token <intval> tMONTH tORDINAL tZONE
230 %token <textintval> tSNUMBER tUNUMBER
231 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
233 %type <intval> o_colon_minutes o_merid
234 %type <timespec> seconds signed_seconds unsigned_seconds
236 %type <rel> relunit relunit_snumber
249 pc->timespec_seen = true;
260 { pc->times_seen++; }
262 { pc->local_zones_seen++; }
264 { pc->zones_seen++; }
266 { pc->dates_seen++; }
270 { pc->rels_seen = true; }
279 pc->seconds.tv_sec = 0;
280 pc->seconds.tv_nsec = 0;
283 | tUNUMBER ':' tUNUMBER o_merid
286 pc->minutes = $3.value;
287 pc->seconds.tv_sec = 0;
288 pc->seconds.tv_nsec = 0;
291 | tUNUMBER ':' tUNUMBER tSNUMBER o_colon_minutes
294 pc->minutes = $3.value;
295 pc->seconds.tv_sec = 0;
296 pc->seconds.tv_nsec = 0;
297 pc->meridian = MER24;
299 pc->time_zone = time_zone_hhmm ($4, $5);
301 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
304 pc->minutes = $3.value;
308 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER o_colon_minutes
311 pc->minutes = $3.value;
313 pc->meridian = MER24;
315 pc->time_zone = time_zone_hhmm ($6, $7);
322 pc->local_isdst = $1;
323 pc->dsts_seen += (0 < $1);
328 pc->dsts_seen += (0 < $1) + 1;
334 { pc->time_zone = $1; }
335 | tZONE relunit_snumber
336 { pc->time_zone = $1;
338 pc->rel.seconds += $2.seconds;
339 pc->rel.minutes += $2.minutes;
340 pc->rel.hour += $2.hour;
341 pc->rel.day += $2.day;
342 pc->rel.month += $2.month;
343 pc->rel.year += $2.year;
344 pc->rels_seen = true; }
345 | tZONE tSNUMBER o_colon_minutes
346 { pc->time_zone = $1 + time_zone_hhmm ($2, $3); }
348 { pc->time_zone = $1 + 60; }
350 { pc->time_zone = $1 + 60; }
366 pc->day_ordinal = $1;
371 pc->day_ordinal = $1.value;
377 tUNUMBER '/' tUNUMBER
379 pc->month = $1.value;
382 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
384 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
385 otherwise as MM/DD/YY.
386 The goal in recognizing YYYY/MM/DD is solely to support legacy
387 machine-generated dates like those in an RCS log listing. If
388 you want portability, use the ISO 8601 format. */
392 pc->month = $3.value;
397 pc->month = $1.value;
402 | tUNUMBER tSNUMBER tSNUMBER
404 /* ISO 8601 format. YYYY-MM-DD. */
406 pc->month = -$2.value;
409 | tUNUMBER tMONTH tSNUMBER
411 /* e.g. 17-JUN-1992. */
414 pc->year.value = -$3.value;
415 pc->year.digits = $3.digits;
417 | tMONTH tSNUMBER tSNUMBER
419 /* e.g. JUN-17-1992. */
422 pc->year.value = -$3.value;
423 pc->year.digits = $3.digits;
430 | tMONTH tUNUMBER ',' tUNUMBER
441 | tUNUMBER tMONTH tUNUMBER
453 pc->rel.seconds -= $1.seconds;
454 pc->rel.minutes -= $1.minutes;
455 pc->rel.hour -= $1.hour;
456 pc->rel.day -= $1.day;
457 pc->rel.month -= $1.month;
458 pc->rel.year -= $1.year;
463 pc->rel.seconds += $1.seconds;
464 pc->rel.minutes += $1.minutes;
465 pc->rel.hour += $1.hour;
466 pc->rel.day += $1.day;
467 pc->rel.month += $1.month;
468 pc->rel.year += $1.year;
474 { $$ = RELATIVE_TIME_0; $$.year = $1; }
475 | tUNUMBER tYEAR_UNIT
476 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
478 { $$ = RELATIVE_TIME_0; $$.year = 1; }
479 | tORDINAL tMONTH_UNIT
480 { $$ = RELATIVE_TIME_0; $$.month = $1; }
481 | tUNUMBER tMONTH_UNIT
482 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
484 { $$ = RELATIVE_TIME_0; $$.month = 1; }
486 { $$ = RELATIVE_TIME_0; $$.day = $1 * $2; }
488 { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
490 { $$ = RELATIVE_TIME_0; $$.day = $1; }
491 | tORDINAL tHOUR_UNIT
492 { $$ = RELATIVE_TIME_0; $$.hour = $1; }
493 | tUNUMBER tHOUR_UNIT
494 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
496 { $$ = RELATIVE_TIME_0; $$.hour = 1; }
497 | tORDINAL tMINUTE_UNIT
498 { $$ = RELATIVE_TIME_0; $$.minutes = $1; }
499 | tUNUMBER tMINUTE_UNIT
500 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
502 { $$ = RELATIVE_TIME_0; $$.minutes = 1; }
504 { $$ = RELATIVE_TIME_0; $$.seconds = $1; }
506 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
507 | tSDECIMAL_NUMBER tSEC_UNIT
508 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
509 | tUDECIMAL_NUMBER tSEC_UNIT
510 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
512 { $$ = RELATIVE_TIME_0; $$.seconds = 1; }
518 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
519 | tSNUMBER tMONTH_UNIT
520 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
522 { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
523 | tSNUMBER tHOUR_UNIT
524 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
525 | tSNUMBER tMINUTE_UNIT
526 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
528 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
531 seconds: signed_seconds | unsigned_seconds;
536 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
542 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
548 if (pc->dates_seen && ! pc->year.digits
549 && ! pc->rels_seen && (pc->times_seen || 2 < $1.digits))
556 pc->day = $1.value % 100;
557 pc->month = ($1.value / 100) % 100;
558 pc->year.value = $1.value / 10000;
559 pc->year.digits = $1.digits - 4;
571 pc->hour = $1.value / 100;
572 pc->minutes = $1.value % 100;
574 pc->seconds.tv_sec = 0;
575 pc->seconds.tv_nsec = 0;
576 pc->meridian = MER24;
598 static table const meridian_table[] =
600 { "AM", tMERIDIAN, MERam },
601 { "A.M.", tMERIDIAN, MERam },
602 { "PM", tMERIDIAN, MERpm },
603 { "P.M.", tMERIDIAN, MERpm },
607 static table const dst_table[] =
612 static table const month_and_day_table[] =
614 { "JANUARY", tMONTH, 1 },
615 { "FEBRUARY", tMONTH, 2 },
616 { "MARCH", tMONTH, 3 },
617 { "APRIL", tMONTH, 4 },
618 { "MAY", tMONTH, 5 },
619 { "JUNE", tMONTH, 6 },
620 { "JULY", tMONTH, 7 },
621 { "AUGUST", tMONTH, 8 },
622 { "SEPTEMBER",tMONTH, 9 },
623 { "SEPT", tMONTH, 9 },
624 { "OCTOBER", tMONTH, 10 },
625 { "NOVEMBER", tMONTH, 11 },
626 { "DECEMBER", tMONTH, 12 },
627 { "SUNDAY", tDAY, 0 },
628 { "MONDAY", tDAY, 1 },
629 { "TUESDAY", tDAY, 2 },
631 { "WEDNESDAY",tDAY, 3 },
632 { "WEDNES", tDAY, 3 },
633 { "THURSDAY", tDAY, 4 },
635 { "THURS", tDAY, 4 },
636 { "FRIDAY", tDAY, 5 },
637 { "SATURDAY", tDAY, 6 },
641 static table const time_units_table[] =
643 { "YEAR", tYEAR_UNIT, 1 },
644 { "MONTH", tMONTH_UNIT, 1 },
645 { "FORTNIGHT",tDAY_UNIT, 14 },
646 { "WEEK", tDAY_UNIT, 7 },
647 { "DAY", tDAY_UNIT, 1 },
648 { "HOUR", tHOUR_UNIT, 1 },
649 { "MINUTE", tMINUTE_UNIT, 1 },
650 { "MIN", tMINUTE_UNIT, 1 },
651 { "SECOND", tSEC_UNIT, 1 },
652 { "SEC", tSEC_UNIT, 1 },
656 /* Assorted relative-time words. */
657 static table const relative_time_table[] =
659 { "TOMORROW", tDAY_UNIT, 1 },
660 { "YESTERDAY",tDAY_UNIT, -1 },
661 { "TODAY", tDAY_UNIT, 0 },
662 { "NOW", tDAY_UNIT, 0 },
663 { "LAST", tORDINAL, -1 },
664 { "THIS", tORDINAL, 0 },
665 { "NEXT", tORDINAL, 1 },
666 { "FIRST", tORDINAL, 1 },
667 /*{ "SECOND", tORDINAL, 2 }, */
668 { "THIRD", tORDINAL, 3 },
669 { "FOURTH", tORDINAL, 4 },
670 { "FIFTH", tORDINAL, 5 },
671 { "SIXTH", tORDINAL, 6 },
672 { "SEVENTH", tORDINAL, 7 },
673 { "EIGHTH", tORDINAL, 8 },
674 { "NINTH", tORDINAL, 9 },
675 { "TENTH", tORDINAL, 10 },
676 { "ELEVENTH", tORDINAL, 11 },
677 { "TWELFTH", tORDINAL, 12 },
682 /* The universal time zone table. These labels can be used even for
683 time stamps that would not otherwise be valid, e.g., GMT time
684 stamps in London during summer. */
685 static table const universal_time_zone_table[] =
687 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
688 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
689 { "UTC", tZONE, HOUR ( 0) },
693 /* The time zone table. This table is necessarily incomplete, as time
694 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
695 as Eastern time in Australia, not as US Eastern Standard Time.
696 You cannot rely on getdate to handle arbitrary time zone
697 abbreviations; use numeric abbreviations like `-0500' instead. */
698 static table const time_zone_table[] =
700 { "WET", tZONE, HOUR ( 0) }, /* Western European */
701 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
702 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
703 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
704 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
705 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
706 { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
707 { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
708 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
709 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
710 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
711 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
712 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
713 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
714 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
715 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
716 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
717 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
718 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
719 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
720 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
721 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
722 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
723 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
724 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
725 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
726 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
727 { "CET", tZONE, HOUR ( 1) }, /* Central European */
728 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
729 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
730 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
731 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
732 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
733 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
734 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
735 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
736 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
737 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
738 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
739 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
740 { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
741 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
742 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
743 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
744 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
745 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
746 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
750 /* Military time zone table. */
751 static table const military_table[] =
753 { "A", tZONE, -HOUR ( 1) },
754 { "B", tZONE, -HOUR ( 2) },
755 { "C", tZONE, -HOUR ( 3) },
756 { "D", tZONE, -HOUR ( 4) },
757 { "E", tZONE, -HOUR ( 5) },
758 { "F", tZONE, -HOUR ( 6) },
759 { "G", tZONE, -HOUR ( 7) },
760 { "H", tZONE, -HOUR ( 8) },
761 { "I", tZONE, -HOUR ( 9) },
762 { "K", tZONE, -HOUR (10) },
763 { "L", tZONE, -HOUR (11) },
764 { "M", tZONE, -HOUR (12) },
765 { "N", tZONE, HOUR ( 1) },
766 { "O", tZONE, HOUR ( 2) },
767 { "P", tZONE, HOUR ( 3) },
768 { "Q", tZONE, HOUR ( 4) },
769 { "R", tZONE, HOUR ( 5) },
770 { "S", tZONE, HOUR ( 6) },
771 { "T", tZONE, HOUR ( 7) },
772 { "U", tZONE, HOUR ( 8) },
773 { "V", tZONE, HOUR ( 9) },
774 { "W", tZONE, HOUR (10) },
775 { "X", tZONE, HOUR (11) },
776 { "Y", tZONE, HOUR (12) },
777 { "Z", tZONE, HOUR ( 0) },
783 /* Convert a time zone expressed as HH:MM into an integer count of
784 minutes. If MM is negative, then S is of the form HHMM and needs
785 to be picked apart; otherwise, S is of the form HH. */
788 time_zone_hhmm (textint s, long int mm)
791 return (s.value / 100) * 60 + s.value % 100;
793 return s.value * 60 + (s.negative ? -mm : mm);
797 to_hour (long int hours, int meridian)
801 default: /* Pacify GCC. */
803 return 0 <= hours && hours < 24 ? hours : -1;
805 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
807 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
812 to_year (textint textyear)
814 long int year = textyear.value;
819 /* XPG4 suggests that years 00-68 map to 2000-2068, and
820 years 69-99 map to 1969-1999. */
821 else if (textyear.digits == 2)
822 year += year < 69 ? 2000 : 1900;
828 lookup_zone (parser_control const *pc, char const *name)
832 for (tp = universal_time_zone_table; tp->name; tp++)
833 if (strcmp (name, tp->name) == 0)
836 /* Try local zone abbreviations before those in time_zone_table, as
837 the local ones are more likely to be right. */
838 for (tp = pc->local_time_zone_table; tp->name; tp++)
839 if (strcmp (name, tp->name) == 0)
842 for (tp = time_zone_table; tp->name; tp++)
843 if (strcmp (name, tp->name) == 0)
850 /* Yield the difference between *A and *B,
851 measured in seconds, ignoring leap seconds.
852 The body of this function is taken directly from the GNU C Library;
853 see src/strftime.c. */
855 tm_diff (struct tm const *a, struct tm const *b)
857 /* Compute intervening leap days correctly even if year is negative.
858 Take care to avoid int overflow in leap day calculations. */
859 int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
860 int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
861 int a100 = a4 / 25 - (a4 % 25 < 0);
862 int b100 = b4 / 25 - (b4 % 25 < 0);
863 int a400 = SHR (a100, 2);
864 int b400 = SHR (b100, 2);
865 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
866 long int ayear = a->tm_year;
867 long int years = ayear - b->tm_year;
868 long int days = (365 * years + intervening_leap_days
869 + (a->tm_yday - b->tm_yday));
870 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
871 + (a->tm_min - b->tm_min))
872 + (a->tm_sec - b->tm_sec));
874 #endif /* ! HAVE_TM_GMTOFF */
877 lookup_word (parser_control const *pc, char *word)
886 /* Make it uppercase. */
887 for (p = word; *p; p++)
889 unsigned char ch = *p;
894 for (tp = meridian_table; tp->name; tp++)
895 if (strcmp (word, tp->name) == 0)
898 /* See if we have an abbreviation for a month. */
899 wordlen = strlen (word);
900 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
902 for (tp = month_and_day_table; tp->name; tp++)
903 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
906 if ((tp = lookup_zone (pc, word)))
909 if (strcmp (word, dst_table[0].name) == 0)
912 for (tp = time_units_table; tp->name; tp++)
913 if (strcmp (word, tp->name) == 0)
916 /* Strip off any plural and try the units table again. */
917 if (word[wordlen - 1] == 'S')
919 word[wordlen - 1] = '\0';
920 for (tp = time_units_table; tp->name; tp++)
921 if (strcmp (word, tp->name) == 0)
923 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
926 for (tp = relative_time_table; tp->name; tp++)
927 if (strcmp (word, tp->name) == 0)
930 /* Military time zones. */
932 for (tp = military_table; tp->name; tp++)
933 if (word[0] == tp->name[0])
936 /* Drop out any periods and try the time zone table again. */
937 for (period_found = false, p = q = word; (*p = *q); q++)
942 if (period_found && (tp = lookup_zone (pc, word)))
949 yylex (YYSTYPE *lvalp, parser_control *pc)
956 while (c = *pc->input, isspace (c))
959 if (ISDIGIT (c) || c == '-' || c == '+')
963 unsigned long int value;
964 if (c == '-' || c == '+')
966 sign = c == '-' ? -1 : 1;
967 while (c = *++pc->input, isspace (c))
970 /* skip the '-' sign */
976 for (value = 0; ; value *= 10)
978 unsigned long int value1 = value + (c - '0');
985 if (ULONG_MAX / 10 < value)
988 if ((c == '.' || c == ',') && ISDIGIT (p[1]))
993 unsigned long int value1;
995 /* Check for overflow when converting value to time_t. */
1010 if (value != value1)
1013 /* Accumulate fraction, to ns precision. */
1016 for (digits = 2; digits <= LOG10_BILLION; digits++)
1023 /* Skip excess digits, truncating toward -Infinity. */
1025 for (; ISDIGIT (*p); p++)
1031 while (ISDIGIT (*p))
1034 /* Adjust to the timespec convention, which is that
1035 tv_nsec is always a positive offset even if tv_sec is
1045 lvalp->timespec.tv_sec = s;
1046 lvalp->timespec.tv_nsec = ns;
1048 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
1052 lvalp->textintval.negative = sign < 0;
1055 lvalp->textintval.value = - value;
1056 if (0 < lvalp->textintval.value)
1061 lvalp->textintval.value = value;
1062 if (lvalp->textintval.value < 0)
1065 lvalp->textintval.digits = p - pc->input;
1067 return sign ? tSNUMBER : tUNUMBER;
1079 if (p < buff + sizeof buff - 1)
1083 while (isalpha (c) || c == '.');
1086 tp = lookup_word (pc, buff);
1089 lvalp->intval = tp->value;
1094 return *pc->input++;
1110 /* Do nothing if the parser reports an error. */
1112 yyerror (parser_control *pc ATTRIBUTE_UNUSED, char *s ATTRIBUTE_UNUSED)
1117 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1118 passing it to mktime, return true if it's OK that mktime returned T.
1119 It's not OK if *TM0 has out-of-range members. */
1122 mktime_ok (struct tm const *tm0, struct tm const *tm1, time_t t)
1124 if (t == (time_t) -1)
1126 /* Guard against falsely reporting an error when parsing a time
1127 stamp that happens to equal (time_t) -1, on a host that
1128 supports such a time stamp. */
1129 tm1 = localtime (&t);
1134 return ! ((tm0->tm_sec ^ tm1->tm_sec)
1135 | (tm0->tm_min ^ tm1->tm_min)
1136 | (tm0->tm_hour ^ tm1->tm_hour)
1137 | (tm0->tm_mday ^ tm1->tm_mday)
1138 | (tm0->tm_mon ^ tm1->tm_mon)
1139 | (tm0->tm_year ^ tm1->tm_year));
1142 /* A reasonable upper bound for the size of ordinary TZ strings.
1143 Use heap allocation if TZ's length exceeds this. */
1144 enum { TZBUFSIZE = 100 };
1146 /* Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
1149 get_tz (char tzbuf[TZBUFSIZE])
1151 char *tz = getenv ("TZ");
1154 size_t tzsize = strlen (tz) + 1;
1155 tz = (tzsize <= TZBUFSIZE
1156 ? memcpy (tzbuf, tz, tzsize)
1157 : xmemdup (tz, tzsize));
1162 /* Parse a date/time string, storing the resulting time value into *RESULT.
1163 The string itself is pointed to by P. Return true if successful.
1164 P can be an incomplete or relative time specification; if so, use
1165 *NOW as the basis for the returned time. */
1167 get_date (struct timespec *result, char const *p, struct timespec const *now)
1171 struct tm const *tmp;
1175 struct timespec gettime_buffer;
1177 bool tz_was_altered = false;
1179 char tz0buf[TZBUFSIZE];
1184 gettime (&gettime_buffer);
1185 now = &gettime_buffer;
1188 Start = now->tv_sec;
1189 Start_ns = now->tv_nsec;
1191 tmp = localtime (&now->tv_sec);
1195 while (c = *p, isspace (c))
1198 if (strncmp (p, "TZ=\"", 4) == 0)
1200 char const *tzbase = p + 4;
1204 for (s = tzbase; *s; s++, tzsize++)
1208 if (! (*s == '\\' || *s == '"'))
1215 char tz1buf[TZBUFSIZE];
1216 bool large_tz = TZBUFSIZE < tzsize;
1218 tz0 = get_tz (tz0buf);
1219 z = tz1 = large_tz ? xmalloc (tzsize) : tz1buf;
1220 for (s = tzbase; *s != '"'; s++)
1221 *z++ = *(s += *s == '\\');
1223 setenv_ok = setenv ("TZ", tz1, 1) == 0;
1228 tz_was_altered = true;
1234 pc.year.value = tmp->tm_year;
1235 pc.year.value += TM_YEAR_BASE;
1237 pc.month = tmp->tm_mon + 1;
1238 pc.day = tmp->tm_mday;
1239 pc.hour = tmp->tm_hour;
1240 pc.minutes = tmp->tm_min;
1241 pc.seconds.tv_sec = tmp->tm_sec;
1242 pc.seconds.tv_nsec = Start_ns;
1243 tm.tm_isdst = tmp->tm_isdst;
1245 pc.meridian = MER24;
1246 pc.rel = RELATIVE_TIME_0;
1247 pc.timespec_seen = false;
1248 pc.rels_seen = false;
1252 pc.local_zones_seen = 0;
1256 #if HAVE_STRUCT_TM_TM_ZONE
1257 pc.local_time_zone_table[0].name = tmp->tm_zone;
1258 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1259 pc.local_time_zone_table[0].value = tmp->tm_isdst;
1260 pc.local_time_zone_table[1].name = NULL;
1262 /* Probe the names used in the next three calendar quarters, looking
1263 for a tm_isdst different from the one we already have. */
1266 for (quarter = 1; quarter <= 3; quarter++)
1268 time_t probe = Start + quarter * (90 * 24 * 60 * 60);
1269 struct tm const *probe_tm = localtime (&probe);
1270 if (probe_tm && probe_tm->tm_zone
1271 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
1274 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
1275 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1276 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
1277 pc.local_time_zone_table[2].name = NULL;
1287 extern char *tzname[];
1290 for (i = 0; i < 2; i++)
1292 pc.local_time_zone_table[i].name = tzname[i];
1293 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1294 pc.local_time_zone_table[i].value = i;
1296 pc.local_time_zone_table[i].name = NULL;
1299 pc.local_time_zone_table[0].name = NULL;
1303 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1304 && ! strcmp (pc.local_time_zone_table[0].name,
1305 pc.local_time_zone_table[1].name))
1307 /* This locale uses the same abbrevation for standard and
1308 daylight times. So if we see that abbreviation, we don't
1309 know whether it's daylight time. */
1310 pc.local_time_zone_table[0].value = -1;
1311 pc.local_time_zone_table[1].name = NULL;
1314 if (yyparse (&pc) != 0)
1317 if (pc.timespec_seen)
1318 *result = pc.seconds;
1321 if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
1322 | (pc.local_zones_seen + pc.zones_seen)))
1325 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
1326 tm.tm_mon = pc.month - 1;
1327 tm.tm_mday = pc.day;
1328 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1330 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1333 tm.tm_min = pc.minutes;
1334 tm.tm_sec = pc.seconds.tv_sec;
1338 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1339 pc.seconds.tv_nsec = 0;
1342 /* Let mktime deduce tm_isdst if we have an absolute time stamp. */
1343 if (pc.dates_seen | pc.days_seen | pc.times_seen)
1346 /* But if the input explicitly specifies local time with or without
1347 DST, give mktime that information. */
1348 if (pc.local_zones_seen)
1349 tm.tm_isdst = pc.local_isdst;
1353 Start = mktime (&tm);
1355 if (! mktime_ok (&tm0, &tm, Start))
1357 if (! pc.zones_seen)
1361 /* Guard against falsely reporting errors near the time_t
1362 boundaries when parsing times in other time zones. For
1363 example, suppose the input string "1969-12-31 23:00:00 -0100",
1364 the current time zone is 8 hours ahead of UTC, and the min
1365 time_t value is 1970-01-01 00:00:00 UTC. Then the min
1366 localtime value is 1970-01-01 08:00:00, and mktime will
1367 therefore fail on 1969-12-31 23:00:00. To work around the
1368 problem, set the time zone to 1 hour behind UTC temporarily
1369 by setting TZ="XXX1:00" and try mktime again. */
1371 long int time_zone = pc.time_zone;
1372 long int abs_time_zone = time_zone < 0 ? - time_zone : time_zone;
1373 long int abs_time_zone_hour = abs_time_zone / 60;
1374 int abs_time_zone_min = abs_time_zone % 60;
1375 char tz1buf[sizeof "XXX+0:00"
1376 + sizeof pc.time_zone * CHAR_BIT / 3];
1377 if (!tz_was_altered)
1378 tz0 = get_tz (tz0buf);
1379 sprintf (tz1buf, "XXX%s%ld:%02d", "-" + (time_zone < 0),
1380 abs_time_zone_hour, abs_time_zone_min);
1381 if (setenv ("TZ", tz1buf, 1) != 0)
1383 tz_was_altered = true;
1385 Start = mktime (&tm);
1386 if (! mktime_ok (&tm0, &tm, Start))
1391 if (pc.days_seen && ! pc.dates_seen)
1393 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1394 + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
1396 Start = mktime (&tm);
1397 if (Start == (time_t) -1)
1403 long int delta = pc.time_zone * 60;
1405 #ifdef HAVE_TM_GMTOFF
1406 delta -= tm.tm_gmtoff;
1409 struct tm const *gmt = gmtime (&t);
1412 delta -= tm_diff (&tm, gmt);
1415 if ((Start < t1) != (delta < 0))
1416 goto fail; /* time_t overflow */
1420 /* Add relative date. */
1421 if (pc.rel.year | pc.rel.month | pc.rel.day)
1423 int year = tm.tm_year + pc.rel.year;
1424 int month = tm.tm_mon + pc.rel.month;
1425 int day = tm.tm_mday + pc.rel.day;
1426 if (((year < tm.tm_year) ^ (pc.rel.year < 0))
1427 | ((month < tm.tm_mon) ^ (pc.rel.month < 0))
1428 | ((day < tm.tm_mday) ^ (pc.rel.day < 0)))
1433 tm.tm_hour = tm0.tm_hour;
1434 tm.tm_min = tm0.tm_min;
1435 tm.tm_sec = tm0.tm_sec;
1436 tm.tm_isdst = tm0.tm_isdst;
1437 Start = mktime (&tm);
1438 if (Start == (time_t) -1)
1442 /* Add relative hours, minutes, and seconds. On hosts that support
1443 leap seconds, ignore the possibility of leap seconds; e.g.,
1444 "+ 10 minutes" adds 600 seconds, even if one of them is a
1445 leap second. Typically this is not what the user wants, but it's
1446 too hard to do it the other way, because the time zone indicator
1447 must be applied before relative times, and if mktime is applied
1448 again the time zone will be lost. */
1450 long int sum_ns = pc.seconds.tv_nsec + pc.rel.ns;
1451 long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
1453 long int d1 = 60 * 60 * pc.rel.hour;
1454 time_t t1 = t0 + d1;
1455 long int d2 = 60 * pc.rel.minutes;
1456 time_t t2 = t1 + d2;
1457 long int d3 = pc.rel.seconds;
1458 time_t t3 = t2 + d3;
1459 long int d4 = (sum_ns - normalized_ns) / BILLION;
1460 time_t t4 = t3 + d4;
1462 if ((d1 / (60 * 60) ^ pc.rel.hour)
1463 | (d2 / 60 ^ pc.rel.minutes)
1464 | ((t1 < t0) ^ (d1 < 0))
1465 | ((t2 < t1) ^ (d2 < 0))
1466 | ((t3 < t2) ^ (d3 < 0))
1467 | ((t4 < t3) ^ (d4 < 0)))
1470 result->tv_sec = t4;
1471 result->tv_nsec = normalized_ns;
1481 ok &= (tz0 ? setenv ("TZ", tz0, 1) : unsetenv ("TZ")) == 0;
1490 main (int ac, char **av)
1494 printf ("Enter date, or blank line to exit.\n\t> ");
1497 buff[BUFSIZ - 1] = '\0';
1498 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1501 struct tm const *tm;
1502 if (! get_date (&d, buff, NULL))
1503 printf ("Bad format - couldn't convert.\n");
1504 else if (! (tm = localtime (&d.tv_sec)))
1506 long int sec = d.tv_sec;
1507 printf ("localtime (%ld) failed\n", sec);
1512 printf ("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1513 tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
1514 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);