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
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;
893 for (tp = meridian_table; tp->name; tp++)
894 if (strcmp (word, tp->name) == 0)
897 /* See if we have an abbreviation for a month. */
898 wordlen = strlen (word);
899 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
901 for (tp = month_and_day_table; tp->name; tp++)
902 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
905 if ((tp = lookup_zone (pc, word)))
908 if (strcmp (word, dst_table[0].name) == 0)
911 for (tp = time_units_table; tp->name; tp++)
912 if (strcmp (word, tp->name) == 0)
915 /* Strip off any plural and try the units table again. */
916 if (word[wordlen - 1] == 'S')
918 word[wordlen - 1] = '\0';
919 for (tp = time_units_table; tp->name; tp++)
920 if (strcmp (word, tp->name) == 0)
922 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
925 for (tp = relative_time_table; tp->name; tp++)
926 if (strcmp (word, tp->name) == 0)
929 /* Military time zones. */
931 for (tp = military_table; tp->name; tp++)
932 if (word[0] == tp->name[0])
935 /* Drop out any periods and try the time zone table again. */
936 for (period_found = false, p = q = word; (*p = *q); q++)
941 if (period_found && (tp = lookup_zone (pc, word)))
948 yylex (YYSTYPE *lvalp, parser_control *pc)
955 while (c = *pc->input, isspace (c))
958 if (ISDIGIT (c) || c == '-' || c == '+')
962 unsigned long int value;
963 if (c == '-' || c == '+')
965 sign = c == '-' ? -1 : 1;
966 while (c = *++pc->input, isspace (c))
969 /* skip the '-' sign */
975 for (value = 0; ; value *= 10)
977 unsigned long int value1 = value + (c - '0');
984 if (ULONG_MAX / 10 < value)
987 if ((c == '.' || c == ',') && ISDIGIT (p[1]))
992 unsigned long int value1;
994 /* Check for overflow when converting value to time_t. */
1009 if (value != value1)
1012 /* Accumulate fraction, to ns precision. */
1015 for (digits = 2; digits <= LOG10_BILLION; digits++)
1022 /* Skip excess digits, truncating toward -Infinity. */
1024 for (; ISDIGIT (*p); p++)
1030 while (ISDIGIT (*p))
1033 /* Adjust to the timespec convention, which is that
1034 tv_nsec is always a positive offset even if tv_sec is
1044 lvalp->timespec.tv_sec = s;
1045 lvalp->timespec.tv_nsec = ns;
1047 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
1051 lvalp->textintval.negative = sign < 0;
1054 lvalp->textintval.value = - value;
1055 if (0 < lvalp->textintval.value)
1060 lvalp->textintval.value = value;
1061 if (lvalp->textintval.value < 0)
1064 lvalp->textintval.digits = p - pc->input;
1066 return sign ? tSNUMBER : tUNUMBER;
1078 if (p < buff + sizeof buff - 1)
1082 while (isalpha (c) || c == '.');
1085 tp = lookup_word (pc, buff);
1088 lvalp->intval = tp->value;
1093 return *pc->input++;
1109 /* Do nothing if the parser reports an error. */
1111 yyerror (parser_control *pc ATTRIBUTE_UNUSED, char *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);