2 /* Parse a string into an internal time stamp.
3 Copyright (C) 1999, 2000, 2002, 2003, 2004 Free Software Foundation, Inc.
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2, or (at your option)
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software Foundation,
17 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
19 /* Originally written by Steven M. Bellovin <smb@research.att.com> while
20 at the University of North Carolina at Chapel Hill. Later tweaked by
21 a couple of people on Usenet. Completely overhauled by Rich $alz
22 <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
24 Modified by Paul Eggert <eggert@twinsun.com> in August 1999 to do
25 the right thing about local DST. Also modified by Paul Eggert
26 <eggert@cs.ucla.edu> in February 2004 to support
27 nanosecond-resolution time stamps, and in October 2004 to support
28 TZ strings in dates. */
30 /* FIXME: Check for arithmetic overflow in all cases, not just
39 /* Use alloca only if it is known to be builtin. */
43 # define YYSTACK_USE_ALLOCA 0
46 /* Tell Bison ow much stack space is needed. 20 should be plenty for
47 this grammar, which is not right recursive. Beware setting it too
48 high, since that might cause problems on machines whose alloca
49 implementations have lame stack-overflow checking. */
52 /* Since the code of getdate.y is not included in the Emacs executable
53 itself, there is no need to #define static in this file. Even if
54 the code were included in the Emacs executable, it probably
55 wouldn't do any harm to #undef it here; this will only cause
56 problems if we try to write to a static variable, which I don't
57 think this code needs to do. */
71 #if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII)
72 # define IN_CTYPE_DOMAIN(c) 1
74 # define IN_CTYPE_DOMAIN(c) isascii (c)
77 #define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c))
78 #define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c))
79 #define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c))
81 /* ISDIGIT differs from isdigit, as follows:
82 - Its arg may be any int or unsigned int; it need not be an unsigned char.
83 - It's guaranteed to evaluate its argument exactly once.
84 - It's typically faster.
85 POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
86 isdigit unless it's important to use the locale's definition
87 of `digit' even when the host does not conform to POSIX. */
88 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
90 #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
91 # define __attribute__(x)
94 #ifndef ATTRIBUTE_UNUSED
95 # define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
98 /* Shift A right by B bits portably, by dividing A by 2**B and
99 truncating towards minus infinity. A and B should be free of side
100 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
101 INT_BITS is the number of useful bits in an int. GNU code can
102 assume that INT_BITS is at least 32.
104 ISO C99 says that A >> B is implementation-defined if A < 0. Some
105 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
106 right in the usual way when A < 0, so SHR falls back on division if
107 ordinary A >> B doesn't seem to be the usual signed shift. */
111 : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
113 #define EPOCH_YEAR 1970
114 #define TM_YEAR_BASE 1900
116 #define HOUR(x) ((x) * 60)
118 /* An integer value, and the number of digits in its textual
127 /* An entry in the lexical lookup table. */
135 /* Meridian: am, pm, or 24-hour style. */
136 enum { MERam, MERpm, MER24 };
138 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
140 /* Information passed to and from the parser. */
143 /* The input string remaining to be parsed. */
146 /* N, if this is the Nth Tuesday. */
147 long int day_ordinal;
149 /* Day of week; Sunday is 0. */
152 /* tm_isdst flag for the local zone. */
155 /* Time zone, in minutes east of UTC. */
158 /* Style used for time. */
161 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
167 struct timespec seconds; /* includes nanoseconds */
169 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
174 long int rel_minutes;
175 long int rel_seconds;
178 /* Counts of nonterminals of various flavors parsed so far. */
182 size_t local_zones_seen;
187 /* Table of local time zone abbrevations, terminated by a null entry. */
188 table local_time_zone_table[3];
192 static int yylex (union YYSTYPE *, parser_control *);
193 static int yyerror (parser_control *, char *);
194 static long int time_zone_hhmm (textint, long int);
198 /* We want a reentrant parser, even if the TZ manipulation and the calls to
199 localtime and gmtime are not reentrant. */
201 %parse-param { parser_control *pc }
202 %lex-param { parser_control *pc }
204 /* This grammar has 14 shift/reduce conflicts. */
211 struct timespec timespec;
216 %token <intval> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN
217 %token <intval> tMINUTE_UNIT tMONTH tMONTH_UNIT tORDINAL
218 %token <intval> tSEC_UNIT tYEAR_UNIT tZONE
220 %token <textintval> tSNUMBER tUNUMBER
221 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
223 %type <intval> o_colon_minutes o_merid
224 %type <timespec> seconds signed_seconds unsigned_seconds
237 pc->timespec_seen = true;
248 { pc->times_seen++; }
250 { pc->local_zones_seen++; }
252 { pc->zones_seen++; }
254 { pc->dates_seen++; }
267 pc->seconds.tv_sec = 0;
268 pc->seconds.tv_nsec = 0;
271 | tUNUMBER ':' tUNUMBER o_merid
274 pc->minutes = $3.value;
275 pc->seconds.tv_sec = 0;
276 pc->seconds.tv_nsec = 0;
279 | tUNUMBER ':' tUNUMBER tSNUMBER o_colon_minutes
282 pc->minutes = $3.value;
283 pc->seconds.tv_sec = 0;
284 pc->seconds.tv_nsec = 0;
285 pc->meridian = MER24;
287 pc->time_zone = time_zone_hhmm ($4, $5);
289 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
292 pc->minutes = $3.value;
296 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER o_colon_minutes
299 pc->minutes = $3.value;
301 pc->meridian = MER24;
303 pc->time_zone = time_zone_hhmm ($6, $7);
309 { pc->local_isdst = $1; }
311 { pc->local_isdst = $1 < 0 ? 1 : $1 + 1; }
316 { pc->time_zone = $1; }
317 | tZONE tSNUMBER o_colon_minutes
318 { pc->time_zone = $1 + time_zone_hhmm ($2, $3); }
320 { pc->time_zone = $1 + 60; }
322 { pc->time_zone = $1 + 60; }
338 pc->day_ordinal = $1;
343 pc->day_ordinal = $1.value;
349 tUNUMBER '/' tUNUMBER
351 pc->month = $1.value;
354 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
356 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
357 otherwise as MM/DD/YY.
358 The goal in recognizing YYYY/MM/DD is solely to support legacy
359 machine-generated dates like those in an RCS log listing. If
360 you want portability, use the ISO 8601 format. */
364 pc->month = $3.value;
369 pc->month = $1.value;
374 | tUNUMBER tSNUMBER tSNUMBER
376 /* ISO 8601 format. YYYY-MM-DD. */
378 pc->month = -$2.value;
381 | tUNUMBER tMONTH tSNUMBER
383 /* e.g. 17-JUN-1992. */
386 pc->year.value = -$3.value;
387 pc->year.digits = $3.digits;
389 | tMONTH tSNUMBER tSNUMBER
391 /* e.g. JUN-17-1992. */
394 pc->year.value = -$3.value;
395 pc->year.digits = $3.digits;
402 | tMONTH tUNUMBER ',' tUNUMBER
413 | tUNUMBER tMONTH tUNUMBER
424 pc->rel_ns = -pc->rel_ns;
425 pc->rel_seconds = -pc->rel_seconds;
426 pc->rel_minutes = -pc->rel_minutes;
427 pc->rel_hour = -pc->rel_hour;
428 pc->rel_day = -pc->rel_day;
429 pc->rel_month = -pc->rel_month;
430 pc->rel_year = -pc->rel_year;
437 { pc->rel_year += $1 * $2; }
438 | tUNUMBER tYEAR_UNIT
439 { pc->rel_year += $1.value * $2; }
440 | tSNUMBER tYEAR_UNIT
441 { pc->rel_year += $1.value * $2; }
443 { pc->rel_year += $1; }
444 | tORDINAL tMONTH_UNIT
445 { pc->rel_month += $1 * $2; }
446 | tUNUMBER tMONTH_UNIT
447 { pc->rel_month += $1.value * $2; }
448 | tSNUMBER tMONTH_UNIT
449 { pc->rel_month += $1.value * $2; }
451 { pc->rel_month += $1; }
453 { pc->rel_day += $1 * $2; }
455 { pc->rel_day += $1.value * $2; }
457 { pc->rel_day += $1.value * $2; }
459 { pc->rel_day += $1; }
460 | tORDINAL tHOUR_UNIT
461 { pc->rel_hour += $1 * $2; }
462 | tUNUMBER tHOUR_UNIT
463 { pc->rel_hour += $1.value * $2; }
464 | tSNUMBER tHOUR_UNIT
465 { pc->rel_hour += $1.value * $2; }
467 { pc->rel_hour += $1; }
468 | tORDINAL tMINUTE_UNIT
469 { pc->rel_minutes += $1 * $2; }
470 | tUNUMBER tMINUTE_UNIT
471 { pc->rel_minutes += $1.value * $2; }
472 | tSNUMBER tMINUTE_UNIT
473 { pc->rel_minutes += $1.value * $2; }
475 { pc->rel_minutes += $1; }
477 { pc->rel_seconds += $1 * $2; }
479 { pc->rel_seconds += $1.value * $2; }
481 { pc->rel_seconds += $1.value * $2; }
482 | tSDECIMAL_NUMBER tSEC_UNIT
483 { pc->rel_seconds += $1.tv_sec * $2; pc->rel_ns += $1.tv_nsec * $2; }
484 | tUDECIMAL_NUMBER tSEC_UNIT
485 { pc->rel_seconds += $1.tv_sec * $2; pc->rel_ns += $1.tv_nsec * $2; }
487 { pc->rel_seconds += $1; }
490 seconds: signed_seconds | unsigned_seconds;
495 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
501 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
508 && ! pc->rels_seen && (pc->times_seen || 2 < $1.digits))
515 pc->day = $1.value % 100;
516 pc->month = ($1.value / 100) % 100;
517 pc->year.value = $1.value / 10000;
518 pc->year.digits = $1.digits - 4;
530 pc->hour = $1.value / 100;
531 pc->minutes = $1.value % 100;
533 pc->seconds.tv_sec = 0;
534 pc->seconds.tv_nsec = 0;
535 pc->meridian = MER24;
557 static table const meridian_table[] =
559 { "AM", tMERIDIAN, MERam },
560 { "A.M.", tMERIDIAN, MERam },
561 { "PM", tMERIDIAN, MERpm },
562 { "P.M.", tMERIDIAN, MERpm },
566 static table const dst_table[] =
571 static table const month_and_day_table[] =
573 { "JANUARY", tMONTH, 1 },
574 { "FEBRUARY", tMONTH, 2 },
575 { "MARCH", tMONTH, 3 },
576 { "APRIL", tMONTH, 4 },
577 { "MAY", tMONTH, 5 },
578 { "JUNE", tMONTH, 6 },
579 { "JULY", tMONTH, 7 },
580 { "AUGUST", tMONTH, 8 },
581 { "SEPTEMBER",tMONTH, 9 },
582 { "SEPT", tMONTH, 9 },
583 { "OCTOBER", tMONTH, 10 },
584 { "NOVEMBER", tMONTH, 11 },
585 { "DECEMBER", tMONTH, 12 },
586 { "SUNDAY", tDAY, 0 },
587 { "MONDAY", tDAY, 1 },
588 { "TUESDAY", tDAY, 2 },
590 { "WEDNESDAY",tDAY, 3 },
591 { "WEDNES", tDAY, 3 },
592 { "THURSDAY", tDAY, 4 },
594 { "THURS", tDAY, 4 },
595 { "FRIDAY", tDAY, 5 },
596 { "SATURDAY", tDAY, 6 },
600 static table const time_units_table[] =
602 { "YEAR", tYEAR_UNIT, 1 },
603 { "MONTH", tMONTH_UNIT, 1 },
604 { "FORTNIGHT",tDAY_UNIT, 14 },
605 { "WEEK", tDAY_UNIT, 7 },
606 { "DAY", tDAY_UNIT, 1 },
607 { "HOUR", tHOUR_UNIT, 1 },
608 { "MINUTE", tMINUTE_UNIT, 1 },
609 { "MIN", tMINUTE_UNIT, 1 },
610 { "SECOND", tSEC_UNIT, 1 },
611 { "SEC", tSEC_UNIT, 1 },
615 /* Assorted relative-time words. */
616 static table const relative_time_table[] =
618 { "TOMORROW", tDAY_UNIT, 1 },
619 { "YESTERDAY",tDAY_UNIT, -1 },
620 { "TODAY", tDAY_UNIT, 0 },
621 { "NOW", tDAY_UNIT, 0 },
622 { "LAST", tORDINAL, -1 },
623 { "THIS", tORDINAL, 0 },
624 { "NEXT", tORDINAL, 1 },
625 { "FIRST", tORDINAL, 1 },
626 /*{ "SECOND", tORDINAL, 2 }, */
627 { "THIRD", tORDINAL, 3 },
628 { "FOURTH", tORDINAL, 4 },
629 { "FIFTH", tORDINAL, 5 },
630 { "SIXTH", tORDINAL, 6 },
631 { "SEVENTH", tORDINAL, 7 },
632 { "EIGHTH", tORDINAL, 8 },
633 { "NINTH", tORDINAL, 9 },
634 { "TENTH", tORDINAL, 10 },
635 { "ELEVENTH", tORDINAL, 11 },
636 { "TWELFTH", tORDINAL, 12 },
641 /* The time zone table. This table is necessarily incomplete, as time
642 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
643 as Eastern time in Australia, not as US Eastern Standard Time.
644 You cannot rely on getdate to handle arbitrary time zone
645 abbreviations; use numeric abbreviations like `-0500' instead. */
646 static table const time_zone_table[] =
648 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
649 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
650 { "UTC", tZONE, HOUR ( 0) },
651 { "WET", tZONE, HOUR ( 0) }, /* Western European */
652 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
653 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
654 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
655 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
656 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
657 { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
658 { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
659 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
660 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
661 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
662 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
663 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
664 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
665 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
666 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
667 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
668 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
669 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
670 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
671 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
672 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
673 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
674 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
675 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
676 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
677 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
678 { "CET", tZONE, HOUR ( 1) }, /* Central European */
679 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
680 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
681 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
682 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
683 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
684 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
685 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
686 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
687 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
688 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
689 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
690 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
691 { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
692 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
693 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
694 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
695 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
696 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
697 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
701 /* Military time zone table. */
702 static table const military_table[] =
704 { "A", tZONE, -HOUR ( 1) },
705 { "B", tZONE, -HOUR ( 2) },
706 { "C", tZONE, -HOUR ( 3) },
707 { "D", tZONE, -HOUR ( 4) },
708 { "E", tZONE, -HOUR ( 5) },
709 { "F", tZONE, -HOUR ( 6) },
710 { "G", tZONE, -HOUR ( 7) },
711 { "H", tZONE, -HOUR ( 8) },
712 { "I", tZONE, -HOUR ( 9) },
713 { "K", tZONE, -HOUR (10) },
714 { "L", tZONE, -HOUR (11) },
715 { "M", tZONE, -HOUR (12) },
716 { "N", tZONE, HOUR ( 1) },
717 { "O", tZONE, HOUR ( 2) },
718 { "P", tZONE, HOUR ( 3) },
719 { "Q", tZONE, HOUR ( 4) },
720 { "R", tZONE, HOUR ( 5) },
721 { "S", tZONE, HOUR ( 6) },
722 { "T", tZONE, HOUR ( 7) },
723 { "U", tZONE, HOUR ( 8) },
724 { "V", tZONE, HOUR ( 9) },
725 { "W", tZONE, HOUR (10) },
726 { "X", tZONE, HOUR (11) },
727 { "Y", tZONE, HOUR (12) },
728 { "Z", tZONE, HOUR ( 0) },
734 /* Convert a time zone expressed as HH:MM into an integer count of
735 minutes. If MM is negative, then S is of the form HHMM and needs
736 to be picked apart; otherwise, S is of the form HH. */
739 time_zone_hhmm (textint s, long int mm)
742 return (s.value / 100) * 60 + s.value % 100;
744 return s.value * 60 + (s.negative ? -mm : mm);
748 to_hour (long int hours, int meridian)
752 default: /* Pacify GCC. */
754 return 0 <= hours && hours < 24 ? hours : -1;
756 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
758 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
763 to_year (textint textyear)
765 long int year = textyear.value;
770 /* XPG4 suggests that years 00-68 map to 2000-2068, and
771 years 69-99 map to 1969-1999. */
772 else if (textyear.digits == 2)
773 year += year < 69 ? 2000 : 1900;
779 lookup_zone (parser_control const *pc, char const *name)
783 /* Try local zone abbreviations first; they're more likely to be right. */
784 for (tp = pc->local_time_zone_table; tp->name; tp++)
785 if (strcmp (name, tp->name) == 0)
788 for (tp = time_zone_table; tp->name; tp++)
789 if (strcmp (name, tp->name) == 0)
796 /* Yield the difference between *A and *B,
797 measured in seconds, ignoring leap seconds.
798 The body of this function is taken directly from the GNU C Library;
799 see src/strftime.c. */
801 tm_diff (struct tm const *a, struct tm const *b)
803 /* Compute intervening leap days correctly even if year is negative.
804 Take care to avoid int overflow in leap day calculations. */
805 int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
806 int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
807 int a100 = a4 / 25 - (a4 % 25 < 0);
808 int b100 = b4 / 25 - (b4 % 25 < 0);
809 int a400 = SHR (a100, 2);
810 int b400 = SHR (b100, 2);
811 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
812 long int ayear = a->tm_year;
813 long int years = ayear - b->tm_year;
814 long int days = (365 * years + intervening_leap_days
815 + (a->tm_yday - b->tm_yday));
816 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
817 + (a->tm_min - b->tm_min))
818 + (a->tm_sec - b->tm_sec));
820 #endif /* ! HAVE_TM_GMTOFF */
823 lookup_word (parser_control const *pc, char *word)
832 /* Make it uppercase. */
833 for (p = word; *p; p++)
835 unsigned char ch = *p;
840 for (tp = meridian_table; tp->name; tp++)
841 if (strcmp (word, tp->name) == 0)
844 /* See if we have an abbreviation for a month. */
845 wordlen = strlen (word);
846 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
848 for (tp = month_and_day_table; tp->name; tp++)
849 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
852 if ((tp = lookup_zone (pc, word)))
855 if (strcmp (word, dst_table[0].name) == 0)
858 for (tp = time_units_table; tp->name; tp++)
859 if (strcmp (word, tp->name) == 0)
862 /* Strip off any plural and try the units table again. */
863 if (word[wordlen - 1] == 'S')
865 word[wordlen - 1] = '\0';
866 for (tp = time_units_table; tp->name; tp++)
867 if (strcmp (word, tp->name) == 0)
869 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
872 for (tp = relative_time_table; tp->name; tp++)
873 if (strcmp (word, tp->name) == 0)
876 /* Military time zones. */
878 for (tp = military_table; tp->name; tp++)
879 if (word[0] == tp->name[0])
882 /* Drop out any periods and try the time zone table again. */
883 for (period_found = false, p = q = word; (*p = *q); q++)
888 if (period_found && (tp = lookup_zone (pc, word)))
895 yylex (YYSTYPE *lvalp, parser_control *pc)
902 while (c = *pc->input, ISSPACE (c))
905 if (ISDIGIT (c) || c == '-' || c == '+')
909 unsigned long int value;
910 if (c == '-' || c == '+')
912 sign = c == '-' ? -1 : 1;
913 while (c = *++pc->input, ISSPACE (c))
916 /* skip the '-' sign */
922 for (value = 0; ; value *= 10)
924 unsigned long int value1 = value + (c - '0');
931 if (ULONG_MAX / 10 < value)
934 if ((c == '.' || c == ',') && ISDIGIT (p[1]))
939 unsigned long int value1;
941 /* Check for overflow when converting value to time_t. */
959 /* Accumulate fraction, to ns precision. */
962 for (digits = 2; digits <= LOG10_BILLION; digits++)
969 /* Skip excess digits, truncating toward -Infinity. */
971 for (; ISDIGIT (*p); p++)
980 /* Adjust to the timespec convention, which is that
981 tv_nsec is always a positive offset even if tv_sec is
991 lvalp->timespec.tv_sec = s;
992 lvalp->timespec.tv_nsec = ns;
994 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
998 lvalp->textintval.negative = sign < 0;
1001 lvalp->textintval.value = - value;
1002 if (0 < lvalp->textintval.value)
1007 lvalp->textintval.value = value;
1008 if (lvalp->textintval.value < 0)
1011 lvalp->textintval.digits = p - pc->input;
1013 return sign ? tSNUMBER : tUNUMBER;
1025 if (p < buff + sizeof buff - 1)
1029 while (ISALPHA (c) || c == '.');
1032 tp = lookup_word (pc, buff);
1035 lvalp->intval = tp->value;
1040 return *pc->input++;
1056 /* Do nothing if the parser reports an error. */
1058 yyerror (parser_control *pc ATTRIBUTE_UNUSED, char *s ATTRIBUTE_UNUSED)
1063 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1064 passing it to mktime, return true if it's OK that mktime returned T.
1065 It's not OK if *TM0 has out-of-range members. */
1068 mktime_ok (struct tm const *tm0, struct tm const *tm1, time_t t)
1070 if (t == (time_t) -1)
1072 /* Guard against falsely reporting an error when parsing a time
1073 stamp that happens to equal (time_t) -1, on a host that
1074 supports such a time stamp. */
1075 tm1 = localtime (&t);
1080 return ! ((tm0->tm_sec ^ tm1->tm_sec)
1081 | (tm0->tm_min ^ tm1->tm_min)
1082 | (tm0->tm_hour ^ tm1->tm_hour)
1083 | (tm0->tm_mday ^ tm1->tm_mday)
1084 | (tm0->tm_mon ^ tm1->tm_mon)
1085 | (tm0->tm_year ^ tm1->tm_year));
1088 /* A reasonable upper bound for the size of ordinary TZ strings.
1089 Use heap allocation if TZ's length exceeds this. */
1090 enum { TZBUFSIZE = 100 };
1092 /* Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
1095 get_tz (char tzbuf[TZBUFSIZE])
1097 char *tz = getenv ("TZ");
1100 size_t tzsize = strlen (tz) + 1;
1101 tz = (tzsize <= TZBUFSIZE
1102 ? memcpy (tzbuf, tz, tzsize)
1103 : xmemdup (tz, tzsize));
1108 /* Parse a date/time string, storing the resulting time value into *RESULT.
1109 The string itself is pointed to by P. Return true if successful.
1110 P can be an incomplete or relative time specification; if so, use
1111 *NOW as the basis for the returned time. */
1113 get_date (struct timespec *result, char const *p, struct timespec const *now)
1117 struct tm const *tmp;
1121 struct timespec gettime_buffer;
1123 bool tz_was_altered = false;
1125 char tz0buf[TZBUFSIZE];
1130 if (gettime (&gettime_buffer) != 0)
1132 now = &gettime_buffer;
1135 Start = now->tv_sec;
1136 Start_ns = now->tv_nsec;
1138 tmp = localtime (&now->tv_sec);
1142 while (c = *p, ISSPACE (c))
1145 if (strncmp (p, "TZ=\"", 4) == 0)
1147 char const *tzbase = p + 4;
1151 for (s = tzbase; *s; s++, tzsize++)
1155 if (! (*s == '\\' || *s == '"'))
1162 char tz1buf[TZBUFSIZE];
1163 bool large_tz = TZBUFSIZE < tzsize;
1165 tz0 = get_tz (tz0buf);
1166 z = tz1 = large_tz ? xmalloc (tzsize) : tz1buf;
1167 for (s = tzbase; *s != '"'; s++)
1168 *z++ = *(s += *s == '\\');
1170 setenv_ok = setenv ("TZ", tz1, 1) == 0;
1175 tz_was_altered = true;
1181 pc.year.value = tmp->tm_year;
1182 pc.year.value += TM_YEAR_BASE;
1184 pc.month = tmp->tm_mon + 1;
1185 pc.day = tmp->tm_mday;
1186 pc.hour = tmp->tm_hour;
1187 pc.minutes = tmp->tm_min;
1188 pc.seconds.tv_sec = tmp->tm_sec;
1189 pc.seconds.tv_nsec = Start_ns;
1190 tm.tm_isdst = tmp->tm_isdst;
1192 pc.meridian = MER24;
1200 pc.timespec_seen = false;
1205 pc.local_zones_seen = 0;
1208 #if HAVE_STRUCT_TM_TM_ZONE
1209 pc.local_time_zone_table[0].name = tmp->tm_zone;
1210 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1211 pc.local_time_zone_table[0].value = tmp->tm_isdst;
1212 pc.local_time_zone_table[1].name = NULL;
1214 /* Probe the names used in the next three calendar quarters, looking
1215 for a tm_isdst different from the one we already have. */
1218 for (quarter = 1; quarter <= 3; quarter++)
1220 time_t probe = Start + quarter * (90 * 24 * 60 * 60);
1221 struct tm const *probe_tm = localtime (&probe);
1222 if (probe_tm && probe_tm->tm_zone
1223 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
1226 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
1227 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1228 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
1229 pc.local_time_zone_table[2].name = NULL;
1239 extern char *tzname[];
1242 for (i = 0; i < 2; i++)
1244 pc.local_time_zone_table[i].name = tzname[i];
1245 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1246 pc.local_time_zone_table[i].value = i;
1248 pc.local_time_zone_table[i].name = NULL;
1251 pc.local_time_zone_table[0].name = NULL;
1255 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1256 && ! strcmp (pc.local_time_zone_table[0].name,
1257 pc.local_time_zone_table[1].name))
1259 /* This locale uses the same abbrevation for standard and
1260 daylight times. So if we see that abbreviation, we don't
1261 know whether it's daylight time. */
1262 pc.local_time_zone_table[0].value = -1;
1263 pc.local_time_zone_table[1].name = NULL;
1266 if (yyparse (&pc) != 0)
1269 if (pc.timespec_seen)
1270 *result = pc.seconds;
1273 if (1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen
1274 || 1 < (pc.local_zones_seen + pc.zones_seen)
1275 || (pc.local_zones_seen && 1 < pc.local_isdst))
1278 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
1279 tm.tm_mon = pc.month - 1;
1280 tm.tm_mday = pc.day;
1281 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1283 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1286 tm.tm_min = pc.minutes;
1287 tm.tm_sec = pc.seconds.tv_sec;
1291 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1292 pc.seconds.tv_nsec = 0;
1295 /* Let mktime deduce tm_isdst if we have an absolute time stamp. */
1296 if (pc.dates_seen | pc.days_seen | pc.times_seen)
1299 /* But if the input explicitly specifies local time with or without
1300 DST, give mktime that information. */
1301 if (pc.local_zones_seen)
1302 tm.tm_isdst = pc.local_isdst;
1306 Start = mktime (&tm);
1308 if (! mktime_ok (&tm0, &tm, Start))
1310 if (! pc.zones_seen)
1314 /* Guard against falsely reporting errors near the time_t
1315 boundaries when parsing times in other time zones. For
1316 example, suppose the input string "1969-12-31 23:00:00 -0100",
1317 the current time zone is 8 hours ahead of UTC, and the min
1318 time_t value is 1970-01-01 00:00:00 UTC. Then the min
1319 localtime value is 1970-01-01 08:00:00, and mktime will
1320 therefore fail on 1969-12-31 23:00:00. To work around the
1321 problem, set the time zone to 1 hour behind UTC temporarily
1322 by setting TZ="XXX1:00" and try mktime again. */
1324 long int time_zone = pc.time_zone;
1325 long int abs_time_zone = time_zone < 0 ? - time_zone : time_zone;
1326 long int abs_time_zone_hour = abs_time_zone / 60;
1327 int abs_time_zone_min = abs_time_zone % 60;
1328 char tz1buf[sizeof "XXX+0:00"
1329 + sizeof pc.time_zone * CHAR_BIT / 3];
1330 if (!tz_was_altered)
1331 tz0 = get_tz (tz0buf);
1332 sprintf (tz1buf, "XXX%s%ld:%02d", "-" + (time_zone < 0),
1333 abs_time_zone_hour, abs_time_zone_min);
1334 if (setenv ("TZ", tz1buf, 1) != 0)
1336 tz_was_altered = true;
1338 Start = mktime (&tm);
1339 if (! mktime_ok (&tm0, &tm, Start))
1344 if (pc.days_seen && ! pc.dates_seen)
1346 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1347 + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
1349 Start = mktime (&tm);
1350 if (Start == (time_t) -1)
1356 long int delta = pc.time_zone * 60;
1358 #ifdef HAVE_TM_GMTOFF
1359 delta -= tm.tm_gmtoff;
1362 struct tm const *gmt = gmtime (&t);
1365 delta -= tm_diff (&tm, gmt);
1368 if ((Start < t1) != (delta < 0))
1369 goto fail; /* time_t overflow */
1373 /* Add relative date. */
1374 if (pc.rel_year | pc.rel_month | pc.rel_day)
1376 int year = tm.tm_year + pc.rel_year;
1377 int month = tm.tm_mon + pc.rel_month;
1378 int day = tm.tm_mday + pc.rel_day;
1379 if (((year < tm.tm_year) ^ (pc.rel_year < 0))
1380 | ((month < tm.tm_mon) ^ (pc.rel_month < 0))
1381 | ((day < tm.tm_mday) ^ (pc.rel_day < 0)))
1386 Start = mktime (&tm);
1387 if (Start == (time_t) -1)
1391 /* Add relative hours, minutes, and seconds. On hosts that support
1392 leap seconds, ignore the possibility of leap seconds; e.g.,
1393 "+ 10 minutes" adds 600 seconds, even if one of them is a
1394 leap second. Typically this is not what the user wants, but it's
1395 too hard to do it the other way, because the time zone indicator
1396 must be applied before relative times, and if mktime is applied
1397 again the time zone will be lost. */
1399 long int sum_ns = pc.seconds.tv_nsec + pc.rel_ns;
1400 long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
1402 long int d1 = 60 * 60 * pc.rel_hour;
1403 time_t t1 = t0 + d1;
1404 long int d2 = 60 * pc.rel_minutes;
1405 time_t t2 = t1 + d2;
1406 long int d3 = pc.rel_seconds;
1407 time_t t3 = t2 + d3;
1408 long int d4 = (sum_ns - normalized_ns) / BILLION;
1409 time_t t4 = t3 + d4;
1411 if ((d1 / (60 * 60) ^ pc.rel_hour)
1412 | (d2 / 60 ^ pc.rel_minutes)
1413 | ((t1 < t0) ^ (d1 < 0))
1414 | ((t2 < t1) ^ (d2 < 0))
1415 | ((t3 < t2) ^ (d3 < 0))
1416 | ((t4 < t3) ^ (d4 < 0)))
1419 result->tv_sec = t4;
1420 result->tv_nsec = normalized_ns;
1430 ok &= (tz0 ? setenv ("TZ", tz0, 1) : unsetenv ("TZ")) == 0;
1439 main (int ac, char **av)
1443 printf ("Enter date, or blank line to exit.\n\t> ");
1446 buff[BUFSIZ - 1] = '\0';
1447 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1450 struct tm const *tm;
1451 if (! get_date (&d, buff, NULL))
1452 printf ("Bad format - couldn't convert.\n");
1453 else if (! (tm = localtime (&d.tv_sec)))
1455 long int sec = d.tv_sec;
1456 printf ("localtime (%ld) failed\n", sec);
1461 printf ("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1462 tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
1463 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);