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
41 /* Since the code of getdate.y is not included in the Emacs executable
42 itself, there is no need to #define static in this file. Even if
43 the code were included in the Emacs executable, it probably
44 wouldn't do any harm to #undef it here; this will only cause
45 problems if we try to write to a static variable, which I don't
46 think this code needs to do. */
60 #if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII)
61 # define IN_CTYPE_DOMAIN(c) 1
63 # define IN_CTYPE_DOMAIN(c) isascii (c)
66 #define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c))
67 #define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c))
68 #define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c))
70 /* ISDIGIT differs from isdigit, as follows:
71 - Its arg may be any int or unsigned int; it need not be an unsigned char.
72 - It's guaranteed to evaluate its argument exactly once.
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)
79 #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
80 # define __attribute__(x)
83 #ifndef ATTRIBUTE_UNUSED
84 # define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
87 /* Shift A right by B bits portably, by dividing A by 2**B and
88 truncating towards minus infinity. A and B should be free of side
89 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
90 INT_BITS is the number of useful bits in an int. GNU code can
91 assume that INT_BITS is at least 32.
93 ISO C99 says that A >> B is implementation-defined if A < 0. Some
94 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
95 right in the usual way when A < 0, so SHR falls back on division if
96 ordinary A >> B doesn't seem to be the usual signed shift. */
100 : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
102 #define EPOCH_YEAR 1970
103 #define TM_YEAR_BASE 1900
105 #define HOUR(x) ((x) * 60)
107 /* An integer value, and the number of digits in its textual
116 /* An entry in the lexical lookup table. */
124 /* Meridian: am, pm, or 24-hour style. */
125 enum { MERam, MERpm, MER24 };
127 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
129 /* Information passed to and from the parser. */
132 /* The input string remaining to be parsed. */
135 /* N, if this is the Nth Tuesday. */
136 long int day_ordinal;
138 /* Day of week; Sunday is 0. */
141 /* tm_isdst flag for the local zone. */
144 /* Time zone, in minutes east of UTC. */
147 /* Style used for time. */
150 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
156 struct timespec seconds; /* includes nanoseconds */
158 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
163 long int rel_minutes;
164 long int rel_seconds;
167 /* Counts of nonterminals of various flavors parsed so far. */
171 size_t local_zones_seen;
176 /* Table of local time zone abbrevations, terminated by a null entry. */
177 table local_time_zone_table[3];
181 static int yylex (union YYSTYPE *, parser_control *);
182 static int yyerror (parser_control *, char *);
183 static long int time_zone_hhmm (textint, long int);
187 /* We want a reentrant parser, even if the TZ manipulation and the calls to
188 localtime and gmtime are not reentrant. */
190 %parse-param { parser_control *pc }
191 %lex-param { parser_control *pc }
193 /* This grammar has 14 shift/reduce conflicts. */
200 struct timespec timespec;
205 %token <intval> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN
206 %token <intval> tMINUTE_UNIT tMONTH tMONTH_UNIT tORDINAL
207 %token <intval> tSEC_UNIT tYEAR_UNIT tZONE
209 %token <textintval> tSNUMBER tUNUMBER
210 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
212 %type <intval> o_colon_minutes o_merid
213 %type <timespec> seconds signed_seconds unsigned_seconds
226 pc->timespec_seen = true;
237 { pc->times_seen++; }
239 { pc->local_zones_seen++; }
241 { pc->zones_seen++; }
243 { pc->dates_seen++; }
256 pc->seconds.tv_sec = 0;
257 pc->seconds.tv_nsec = 0;
260 | tUNUMBER ':' tUNUMBER o_merid
263 pc->minutes = $3.value;
264 pc->seconds.tv_sec = 0;
265 pc->seconds.tv_nsec = 0;
268 | tUNUMBER ':' tUNUMBER tSNUMBER o_colon_minutes
271 pc->minutes = $3.value;
272 pc->seconds.tv_sec = 0;
273 pc->seconds.tv_nsec = 0;
274 pc->meridian = MER24;
276 pc->time_zone = time_zone_hhmm ($4, $5);
278 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
281 pc->minutes = $3.value;
285 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER o_colon_minutes
288 pc->minutes = $3.value;
290 pc->meridian = MER24;
292 pc->time_zone = time_zone_hhmm ($6, $7);
298 { pc->local_isdst = $1; }
300 { pc->local_isdst = $1 < 0 ? 1 : $1 + 1; }
305 { pc->time_zone = $1; }
306 | tZONE tSNUMBER o_colon_minutes
307 { pc->time_zone = $1 + time_zone_hhmm ($2, $3); }
309 { pc->time_zone = $1 + 60; }
311 { pc->time_zone = $1 + 60; }
327 pc->day_ordinal = $1;
332 pc->day_ordinal = $1.value;
338 tUNUMBER '/' tUNUMBER
340 pc->month = $1.value;
343 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
345 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
346 otherwise as MM/DD/YY.
347 The goal in recognizing YYYY/MM/DD is solely to support legacy
348 machine-generated dates like those in an RCS log listing. If
349 you want portability, use the ISO 8601 format. */
353 pc->month = $3.value;
358 pc->month = $1.value;
363 | tUNUMBER tSNUMBER tSNUMBER
365 /* ISO 8601 format. YYYY-MM-DD. */
367 pc->month = -$2.value;
370 | tUNUMBER tMONTH tSNUMBER
372 /* e.g. 17-JUN-1992. */
375 pc->year.value = -$3.value;
376 pc->year.digits = $3.digits;
378 | tMONTH tSNUMBER tSNUMBER
380 /* e.g. JUN-17-1992. */
383 pc->year.value = -$3.value;
384 pc->year.digits = $3.digits;
391 | tMONTH tUNUMBER ',' tUNUMBER
402 | tUNUMBER tMONTH tUNUMBER
413 pc->rel_ns = -pc->rel_ns;
414 pc->rel_seconds = -pc->rel_seconds;
415 pc->rel_minutes = -pc->rel_minutes;
416 pc->rel_hour = -pc->rel_hour;
417 pc->rel_day = -pc->rel_day;
418 pc->rel_month = -pc->rel_month;
419 pc->rel_year = -pc->rel_year;
426 { pc->rel_year += $1 * $2; }
427 | tUNUMBER tYEAR_UNIT
428 { pc->rel_year += $1.value * $2; }
429 | tSNUMBER tYEAR_UNIT
430 { pc->rel_year += $1.value * $2; }
432 { pc->rel_year += $1; }
433 | tORDINAL tMONTH_UNIT
434 { pc->rel_month += $1 * $2; }
435 | tUNUMBER tMONTH_UNIT
436 { pc->rel_month += $1.value * $2; }
437 | tSNUMBER tMONTH_UNIT
438 { pc->rel_month += $1.value * $2; }
440 { pc->rel_month += $1; }
442 { pc->rel_day += $1 * $2; }
444 { pc->rel_day += $1.value * $2; }
446 { pc->rel_day += $1.value * $2; }
448 { pc->rel_day += $1; }
449 | tORDINAL tHOUR_UNIT
450 { pc->rel_hour += $1 * $2; }
451 | tUNUMBER tHOUR_UNIT
452 { pc->rel_hour += $1.value * $2; }
453 | tSNUMBER tHOUR_UNIT
454 { pc->rel_hour += $1.value * $2; }
456 { pc->rel_hour += $1; }
457 | tORDINAL tMINUTE_UNIT
458 { pc->rel_minutes += $1 * $2; }
459 | tUNUMBER tMINUTE_UNIT
460 { pc->rel_minutes += $1.value * $2; }
461 | tSNUMBER tMINUTE_UNIT
462 { pc->rel_minutes += $1.value * $2; }
464 { pc->rel_minutes += $1; }
466 { pc->rel_seconds += $1 * $2; }
468 { pc->rel_seconds += $1.value * $2; }
470 { pc->rel_seconds += $1.value * $2; }
471 | tSDECIMAL_NUMBER tSEC_UNIT
472 { pc->rel_seconds += $1.tv_sec * $2; pc->rel_ns += $1.tv_nsec * $2; }
473 | tUDECIMAL_NUMBER tSEC_UNIT
474 { pc->rel_seconds += $1.tv_sec * $2; pc->rel_ns += $1.tv_nsec * $2; }
476 { pc->rel_seconds += $1; }
479 seconds: signed_seconds | unsigned_seconds;
484 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
490 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
497 && ! pc->rels_seen && (pc->times_seen || 2 < $1.digits))
504 pc->day = $1.value % 100;
505 pc->month = ($1.value / 100) % 100;
506 pc->year.value = $1.value / 10000;
507 pc->year.digits = $1.digits - 4;
519 pc->hour = $1.value / 100;
520 pc->minutes = $1.value % 100;
522 pc->seconds.tv_sec = 0;
523 pc->seconds.tv_nsec = 0;
524 pc->meridian = MER24;
546 static table const meridian_table[] =
548 { "AM", tMERIDIAN, MERam },
549 { "A.M.", tMERIDIAN, MERam },
550 { "PM", tMERIDIAN, MERpm },
551 { "P.M.", tMERIDIAN, MERpm },
555 static table const dst_table[] =
560 static table const month_and_day_table[] =
562 { "JANUARY", tMONTH, 1 },
563 { "FEBRUARY", tMONTH, 2 },
564 { "MARCH", tMONTH, 3 },
565 { "APRIL", tMONTH, 4 },
566 { "MAY", tMONTH, 5 },
567 { "JUNE", tMONTH, 6 },
568 { "JULY", tMONTH, 7 },
569 { "AUGUST", tMONTH, 8 },
570 { "SEPTEMBER",tMONTH, 9 },
571 { "SEPT", tMONTH, 9 },
572 { "OCTOBER", tMONTH, 10 },
573 { "NOVEMBER", tMONTH, 11 },
574 { "DECEMBER", tMONTH, 12 },
575 { "SUNDAY", tDAY, 0 },
576 { "MONDAY", tDAY, 1 },
577 { "TUESDAY", tDAY, 2 },
579 { "WEDNESDAY",tDAY, 3 },
580 { "WEDNES", tDAY, 3 },
581 { "THURSDAY", tDAY, 4 },
583 { "THURS", tDAY, 4 },
584 { "FRIDAY", tDAY, 5 },
585 { "SATURDAY", tDAY, 6 },
589 static table const time_units_table[] =
591 { "YEAR", tYEAR_UNIT, 1 },
592 { "MONTH", tMONTH_UNIT, 1 },
593 { "FORTNIGHT",tDAY_UNIT, 14 },
594 { "WEEK", tDAY_UNIT, 7 },
595 { "DAY", tDAY_UNIT, 1 },
596 { "HOUR", tHOUR_UNIT, 1 },
597 { "MINUTE", tMINUTE_UNIT, 1 },
598 { "MIN", tMINUTE_UNIT, 1 },
599 { "SECOND", tSEC_UNIT, 1 },
600 { "SEC", tSEC_UNIT, 1 },
604 /* Assorted relative-time words. */
605 static table const relative_time_table[] =
607 { "TOMORROW", tDAY_UNIT, 1 },
608 { "YESTERDAY",tDAY_UNIT, -1 },
609 { "TODAY", tDAY_UNIT, 0 },
610 { "NOW", tDAY_UNIT, 0 },
611 { "LAST", tORDINAL, -1 },
612 { "THIS", tORDINAL, 0 },
613 { "NEXT", tORDINAL, 1 },
614 { "FIRST", tORDINAL, 1 },
615 /*{ "SECOND", tORDINAL, 2 }, */
616 { "THIRD", tORDINAL, 3 },
617 { "FOURTH", tORDINAL, 4 },
618 { "FIFTH", tORDINAL, 5 },
619 { "SIXTH", tORDINAL, 6 },
620 { "SEVENTH", tORDINAL, 7 },
621 { "EIGHTH", tORDINAL, 8 },
622 { "NINTH", tORDINAL, 9 },
623 { "TENTH", tORDINAL, 10 },
624 { "ELEVENTH", tORDINAL, 11 },
625 { "TWELFTH", tORDINAL, 12 },
630 /* The time zone table. This table is necessarily incomplete, as time
631 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
632 as Eastern time in Australia, not as US Eastern Standard Time.
633 You cannot rely on getdate to handle arbitrary time zone
634 abbreviations; use numeric abbreviations like `-0500' instead. */
635 static table const time_zone_table[] =
637 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
638 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
639 { "UTC", tZONE, HOUR ( 0) },
640 { "WET", tZONE, HOUR ( 0) }, /* Western European */
641 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
642 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
643 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
644 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
645 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
646 { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
647 { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
648 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
649 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
650 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
651 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
652 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
653 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
654 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
655 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
656 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
657 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
658 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
659 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
660 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
661 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
662 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
663 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
664 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
665 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
666 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
667 { "CET", tZONE, HOUR ( 1) }, /* Central European */
668 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
669 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
670 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
671 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
672 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
673 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
674 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
675 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
676 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
677 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
678 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
679 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
680 { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
681 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
682 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
683 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
684 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
685 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
686 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
690 /* Military time zone table. */
691 static table const military_table[] =
693 { "A", tZONE, -HOUR ( 1) },
694 { "B", tZONE, -HOUR ( 2) },
695 { "C", tZONE, -HOUR ( 3) },
696 { "D", tZONE, -HOUR ( 4) },
697 { "E", tZONE, -HOUR ( 5) },
698 { "F", tZONE, -HOUR ( 6) },
699 { "G", tZONE, -HOUR ( 7) },
700 { "H", tZONE, -HOUR ( 8) },
701 { "I", tZONE, -HOUR ( 9) },
702 { "K", tZONE, -HOUR (10) },
703 { "L", tZONE, -HOUR (11) },
704 { "M", tZONE, -HOUR (12) },
705 { "N", tZONE, HOUR ( 1) },
706 { "O", tZONE, HOUR ( 2) },
707 { "P", tZONE, HOUR ( 3) },
708 { "Q", tZONE, HOUR ( 4) },
709 { "R", tZONE, HOUR ( 5) },
710 { "S", tZONE, HOUR ( 6) },
711 { "T", tZONE, HOUR ( 7) },
712 { "U", tZONE, HOUR ( 8) },
713 { "V", tZONE, HOUR ( 9) },
714 { "W", tZONE, HOUR (10) },
715 { "X", tZONE, HOUR (11) },
716 { "Y", tZONE, HOUR (12) },
717 { "Z", tZONE, HOUR ( 0) },
723 /* Convert a time zone expressed as HH:MM into an integer count of
724 minutes. If MM is negative, then S is of the form HHMM and needs
725 to be picked apart; otherwise, S is of the form HH. */
728 time_zone_hhmm (textint s, long int mm)
731 return (s.value / 100) * 60 + s.value % 100;
733 return s.value * 60 + (s.negative ? -mm : mm);
737 to_hour (long int hours, int meridian)
741 default: /* Pacify GCC. */
743 return 0 <= hours && hours < 24 ? hours : -1;
745 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
747 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
752 to_year (textint textyear)
754 long int year = textyear.value;
759 /* XPG4 suggests that years 00-68 map to 2000-2068, and
760 years 69-99 map to 1969-1999. */
761 else if (textyear.digits == 2)
762 year += year < 69 ? 2000 : 1900;
768 lookup_zone (parser_control const *pc, char const *name)
772 /* Try local zone abbreviations first; they're more likely to be right. */
773 for (tp = pc->local_time_zone_table; tp->name; tp++)
774 if (strcmp (name, tp->name) == 0)
777 for (tp = time_zone_table; tp->name; tp++)
778 if (strcmp (name, tp->name) == 0)
785 /* Yield the difference between *A and *B,
786 measured in seconds, ignoring leap seconds.
787 The body of this function is taken directly from the GNU C Library;
788 see src/strftime.c. */
790 tm_diff (struct tm const *a, struct tm const *b)
792 /* Compute intervening leap days correctly even if year is negative.
793 Take care to avoid int overflow in leap day calculations. */
794 int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
795 int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
796 int a100 = a4 / 25 - (a4 % 25 < 0);
797 int b100 = b4 / 25 - (b4 % 25 < 0);
798 int a400 = SHR (a100, 2);
799 int b400 = SHR (b100, 2);
800 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
801 long int ayear = a->tm_year;
802 long int years = ayear - b->tm_year;
803 long int days = (365 * years + intervening_leap_days
804 + (a->tm_yday - b->tm_yday));
805 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
806 + (a->tm_min - b->tm_min))
807 + (a->tm_sec - b->tm_sec));
809 #endif /* ! HAVE_TM_GMTOFF */
812 lookup_word (parser_control const *pc, char *word)
821 /* Make it uppercase. */
822 for (p = word; *p; p++)
824 unsigned char ch = *p;
829 for (tp = meridian_table; tp->name; tp++)
830 if (strcmp (word, tp->name) == 0)
833 /* See if we have an abbreviation for a month. */
834 wordlen = strlen (word);
835 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
837 for (tp = month_and_day_table; tp->name; tp++)
838 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
841 if ((tp = lookup_zone (pc, word)))
844 if (strcmp (word, dst_table[0].name) == 0)
847 for (tp = time_units_table; tp->name; tp++)
848 if (strcmp (word, tp->name) == 0)
851 /* Strip off any plural and try the units table again. */
852 if (word[wordlen - 1] == 'S')
854 word[wordlen - 1] = '\0';
855 for (tp = time_units_table; tp->name; tp++)
856 if (strcmp (word, tp->name) == 0)
858 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
861 for (tp = relative_time_table; tp->name; tp++)
862 if (strcmp (word, tp->name) == 0)
865 /* Military time zones. */
867 for (tp = military_table; tp->name; tp++)
868 if (word[0] == tp->name[0])
871 /* Drop out any periods and try the time zone table again. */
872 for (period_found = false, p = q = word; (*p = *q); q++)
877 if (period_found && (tp = lookup_zone (pc, word)))
884 yylex (YYSTYPE *lvalp, parser_control *pc)
891 while (c = *pc->input, ISSPACE (c))
894 if (ISDIGIT (c) || c == '-' || c == '+')
898 unsigned long int value;
899 if (c == '-' || c == '+')
901 sign = c == '-' ? -1 : 1;
902 while (c = *++pc->input, ISSPACE (c))
905 /* skip the '-' sign */
911 for (value = 0; ; value *= 10)
913 unsigned long int value1 = value + (c - '0');
920 if (ULONG_MAX / 10 < value)
923 if ((c == '.' || c == ',') && ISDIGIT (p[1]))
928 unsigned long int value1;
930 /* Check for overflow when converting value to time_t. */
948 /* Accumulate fraction, to ns precision. */
951 for (digits = 2; digits <= LOG10_BILLION; digits++)
958 /* Skip excess digits, truncating toward -Infinity. */
960 for (; ISDIGIT (*p); p++)
969 /* Adjust to the timespec convention, which is that
970 tv_nsec is always a positive offset even if tv_sec is
980 lvalp->timespec.tv_sec = s;
981 lvalp->timespec.tv_nsec = ns;
983 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
987 lvalp->textintval.negative = sign < 0;
990 lvalp->textintval.value = - value;
991 if (0 < lvalp->textintval.value)
996 lvalp->textintval.value = value;
997 if (lvalp->textintval.value < 0)
1000 lvalp->textintval.digits = p - pc->input;
1002 return sign ? tSNUMBER : tUNUMBER;
1014 if (p < buff + sizeof buff - 1)
1018 while (ISALPHA (c) || c == '.');
1021 tp = lookup_word (pc, buff);
1024 lvalp->intval = tp->value;
1029 return *pc->input++;
1045 /* Do nothing if the parser reports an error. */
1047 yyerror (parser_control *pc ATTRIBUTE_UNUSED, char *s ATTRIBUTE_UNUSED)
1052 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1053 passing it to mktime, return true if it's OK that mktime returned T.
1054 It's not OK if *TM0 has out-of-range members. */
1057 mktime_ok (struct tm const *tm0, struct tm const *tm1, time_t t)
1059 if (t == (time_t) -1)
1061 /* Guard against falsely reporting an error when parsing a time
1062 stamp that happens to equal (time_t) -1, on a host that
1063 supports such a time stamp. */
1064 tm1 = localtime (&t);
1069 return ! ((tm0->tm_sec ^ tm1->tm_sec)
1070 | (tm0->tm_min ^ tm1->tm_min)
1071 | (tm0->tm_hour ^ tm1->tm_hour)
1072 | (tm0->tm_mday ^ tm1->tm_mday)
1073 | (tm0->tm_mon ^ tm1->tm_mon)
1074 | (tm0->tm_year ^ tm1->tm_year));
1077 /* A reasonable upper bound for the size of ordinary TZ strings.
1078 Use heap allocation if TZ's length exceeds this. */
1079 enum { TZBUFSIZE = 100 };
1081 /* Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
1084 get_tz (char tzbuf[TZBUFSIZE])
1086 char *tz = getenv ("TZ");
1089 size_t tzsize = strlen (tz) + 1;
1090 tz = (tzsize <= TZBUFSIZE
1091 ? memcpy (tzbuf, tz, tzsize)
1092 : xmemdup (tz, tzsize));
1097 /* Parse a date/time string, storing the resulting time value into *RESULT.
1098 The string itself is pointed to by P. Return true if successful.
1099 P can be an incomplete or relative time specification; if so, use
1100 *NOW as the basis for the returned time. */
1102 get_date (struct timespec *result, char const *p, struct timespec const *now)
1106 struct tm const *tmp;
1110 struct timespec gettime_buffer;
1112 bool tz_was_altered = false;
1114 char tz0buf[TZBUFSIZE];
1119 if (gettime (&gettime_buffer) != 0)
1121 now = &gettime_buffer;
1124 Start = now->tv_sec;
1125 Start_ns = now->tv_nsec;
1127 tmp = localtime (&now->tv_sec);
1131 while (c = *p, ISSPACE (c))
1134 if (strncmp (p, "TZ=\"", 4) == 0)
1136 char const *tzbase = p + 4;
1140 for (s = tzbase; *s; s++, tzsize++)
1144 if (! (*s == '\\' || *s == '"'))
1151 char tz1buf[TZBUFSIZE];
1152 bool large_tz = TZBUFSIZE < tzsize;
1154 tz0 = get_tz (tz0buf);
1155 z = tz1 = large_tz ? xmalloc (tzsize) : tz1buf;
1156 for (s = tzbase; *s != '"'; s++)
1157 *z++ = *(s += *s == '\\');
1159 setenv_ok = setenv ("TZ", tz1, 1) == 0;
1164 tz_was_altered = true;
1170 pc.year.value = tmp->tm_year;
1171 pc.year.value += TM_YEAR_BASE;
1173 pc.month = tmp->tm_mon + 1;
1174 pc.day = tmp->tm_mday;
1175 pc.hour = tmp->tm_hour;
1176 pc.minutes = tmp->tm_min;
1177 pc.seconds.tv_sec = tmp->tm_sec;
1178 pc.seconds.tv_nsec = Start_ns;
1179 tm.tm_isdst = tmp->tm_isdst;
1181 pc.meridian = MER24;
1189 pc.timespec_seen = false;
1194 pc.local_zones_seen = 0;
1197 #if HAVE_STRUCT_TM_TM_ZONE
1198 pc.local_time_zone_table[0].name = tmp->tm_zone;
1199 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1200 pc.local_time_zone_table[0].value = tmp->tm_isdst;
1201 pc.local_time_zone_table[1].name = NULL;
1203 /* Probe the names used in the next three calendar quarters, looking
1204 for a tm_isdst different from the one we already have. */
1207 for (quarter = 1; quarter <= 3; quarter++)
1209 time_t probe = Start + quarter * (90 * 24 * 60 * 60);
1210 struct tm const *probe_tm = localtime (&probe);
1211 if (probe_tm && probe_tm->tm_zone
1212 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
1215 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
1216 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1217 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
1218 pc.local_time_zone_table[2].name = NULL;
1228 extern char *tzname[];
1231 for (i = 0; i < 2; i++)
1233 pc.local_time_zone_table[i].name = tzname[i];
1234 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1235 pc.local_time_zone_table[i].value = i;
1237 pc.local_time_zone_table[i].name = NULL;
1240 pc.local_time_zone_table[0].name = NULL;
1244 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1245 && ! strcmp (pc.local_time_zone_table[0].name,
1246 pc.local_time_zone_table[1].name))
1248 /* This locale uses the same abbrevation for standard and
1249 daylight times. So if we see that abbreviation, we don't
1250 know whether it's daylight time. */
1251 pc.local_time_zone_table[0].value = -1;
1252 pc.local_time_zone_table[1].name = NULL;
1255 if (yyparse (&pc) != 0)
1258 if (pc.timespec_seen)
1259 *result = pc.seconds;
1262 if (1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen
1263 || 1 < (pc.local_zones_seen + pc.zones_seen)
1264 || (pc.local_zones_seen && 1 < pc.local_isdst))
1267 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
1268 tm.tm_mon = pc.month - 1;
1269 tm.tm_mday = pc.day;
1270 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1272 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1275 tm.tm_min = pc.minutes;
1276 tm.tm_sec = pc.seconds.tv_sec;
1280 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1281 pc.seconds.tv_nsec = 0;
1284 /* Let mktime deduce tm_isdst if we have an absolute time stamp. */
1285 if (pc.dates_seen | pc.days_seen | pc.times_seen)
1288 /* But if the input explicitly specifies local time with or without
1289 DST, give mktime that information. */
1290 if (pc.local_zones_seen)
1291 tm.tm_isdst = pc.local_isdst;
1295 Start = mktime (&tm);
1297 if (! mktime_ok (&tm0, &tm, Start))
1299 if (! pc.zones_seen)
1303 /* Guard against falsely reporting errors near the time_t
1304 boundaries when parsing times in other time zones. For
1305 example, suppose the input string "1969-12-31 23:00:00 -0100",
1306 the current time zone is 8 hours ahead of UTC, and the min
1307 time_t value is 1970-01-01 00:00:00 UTC. Then the min
1308 localtime value is 1970-01-01 08:00:00, and mktime will
1309 therefore fail on 1969-12-31 23:00:00. To work around the
1310 problem, set the time zone to 1 hour behind UTC temporarily
1311 by setting TZ="XXX1:00" and try mktime again. */
1313 long int time_zone = pc.time_zone;
1314 long int abs_time_zone = time_zone < 0 ? - time_zone : time_zone;
1315 long int abs_time_zone_hour = abs_time_zone / 60;
1316 int abs_time_zone_min = abs_time_zone % 60;
1317 char tz1buf[sizeof "XXX+0:00"
1318 + sizeof pc.time_zone * CHAR_BIT / 3];
1319 if (!tz_was_altered)
1320 tz0 = get_tz (tz0buf);
1321 sprintf (tz1buf, "XXX%s%ld:%02d", "-" + (time_zone < 0),
1322 abs_time_zone_hour, abs_time_zone_min);
1323 if (setenv ("TZ", tz1buf, 1) != 0)
1325 tz_was_altered = true;
1327 Start = mktime (&tm);
1328 if (! mktime_ok (&tm0, &tm, Start))
1333 if (pc.days_seen && ! pc.dates_seen)
1335 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1336 + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
1338 Start = mktime (&tm);
1339 if (Start == (time_t) -1)
1345 long int delta = pc.time_zone * 60;
1347 #ifdef HAVE_TM_GMTOFF
1348 delta -= tm.tm_gmtoff;
1351 struct tm const *gmt = gmtime (&t);
1354 delta -= tm_diff (&tm, gmt);
1357 if ((Start < t1) != (delta < 0))
1358 goto fail; /* time_t overflow */
1362 /* Add relative date. */
1363 if (pc.rel_year | pc.rel_month | pc.rel_day)
1365 int year = tm.tm_year + pc.rel_year;
1366 int month = tm.tm_mon + pc.rel_month;
1367 int day = tm.tm_mday + pc.rel_day;
1368 if (((year < tm.tm_year) ^ (pc.rel_year < 0))
1369 | ((month < tm.tm_mon) ^ (pc.rel_month < 0))
1370 | ((day < tm.tm_mday) ^ (pc.rel_day < 0)))
1375 Start = mktime (&tm);
1376 if (Start == (time_t) -1)
1380 /* Add relative hours, minutes, and seconds. On hosts that support
1381 leap seconds, ignore the possibility of leap seconds; e.g.,
1382 "+ 10 minutes" adds 600 seconds, even if one of them is a
1383 leap second. Typically this is not what the user wants, but it's
1384 too hard to do it the other way, because the time zone indicator
1385 must be applied before relative times, and if mktime is applied
1386 again the time zone will be lost. */
1388 long int sum_ns = pc.seconds.tv_nsec + pc.rel_ns;
1389 long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
1391 long int d1 = 60 * 60 * pc.rel_hour;
1392 time_t t1 = t0 + d1;
1393 long int d2 = 60 * pc.rel_minutes;
1394 time_t t2 = t1 + d2;
1395 long int d3 = pc.rel_seconds;
1396 time_t t3 = t2 + d3;
1397 long int d4 = (sum_ns - normalized_ns) / BILLION;
1398 time_t t4 = t3 + d4;
1400 if ((d1 / (60 * 60) ^ pc.rel_hour)
1401 | (d2 / 60 ^ pc.rel_minutes)
1402 | ((t1 < t0) ^ (d1 < 0))
1403 | ((t2 < t1) ^ (d2 < 0))
1404 | ((t3 < t2) ^ (d3 < 0))
1405 | ((t4 < t3) ^ (d4 < 0)))
1408 result->tv_sec = t4;
1409 result->tv_nsec = normalized_ns;
1419 ok &= (tz0 ? setenv ("TZ", tz0, 1) : unsetenv ("TZ")) == 0;
1428 main (int ac, char **av)
1432 printf ("Enter date, or blank line to exit.\n\t> ");
1435 buff[BUFSIZ - 1] = '\0';
1436 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1439 struct tm const *tm;
1440 if (! get_date (&d, buff, NULL))
1441 printf ("Bad format - couldn't convert.\n");
1442 else if (! (tm = localtime (&d.tv_sec)))
1444 long int sec = d.tv_sec;
1445 printf ("localtime (%ld) failed\n", sec);
1450 printf ("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1451 tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
1452 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);