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;
847 while (c = *++pc->input, ISSPACE (c))
850 /* skip the '-' sign */
856 for (value = 0; ; value *= 10)
858 unsigned long int value1 = value + (c - '0');
865 if (ULONG_MAX / 10 < value)
868 if ((c == '.' || c == ',') && ISDIGIT (p[1]))
873 unsigned long int value1;
875 /* Check for overflow when converting value to time_t. */
893 /* Accumulate fraction, to ns precision. */
896 for (digits = 2; digits <= LOG10_BILLION; digits++)
903 /* Skip excess digits, truncating toward -Infinity. */
905 for (; ISDIGIT (*p); p++)
914 /* Adjust to the timespec convention, which is that
915 tv_nsec is always a positive offset even if tv_sec is
925 lvalp->timespec.tv_sec = s;
926 lvalp->timespec.tv_nsec = ns;
928 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
934 lvalp->textintval.value = - value;
935 if (0 < lvalp->textintval.value)
940 lvalp->textintval.value = value;
941 if (lvalp->textintval.value < 0)
944 lvalp->textintval.digits = p - pc->input;
946 return sign ? tSNUMBER : tUNUMBER;
958 if (p < buff + sizeof buff - 1)
962 while (ISALPHA (c) || c == '.');
965 tp = lookup_word (pc, buff);
968 lvalp->intval = tp->value;
989 /* Do nothing if the parser reports an error. */
991 yyerror (char *s ATTRIBUTE_UNUSED)
996 /* Parse a date/time string, storing the resulting time value into *RESULT.
997 The string itself is pointed to by P. Return true if successful.
998 P can be an incomplete or relative time specification; if so, use
999 *NOW as the basis for the returned time. */
1001 get_date (struct timespec *result, char const *p, struct timespec const *now)
1005 struct tm const *tmp;
1009 struct timespec gettime_buffer;
1013 if (gettime (&gettime_buffer) != 0)
1015 now = &gettime_buffer;
1018 Start = now->tv_sec;
1019 Start_ns = now->tv_nsec;
1021 tmp = localtime (&now->tv_sec);
1026 pc.year.value = tmp->tm_year;
1027 pc.year.value += TM_YEAR_BASE;
1029 pc.month = tmp->tm_mon + 1;
1030 pc.day = tmp->tm_mday;
1031 pc.hour = tmp->tm_hour;
1032 pc.minutes = tmp->tm_min;
1033 pc.seconds.tv_sec = tmp->tm_sec;
1034 pc.seconds.tv_nsec = Start_ns;
1035 tm.tm_isdst = tmp->tm_isdst;
1037 pc.meridian = MER24;
1045 pc.timespec_seen = false;
1050 pc.local_zones_seen = 0;
1053 #if HAVE_STRUCT_TM_TM_ZONE
1054 pc.local_time_zone_table[0].name = tmp->tm_zone;
1055 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1056 pc.local_time_zone_table[0].value = tmp->tm_isdst;
1057 pc.local_time_zone_table[1].name = 0;
1059 /* Probe the names used in the next three calendar quarters, looking
1060 for a tm_isdst different from the one we already have. */
1063 for (quarter = 1; quarter <= 3; quarter++)
1065 time_t probe = Start + quarter * (90 * 24 * 60 * 60);
1066 struct tm const *probe_tm = localtime (&probe);
1067 if (probe_tm && probe_tm->tm_zone
1068 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
1071 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
1072 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1073 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
1074 pc.local_time_zone_table[2].name = 0;
1084 extern char *tzname[];
1087 for (i = 0; i < 2; i++)
1089 pc.local_time_zone_table[i].name = tzname[i];
1090 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1091 pc.local_time_zone_table[i].value = i;
1093 pc.local_time_zone_table[i].name = 0;
1096 pc.local_time_zone_table[0].name = 0;
1100 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1101 && ! strcmp (pc.local_time_zone_table[0].name,
1102 pc.local_time_zone_table[1].name))
1104 /* This locale uses the same abbrevation for standard and
1105 daylight times. So if we see that abbreviation, we don't
1106 know whether it's daylight time. */
1107 pc.local_time_zone_table[0].value = -1;
1108 pc.local_time_zone_table[1].name = 0;
1111 if (yyparse (&pc) != 0)
1114 if (pc.timespec_seen)
1116 *result = pc.seconds;
1120 if (1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen
1121 || 1 < (pc.local_zones_seen + pc.zones_seen)
1122 || (pc.local_zones_seen && 1 < pc.local_isdst))
1125 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE + pc.rel_year;
1126 tm.tm_mon = pc.month - 1 + pc.rel_month;
1127 tm.tm_mday = pc.day + pc.rel_day;
1128 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1130 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1133 tm.tm_min = pc.minutes;
1134 tm.tm_sec = pc.seconds.tv_sec;
1138 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1139 pc.seconds.tv_nsec = 0;
1142 /* Let mktime deduce tm_isdst if we have an absolute time stamp,
1143 or if the relative time stamp mentions days, months, or years. */
1144 if (pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day
1145 | pc.rel_month | pc.rel_year)
1148 /* But if the input explicitly specifies local time with or without
1149 DST, give mktime that information. */
1150 if (pc.local_zones_seen)
1151 tm.tm_isdst = pc.local_isdst;
1155 Start = mktime (&tm);
1157 if (Start == (time_t) -1)
1160 /* Guard against falsely reporting errors near the time_t boundaries
1161 when parsing times in other time zones. For example, if the min
1162 time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
1163 of UTC, then the min localtime value is 1970-01-01 08:00:00; if
1164 we apply mktime to 1970-01-01 00:00:00 we will get an error, so
1165 we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
1166 zone by 24 hours to compensate. This algorithm assumes that
1167 there is no DST transition within a day of the time_t boundaries. */
1171 if (tm.tm_year <= EPOCH_YEAR - TM_YEAR_BASE)
1174 pc.time_zone += 24 * 60;
1179 pc.time_zone -= 24 * 60;
1181 Start = mktime (&tm);
1184 if (Start == (time_t) -1)
1188 if (pc.days_seen && ! pc.dates_seen)
1190 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1191 + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
1193 Start = mktime (&tm);
1194 if (Start == (time_t) -1)
1200 long int delta = pc.time_zone * 60;
1202 #ifdef HAVE_TM_GMTOFF
1203 delta -= tm.tm_gmtoff;
1206 struct tm const *gmt = gmtime (&t);
1209 delta -= tm_diff (&tm, gmt);
1212 if ((Start < t1) != (delta < 0))
1213 return false; /* time_t overflow */
1217 /* Add relative hours, minutes, and seconds. Ignore leap seconds;
1218 i.e. "+ 10 minutes" means 600 seconds, even if one of them is a
1219 leap second. Typically this is not what the user wants, but it's
1220 too hard to do it the other way, because the time zone indicator
1221 must be applied before relative times, and if mktime is applied
1222 again the time zone will be lost. */
1224 long int sum_ns = pc.seconds.tv_nsec + pc.rel_ns;
1225 long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
1227 long int d1 = 60 * 60 * pc.rel_hour;
1228 time_t t1 = t0 + d1;
1229 long int d2 = 60 * pc.rel_minutes;
1230 time_t t2 = t1 + d2;
1231 long int d3 = pc.rel_seconds;
1232 time_t t3 = t2 + d3;
1233 long int d4 = (sum_ns - normalized_ns) / BILLION;
1234 time_t t4 = t3 + d4;
1236 if ((d1 / (60 * 60) ^ pc.rel_hour)
1237 | (d2 / 60 ^ pc.rel_minutes)
1238 | ((t1 < t0) ^ (d1 < 0))
1239 | ((t2 < t1) ^ (d2 < 0))
1240 | ((t3 < t2) ^ (d3 < 0))
1241 | ((t4 < t3) ^ (d4 < 0)))
1244 result->tv_sec = t4;
1245 result->tv_nsec = normalized_ns;
1255 main (int ac, char **av)
1259 printf ("Enter date, or blank line to exit.\n\t> ");
1262 buff[BUFSIZ - 1] = 0;
1263 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1266 struct tm const *tm;
1267 if (! get_date (&d, buff, NULL))
1268 printf ("Bad format - couldn't convert.\n");
1269 else if (! (tm = localtime (&d.tv_sec)))
1271 long int sec = d.tv_sec;
1272 printf ("localtime (%ld) failed\n", sec);
1277 printf ("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1278 tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
1279 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);
1286 #endif /* defined TEST */