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++)
772 unsigned char ch = *p;
777 for (tp = meridian_table; tp->name; tp++)
778 if (strcmp (word, tp->name) == 0)
781 /* See if we have an abbreviation for a month. */
782 wordlen = strlen (word);
783 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
785 for (tp = month_and_day_table; tp->name; tp++)
786 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
789 if ((tp = lookup_zone (pc, word)))
792 if (strcmp (word, dst_table[0].name) == 0)
795 for (tp = time_units_table; tp->name; tp++)
796 if (strcmp (word, tp->name) == 0)
799 /* Strip off any plural and try the units table again. */
800 if (word[wordlen - 1] == 'S')
802 word[wordlen - 1] = '\0';
803 for (tp = time_units_table; tp->name; tp++)
804 if (strcmp (word, tp->name) == 0)
806 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
809 for (tp = relative_time_table; tp->name; tp++)
810 if (strcmp (word, tp->name) == 0)
813 /* Military time zones. */
815 for (tp = military_table; tp->name; tp++)
816 if (word[0] == tp->name[0])
819 /* Drop out any periods and try the time zone table again. */
820 for (period_found = false, p = q = word; (*p = *q); q++)
825 if (period_found && (tp = lookup_zone (pc, word)))
832 yylex (YYSTYPE *lvalp, parser_control *pc)
839 while (c = *pc->input, ISSPACE (c))
842 if (ISDIGIT (c) || c == '-' || c == '+')
846 unsigned long int value;
847 if (c == '-' || c == '+')
849 sign = c == '-' ? -1 : 1;
850 while (c = *++pc->input, ISSPACE (c))
853 /* skip the '-' sign */
859 for (value = 0; ; value *= 10)
861 unsigned long int value1 = value + (c - '0');
868 if (ULONG_MAX / 10 < value)
871 if ((c == '.' || c == ',') && ISDIGIT (p[1]))
876 unsigned long int value1;
878 /* Check for overflow when converting value to time_t. */
896 /* Accumulate fraction, to ns precision. */
899 for (digits = 2; digits <= LOG10_BILLION; digits++)
906 /* Skip excess digits, truncating toward -Infinity. */
908 for (; ISDIGIT (*p); p++)
917 /* Adjust to the timespec convention, which is that
918 tv_nsec is always a positive offset even if tv_sec is
928 lvalp->timespec.tv_sec = s;
929 lvalp->timespec.tv_nsec = ns;
931 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
937 lvalp->textintval.value = - value;
938 if (0 < lvalp->textintval.value)
943 lvalp->textintval.value = value;
944 if (lvalp->textintval.value < 0)
947 lvalp->textintval.digits = p - pc->input;
949 return sign ? tSNUMBER : tUNUMBER;
961 if (p < buff + sizeof buff - 1)
965 while (ISALPHA (c) || c == '.');
968 tp = lookup_word (pc, buff);
971 lvalp->intval = tp->value;
992 /* Do nothing if the parser reports an error. */
994 yyerror (char *s ATTRIBUTE_UNUSED)
999 /* Parse a date/time string, storing the resulting time value into *RESULT.
1000 The string itself is pointed to by P. Return true if successful.
1001 P can be an incomplete or relative time specification; if so, use
1002 *NOW as the basis for the returned time. */
1004 get_date (struct timespec *result, char const *p, struct timespec const *now)
1008 struct tm const *tmp;
1012 struct timespec gettime_buffer;
1016 if (gettime (&gettime_buffer) != 0)
1018 now = &gettime_buffer;
1021 Start = now->tv_sec;
1022 Start_ns = now->tv_nsec;
1024 tmp = localtime (&now->tv_sec);
1029 pc.year.value = tmp->tm_year;
1030 pc.year.value += TM_YEAR_BASE;
1032 pc.month = tmp->tm_mon + 1;
1033 pc.day = tmp->tm_mday;
1034 pc.hour = tmp->tm_hour;
1035 pc.minutes = tmp->tm_min;
1036 pc.seconds.tv_sec = tmp->tm_sec;
1037 pc.seconds.tv_nsec = Start_ns;
1038 tm.tm_isdst = tmp->tm_isdst;
1040 pc.meridian = MER24;
1048 pc.timespec_seen = false;
1053 pc.local_zones_seen = 0;
1056 #if HAVE_STRUCT_TM_TM_ZONE
1057 pc.local_time_zone_table[0].name = tmp->tm_zone;
1058 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1059 pc.local_time_zone_table[0].value = tmp->tm_isdst;
1060 pc.local_time_zone_table[1].name = 0;
1062 /* Probe the names used in the next three calendar quarters, looking
1063 for a tm_isdst different from the one we already have. */
1066 for (quarter = 1; quarter <= 3; quarter++)
1068 time_t probe = Start + quarter * (90 * 24 * 60 * 60);
1069 struct tm const *probe_tm = localtime (&probe);
1070 if (probe_tm && probe_tm->tm_zone
1071 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
1074 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
1075 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1076 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
1077 pc.local_time_zone_table[2].name = 0;
1087 extern char *tzname[];
1090 for (i = 0; i < 2; i++)
1092 pc.local_time_zone_table[i].name = tzname[i];
1093 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1094 pc.local_time_zone_table[i].value = i;
1096 pc.local_time_zone_table[i].name = 0;
1099 pc.local_time_zone_table[0].name = 0;
1103 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1104 && ! strcmp (pc.local_time_zone_table[0].name,
1105 pc.local_time_zone_table[1].name))
1107 /* This locale uses the same abbrevation for standard and
1108 daylight times. So if we see that abbreviation, we don't
1109 know whether it's daylight time. */
1110 pc.local_time_zone_table[0].value = -1;
1111 pc.local_time_zone_table[1].name = 0;
1114 if (yyparse (&pc) != 0)
1117 if (pc.timespec_seen)
1119 *result = pc.seconds;
1123 if (1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen
1124 || 1 < (pc.local_zones_seen + pc.zones_seen)
1125 || (pc.local_zones_seen && 1 < pc.local_isdst))
1128 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE + pc.rel_year;
1129 tm.tm_mon = pc.month - 1 + pc.rel_month;
1130 tm.tm_mday = pc.day + pc.rel_day;
1131 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1133 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1136 tm.tm_min = pc.minutes;
1137 tm.tm_sec = pc.seconds.tv_sec;
1141 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1142 pc.seconds.tv_nsec = 0;
1145 /* Let mktime deduce tm_isdst if we have an absolute time stamp,
1146 or if the relative time stamp mentions days, months, or years. */
1147 if (pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day
1148 | pc.rel_month | pc.rel_year)
1151 /* But if the input explicitly specifies local time with or without
1152 DST, give mktime that information. */
1153 if (pc.local_zones_seen)
1154 tm.tm_isdst = pc.local_isdst;
1158 Start = mktime (&tm);
1160 if (Start == (time_t) -1)
1163 /* Guard against falsely reporting errors near the time_t boundaries
1164 when parsing times in other time zones. For example, if the min
1165 time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
1166 of UTC, then the min localtime value is 1970-01-01 08:00:00; if
1167 we apply mktime to 1970-01-01 00:00:00 we will get an error, so
1168 we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
1169 zone by 24 hours to compensate. This algorithm assumes that
1170 there is no DST transition within a day of the time_t boundaries. */
1174 if (tm.tm_year <= EPOCH_YEAR - TM_YEAR_BASE)
1177 pc.time_zone += 24 * 60;
1182 pc.time_zone -= 24 * 60;
1184 Start = mktime (&tm);
1187 if (Start == (time_t) -1)
1191 if (pc.days_seen && ! pc.dates_seen)
1193 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1194 + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
1196 Start = mktime (&tm);
1197 if (Start == (time_t) -1)
1203 long int delta = pc.time_zone * 60;
1205 #ifdef HAVE_TM_GMTOFF
1206 delta -= tm.tm_gmtoff;
1209 struct tm const *gmt = gmtime (&t);
1212 delta -= tm_diff (&tm, gmt);
1215 if ((Start < t1) != (delta < 0))
1216 return false; /* time_t overflow */
1220 /* Add relative hours, minutes, and seconds. Ignore leap seconds;
1221 i.e. "+ 10 minutes" means 600 seconds, even if one of them is a
1222 leap second. Typically this is not what the user wants, but it's
1223 too hard to do it the other way, because the time zone indicator
1224 must be applied before relative times, and if mktime is applied
1225 again the time zone will be lost. */
1227 long int sum_ns = pc.seconds.tv_nsec + pc.rel_ns;
1228 long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
1230 long int d1 = 60 * 60 * pc.rel_hour;
1231 time_t t1 = t0 + d1;
1232 long int d2 = 60 * pc.rel_minutes;
1233 time_t t2 = t1 + d2;
1234 long int d3 = pc.rel_seconds;
1235 time_t t3 = t2 + d3;
1236 long int d4 = (sum_ns - normalized_ns) / BILLION;
1237 time_t t4 = t3 + d4;
1239 if ((d1 / (60 * 60) ^ pc.rel_hour)
1240 | (d2 / 60 ^ pc.rel_minutes)
1241 | ((t1 < t0) ^ (d1 < 0))
1242 | ((t2 < t1) ^ (d2 < 0))
1243 | ((t3 < t2) ^ (d3 < 0))
1244 | ((t4 < t3) ^ (d4 < 0)))
1247 result->tv_sec = t4;
1248 result->tv_nsec = normalized_ns;
1258 main (int ac, char **av)
1262 printf ("Enter date, or blank line to exit.\n\t> ");
1265 buff[BUFSIZ - 1] = 0;
1266 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1269 struct tm const *tm;
1270 if (! get_date (&d, buff, NULL))
1271 printf ("Bad format - couldn't convert.\n");
1272 else if (! (tm = localtime (&d.tv_sec)))
1274 long int sec = d.tv_sec;
1275 printf ("localtime (%ld) failed\n", sec);
1280 printf ("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1281 tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
1282 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);
1289 #endif /* defined TEST */