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
39 /* There's no need to extend the stack, so there's no need to involve
41 #define YYSTACK_USE_ALLOCA 0
43 /* Tell Bison how much stack space is needed. 20 should be plenty for
44 this grammar, which is not right recursive. Beware setting it too
45 high, since that might cause problems on machines whose
46 implementations have lame stack-overflow checking. */
48 #define YYINITDEPTH YYMAXDEPTH
50 /* Since the code of getdate.y is not included in the Emacs executable
51 itself, there is no need to #define static in this file. Even if
52 the code were included in the Emacs executable, it probably
53 wouldn't do any harm to #undef it here; this will only cause
54 problems if we try to write to a static variable, which I don't
55 think this code needs to do. */
70 /* ISDIGIT differs from isdigit, as follows:
71 - Its arg may be any int or unsigned int; it need not be an unsigned char
73 - It's typically faster.
74 POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
75 isdigit unless it's important to use the locale's definition
76 of `digit' even when the host does not conform to POSIX. */
77 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
80 # if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
81 # define __attribute__(x)
85 #ifndef ATTRIBUTE_UNUSED
86 # define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
89 /* Shift A right by B bits portably, by dividing A by 2**B and
90 truncating towards minus infinity. A and B should be free of side
91 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
92 INT_BITS is the number of useful bits in an int. GNU code can
93 assume that INT_BITS is at least 32.
95 ISO C99 says that A >> B is implementation-defined if A < 0. Some
96 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
97 right in the usual way when A < 0, so SHR falls back on division if
98 ordinary A >> B doesn't seem to be the usual signed shift. */
102 : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
104 #define EPOCH_YEAR 1970
105 #define TM_YEAR_BASE 1900
107 #define HOUR(x) ((x) * 60)
109 /* An integer value, and the number of digits in its textual
118 /* An entry in the lexical lookup table. */
126 /* Meridian: am, pm, or 24-hour style. */
127 enum { MERam, MERpm, MER24 };
129 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
131 /* Relative times. */
134 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
144 #if HAVE_COMPOUND_LITERALS
145 # define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
147 static relative_time const RELATIVE_TIME_0;
150 /* Information passed to and from the parser. */
153 /* The input string remaining to be parsed. */
156 /* N, if this is the Nth Tuesday. */
157 long int day_ordinal;
159 /* Day of week; Sunday is 0. */
162 /* tm_isdst flag for the local zone. */
165 /* Time zone, in minutes east of UTC. */
168 /* Style used for time. */
171 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
177 struct timespec seconds; /* includes nanoseconds */
179 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
182 /* Presence or counts of nonterminals of various flavors parsed so far. */
187 size_t local_zones_seen;
192 /* Table of local time zone abbrevations, terminated by a null entry. */
193 table local_time_zone_table[3];
197 static int yylex (union YYSTYPE *, parser_control *);
198 static int yyerror (parser_control const *, char const *);
199 static long int time_zone_hhmm (textint, long int);
203 /* We want a reentrant parser, even if the TZ manipulation and the calls to
204 localtime and gmtime are not reentrant. */
206 %parse-param { parser_control *pc }
207 %lex-param { parser_control *pc }
209 /* This grammar has 20 shift/reduce conflicts. */
216 struct timespec timespec;
222 %token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
223 %token <intval> tDAY_UNIT
225 %token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
226 %token <intval> tMONTH tORDINAL tZONE
228 %token <textintval> tSNUMBER tUNUMBER
229 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
231 %type <intval> o_colon_minutes o_merid
232 %type <timespec> seconds signed_seconds unsigned_seconds
234 %type <rel> relunit relunit_snumber
247 pc->timespec_seen = true;
258 { pc->times_seen++; }
260 { pc->local_zones_seen++; }
262 { pc->zones_seen++; }
264 { pc->dates_seen++; }
268 { pc->rels_seen = true; }
277 pc->seconds.tv_sec = 0;
278 pc->seconds.tv_nsec = 0;
281 | tUNUMBER ':' tUNUMBER o_merid
284 pc->minutes = $3.value;
285 pc->seconds.tv_sec = 0;
286 pc->seconds.tv_nsec = 0;
289 | tUNUMBER ':' tUNUMBER tSNUMBER o_colon_minutes
292 pc->minutes = $3.value;
293 pc->seconds.tv_sec = 0;
294 pc->seconds.tv_nsec = 0;
295 pc->meridian = MER24;
297 pc->time_zone = time_zone_hhmm ($4, $5);
299 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
302 pc->minutes = $3.value;
306 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER o_colon_minutes
309 pc->minutes = $3.value;
311 pc->meridian = MER24;
313 pc->time_zone = time_zone_hhmm ($6, $7);
320 pc->local_isdst = $1;
321 pc->dsts_seen += (0 < $1);
326 pc->dsts_seen += (0 < $1) + 1;
332 { pc->time_zone = $1; }
333 | tZONE relunit_snumber
334 { pc->time_zone = $1;
336 pc->rel.seconds += $2.seconds;
337 pc->rel.minutes += $2.minutes;
338 pc->rel.hour += $2.hour;
339 pc->rel.day += $2.day;
340 pc->rel.month += $2.month;
341 pc->rel.year += $2.year;
342 pc->rels_seen = true; }
343 | tZONE tSNUMBER o_colon_minutes
344 { pc->time_zone = $1 + time_zone_hhmm ($2, $3); }
346 { pc->time_zone = $1 + 60; }
348 { pc->time_zone = $1 + 60; }
364 pc->day_ordinal = $1;
369 pc->day_ordinal = $1.value;
375 tUNUMBER '/' tUNUMBER
377 pc->month = $1.value;
380 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
382 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
383 otherwise as MM/DD/YY.
384 The goal in recognizing YYYY/MM/DD is solely to support legacy
385 machine-generated dates like those in an RCS log listing. If
386 you want portability, use the ISO 8601 format. */
390 pc->month = $3.value;
395 pc->month = $1.value;
400 | tUNUMBER tSNUMBER tSNUMBER
402 /* ISO 8601 format. YYYY-MM-DD. */
404 pc->month = -$2.value;
407 | tUNUMBER tMONTH tSNUMBER
409 /* e.g. 17-JUN-1992. */
412 pc->year.value = -$3.value;
413 pc->year.digits = $3.digits;
415 | tMONTH tSNUMBER tSNUMBER
417 /* e.g. JUN-17-1992. */
420 pc->year.value = -$3.value;
421 pc->year.digits = $3.digits;
428 | tMONTH tUNUMBER ',' tUNUMBER
439 | tUNUMBER tMONTH tUNUMBER
451 pc->rel.seconds -= $1.seconds;
452 pc->rel.minutes -= $1.minutes;
453 pc->rel.hour -= $1.hour;
454 pc->rel.day -= $1.day;
455 pc->rel.month -= $1.month;
456 pc->rel.year -= $1.year;
461 pc->rel.seconds += $1.seconds;
462 pc->rel.minutes += $1.minutes;
463 pc->rel.hour += $1.hour;
464 pc->rel.day += $1.day;
465 pc->rel.month += $1.month;
466 pc->rel.year += $1.year;
472 { $$ = RELATIVE_TIME_0; $$.year = $1; }
473 | tUNUMBER tYEAR_UNIT
474 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
476 { $$ = RELATIVE_TIME_0; $$.year = 1; }
477 | tORDINAL tMONTH_UNIT
478 { $$ = RELATIVE_TIME_0; $$.month = $1; }
479 | tUNUMBER tMONTH_UNIT
480 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
482 { $$ = RELATIVE_TIME_0; $$.month = 1; }
484 { $$ = RELATIVE_TIME_0; $$.day = $1 * $2; }
486 { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
488 { $$ = RELATIVE_TIME_0; $$.day = $1; }
489 | tORDINAL tHOUR_UNIT
490 { $$ = RELATIVE_TIME_0; $$.hour = $1; }
491 | tUNUMBER tHOUR_UNIT
492 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
494 { $$ = RELATIVE_TIME_0; $$.hour = 1; }
495 | tORDINAL tMINUTE_UNIT
496 { $$ = RELATIVE_TIME_0; $$.minutes = $1; }
497 | tUNUMBER tMINUTE_UNIT
498 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
500 { $$ = RELATIVE_TIME_0; $$.minutes = 1; }
502 { $$ = RELATIVE_TIME_0; $$.seconds = $1; }
504 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
505 | tSDECIMAL_NUMBER tSEC_UNIT
506 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
507 | tUDECIMAL_NUMBER tSEC_UNIT
508 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
510 { $$ = RELATIVE_TIME_0; $$.seconds = 1; }
516 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
517 | tSNUMBER tMONTH_UNIT
518 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
520 { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
521 | tSNUMBER tHOUR_UNIT
522 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
523 | tSNUMBER tMINUTE_UNIT
524 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
526 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
529 seconds: signed_seconds | unsigned_seconds;
534 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
540 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
546 if (pc->dates_seen && ! pc->year.digits
547 && ! pc->rels_seen && (pc->times_seen || 2 < $1.digits))
554 pc->day = $1.value % 100;
555 pc->month = ($1.value / 100) % 100;
556 pc->year.value = $1.value / 10000;
557 pc->year.digits = $1.digits - 4;
569 pc->hour = $1.value / 100;
570 pc->minutes = $1.value % 100;
572 pc->seconds.tv_sec = 0;
573 pc->seconds.tv_nsec = 0;
574 pc->meridian = MER24;
596 static table const meridian_table[] =
598 { "AM", tMERIDIAN, MERam },
599 { "A.M.", tMERIDIAN, MERam },
600 { "PM", tMERIDIAN, MERpm },
601 { "P.M.", tMERIDIAN, MERpm },
605 static table const dst_table[] =
610 static table const month_and_day_table[] =
612 { "JANUARY", tMONTH, 1 },
613 { "FEBRUARY", tMONTH, 2 },
614 { "MARCH", tMONTH, 3 },
615 { "APRIL", tMONTH, 4 },
616 { "MAY", tMONTH, 5 },
617 { "JUNE", tMONTH, 6 },
618 { "JULY", tMONTH, 7 },
619 { "AUGUST", tMONTH, 8 },
620 { "SEPTEMBER",tMONTH, 9 },
621 { "SEPT", tMONTH, 9 },
622 { "OCTOBER", tMONTH, 10 },
623 { "NOVEMBER", tMONTH, 11 },
624 { "DECEMBER", tMONTH, 12 },
625 { "SUNDAY", tDAY, 0 },
626 { "MONDAY", tDAY, 1 },
627 { "TUESDAY", tDAY, 2 },
629 { "WEDNESDAY",tDAY, 3 },
630 { "WEDNES", tDAY, 3 },
631 { "THURSDAY", tDAY, 4 },
633 { "THURS", tDAY, 4 },
634 { "FRIDAY", tDAY, 5 },
635 { "SATURDAY", tDAY, 6 },
639 static table const time_units_table[] =
641 { "YEAR", tYEAR_UNIT, 1 },
642 { "MONTH", tMONTH_UNIT, 1 },
643 { "FORTNIGHT",tDAY_UNIT, 14 },
644 { "WEEK", tDAY_UNIT, 7 },
645 { "DAY", tDAY_UNIT, 1 },
646 { "HOUR", tHOUR_UNIT, 1 },
647 { "MINUTE", tMINUTE_UNIT, 1 },
648 { "MIN", tMINUTE_UNIT, 1 },
649 { "SECOND", tSEC_UNIT, 1 },
650 { "SEC", tSEC_UNIT, 1 },
654 /* Assorted relative-time words. */
655 static table const relative_time_table[] =
657 { "TOMORROW", tDAY_UNIT, 1 },
658 { "YESTERDAY",tDAY_UNIT, -1 },
659 { "TODAY", tDAY_UNIT, 0 },
660 { "NOW", tDAY_UNIT, 0 },
661 { "LAST", tORDINAL, -1 },
662 { "THIS", tORDINAL, 0 },
663 { "NEXT", tORDINAL, 1 },
664 { "FIRST", tORDINAL, 1 },
665 /*{ "SECOND", tORDINAL, 2 }, */
666 { "THIRD", tORDINAL, 3 },
667 { "FOURTH", tORDINAL, 4 },
668 { "FIFTH", tORDINAL, 5 },
669 { "SIXTH", tORDINAL, 6 },
670 { "SEVENTH", tORDINAL, 7 },
671 { "EIGHTH", tORDINAL, 8 },
672 { "NINTH", tORDINAL, 9 },
673 { "TENTH", tORDINAL, 10 },
674 { "ELEVENTH", tORDINAL, 11 },
675 { "TWELFTH", tORDINAL, 12 },
680 /* The universal time zone table. These labels can be used even for
681 time stamps that would not otherwise be valid, e.g., GMT time
682 stamps in London during summer. */
683 static table const universal_time_zone_table[] =
685 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
686 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
687 { "UTC", tZONE, HOUR ( 0) },
691 /* The time zone table. This table is necessarily incomplete, as time
692 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
693 as Eastern time in Australia, not as US Eastern Standard Time.
694 You cannot rely on getdate to handle arbitrary time zone
695 abbreviations; use numeric abbreviations like `-0500' instead. */
696 static table const time_zone_table[] =
698 { "WET", tZONE, HOUR ( 0) }, /* Western European */
699 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
700 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
701 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
702 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
703 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
704 { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
705 { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
706 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
707 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
708 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
709 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
710 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
711 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
712 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
713 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
714 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
715 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
716 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
717 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
718 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
719 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
720 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
721 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
722 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
723 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
724 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
725 { "CET", tZONE, HOUR ( 1) }, /* Central European */
726 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
727 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
728 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
729 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
730 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
731 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
732 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
733 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
734 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
735 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
736 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
737 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
738 { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
739 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
740 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
741 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
742 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
743 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
744 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
748 /* Military time zone table. */
749 static table const military_table[] =
751 { "A", tZONE, -HOUR ( 1) },
752 { "B", tZONE, -HOUR ( 2) },
753 { "C", tZONE, -HOUR ( 3) },
754 { "D", tZONE, -HOUR ( 4) },
755 { "E", tZONE, -HOUR ( 5) },
756 { "F", tZONE, -HOUR ( 6) },
757 { "G", tZONE, -HOUR ( 7) },
758 { "H", tZONE, -HOUR ( 8) },
759 { "I", tZONE, -HOUR ( 9) },
760 { "K", tZONE, -HOUR (10) },
761 { "L", tZONE, -HOUR (11) },
762 { "M", tZONE, -HOUR (12) },
763 { "N", tZONE, HOUR ( 1) },
764 { "O", tZONE, HOUR ( 2) },
765 { "P", tZONE, HOUR ( 3) },
766 { "Q", tZONE, HOUR ( 4) },
767 { "R", tZONE, HOUR ( 5) },
768 { "S", tZONE, HOUR ( 6) },
769 { "T", tZONE, HOUR ( 7) },
770 { "U", tZONE, HOUR ( 8) },
771 { "V", tZONE, HOUR ( 9) },
772 { "W", tZONE, HOUR (10) },
773 { "X", tZONE, HOUR (11) },
774 { "Y", tZONE, HOUR (12) },
775 { "Z", tZONE, HOUR ( 0) },
781 /* Convert a time zone expressed as HH:MM into an integer count of
782 minutes. If MM is negative, then S is of the form HHMM and needs
783 to be picked apart; otherwise, S is of the form HH. */
786 time_zone_hhmm (textint s, long int mm)
789 return (s.value / 100) * 60 + s.value % 100;
791 return s.value * 60 + (s.negative ? -mm : mm);
795 to_hour (long int hours, int meridian)
799 default: /* Pacify GCC. */
801 return 0 <= hours && hours < 24 ? hours : -1;
803 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
805 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
810 to_year (textint textyear)
812 long int year = textyear.value;
817 /* XPG4 suggests that years 00-68 map to 2000-2068, and
818 years 69-99 map to 1969-1999. */
819 else if (textyear.digits == 2)
820 year += year < 69 ? 2000 : 1900;
826 lookup_zone (parser_control const *pc, char const *name)
830 for (tp = universal_time_zone_table; tp->name; tp++)
831 if (strcmp (name, tp->name) == 0)
834 /* Try local zone abbreviations before those in time_zone_table, as
835 the local ones are more likely to be right. */
836 for (tp = pc->local_time_zone_table; tp->name; tp++)
837 if (strcmp (name, tp->name) == 0)
840 for (tp = time_zone_table; tp->name; tp++)
841 if (strcmp (name, tp->name) == 0)
848 /* Yield the difference between *A and *B,
849 measured in seconds, ignoring leap seconds.
850 The body of this function is taken directly from the GNU C Library;
851 see src/strftime.c. */
853 tm_diff (struct tm const *a, struct tm const *b)
855 /* Compute intervening leap days correctly even if year is negative.
856 Take care to avoid int overflow in leap day calculations. */
857 int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
858 int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
859 int a100 = a4 / 25 - (a4 % 25 < 0);
860 int b100 = b4 / 25 - (b4 % 25 < 0);
861 int a400 = SHR (a100, 2);
862 int b400 = SHR (b100, 2);
863 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
864 long int ayear = a->tm_year;
865 long int years = ayear - b->tm_year;
866 long int days = (365 * years + intervening_leap_days
867 + (a->tm_yday - b->tm_yday));
868 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
869 + (a->tm_min - b->tm_min))
870 + (a->tm_sec - b->tm_sec));
872 #endif /* ! HAVE_TM_GMTOFF */
875 lookup_word (parser_control const *pc, char *word)
884 /* Make it uppercase. */
885 for (p = word; *p; p++)
887 unsigned char ch = *p;
891 for (tp = meridian_table; tp->name; tp++)
892 if (strcmp (word, tp->name) == 0)
895 /* See if we have an abbreviation for a month. */
896 wordlen = strlen (word);
897 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
899 for (tp = month_and_day_table; tp->name; tp++)
900 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
903 if ((tp = lookup_zone (pc, word)))
906 if (strcmp (word, dst_table[0].name) == 0)
909 for (tp = time_units_table; tp->name; tp++)
910 if (strcmp (word, tp->name) == 0)
913 /* Strip off any plural and try the units table again. */
914 if (word[wordlen - 1] == 'S')
916 word[wordlen - 1] = '\0';
917 for (tp = time_units_table; tp->name; tp++)
918 if (strcmp (word, tp->name) == 0)
920 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
923 for (tp = relative_time_table; tp->name; tp++)
924 if (strcmp (word, tp->name) == 0)
927 /* Military time zones. */
929 for (tp = military_table; tp->name; tp++)
930 if (word[0] == tp->name[0])
933 /* Drop out any periods and try the time zone table again. */
934 for (period_found = false, p = q = word; (*p = *q); q++)
939 if (period_found && (tp = lookup_zone (pc, word)))
946 yylex (YYSTYPE *lvalp, parser_control *pc)
953 while (c = *pc->input, isspace (c))
956 if (ISDIGIT (c) || c == '-' || c == '+')
960 unsigned long int value;
961 if (c == '-' || c == '+')
963 sign = c == '-' ? -1 : 1;
964 while (c = *++pc->input, isspace (c))
967 /* skip the '-' sign */
973 for (value = 0; ; value *= 10)
975 unsigned long int value1 = value + (c - '0');
982 if (ULONG_MAX / 10 < value)
985 if ((c == '.' || c == ',') && ISDIGIT (p[1]))
990 unsigned long int value1;
992 /* Check for overflow when converting value to time_t. */
1007 if (value != value1)
1010 /* Accumulate fraction, to ns precision. */
1013 for (digits = 2; digits <= LOG10_BILLION; digits++)
1020 /* Skip excess digits, truncating toward -Infinity. */
1022 for (; ISDIGIT (*p); p++)
1028 while (ISDIGIT (*p))
1031 /* Adjust to the timespec convention, which is that
1032 tv_nsec is always a positive offset even if tv_sec is
1042 lvalp->timespec.tv_sec = s;
1043 lvalp->timespec.tv_nsec = ns;
1045 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
1049 lvalp->textintval.negative = sign < 0;
1052 lvalp->textintval.value = - value;
1053 if (0 < lvalp->textintval.value)
1058 lvalp->textintval.value = value;
1059 if (lvalp->textintval.value < 0)
1062 lvalp->textintval.digits = p - pc->input;
1064 return sign ? tSNUMBER : tUNUMBER;
1076 if (p < buff + sizeof buff - 1)
1080 while (isalpha (c) || c == '.');
1083 tp = lookup_word (pc, buff);
1086 lvalp->intval = tp->value;
1091 return *pc->input++;
1107 /* Do nothing if the parser reports an error. */
1109 yyerror (parser_control const *pc ATTRIBUTE_UNUSED,
1110 char const *s ATTRIBUTE_UNUSED)
1115 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1116 passing it to mktime, return true if it's OK that mktime returned T.
1117 It's not OK if *TM0 has out-of-range members. */
1120 mktime_ok (struct tm const *tm0, struct tm const *tm1, time_t t)
1122 if (t == (time_t) -1)
1124 /* Guard against falsely reporting an error when parsing a time
1125 stamp that happens to equal (time_t) -1, on a host that
1126 supports such a time stamp. */
1127 tm1 = localtime (&t);
1132 return ! ((tm0->tm_sec ^ tm1->tm_sec)
1133 | (tm0->tm_min ^ tm1->tm_min)
1134 | (tm0->tm_hour ^ tm1->tm_hour)
1135 | (tm0->tm_mday ^ tm1->tm_mday)
1136 | (tm0->tm_mon ^ tm1->tm_mon)
1137 | (tm0->tm_year ^ tm1->tm_year));
1140 /* A reasonable upper bound for the size of ordinary TZ strings.
1141 Use heap allocation if TZ's length exceeds this. */
1142 enum { TZBUFSIZE = 100 };
1144 /* Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
1147 get_tz (char tzbuf[TZBUFSIZE])
1149 char *tz = getenv ("TZ");
1152 size_t tzsize = strlen (tz) + 1;
1153 tz = (tzsize <= TZBUFSIZE
1154 ? memcpy (tzbuf, tz, tzsize)
1155 : xmemdup (tz, tzsize));
1160 /* Parse a date/time string, storing the resulting time value into *RESULT.
1161 The string itself is pointed to by P. Return true if successful.
1162 P can be an incomplete or relative time specification; if so, use
1163 *NOW as the basis for the returned time. */
1165 get_date (struct timespec *result, char const *p, struct timespec const *now)
1169 struct tm const *tmp;
1173 struct timespec gettime_buffer;
1175 bool tz_was_altered = false;
1177 char tz0buf[TZBUFSIZE];
1182 gettime (&gettime_buffer);
1183 now = &gettime_buffer;
1186 Start = now->tv_sec;
1187 Start_ns = now->tv_nsec;
1189 tmp = localtime (&now->tv_sec);
1193 while (c = *p, isspace (c))
1196 if (strncmp (p, "TZ=\"", 4) == 0)
1198 char const *tzbase = p + 4;
1202 for (s = tzbase; *s; s++, tzsize++)
1206 if (! (*s == '\\' || *s == '"'))
1213 char tz1buf[TZBUFSIZE];
1214 bool large_tz = TZBUFSIZE < tzsize;
1216 tz0 = get_tz (tz0buf);
1217 z = tz1 = large_tz ? xmalloc (tzsize) : tz1buf;
1218 for (s = tzbase; *s != '"'; s++)
1219 *z++ = *(s += *s == '\\');
1221 setenv_ok = setenv ("TZ", tz1, 1) == 0;
1226 tz_was_altered = true;
1232 pc.year.value = tmp->tm_year;
1233 pc.year.value += TM_YEAR_BASE;
1235 pc.month = tmp->tm_mon + 1;
1236 pc.day = tmp->tm_mday;
1237 pc.hour = tmp->tm_hour;
1238 pc.minutes = tmp->tm_min;
1239 pc.seconds.tv_sec = tmp->tm_sec;
1240 pc.seconds.tv_nsec = Start_ns;
1241 tm.tm_isdst = tmp->tm_isdst;
1243 pc.meridian = MER24;
1244 pc.rel = RELATIVE_TIME_0;
1245 pc.timespec_seen = false;
1246 pc.rels_seen = false;
1250 pc.local_zones_seen = 0;
1254 #if HAVE_STRUCT_TM_TM_ZONE
1255 pc.local_time_zone_table[0].name = tmp->tm_zone;
1256 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1257 pc.local_time_zone_table[0].value = tmp->tm_isdst;
1258 pc.local_time_zone_table[1].name = NULL;
1260 /* Probe the names used in the next three calendar quarters, looking
1261 for a tm_isdst different from the one we already have. */
1264 for (quarter = 1; quarter <= 3; quarter++)
1266 time_t probe = Start + quarter * (90 * 24 * 60 * 60);
1267 struct tm const *probe_tm = localtime (&probe);
1268 if (probe_tm && probe_tm->tm_zone
1269 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
1272 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
1273 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1274 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
1275 pc.local_time_zone_table[2].name = NULL;
1285 extern char *tzname[];
1288 for (i = 0; i < 2; i++)
1290 pc.local_time_zone_table[i].name = tzname[i];
1291 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1292 pc.local_time_zone_table[i].value = i;
1294 pc.local_time_zone_table[i].name = NULL;
1297 pc.local_time_zone_table[0].name = NULL;
1301 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1302 && ! strcmp (pc.local_time_zone_table[0].name,
1303 pc.local_time_zone_table[1].name))
1305 /* This locale uses the same abbrevation for standard and
1306 daylight times. So if we see that abbreviation, we don't
1307 know whether it's daylight time. */
1308 pc.local_time_zone_table[0].value = -1;
1309 pc.local_time_zone_table[1].name = NULL;
1312 if (yyparse (&pc) != 0)
1315 if (pc.timespec_seen)
1316 *result = pc.seconds;
1319 if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
1320 | (pc.local_zones_seen + pc.zones_seen)))
1323 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
1324 tm.tm_mon = pc.month - 1;
1325 tm.tm_mday = pc.day;
1326 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1328 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1331 tm.tm_min = pc.minutes;
1332 tm.tm_sec = pc.seconds.tv_sec;
1336 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1337 pc.seconds.tv_nsec = 0;
1340 /* Let mktime deduce tm_isdst if we have an absolute time stamp. */
1341 if (pc.dates_seen | pc.days_seen | pc.times_seen)
1344 /* But if the input explicitly specifies local time with or without
1345 DST, give mktime that information. */
1346 if (pc.local_zones_seen)
1347 tm.tm_isdst = pc.local_isdst;
1351 Start = mktime (&tm);
1353 if (! mktime_ok (&tm0, &tm, Start))
1355 if (! pc.zones_seen)
1359 /* Guard against falsely reporting errors near the time_t
1360 boundaries when parsing times in other time zones. For
1361 example, suppose the input string "1969-12-31 23:00:00 -0100",
1362 the current time zone is 8 hours ahead of UTC, and the min
1363 time_t value is 1970-01-01 00:00:00 UTC. Then the min
1364 localtime value is 1970-01-01 08:00:00, and mktime will
1365 therefore fail on 1969-12-31 23:00:00. To work around the
1366 problem, set the time zone to 1 hour behind UTC temporarily
1367 by setting TZ="XXX1:00" and try mktime again. */
1369 long int time_zone = pc.time_zone;
1370 long int abs_time_zone = time_zone < 0 ? - time_zone : time_zone;
1371 long int abs_time_zone_hour = abs_time_zone / 60;
1372 int abs_time_zone_min = abs_time_zone % 60;
1373 char tz1buf[sizeof "XXX+0:00"
1374 + sizeof pc.time_zone * CHAR_BIT / 3];
1375 if (!tz_was_altered)
1376 tz0 = get_tz (tz0buf);
1377 sprintf (tz1buf, "XXX%s%ld:%02d", "-" + (time_zone < 0),
1378 abs_time_zone_hour, abs_time_zone_min);
1379 if (setenv ("TZ", tz1buf, 1) != 0)
1381 tz_was_altered = true;
1383 Start = mktime (&tm);
1384 if (! mktime_ok (&tm0, &tm, Start))
1389 if (pc.days_seen && ! pc.dates_seen)
1391 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1392 + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
1394 Start = mktime (&tm);
1395 if (Start == (time_t) -1)
1401 long int delta = pc.time_zone * 60;
1403 #ifdef HAVE_TM_GMTOFF
1404 delta -= tm.tm_gmtoff;
1407 struct tm const *gmt = gmtime (&t);
1410 delta -= tm_diff (&tm, gmt);
1413 if ((Start < t1) != (delta < 0))
1414 goto fail; /* time_t overflow */
1418 /* Add relative date. */
1419 if (pc.rel.year | pc.rel.month | pc.rel.day)
1421 int year = tm.tm_year + pc.rel.year;
1422 int month = tm.tm_mon + pc.rel.month;
1423 int day = tm.tm_mday + pc.rel.day;
1424 if (((year < tm.tm_year) ^ (pc.rel.year < 0))
1425 | ((month < tm.tm_mon) ^ (pc.rel.month < 0))
1426 | ((day < tm.tm_mday) ^ (pc.rel.day < 0)))
1431 tm.tm_hour = tm0.tm_hour;
1432 tm.tm_min = tm0.tm_min;
1433 tm.tm_sec = tm0.tm_sec;
1434 tm.tm_isdst = tm0.tm_isdst;
1435 Start = mktime (&tm);
1436 if (Start == (time_t) -1)
1440 /* Add relative hours, minutes, and seconds. On hosts that support
1441 leap seconds, ignore the possibility of leap seconds; e.g.,
1442 "+ 10 minutes" adds 600 seconds, even if one of them is a
1443 leap second. Typically this is not what the user wants, but it's
1444 too hard to do it the other way, because the time zone indicator
1445 must be applied before relative times, and if mktime is applied
1446 again the time zone will be lost. */
1448 long int sum_ns = pc.seconds.tv_nsec + pc.rel.ns;
1449 long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
1451 long int d1 = 60 * 60 * pc.rel.hour;
1452 time_t t1 = t0 + d1;
1453 long int d2 = 60 * pc.rel.minutes;
1454 time_t t2 = t1 + d2;
1455 long int d3 = pc.rel.seconds;
1456 time_t t3 = t2 + d3;
1457 long int d4 = (sum_ns - normalized_ns) / BILLION;
1458 time_t t4 = t3 + d4;
1460 if ((d1 / (60 * 60) ^ pc.rel.hour)
1461 | (d2 / 60 ^ pc.rel.minutes)
1462 | ((t1 < t0) ^ (d1 < 0))
1463 | ((t2 < t1) ^ (d2 < 0))
1464 | ((t3 < t2) ^ (d3 < 0))
1465 | ((t4 < t3) ^ (d4 < 0)))
1468 result->tv_sec = t4;
1469 result->tv_nsec = normalized_ns;
1479 ok &= (tz0 ? setenv ("TZ", tz0, 1) : unsetenv ("TZ")) == 0;
1488 main (int ac, char **av)
1492 printf ("Enter date, or blank line to exit.\n\t> ");
1495 buff[BUFSIZ - 1] = '\0';
1496 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1499 struct tm const *tm;
1500 if (! get_date (&d, buff, NULL))
1501 printf ("Bad format - couldn't convert.\n");
1502 else if (! (tm = localtime (&d.tv_sec)))
1504 long int sec = d.tv_sec;
1505 printf ("localtime (%ld) failed\n", sec);
1510 printf ("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1511 tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
1512 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);