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, and in February 2004 to support
26 nanosecond-resolution time stamps. Unlike previous versions, this
27 version is reentrant. */
29 /* FIXME: Check for arithmetic overflow in all cases, not just
32 FIXME: The current code uses 'int' to count seconds; it should use
33 something like 'intmax_t' to support time stamps that don't fit in
44 /* Since the code of getdate.y is not included in the Emacs executable
45 itself, there is no need to #define static in this file. Even if
46 the code were included in the Emacs executable, it probably
47 wouldn't do any harm to #undef it here; this will only cause
48 problems if we try to write to a static variable, which I don't
49 think this code needs to do. */
56 #include <stdlib.h> /* for `free'; used by Bison 1.27 */
58 #if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII)
59 # define IN_CTYPE_DOMAIN(c) 1
61 # define IN_CTYPE_DOMAIN(c) isascii (c)
64 #define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c))
65 #define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c))
66 #define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c))
67 #define ISDIGIT_LOCALE(c) (IN_CTYPE_DOMAIN (c) && isdigit (c))
69 /* ISDIGIT differs from ISDIGIT_LOCALE, as follows:
70 - Its arg may be any int or unsigned int; it need not be an unsigned char.
71 - It's guaranteed to evaluate its argument exactly once.
72 - It's typically faster.
73 POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
74 ISDIGIT_LOCALE unless it's important to use the locale's definition
75 of `digit' even when the host does not conform to POSIX. */
76 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
80 #include "unlocked-io.h"
82 #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
83 # define __attribute__(x)
86 #ifndef ATTRIBUTE_UNUSED
87 # define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
90 #define EPOCH_YEAR 1970
91 #define TM_YEAR_BASE 1900
93 #define HOUR(x) ((x) * 60)
95 /* An integer value, and the number of digits in its textual
103 /* An entry in the lexical lookup table. */
111 /* Meridian: am, pm, or 24-hour style. */
112 enum { MERam, MERpm, MER24 };
114 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
116 /* Information passed to and from the parser. */
119 /* The input string remaining to be parsed. */
122 /* N, if this is the Nth Tuesday. */
123 long int day_ordinal;
125 /* Day of week; Sunday is 0. */
128 /* tm_isdst flag for the local zone. */
131 /* Time zone, in minutes east of UTC. */
134 /* Style used for time. */
137 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
143 struct timespec seconds; /* includes nanoseconds */
145 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
150 long int rel_minutes;
151 long int rel_seconds;
154 /* Counts of nonterminals of various flavors parsed so far. */
158 size_t local_zones_seen;
163 /* Table of local time zone abbrevations, terminated by a null entry. */
164 table local_time_zone_table[3];
167 #define PC (* (parser_control *) parm)
168 #define YYLEX_PARAM parm
169 #define YYPARSE_PARAM parm
171 static int yyerror ();
176 /* We want a reentrant parser. */
179 /* This grammar has 13 shift/reduce conflicts. */
186 struct timespec timespec;
191 %token <intval> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN
192 %token <intval> tMINUTE_UNIT tMONTH tMONTH_UNIT tSEC_UNIT tYEAR_UNIT tZONE
194 %token <textintval> tSNUMBER tUNUMBER
195 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
197 %type <intval> o_merid
198 %type <timespec> seconds signed_seconds unsigned_seconds
211 PC.timespec_seen = true;
224 { PC.local_zones_seen++; }
241 PC.seconds.tv_sec = 0;
242 PC.seconds.tv_nsec = 0;
245 | tUNUMBER ':' tUNUMBER o_merid
248 PC.minutes = $3.value;
249 PC.seconds.tv_sec = 0;
250 PC.seconds.tv_nsec = 0;
253 | tUNUMBER ':' tUNUMBER tSNUMBER
256 PC.minutes = $3.value;
257 PC.seconds.tv_sec = 0;
258 PC.seconds.tv_nsec = 0;
261 PC.time_zone = $4.value % 100 + ($4.value / 100) * 60;
263 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
266 PC.minutes = $3.value;
270 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER
273 PC.minutes = $3.value;
277 PC.time_zone = $6.value % 100 + ($6.value / 100) * 60;
283 { PC.local_isdst = $1; }
285 { PC.local_isdst = $1 < 0 ? 1 : $1 + 1; }
290 { PC.time_zone = $1; }
292 { PC.time_zone = $1 + 60; }
294 { PC.time_zone = $1 + 60; }
310 PC.day_ordinal = $1.value;
316 tUNUMBER '/' tUNUMBER
321 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
323 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
324 otherwise as MM/DD/YY.
325 The goal in recognizing YYYY/MM/DD is solely to support legacy
326 machine-generated dates like those in an RCS log listing. If
327 you want portability, use the ISO 8601 format. */
341 | tUNUMBER tSNUMBER tSNUMBER
343 /* ISO 8601 format. YYYY-MM-DD. */
345 PC.month = -$2.value;
348 | tUNUMBER tMONTH tSNUMBER
350 /* e.g. 17-JUN-1992. */
353 PC.year.value = -$3.value;
354 PC.year.digits = $3.digits;
356 | tMONTH tSNUMBER tSNUMBER
358 /* e.g. JUN-17-1992. */
361 PC.year.value = -$3.value;
362 PC.year.digits = $3.digits;
369 | tMONTH tUNUMBER ',' tUNUMBER
380 | tUNUMBER tMONTH tUNUMBER
391 PC.rel_ns = -PC.rel_ns;
392 PC.rel_seconds = -PC.rel_seconds;
393 PC.rel_minutes = -PC.rel_minutes;
394 PC.rel_hour = -PC.rel_hour;
395 PC.rel_day = -PC.rel_day;
396 PC.rel_month = -PC.rel_month;
397 PC.rel_year = -PC.rel_year;
404 { PC.rel_year += $1.value * $2; }
405 | tSNUMBER tYEAR_UNIT
406 { PC.rel_year += $1.value * $2; }
408 { PC.rel_year += $1; }
409 | tUNUMBER tMONTH_UNIT
410 { PC.rel_month += $1.value * $2; }
411 | tSNUMBER tMONTH_UNIT
412 { PC.rel_month += $1.value * $2; }
414 { PC.rel_month += $1; }
416 { PC.rel_day += $1.value * $2; }
418 { PC.rel_day += $1.value * $2; }
420 { PC.rel_day += $1; }
421 | tUNUMBER tHOUR_UNIT
422 { PC.rel_hour += $1.value * $2; }
423 | tSNUMBER tHOUR_UNIT
424 { PC.rel_hour += $1.value * $2; }
426 { PC.rel_hour += $1; }
427 | tUNUMBER tMINUTE_UNIT
428 { PC.rel_minutes += $1.value * $2; }
429 | tSNUMBER tMINUTE_UNIT
430 { PC.rel_minutes += $1.value * $2; }
432 { PC.rel_minutes += $1; }
434 { PC.rel_seconds += $1.value * $2; }
436 { PC.rel_seconds += $1.value * $2; }
437 | tSDECIMAL_NUMBER tSEC_UNIT
438 { PC.rel_seconds += $1.tv_sec * $2; PC.rel_ns += $1.tv_nsec * $2; }
439 | tUDECIMAL_NUMBER tSEC_UNIT
440 { PC.rel_seconds += $1.tv_sec * $2; PC.rel_ns += $1.tv_nsec * $2; }
442 { PC.rel_seconds += $1; }
445 seconds: signed_seconds | unsigned_seconds;
450 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
456 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
463 && ! PC.rels_seen && (PC.times_seen || 2 < $1.digits))
470 PC.day = $1.value % 100;
471 PC.month = ($1.value / 100) % 100;
472 PC.year.value = $1.value / 10000;
473 PC.year.digits = $1.digits - 4;
485 PC.hour = $1.value / 100;
486 PC.minutes = $1.value % 100;
488 PC.seconds.tv_sec = 0;
489 PC.seconds.tv_nsec = 0;
505 static table const meridian_table[] =
507 { "AM", tMERIDIAN, MERam },
508 { "A.M.", tMERIDIAN, MERam },
509 { "PM", tMERIDIAN, MERpm },
510 { "P.M.", tMERIDIAN, MERpm },
514 static table const dst_table[] =
519 static table const month_and_day_table[] =
521 { "JANUARY", tMONTH, 1 },
522 { "FEBRUARY", tMONTH, 2 },
523 { "MARCH", tMONTH, 3 },
524 { "APRIL", tMONTH, 4 },
525 { "MAY", tMONTH, 5 },
526 { "JUNE", tMONTH, 6 },
527 { "JULY", tMONTH, 7 },
528 { "AUGUST", tMONTH, 8 },
529 { "SEPTEMBER",tMONTH, 9 },
530 { "SEPT", tMONTH, 9 },
531 { "OCTOBER", tMONTH, 10 },
532 { "NOVEMBER", tMONTH, 11 },
533 { "DECEMBER", tMONTH, 12 },
534 { "SUNDAY", tDAY, 0 },
535 { "MONDAY", tDAY, 1 },
536 { "TUESDAY", tDAY, 2 },
538 { "WEDNESDAY",tDAY, 3 },
539 { "WEDNES", tDAY, 3 },
540 { "THURSDAY", tDAY, 4 },
542 { "THURS", tDAY, 4 },
543 { "FRIDAY", tDAY, 5 },
544 { "SATURDAY", tDAY, 6 },
548 static table const time_units_table[] =
550 { "YEAR", tYEAR_UNIT, 1 },
551 { "MONTH", tMONTH_UNIT, 1 },
552 { "FORTNIGHT",tDAY_UNIT, 14 },
553 { "WEEK", tDAY_UNIT, 7 },
554 { "DAY", tDAY_UNIT, 1 },
555 { "HOUR", tHOUR_UNIT, 1 },
556 { "MINUTE", tMINUTE_UNIT, 1 },
557 { "MIN", tMINUTE_UNIT, 1 },
558 { "SECOND", tSEC_UNIT, 1 },
559 { "SEC", tSEC_UNIT, 1 },
563 /* Assorted relative-time words. */
564 static table const relative_time_table[] =
566 { "TOMORROW", tDAY_UNIT, 1 },
567 { "YESTERDAY",tDAY_UNIT, -1 },
568 { "TODAY", tDAY_UNIT, 0 },
569 { "NOW", tDAY_UNIT, 0 },
570 { "LAST", tUNUMBER, -1 },
571 { "THIS", tUNUMBER, 0 },
572 { "NEXT", tUNUMBER, 1 },
573 { "FIRST", tUNUMBER, 1 },
574 /*{ "SECOND", tUNUMBER, 2 }, */
575 { "THIRD", tUNUMBER, 3 },
576 { "FOURTH", tUNUMBER, 4 },
577 { "FIFTH", tUNUMBER, 5 },
578 { "SIXTH", tUNUMBER, 6 },
579 { "SEVENTH", tUNUMBER, 7 },
580 { "EIGHTH", tUNUMBER, 8 },
581 { "NINTH", tUNUMBER, 9 },
582 { "TENTH", tUNUMBER, 10 },
583 { "ELEVENTH", tUNUMBER, 11 },
584 { "TWELFTH", tUNUMBER, 12 },
589 /* The time zone table. This table is necessarily incomplete, as time
590 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
591 as Eastern time in Australia, not as US Eastern Standard Time.
592 You cannot rely on getdate to handle arbitrary time zone
593 abbreviations; use numeric abbreviations like `-0500' instead. */
594 static table const time_zone_table[] =
596 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
597 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
598 { "UTC", tZONE, HOUR ( 0) },
599 { "WET", tZONE, HOUR ( 0) }, /* Western European */
600 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
601 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
602 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
603 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
604 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
605 { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
606 { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
607 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
608 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
609 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
610 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
611 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
612 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
613 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
614 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
615 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
616 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
617 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
618 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
619 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
620 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
621 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
622 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
623 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
624 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
625 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
626 { "CET", tZONE, HOUR ( 1) }, /* Central European */
627 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
628 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
629 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
630 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
631 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
632 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
633 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
634 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
635 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
636 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
637 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
638 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
639 { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
640 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
641 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
642 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
643 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
644 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
645 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
649 /* Military time zone table. */
650 static table const military_table[] =
652 { "A", tZONE, -HOUR ( 1) },
653 { "B", tZONE, -HOUR ( 2) },
654 { "C", tZONE, -HOUR ( 3) },
655 { "D", tZONE, -HOUR ( 4) },
656 { "E", tZONE, -HOUR ( 5) },
657 { "F", tZONE, -HOUR ( 6) },
658 { "G", tZONE, -HOUR ( 7) },
659 { "H", tZONE, -HOUR ( 8) },
660 { "I", tZONE, -HOUR ( 9) },
661 { "K", tZONE, -HOUR (10) },
662 { "L", tZONE, -HOUR (11) },
663 { "M", tZONE, -HOUR (12) },
664 { "N", tZONE, HOUR ( 1) },
665 { "O", tZONE, HOUR ( 2) },
666 { "P", tZONE, HOUR ( 3) },
667 { "Q", tZONE, HOUR ( 4) },
668 { "R", tZONE, HOUR ( 5) },
669 { "S", tZONE, HOUR ( 6) },
670 { "T", tZONE, HOUR ( 7) },
671 { "U", tZONE, HOUR ( 8) },
672 { "V", tZONE, HOUR ( 9) },
673 { "W", tZONE, HOUR (10) },
674 { "X", tZONE, HOUR (11) },
675 { "Y", tZONE, HOUR (12) },
676 { "Z", tZONE, HOUR ( 0) },
683 to_hour (long int hours, int meridian)
688 return 0 <= hours && hours < 24 ? hours : -1;
690 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
692 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
700 to_year (textint textyear)
702 long int year = textyear.value;
707 /* XPG4 suggests that years 00-68 map to 2000-2068, and
708 years 69-99 map to 1969-1999. */
709 else if (textyear.digits == 2)
710 year += year < 69 ? 2000 : 1900;
716 lookup_zone (parser_control const *pc, char const *name)
720 /* Try local zone abbreviations first; they're more likely to be right. */
721 for (tp = pc->local_time_zone_table; tp->name; tp++)
722 if (strcmp (name, tp->name) == 0)
725 for (tp = time_zone_table; tp->name; tp++)
726 if (strcmp (name, tp->name) == 0)
733 /* Yield the difference between *A and *B,
734 measured in seconds, ignoring leap seconds.
735 The body of this function is taken directly from the GNU C Library;
736 see src/strftime.c. */
738 tm_diff (struct tm const *a, struct tm const *b)
740 /* Compute intervening leap days correctly even if year is negative.
741 Take care to avoid int overflow in leap day calculations. */
742 int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3);
743 int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3);
744 int a100 = a4 / 25 - (a4 % 25 < 0);
745 int b100 = b4 / 25 - (b4 % 25 < 0);
746 int a400 = a100 >> 2;
747 int b400 = b100 >> 2;
748 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
749 long int ayear = a->tm_year;
750 long int years = ayear - b->tm_year;
751 long int days = (365 * years + intervening_leap_days
752 + (a->tm_yday - b->tm_yday));
753 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
754 + (a->tm_min - b->tm_min))
755 + (a->tm_sec - b->tm_sec));
757 #endif /* ! HAVE_TM_GMTOFF */
760 lookup_word (parser_control const *pc, char *word)
769 /* Make it uppercase. */
770 for (p = word; *p; p++)
771 if (ISLOWER ((unsigned char) *p))
772 *p = toupper ((unsigned char) *p);
774 for (tp = meridian_table; tp->name; tp++)
775 if (strcmp (word, tp->name) == 0)
778 /* See if we have an abbreviation for a month. */
779 wordlen = strlen (word);
780 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
782 for (tp = month_and_day_table; tp->name; tp++)
783 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
786 if ((tp = lookup_zone (pc, word)))
789 if (strcmp (word, dst_table[0].name) == 0)
792 for (tp = time_units_table; tp->name; tp++)
793 if (strcmp (word, tp->name) == 0)
796 /* Strip off any plural and try the units table again. */
797 if (word[wordlen - 1] == 'S')
799 word[wordlen - 1] = '\0';
800 for (tp = time_units_table; tp->name; tp++)
801 if (strcmp (word, tp->name) == 0)
803 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
806 for (tp = relative_time_table; tp->name; tp++)
807 if (strcmp (word, tp->name) == 0)
810 /* Military time zones. */
812 for (tp = military_table; tp->name; tp++)
813 if (word[0] == tp->name[0])
816 /* Drop out any periods and try the time zone table again. */
817 for (period_found = false, p = q = word; (*p = *q); q++)
822 if (period_found && (tp = lookup_zone (pc, word)))
829 yylex (YYSTYPE *lvalp, parser_control *pc)
836 while (c = *pc->input, ISSPACE (c))
839 if (ISDIGIT (c) || c == '-' || c == '+')
843 unsigned long int value;
844 if (c == '-' || c == '+')
846 sign = c == '-' ? -1 : 1;
849 /* skip the '-' sign */
855 for (value = 0; ; value *= 10)
857 unsigned long int value1 = value + (c - '0');
864 if (ULONG_MAX / 10 < value)
867 if ((c == '.' || c == ',') && ISDIGIT (p[1]))
872 unsigned long int value1;
874 /* Check for overflow when converting value to time_t. */
892 /* Accumulate fraction, to ns precision. */
895 for (digits = 2; digits <= LOG10_BILLION; digits++)
902 /* Skip excess digits, truncating toward -Infinity. */
904 for (; ISDIGIT (*p); p++)
913 /* Adjust to the timespec convention, which is that
914 tv_nsec is always a positive offset even if tv_sec is
924 lvalp->timespec.tv_sec = s;
925 lvalp->timespec.tv_nsec = ns;
927 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
933 lvalp->textintval.value = - value;
934 if (0 < lvalp->textintval.value)
939 lvalp->textintval.value = value;
940 if (lvalp->textintval.value < 0)
943 lvalp->textintval.digits = p - pc->input;
945 return sign ? tSNUMBER : tUNUMBER;
957 if (p < buff + sizeof buff - 1)
961 while (ISALPHA (c) || c == '.');
964 tp = lookup_word (pc, buff);
967 lvalp->intval = tp->value;
988 /* Do nothing if the parser reports an error. */
990 yyerror (char *s ATTRIBUTE_UNUSED)
995 /* Parse a date/time string, storing the resulting time value into *RESULT.
996 The string itself is pointed to by P. Return true if successful.
997 P can be an incomplete or relative time specification; if so, use
998 *NOW as the basis for the returned time. */
1000 get_date (struct timespec *result, char const *p, struct timespec const *now)
1004 struct tm const *tmp;
1008 struct timespec gettime_buffer;
1012 if (gettime (&gettime_buffer) != 0)
1014 now = &gettime_buffer;
1017 Start = now->tv_sec;
1018 Start_ns = now->tv_nsec;
1020 tmp = localtime (&now->tv_sec);
1025 pc.year.value = tmp->tm_year;
1026 pc.year.value += TM_YEAR_BASE;
1028 pc.month = tmp->tm_mon + 1;
1029 pc.day = tmp->tm_mday;
1030 pc.hour = tmp->tm_hour;
1031 pc.minutes = tmp->tm_min;
1032 pc.seconds.tv_sec = tmp->tm_sec;
1033 pc.seconds.tv_nsec = Start_ns;
1034 tm.tm_isdst = tmp->tm_isdst;
1036 pc.meridian = MER24;
1044 pc.timespec_seen = false;
1049 pc.local_zones_seen = 0;
1052 #if HAVE_STRUCT_TM_TM_ZONE
1053 pc.local_time_zone_table[0].name = tmp->tm_zone;
1054 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1055 pc.local_time_zone_table[0].value = tmp->tm_isdst;
1056 pc.local_time_zone_table[1].name = 0;
1058 /* Probe the names used in the next three calendar quarters, looking
1059 for a tm_isdst different from the one we already have. */
1062 for (quarter = 1; quarter <= 3; quarter++)
1064 time_t probe = Start + quarter * (90 * 24 * 60 * 60);
1065 struct tm const *probe_tm = localtime (&probe);
1066 if (probe_tm && probe_tm->tm_zone
1067 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
1070 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
1071 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1072 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
1073 pc.local_time_zone_table[2].name = 0;
1083 extern char *tzname[];
1086 for (i = 0; i < 2; i++)
1088 pc.local_time_zone_table[i].name = tzname[i];
1089 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1090 pc.local_time_zone_table[i].value = i;
1092 pc.local_time_zone_table[i].name = 0;
1095 pc.local_time_zone_table[0].name = 0;
1099 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1100 && ! strcmp (pc.local_time_zone_table[0].name,
1101 pc.local_time_zone_table[1].name))
1103 /* This locale uses the same abbrevation for standard and
1104 daylight times. So if we see that abbreviation, we don't
1105 know whether it's daylight time. */
1106 pc.local_time_zone_table[0].value = -1;
1107 pc.local_time_zone_table[1].name = 0;
1110 if (yyparse (&pc) != 0)
1113 if (pc.timespec_seen)
1115 *result = pc.seconds;
1119 if (1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen
1120 || 1 < (pc.local_zones_seen + pc.zones_seen)
1121 || (pc.local_zones_seen && 1 < pc.local_isdst))
1124 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE + pc.rel_year;
1125 tm.tm_mon = pc.month - 1 + pc.rel_month;
1126 tm.tm_mday = pc.day + pc.rel_day;
1127 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1129 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1132 tm.tm_min = pc.minutes;
1133 tm.tm_sec = pc.seconds.tv_sec;
1137 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1138 pc.seconds.tv_nsec = 0;
1141 /* Let mktime deduce tm_isdst if we have an absolute time stamp,
1142 or if the relative time stamp mentions days, months, or years. */
1143 if (pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day
1144 | pc.rel_month | pc.rel_year)
1147 /* But if the input explicitly specifies local time with or without
1148 DST, give mktime that information. */
1149 if (pc.local_zones_seen)
1150 tm.tm_isdst = pc.local_isdst;
1154 Start = mktime (&tm);
1156 if (Start == (time_t) -1)
1159 /* Guard against falsely reporting errors near the time_t boundaries
1160 when parsing times in other time zones. For example, if the min
1161 time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
1162 of UTC, then the min localtime value is 1970-01-01 08:00:00; if
1163 we apply mktime to 1970-01-01 00:00:00 we will get an error, so
1164 we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
1165 zone by 24 hours to compensate. This algorithm assumes that
1166 there is no DST transition within a day of the time_t boundaries. */
1170 if (tm.tm_year <= EPOCH_YEAR - TM_YEAR_BASE)
1173 pc.time_zone += 24 * 60;
1178 pc.time_zone -= 24 * 60;
1180 Start = mktime (&tm);
1183 if (Start == (time_t) -1)
1187 if (pc.days_seen && ! pc.dates_seen)
1189 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1190 + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
1192 Start = mktime (&tm);
1193 if (Start == (time_t) -1)
1199 long int delta = pc.time_zone * 60;
1201 #ifdef HAVE_TM_GMTOFF
1202 delta -= tm.tm_gmtoff;
1205 struct tm const *gmt = gmtime (&t);
1208 delta -= tm_diff (&tm, gmt);
1211 if ((Start < t1) != (delta < 0))
1212 return false; /* time_t overflow */
1216 /* Add relative hours, minutes, and seconds. Ignore leap seconds;
1217 i.e. "+ 10 minutes" means 600 seconds, even if one of them is a
1218 leap second. Typically this is not what the user wants, but it's
1219 too hard to do it the other way, because the time zone indicator
1220 must be applied before relative times, and if mktime is applied
1221 again the time zone will be lost. */
1223 long int sum_ns = pc.seconds.tv_nsec + pc.rel_ns;
1224 long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
1226 long int d1 = 60 * 60 * pc.rel_hour;
1227 time_t t1 = t0 + d1;
1228 long int d2 = 60 * pc.rel_minutes;
1229 time_t t2 = t1 + d2;
1230 long int d3 = pc.rel_seconds;
1231 time_t t3 = t2 + d3;
1232 long int d4 = (sum_ns - normalized_ns) / BILLION;
1233 time_t t4 = t3 + d4;
1235 if ((d1 / (60 * 60) ^ pc.rel_hour)
1236 | (d2 / 60 ^ pc.rel_minutes)
1237 | ((t1 < t0) ^ (d1 < 0))
1238 | ((t2 < t1) ^ (d2 < 0))
1239 | ((t3 < t2) ^ (d3 < 0))
1240 | ((t4 < t3) ^ (d4 < 0)))
1243 result->tv_sec = t4;
1244 result->tv_nsec = normalized_ns;
1254 main (int ac, char **av)
1258 printf ("Enter date, or blank line to exit.\n\t> ");
1261 buff[BUFSIZ - 1] = 0;
1262 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1265 struct tm const *tm;
1266 if (! get_date (&d, buff, NULL))
1267 printf ("Bad format - couldn't convert.\n");
1268 else if (! (tm = localtime (&d.tv_sec)))
1270 long int sec = d.tv_sec;
1271 printf ("localtime (%ld) failed\n", sec);
1276 printf ("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1277 tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
1278 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);
1285 #endif /* defined TEST */