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
115 /* An entry in the lexical lookup table. */
123 /* Meridian: am, pm, or 24-hour style. */
124 enum { MERam, MERpm, MER24 };
126 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
128 /* Information passed to and from the parser. */
131 /* The input string remaining to be parsed. */
134 /* N, if this is the Nth Tuesday. */
135 long int day_ordinal;
137 /* Day of week; Sunday is 0. */
140 /* tm_isdst flag for the local zone. */
143 /* Time zone, in minutes east of UTC. */
146 /* Style used for time. */
149 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
155 struct timespec seconds; /* includes nanoseconds */
157 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
162 long int rel_minutes;
163 long int rel_seconds;
166 /* Counts of nonterminals of various flavors parsed so far. */
170 size_t local_zones_seen;
175 /* Table of local time zone abbrevations, terminated by a null entry. */
176 table local_time_zone_table[3];
180 static int yylex (union YYSTYPE *, parser_control *);
181 static int yyerror (parser_control *, char *);
185 /* We want a reentrant parser, even if the TZ manipulation and the calls to
186 localtime and gmtime are not reentrant. */
188 %parse-param { parser_control *pc }
189 %lex-param { parser_control *pc }
191 /* This grammar has 13 shift/reduce conflicts. */
198 struct timespec timespec;
203 %token <intval> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN
204 %token <intval> tMINUTE_UNIT tMONTH tMONTH_UNIT tORDINAL
205 %token <intval> tSEC_UNIT tYEAR_UNIT tZONE
207 %token <textintval> tSNUMBER tUNUMBER
208 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
210 %type <intval> o_merid
211 %type <timespec> seconds signed_seconds unsigned_seconds
224 pc->timespec_seen = true;
235 { pc->times_seen++; }
237 { pc->local_zones_seen++; }
239 { pc->zones_seen++; }
241 { pc->dates_seen++; }
254 pc->seconds.tv_sec = 0;
255 pc->seconds.tv_nsec = 0;
258 | tUNUMBER ':' tUNUMBER o_merid
261 pc->minutes = $3.value;
262 pc->seconds.tv_sec = 0;
263 pc->seconds.tv_nsec = 0;
266 | tUNUMBER ':' tUNUMBER tSNUMBER
269 pc->minutes = $3.value;
270 pc->seconds.tv_sec = 0;
271 pc->seconds.tv_nsec = 0;
272 pc->meridian = MER24;
274 pc->time_zone = $4.value % 100 + ($4.value / 100) * 60;
276 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
279 pc->minutes = $3.value;
283 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER
286 pc->minutes = $3.value;
288 pc->meridian = MER24;
290 pc->time_zone = $6.value % 100 + ($6.value / 100) * 60;
296 { pc->local_isdst = $1; }
298 { pc->local_isdst = $1 < 0 ? 1 : $1 + 1; }
303 { pc->time_zone = $1; }
305 { pc->time_zone = $1 + 60; }
307 { pc->time_zone = $1 + 60; }
323 pc->day_ordinal = $1;
328 pc->day_ordinal = $1.value;
334 tUNUMBER '/' tUNUMBER
336 pc->month = $1.value;
339 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
341 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
342 otherwise as MM/DD/YY.
343 The goal in recognizing YYYY/MM/DD is solely to support legacy
344 machine-generated dates like those in an RCS log listing. If
345 you want portability, use the ISO 8601 format. */
349 pc->month = $3.value;
354 pc->month = $1.value;
359 | tUNUMBER tSNUMBER tSNUMBER
361 /* ISO 8601 format. YYYY-MM-DD. */
363 pc->month = -$2.value;
366 | tUNUMBER tMONTH tSNUMBER
368 /* e.g. 17-JUN-1992. */
371 pc->year.value = -$3.value;
372 pc->year.digits = $3.digits;
374 | tMONTH tSNUMBER tSNUMBER
376 /* e.g. JUN-17-1992. */
379 pc->year.value = -$3.value;
380 pc->year.digits = $3.digits;
387 | tMONTH tUNUMBER ',' tUNUMBER
398 | tUNUMBER tMONTH tUNUMBER
409 pc->rel_ns = -pc->rel_ns;
410 pc->rel_seconds = -pc->rel_seconds;
411 pc->rel_minutes = -pc->rel_minutes;
412 pc->rel_hour = -pc->rel_hour;
413 pc->rel_day = -pc->rel_day;
414 pc->rel_month = -pc->rel_month;
415 pc->rel_year = -pc->rel_year;
422 { pc->rel_year += $1 * $2; }
423 | tUNUMBER tYEAR_UNIT
424 { pc->rel_year += $1.value * $2; }
425 | tSNUMBER tYEAR_UNIT
426 { pc->rel_year += $1.value * $2; }
428 { pc->rel_year += $1; }
429 | tORDINAL tMONTH_UNIT
430 { pc->rel_month += $1 * $2; }
431 | tUNUMBER tMONTH_UNIT
432 { pc->rel_month += $1.value * $2; }
433 | tSNUMBER tMONTH_UNIT
434 { pc->rel_month += $1.value * $2; }
436 { pc->rel_month += $1; }
438 { pc->rel_day += $1 * $2; }
440 { pc->rel_day += $1.value * $2; }
442 { pc->rel_day += $1.value * $2; }
444 { pc->rel_day += $1; }
445 | tORDINAL tHOUR_UNIT
446 { pc->rel_hour += $1 * $2; }
447 | tUNUMBER tHOUR_UNIT
448 { pc->rel_hour += $1.value * $2; }
449 | tSNUMBER tHOUR_UNIT
450 { pc->rel_hour += $1.value * $2; }
452 { pc->rel_hour += $1; }
453 | tORDINAL tMINUTE_UNIT
454 { pc->rel_minutes += $1 * $2; }
455 | tUNUMBER tMINUTE_UNIT
456 { pc->rel_minutes += $1.value * $2; }
457 | tSNUMBER tMINUTE_UNIT
458 { pc->rel_minutes += $1.value * $2; }
460 { pc->rel_minutes += $1; }
462 { pc->rel_seconds += $1 * $2; }
464 { pc->rel_seconds += $1.value * $2; }
466 { pc->rel_seconds += $1.value * $2; }
467 | tSDECIMAL_NUMBER tSEC_UNIT
468 { pc->rel_seconds += $1.tv_sec * $2; pc->rel_ns += $1.tv_nsec * $2; }
469 | tUDECIMAL_NUMBER tSEC_UNIT
470 { pc->rel_seconds += $1.tv_sec * $2; pc->rel_ns += $1.tv_nsec * $2; }
472 { pc->rel_seconds += $1; }
475 seconds: signed_seconds | unsigned_seconds;
480 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
486 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
493 && ! pc->rels_seen && (pc->times_seen || 2 < $1.digits))
500 pc->day = $1.value % 100;
501 pc->month = ($1.value / 100) % 100;
502 pc->year.value = $1.value / 10000;
503 pc->year.digits = $1.digits - 4;
515 pc->hour = $1.value / 100;
516 pc->minutes = $1.value % 100;
518 pc->seconds.tv_sec = 0;
519 pc->seconds.tv_nsec = 0;
520 pc->meridian = MER24;
535 static table const meridian_table[] =
537 { "AM", tMERIDIAN, MERam },
538 { "A.M.", tMERIDIAN, MERam },
539 { "PM", tMERIDIAN, MERpm },
540 { "P.M.", tMERIDIAN, MERpm },
544 static table const dst_table[] =
549 static table const month_and_day_table[] =
551 { "JANUARY", tMONTH, 1 },
552 { "FEBRUARY", tMONTH, 2 },
553 { "MARCH", tMONTH, 3 },
554 { "APRIL", tMONTH, 4 },
555 { "MAY", tMONTH, 5 },
556 { "JUNE", tMONTH, 6 },
557 { "JULY", tMONTH, 7 },
558 { "AUGUST", tMONTH, 8 },
559 { "SEPTEMBER",tMONTH, 9 },
560 { "SEPT", tMONTH, 9 },
561 { "OCTOBER", tMONTH, 10 },
562 { "NOVEMBER", tMONTH, 11 },
563 { "DECEMBER", tMONTH, 12 },
564 { "SUNDAY", tDAY, 0 },
565 { "MONDAY", tDAY, 1 },
566 { "TUESDAY", tDAY, 2 },
568 { "WEDNESDAY",tDAY, 3 },
569 { "WEDNES", tDAY, 3 },
570 { "THURSDAY", tDAY, 4 },
572 { "THURS", tDAY, 4 },
573 { "FRIDAY", tDAY, 5 },
574 { "SATURDAY", tDAY, 6 },
578 static table const time_units_table[] =
580 { "YEAR", tYEAR_UNIT, 1 },
581 { "MONTH", tMONTH_UNIT, 1 },
582 { "FORTNIGHT",tDAY_UNIT, 14 },
583 { "WEEK", tDAY_UNIT, 7 },
584 { "DAY", tDAY_UNIT, 1 },
585 { "HOUR", tHOUR_UNIT, 1 },
586 { "MINUTE", tMINUTE_UNIT, 1 },
587 { "MIN", tMINUTE_UNIT, 1 },
588 { "SECOND", tSEC_UNIT, 1 },
589 { "SEC", tSEC_UNIT, 1 },
593 /* Assorted relative-time words. */
594 static table const relative_time_table[] =
596 { "TOMORROW", tDAY_UNIT, 1 },
597 { "YESTERDAY",tDAY_UNIT, -1 },
598 { "TODAY", tDAY_UNIT, 0 },
599 { "NOW", tDAY_UNIT, 0 },
600 { "LAST", tORDINAL, -1 },
601 { "THIS", tORDINAL, 0 },
602 { "NEXT", tORDINAL, 1 },
603 { "FIRST", tORDINAL, 1 },
604 /*{ "SECOND", tORDINAL, 2 }, */
605 { "THIRD", tORDINAL, 3 },
606 { "FOURTH", tORDINAL, 4 },
607 { "FIFTH", tORDINAL, 5 },
608 { "SIXTH", tORDINAL, 6 },
609 { "SEVENTH", tORDINAL, 7 },
610 { "EIGHTH", tORDINAL, 8 },
611 { "NINTH", tORDINAL, 9 },
612 { "TENTH", tORDINAL, 10 },
613 { "ELEVENTH", tORDINAL, 11 },
614 { "TWELFTH", tORDINAL, 12 },
619 /* The time zone table. This table is necessarily incomplete, as time
620 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
621 as Eastern time in Australia, not as US Eastern Standard Time.
622 You cannot rely on getdate to handle arbitrary time zone
623 abbreviations; use numeric abbreviations like `-0500' instead. */
624 static table const time_zone_table[] =
626 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
627 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
628 { "UTC", tZONE, HOUR ( 0) },
629 { "WET", tZONE, HOUR ( 0) }, /* Western European */
630 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
631 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
632 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
633 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
634 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
635 { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
636 { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
637 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
638 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
639 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
640 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
641 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
642 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
643 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
644 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
645 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
646 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
647 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
648 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
649 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
650 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
651 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
652 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
653 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
654 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
655 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
656 { "CET", tZONE, HOUR ( 1) }, /* Central European */
657 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
658 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
659 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
660 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
661 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
662 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
663 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
664 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
665 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
666 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
667 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
668 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
669 { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
670 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
671 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
672 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
673 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
674 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
675 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
679 /* Military time zone table. */
680 static table const military_table[] =
682 { "A", tZONE, -HOUR ( 1) },
683 { "B", tZONE, -HOUR ( 2) },
684 { "C", tZONE, -HOUR ( 3) },
685 { "D", tZONE, -HOUR ( 4) },
686 { "E", tZONE, -HOUR ( 5) },
687 { "F", tZONE, -HOUR ( 6) },
688 { "G", tZONE, -HOUR ( 7) },
689 { "H", tZONE, -HOUR ( 8) },
690 { "I", tZONE, -HOUR ( 9) },
691 { "K", tZONE, -HOUR (10) },
692 { "L", tZONE, -HOUR (11) },
693 { "M", tZONE, -HOUR (12) },
694 { "N", tZONE, HOUR ( 1) },
695 { "O", tZONE, HOUR ( 2) },
696 { "P", tZONE, HOUR ( 3) },
697 { "Q", tZONE, HOUR ( 4) },
698 { "R", tZONE, HOUR ( 5) },
699 { "S", tZONE, HOUR ( 6) },
700 { "T", tZONE, HOUR ( 7) },
701 { "U", tZONE, HOUR ( 8) },
702 { "V", tZONE, HOUR ( 9) },
703 { "W", tZONE, HOUR (10) },
704 { "X", tZONE, HOUR (11) },
705 { "Y", tZONE, HOUR (12) },
706 { "Z", tZONE, HOUR ( 0) },
713 to_hour (long int hours, int meridian)
717 default: /* Pacify GCC. */
719 return 0 <= hours && hours < 24 ? hours : -1;
721 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
723 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
728 to_year (textint textyear)
730 long int year = textyear.value;
735 /* XPG4 suggests that years 00-68 map to 2000-2068, and
736 years 69-99 map to 1969-1999. */
737 else if (textyear.digits == 2)
738 year += year < 69 ? 2000 : 1900;
744 lookup_zone (parser_control const *pc, char const *name)
748 /* Try local zone abbreviations first; they're more likely to be right. */
749 for (tp = pc->local_time_zone_table; tp->name; tp++)
750 if (strcmp (name, tp->name) == 0)
753 for (tp = time_zone_table; tp->name; tp++)
754 if (strcmp (name, tp->name) == 0)
761 /* Yield the difference between *A and *B,
762 measured in seconds, ignoring leap seconds.
763 The body of this function is taken directly from the GNU C Library;
764 see src/strftime.c. */
766 tm_diff (struct tm const *a, struct tm const *b)
768 /* Compute intervening leap days correctly even if year is negative.
769 Take care to avoid int overflow in leap day calculations. */
770 int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
771 int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
772 int a100 = a4 / 25 - (a4 % 25 < 0);
773 int b100 = b4 / 25 - (b4 % 25 < 0);
774 int a400 = SHR (a100, 2);
775 int b400 = SHR (b100, 2);
776 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
777 long int ayear = a->tm_year;
778 long int years = ayear - b->tm_year;
779 long int days = (365 * years + intervening_leap_days
780 + (a->tm_yday - b->tm_yday));
781 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
782 + (a->tm_min - b->tm_min))
783 + (a->tm_sec - b->tm_sec));
785 #endif /* ! HAVE_TM_GMTOFF */
788 lookup_word (parser_control const *pc, char *word)
797 /* Make it uppercase. */
798 for (p = word; *p; p++)
800 unsigned char ch = *p;
805 for (tp = meridian_table; tp->name; tp++)
806 if (strcmp (word, tp->name) == 0)
809 /* See if we have an abbreviation for a month. */
810 wordlen = strlen (word);
811 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
813 for (tp = month_and_day_table; tp->name; tp++)
814 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
817 if ((tp = lookup_zone (pc, word)))
820 if (strcmp (word, dst_table[0].name) == 0)
823 for (tp = time_units_table; tp->name; tp++)
824 if (strcmp (word, tp->name) == 0)
827 /* Strip off any plural and try the units table again. */
828 if (word[wordlen - 1] == 'S')
830 word[wordlen - 1] = '\0';
831 for (tp = time_units_table; tp->name; tp++)
832 if (strcmp (word, tp->name) == 0)
834 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
837 for (tp = relative_time_table; tp->name; tp++)
838 if (strcmp (word, tp->name) == 0)
841 /* Military time zones. */
843 for (tp = military_table; tp->name; tp++)
844 if (word[0] == tp->name[0])
847 /* Drop out any periods and try the time zone table again. */
848 for (period_found = false, p = q = word; (*p = *q); q++)
853 if (period_found && (tp = lookup_zone (pc, word)))
860 yylex (YYSTYPE *lvalp, parser_control *pc)
867 while (c = *pc->input, ISSPACE (c))
870 if (ISDIGIT (c) || c == '-' || c == '+')
874 unsigned long int value;
875 if (c == '-' || c == '+')
877 sign = c == '-' ? -1 : 1;
878 while (c = *++pc->input, ISSPACE (c))
881 /* skip the '-' sign */
887 for (value = 0; ; value *= 10)
889 unsigned long int value1 = value + (c - '0');
896 if (ULONG_MAX / 10 < value)
899 if ((c == '.' || c == ',') && ISDIGIT (p[1]))
904 unsigned long int value1;
906 /* Check for overflow when converting value to time_t. */
924 /* Accumulate fraction, to ns precision. */
927 for (digits = 2; digits <= LOG10_BILLION; digits++)
934 /* Skip excess digits, truncating toward -Infinity. */
936 for (; ISDIGIT (*p); p++)
945 /* Adjust to the timespec convention, which is that
946 tv_nsec is always a positive offset even if tv_sec is
956 lvalp->timespec.tv_sec = s;
957 lvalp->timespec.tv_nsec = ns;
959 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
965 lvalp->textintval.value = - value;
966 if (0 < lvalp->textintval.value)
971 lvalp->textintval.value = value;
972 if (lvalp->textintval.value < 0)
975 lvalp->textintval.digits = p - pc->input;
977 return sign ? tSNUMBER : tUNUMBER;
989 if (p < buff + sizeof buff - 1)
993 while (ISALPHA (c) || c == '.');
996 tp = lookup_word (pc, buff);
999 lvalp->intval = tp->value;
1004 return *pc->input++;
1020 /* Do nothing if the parser reports an error. */
1022 yyerror (parser_control *pc ATTRIBUTE_UNUSED, char *s ATTRIBUTE_UNUSED)
1027 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1028 passing it to mktime, return true if it's OK that mktime returned T.
1029 It's not OK if *TM0 has out-of-range members. */
1032 mktime_ok (struct tm const *tm0, struct tm const *tm1, time_t t)
1034 if (t == (time_t) -1)
1036 /* Guard against falsely reporting an error when parsing a time
1037 stamp that happens to equal (time_t) -1, on a host that
1038 supports such a time stamp. */
1039 tm1 = localtime (&t);
1044 return ! ((tm0->tm_sec ^ tm1->tm_sec)
1045 | (tm0->tm_min ^ tm1->tm_min)
1046 | (tm0->tm_hour ^ tm1->tm_hour)
1047 | (tm0->tm_mday ^ tm1->tm_mday)
1048 | (tm0->tm_mon ^ tm1->tm_mon)
1049 | (tm0->tm_year ^ tm1->tm_year));
1052 /* A reasonable upper bound for the size of ordinary TZ strings.
1053 Use heap allocation if TZ's length exceeds this. */
1054 enum { TZBUFSIZE = 100 };
1056 /* Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
1059 get_tz (char tzbuf[TZBUFSIZE])
1061 char *tz = getenv ("TZ");
1064 size_t tzsize = strlen (tz) + 1;
1065 tz = (tzsize <= TZBUFSIZE
1066 ? memcpy (tzbuf, tz, tzsize)
1067 : xmemdup (tz, tzsize));
1072 /* Parse a date/time string, storing the resulting time value into *RESULT.
1073 The string itself is pointed to by P. Return true if successful.
1074 P can be an incomplete or relative time specification; if so, use
1075 *NOW as the basis for the returned time. */
1077 get_date (struct timespec *result, char const *p, struct timespec const *now)
1081 struct tm const *tmp;
1085 struct timespec gettime_buffer;
1087 bool tz_was_altered = false;
1089 char tz0buf[TZBUFSIZE];
1094 if (gettime (&gettime_buffer) != 0)
1096 now = &gettime_buffer;
1099 Start = now->tv_sec;
1100 Start_ns = now->tv_nsec;
1102 tmp = localtime (&now->tv_sec);
1106 while (c = *p, ISSPACE (c))
1109 if (strncmp (p, "TZ=\"", 4) == 0)
1111 char const *tzbase = p + 4;
1115 for (s = tzbase; *s; s++, tzsize++)
1119 if (! (*s == '\\' || *s == '"'))
1126 char tz1buf[TZBUFSIZE];
1127 bool large_tz = TZBUFSIZE < tzsize;
1129 tz0 = get_tz (tz0buf);
1130 z = tz1 = large_tz ? xmalloc (tzsize) : tz1buf;
1131 for (s = tzbase; *s != '"'; s++)
1132 *z++ = *(s += *s == '\\');
1134 setenv_ok = setenv ("TZ", tz1, 1) == 0;
1139 tz_was_altered = true;
1145 pc.year.value = tmp->tm_year;
1146 pc.year.value += TM_YEAR_BASE;
1148 pc.month = tmp->tm_mon + 1;
1149 pc.day = tmp->tm_mday;
1150 pc.hour = tmp->tm_hour;
1151 pc.minutes = tmp->tm_min;
1152 pc.seconds.tv_sec = tmp->tm_sec;
1153 pc.seconds.tv_nsec = Start_ns;
1154 tm.tm_isdst = tmp->tm_isdst;
1156 pc.meridian = MER24;
1164 pc.timespec_seen = false;
1169 pc.local_zones_seen = 0;
1172 #if HAVE_STRUCT_TM_TM_ZONE
1173 pc.local_time_zone_table[0].name = tmp->tm_zone;
1174 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1175 pc.local_time_zone_table[0].value = tmp->tm_isdst;
1176 pc.local_time_zone_table[1].name = NULL;
1178 /* Probe the names used in the next three calendar quarters, looking
1179 for a tm_isdst different from the one we already have. */
1182 for (quarter = 1; quarter <= 3; quarter++)
1184 time_t probe = Start + quarter * (90 * 24 * 60 * 60);
1185 struct tm const *probe_tm = localtime (&probe);
1186 if (probe_tm && probe_tm->tm_zone
1187 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
1190 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
1191 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1192 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
1193 pc.local_time_zone_table[2].name = NULL;
1203 extern char *tzname[];
1206 for (i = 0; i < 2; i++)
1208 pc.local_time_zone_table[i].name = tzname[i];
1209 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1210 pc.local_time_zone_table[i].value = i;
1212 pc.local_time_zone_table[i].name = NULL;
1215 pc.local_time_zone_table[0].name = NULL;
1219 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1220 && ! strcmp (pc.local_time_zone_table[0].name,
1221 pc.local_time_zone_table[1].name))
1223 /* This locale uses the same abbrevation for standard and
1224 daylight times. So if we see that abbreviation, we don't
1225 know whether it's daylight time. */
1226 pc.local_time_zone_table[0].value = -1;
1227 pc.local_time_zone_table[1].name = NULL;
1230 if (yyparse (&pc) != 0)
1233 if (pc.timespec_seen)
1234 *result = pc.seconds;
1237 if (1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen
1238 || 1 < (pc.local_zones_seen + pc.zones_seen)
1239 || (pc.local_zones_seen && 1 < pc.local_isdst))
1242 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
1243 tm.tm_mon = pc.month - 1;
1244 tm.tm_mday = pc.day;
1245 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1247 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1250 tm.tm_min = pc.minutes;
1251 tm.tm_sec = pc.seconds.tv_sec;
1255 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1256 pc.seconds.tv_nsec = 0;
1259 /* Let mktime deduce tm_isdst if we have an absolute time stamp. */
1260 if (pc.dates_seen | pc.days_seen | pc.times_seen)
1263 /* But if the input explicitly specifies local time with or without
1264 DST, give mktime that information. */
1265 if (pc.local_zones_seen)
1266 tm.tm_isdst = pc.local_isdst;
1270 Start = mktime (&tm);
1272 if (! mktime_ok (&tm0, &tm, Start))
1274 if (! pc.zones_seen)
1278 /* Guard against falsely reporting errors near the time_t
1279 boundaries when parsing times in other time zones. For
1280 example, suppose the input string "1969-12-31 23:00:00 -0100",
1281 the current time zone is 8 hours ahead of UTC, and the min
1282 time_t value is 1970-01-01 00:00:00 UTC. Then the min
1283 localtime value is 1970-01-01 08:00:00, and mktime will
1284 therefore fail on 1969-12-31 23:00:00. To work around the
1285 problem, set the time zone to 1 hour behind UTC temporarily
1286 by setting TZ="XXX1:00" and try mktime again. */
1288 long int time_zone = pc.time_zone;
1289 long int abs_time_zone = time_zone < 0 ? - time_zone : time_zone;
1290 long int abs_time_zone_hour = abs_time_zone / 60;
1291 int abs_time_zone_min = abs_time_zone % 60;
1292 char tz1buf[sizeof "XXX+0:00"
1293 + sizeof pc.time_zone * CHAR_BIT / 3];
1294 if (!tz_was_altered)
1295 tz0 = get_tz (tz0buf);
1296 sprintf (tz1buf, "XXX%s%ld:%02d", "-" + (time_zone < 0),
1297 abs_time_zone_hour, abs_time_zone_min);
1298 if (setenv ("TZ", tz1buf, 1) != 0)
1300 tz_was_altered = true;
1302 Start = mktime (&tm);
1303 if (! mktime_ok (&tm0, &tm, Start))
1308 if (pc.days_seen && ! pc.dates_seen)
1310 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1311 + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
1313 Start = mktime (&tm);
1314 if (Start == (time_t) -1)
1320 long int delta = pc.time_zone * 60;
1322 #ifdef HAVE_TM_GMTOFF
1323 delta -= tm.tm_gmtoff;
1326 struct tm const *gmt = gmtime (&t);
1329 delta -= tm_diff (&tm, gmt);
1332 if ((Start < t1) != (delta < 0))
1333 goto fail; /* time_t overflow */
1337 /* Add relative date. */
1338 if (pc.rel_year | pc.rel_month | pc.rel_day)
1340 int year = tm.tm_year + pc.rel_year;
1341 int month = tm.tm_mon + pc.rel_month;
1342 int day = tm.tm_mday + pc.rel_day;
1343 if (((year < tm.tm_year) ^ (pc.rel_year < 0))
1344 | ((month < tm.tm_mon) ^ (pc.rel_month < 0))
1345 | ((day < tm.tm_mday) ^ (pc.rel_day < 0)))
1350 Start = mktime (&tm);
1351 if (Start == (time_t) -1)
1355 /* Add relative hours, minutes, and seconds. On hosts that support
1356 leap seconds, ignore the possibility of leap seconds; e.g.,
1357 "+ 10 minutes" adds 600 seconds, even if one of them is a
1358 leap second. Typically this is not what the user wants, but it's
1359 too hard to do it the other way, because the time zone indicator
1360 must be applied before relative times, and if mktime is applied
1361 again the time zone will be lost. */
1363 long int sum_ns = pc.seconds.tv_nsec + pc.rel_ns;
1364 long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
1366 long int d1 = 60 * 60 * pc.rel_hour;
1367 time_t t1 = t0 + d1;
1368 long int d2 = 60 * pc.rel_minutes;
1369 time_t t2 = t1 + d2;
1370 long int d3 = pc.rel_seconds;
1371 time_t t3 = t2 + d3;
1372 long int d4 = (sum_ns - normalized_ns) / BILLION;
1373 time_t t4 = t3 + d4;
1375 if ((d1 / (60 * 60) ^ pc.rel_hour)
1376 | (d2 / 60 ^ pc.rel_minutes)
1377 | ((t1 < t0) ^ (d1 < 0))
1378 | ((t2 < t1) ^ (d2 < 0))
1379 | ((t3 < t2) ^ (d3 < 0))
1380 | ((t4 < t3) ^ (d4 < 0)))
1383 result->tv_sec = t4;
1384 result->tv_nsec = normalized_ns;
1394 ok &= (tz0 ? setenv ("TZ", tz0, 1) : unsetenv ("TZ")) == 0;
1403 main (int ac, char **av)
1407 printf ("Enter date, or blank line to exit.\n\t> ");
1410 buff[BUFSIZ - 1] = '\0';
1411 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1414 struct tm const *tm;
1415 if (! get_date (&d, buff, NULL))
1416 printf ("Bad format - couldn't convert.\n");
1417 else if (! (tm = localtime (&d.tv_sec)))
1419 long int sec = d.tv_sec;
1420 printf ("localtime (%ld) failed\n", sec);
1425 printf ("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1426 tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
1427 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);