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)
81 # include "unlocked-io.h"
84 #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
85 # define __attribute__(x)
88 #ifndef ATTRIBUTE_UNUSED
89 # define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
92 #define EPOCH_YEAR 1970
93 #define TM_YEAR_BASE 1900
95 #define HOUR(x) ((x) * 60)
97 /* An integer value, and the number of digits in its textual
105 /* An entry in the lexical lookup table. */
113 /* Meridian: am, pm, or 24-hour style. */
114 enum { MERam, MERpm, MER24 };
116 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
118 /* Information passed to and from the parser. */
121 /* The input string remaining to be parsed. */
124 /* N, if this is the Nth Tuesday. */
125 long int day_ordinal;
127 /* Day of week; Sunday is 0. */
130 /* tm_isdst flag for the local zone. */
133 /* Time zone, in minutes east of UTC. */
136 /* Style used for time. */
139 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
145 struct timespec seconds; /* includes nanoseconds */
147 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
152 long int rel_minutes;
153 long int rel_seconds;
156 /* Counts of nonterminals of various flavors parsed so far. */
160 size_t local_zones_seen;
165 /* Table of local time zone abbrevations, terminated by a null entry. */
166 table local_time_zone_table[3];
169 #define PC (* (parser_control *) parm)
170 #define YYLEX_PARAM parm
171 #define YYPARSE_PARAM parm
173 static int yyerror ();
178 /* We want a reentrant parser. */
181 /* This grammar has 13 shift/reduce conflicts. */
188 struct timespec timespec;
193 %token <intval> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN
194 %token <intval> tMINUTE_UNIT tMONTH tMONTH_UNIT tSEC_UNIT tYEAR_UNIT tZONE
196 %token <textintval> tSNUMBER tUNUMBER
197 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
199 %type <intval> o_merid
200 %type <timespec> seconds signed_seconds unsigned_seconds
213 PC.timespec_seen = true;
226 { PC.local_zones_seen++; }
243 PC.seconds.tv_sec = 0;
244 PC.seconds.tv_nsec = 0;
247 | tUNUMBER ':' tUNUMBER o_merid
250 PC.minutes = $3.value;
251 PC.seconds.tv_sec = 0;
252 PC.seconds.tv_nsec = 0;
255 | tUNUMBER ':' tUNUMBER tSNUMBER
258 PC.minutes = $3.value;
259 PC.seconds.tv_sec = 0;
260 PC.seconds.tv_nsec = 0;
263 PC.time_zone = $4.value % 100 + ($4.value / 100) * 60;
265 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
268 PC.minutes = $3.value;
272 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER
275 PC.minutes = $3.value;
279 PC.time_zone = $6.value % 100 + ($6.value / 100) * 60;
285 { PC.local_isdst = $1; }
287 { PC.local_isdst = $1 < 0 ? 1 : $1 + 1; }
292 { PC.time_zone = $1; }
294 { PC.time_zone = $1 + 60; }
296 { PC.time_zone = $1 + 60; }
312 PC.day_ordinal = $1.value;
318 tUNUMBER '/' tUNUMBER
323 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
325 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
326 otherwise as MM/DD/YY.
327 The goal in recognizing YYYY/MM/DD is solely to support legacy
328 machine-generated dates like those in an RCS log listing. If
329 you want portability, use the ISO 8601 format. */
343 | tUNUMBER tSNUMBER tSNUMBER
345 /* ISO 8601 format. YYYY-MM-DD. */
347 PC.month = -$2.value;
350 | tUNUMBER tMONTH tSNUMBER
352 /* e.g. 17-JUN-1992. */
355 PC.year.value = -$3.value;
356 PC.year.digits = $3.digits;
358 | tMONTH tSNUMBER tSNUMBER
360 /* e.g. JUN-17-1992. */
363 PC.year.value = -$3.value;
364 PC.year.digits = $3.digits;
371 | tMONTH tUNUMBER ',' tUNUMBER
382 | tUNUMBER tMONTH tUNUMBER
393 PC.rel_ns = -PC.rel_ns;
394 PC.rel_seconds = -PC.rel_seconds;
395 PC.rel_minutes = -PC.rel_minutes;
396 PC.rel_hour = -PC.rel_hour;
397 PC.rel_day = -PC.rel_day;
398 PC.rel_month = -PC.rel_month;
399 PC.rel_year = -PC.rel_year;
406 { PC.rel_year += $1.value * $2; }
407 | tSNUMBER tYEAR_UNIT
408 { PC.rel_year += $1.value * $2; }
410 { PC.rel_year += $1; }
411 | tUNUMBER tMONTH_UNIT
412 { PC.rel_month += $1.value * $2; }
413 | tSNUMBER tMONTH_UNIT
414 { PC.rel_month += $1.value * $2; }
416 { PC.rel_month += $1; }
418 { PC.rel_day += $1.value * $2; }
420 { PC.rel_day += $1.value * $2; }
422 { PC.rel_day += $1; }
423 | tUNUMBER tHOUR_UNIT
424 { PC.rel_hour += $1.value * $2; }
425 | tSNUMBER tHOUR_UNIT
426 { PC.rel_hour += $1.value * $2; }
428 { PC.rel_hour += $1; }
429 | tUNUMBER tMINUTE_UNIT
430 { PC.rel_minutes += $1.value * $2; }
431 | tSNUMBER tMINUTE_UNIT
432 { PC.rel_minutes += $1.value * $2; }
434 { PC.rel_minutes += $1; }
436 { PC.rel_seconds += $1.value * $2; }
438 { PC.rel_seconds += $1.value * $2; }
439 | tSDECIMAL_NUMBER tSEC_UNIT
440 { PC.rel_seconds += $1.tv_sec * $2; PC.rel_ns += $1.tv_nsec * $2; }
441 | tUDECIMAL_NUMBER tSEC_UNIT
442 { PC.rel_seconds += $1.tv_sec * $2; PC.rel_ns += $1.tv_nsec * $2; }
444 { PC.rel_seconds += $1; }
447 seconds: signed_seconds | unsigned_seconds;
452 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
458 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
465 && ! PC.rels_seen && (PC.times_seen || 2 < $1.digits))
472 PC.day = $1.value % 100;
473 PC.month = ($1.value / 100) % 100;
474 PC.year.value = $1.value / 10000;
475 PC.year.digits = $1.digits - 4;
487 PC.hour = $1.value / 100;
488 PC.minutes = $1.value % 100;
490 PC.seconds.tv_sec = 0;
491 PC.seconds.tv_nsec = 0;
507 static table const meridian_table[] =
509 { "AM", tMERIDIAN, MERam },
510 { "A.M.", tMERIDIAN, MERam },
511 { "PM", tMERIDIAN, MERpm },
512 { "P.M.", tMERIDIAN, MERpm },
516 static table const dst_table[] =
521 static table const month_and_day_table[] =
523 { "JANUARY", tMONTH, 1 },
524 { "FEBRUARY", tMONTH, 2 },
525 { "MARCH", tMONTH, 3 },
526 { "APRIL", tMONTH, 4 },
527 { "MAY", tMONTH, 5 },
528 { "JUNE", tMONTH, 6 },
529 { "JULY", tMONTH, 7 },
530 { "AUGUST", tMONTH, 8 },
531 { "SEPTEMBER",tMONTH, 9 },
532 { "SEPT", tMONTH, 9 },
533 { "OCTOBER", tMONTH, 10 },
534 { "NOVEMBER", tMONTH, 11 },
535 { "DECEMBER", tMONTH, 12 },
536 { "SUNDAY", tDAY, 0 },
537 { "MONDAY", tDAY, 1 },
538 { "TUESDAY", tDAY, 2 },
540 { "WEDNESDAY",tDAY, 3 },
541 { "WEDNES", tDAY, 3 },
542 { "THURSDAY", tDAY, 4 },
544 { "THURS", tDAY, 4 },
545 { "FRIDAY", tDAY, 5 },
546 { "SATURDAY", tDAY, 6 },
550 static table const time_units_table[] =
552 { "YEAR", tYEAR_UNIT, 1 },
553 { "MONTH", tMONTH_UNIT, 1 },
554 { "FORTNIGHT",tDAY_UNIT, 14 },
555 { "WEEK", tDAY_UNIT, 7 },
556 { "DAY", tDAY_UNIT, 1 },
557 { "HOUR", tHOUR_UNIT, 1 },
558 { "MINUTE", tMINUTE_UNIT, 1 },
559 { "MIN", tMINUTE_UNIT, 1 },
560 { "SECOND", tSEC_UNIT, 1 },
561 { "SEC", tSEC_UNIT, 1 },
565 /* Assorted relative-time words. */
566 static table const relative_time_table[] =
568 { "TOMORROW", tDAY_UNIT, 1 },
569 { "YESTERDAY",tDAY_UNIT, -1 },
570 { "TODAY", tDAY_UNIT, 0 },
571 { "NOW", tDAY_UNIT, 0 },
572 { "LAST", tUNUMBER, -1 },
573 { "THIS", tUNUMBER, 0 },
574 { "NEXT", tUNUMBER, 1 },
575 { "FIRST", tUNUMBER, 1 },
576 /*{ "SECOND", tUNUMBER, 2 }, */
577 { "THIRD", tUNUMBER, 3 },
578 { "FOURTH", tUNUMBER, 4 },
579 { "FIFTH", tUNUMBER, 5 },
580 { "SIXTH", tUNUMBER, 6 },
581 { "SEVENTH", tUNUMBER, 7 },
582 { "EIGHTH", tUNUMBER, 8 },
583 { "NINTH", tUNUMBER, 9 },
584 { "TENTH", tUNUMBER, 10 },
585 { "ELEVENTH", tUNUMBER, 11 },
586 { "TWELFTH", tUNUMBER, 12 },
591 /* The time zone table. This table is necessarily incomplete, as time
592 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
593 as Eastern time in Australia, not as US Eastern Standard Time.
594 You cannot rely on getdate to handle arbitrary time zone
595 abbreviations; use numeric abbreviations like `-0500' instead. */
596 static table const time_zone_table[] =
598 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
599 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
600 { "UTC", tZONE, HOUR ( 0) },
601 { "WET", tZONE, HOUR ( 0) }, /* Western European */
602 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
603 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
604 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
605 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
606 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
607 { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
608 { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
609 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
610 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
611 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
612 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
613 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
614 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
615 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
616 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
617 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
618 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
619 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
620 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
621 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
622 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
623 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
624 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
625 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
626 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
627 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
628 { "CET", tZONE, HOUR ( 1) }, /* Central European */
629 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
630 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
631 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
632 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
633 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
634 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
635 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
636 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
637 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
638 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
639 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
640 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
641 { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
642 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
643 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
644 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
645 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
646 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
647 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
651 /* Military time zone table. */
652 static table const military_table[] =
654 { "A", tZONE, -HOUR ( 1) },
655 { "B", tZONE, -HOUR ( 2) },
656 { "C", tZONE, -HOUR ( 3) },
657 { "D", tZONE, -HOUR ( 4) },
658 { "E", tZONE, -HOUR ( 5) },
659 { "F", tZONE, -HOUR ( 6) },
660 { "G", tZONE, -HOUR ( 7) },
661 { "H", tZONE, -HOUR ( 8) },
662 { "I", tZONE, -HOUR ( 9) },
663 { "K", tZONE, -HOUR (10) },
664 { "L", tZONE, -HOUR (11) },
665 { "M", tZONE, -HOUR (12) },
666 { "N", tZONE, HOUR ( 1) },
667 { "O", tZONE, HOUR ( 2) },
668 { "P", tZONE, HOUR ( 3) },
669 { "Q", tZONE, HOUR ( 4) },
670 { "R", tZONE, HOUR ( 5) },
671 { "S", tZONE, HOUR ( 6) },
672 { "T", tZONE, HOUR ( 7) },
673 { "U", tZONE, HOUR ( 8) },
674 { "V", tZONE, HOUR ( 9) },
675 { "W", tZONE, HOUR (10) },
676 { "X", tZONE, HOUR (11) },
677 { "Y", tZONE, HOUR (12) },
678 { "Z", tZONE, HOUR ( 0) },
685 to_hour (long int hours, int meridian)
690 return 0 <= hours && hours < 24 ? hours : -1;
692 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
694 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
702 to_year (textint textyear)
704 long int year = textyear.value;
709 /* XPG4 suggests that years 00-68 map to 2000-2068, and
710 years 69-99 map to 1969-1999. */
711 else if (textyear.digits == 2)
712 year += year < 69 ? 2000 : 1900;
718 lookup_zone (parser_control const *pc, char const *name)
722 /* Try local zone abbreviations first; they're more likely to be right. */
723 for (tp = pc->local_time_zone_table; tp->name; tp++)
724 if (strcmp (name, tp->name) == 0)
727 for (tp = time_zone_table; tp->name; tp++)
728 if (strcmp (name, tp->name) == 0)
735 /* Yield the difference between *A and *B,
736 measured in seconds, ignoring leap seconds.
737 The body of this function is taken directly from the GNU C Library;
738 see src/strftime.c. */
740 tm_diff (struct tm const *a, struct tm const *b)
742 /* Compute intervening leap days correctly even if year is negative.
743 Take care to avoid int overflow in leap day calculations. */
744 int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3);
745 int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3);
746 int a100 = a4 / 25 - (a4 % 25 < 0);
747 int b100 = b4 / 25 - (b4 % 25 < 0);
748 int a400 = a100 >> 2;
749 int b400 = b100 >> 2;
750 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
751 long int ayear = a->tm_year;
752 long int years = ayear - b->tm_year;
753 long int days = (365 * years + intervening_leap_days
754 + (a->tm_yday - b->tm_yday));
755 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
756 + (a->tm_min - b->tm_min))
757 + (a->tm_sec - b->tm_sec));
759 #endif /* ! HAVE_TM_GMTOFF */
762 lookup_word (parser_control const *pc, char *word)
771 /* Make it uppercase. */
772 for (p = word; *p; p++)
774 unsigned char ch = *p;
779 for (tp = meridian_table; tp->name; tp++)
780 if (strcmp (word, tp->name) == 0)
783 /* See if we have an abbreviation for a month. */
784 wordlen = strlen (word);
785 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
787 for (tp = month_and_day_table; tp->name; tp++)
788 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
791 if ((tp = lookup_zone (pc, word)))
794 if (strcmp (word, dst_table[0].name) == 0)
797 for (tp = time_units_table; tp->name; tp++)
798 if (strcmp (word, tp->name) == 0)
801 /* Strip off any plural and try the units table again. */
802 if (word[wordlen - 1] == 'S')
804 word[wordlen - 1] = '\0';
805 for (tp = time_units_table; tp->name; tp++)
806 if (strcmp (word, tp->name) == 0)
808 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
811 for (tp = relative_time_table; tp->name; tp++)
812 if (strcmp (word, tp->name) == 0)
815 /* Military time zones. */
817 for (tp = military_table; tp->name; tp++)
818 if (word[0] == tp->name[0])
821 /* Drop out any periods and try the time zone table again. */
822 for (period_found = false, p = q = word; (*p = *q); q++)
827 if (period_found && (tp = lookup_zone (pc, word)))
834 yylex (YYSTYPE *lvalp, parser_control *pc)
841 while (c = *pc->input, ISSPACE (c))
844 if (ISDIGIT (c) || c == '-' || c == '+')
848 unsigned long int value;
849 if (c == '-' || c == '+')
851 sign = c == '-' ? -1 : 1;
852 while (c = *++pc->input, ISSPACE (c))
855 /* skip the '-' sign */
861 for (value = 0; ; value *= 10)
863 unsigned long int value1 = value + (c - '0');
870 if (ULONG_MAX / 10 < value)
873 if ((c == '.' || c == ',') && ISDIGIT (p[1]))
878 unsigned long int value1;
880 /* Check for overflow when converting value to time_t. */
898 /* Accumulate fraction, to ns precision. */
901 for (digits = 2; digits <= LOG10_BILLION; digits++)
908 /* Skip excess digits, truncating toward -Infinity. */
910 for (; ISDIGIT (*p); p++)
919 /* Adjust to the timespec convention, which is that
920 tv_nsec is always a positive offset even if tv_sec is
930 lvalp->timespec.tv_sec = s;
931 lvalp->timespec.tv_nsec = ns;
933 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
939 lvalp->textintval.value = - value;
940 if (0 < lvalp->textintval.value)
945 lvalp->textintval.value = value;
946 if (lvalp->textintval.value < 0)
949 lvalp->textintval.digits = p - pc->input;
951 return sign ? tSNUMBER : tUNUMBER;
963 if (p < buff + sizeof buff - 1)
967 while (ISALPHA (c) || c == '.');
970 tp = lookup_word (pc, buff);
973 lvalp->intval = tp->value;
994 /* Do nothing if the parser reports an error. */
996 yyerror (char *s ATTRIBUTE_UNUSED)
1001 /* Parse a date/time string, storing the resulting time value into *RESULT.
1002 The string itself is pointed to by P. Return true if successful.
1003 P can be an incomplete or relative time specification; if so, use
1004 *NOW as the basis for the returned time. */
1006 get_date (struct timespec *result, char const *p, struct timespec const *now)
1010 struct tm const *tmp;
1014 struct timespec gettime_buffer;
1018 if (gettime (&gettime_buffer) != 0)
1020 now = &gettime_buffer;
1023 Start = now->tv_sec;
1024 Start_ns = now->tv_nsec;
1026 tmp = localtime (&now->tv_sec);
1031 pc.year.value = tmp->tm_year;
1032 pc.year.value += TM_YEAR_BASE;
1034 pc.month = tmp->tm_mon + 1;
1035 pc.day = tmp->tm_mday;
1036 pc.hour = tmp->tm_hour;
1037 pc.minutes = tmp->tm_min;
1038 pc.seconds.tv_sec = tmp->tm_sec;
1039 pc.seconds.tv_nsec = Start_ns;
1040 tm.tm_isdst = tmp->tm_isdst;
1042 pc.meridian = MER24;
1050 pc.timespec_seen = false;
1055 pc.local_zones_seen = 0;
1058 #if HAVE_STRUCT_TM_TM_ZONE
1059 pc.local_time_zone_table[0].name = tmp->tm_zone;
1060 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1061 pc.local_time_zone_table[0].value = tmp->tm_isdst;
1062 pc.local_time_zone_table[1].name = 0;
1064 /* Probe the names used in the next three calendar quarters, looking
1065 for a tm_isdst different from the one we already have. */
1068 for (quarter = 1; quarter <= 3; quarter++)
1070 time_t probe = Start + quarter * (90 * 24 * 60 * 60);
1071 struct tm const *probe_tm = localtime (&probe);
1072 if (probe_tm && probe_tm->tm_zone
1073 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
1076 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
1077 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1078 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
1079 pc.local_time_zone_table[2].name = 0;
1089 extern char *tzname[];
1092 for (i = 0; i < 2; i++)
1094 pc.local_time_zone_table[i].name = tzname[i];
1095 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1096 pc.local_time_zone_table[i].value = i;
1098 pc.local_time_zone_table[i].name = 0;
1101 pc.local_time_zone_table[0].name = 0;
1105 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1106 && ! strcmp (pc.local_time_zone_table[0].name,
1107 pc.local_time_zone_table[1].name))
1109 /* This locale uses the same abbrevation for standard and
1110 daylight times. So if we see that abbreviation, we don't
1111 know whether it's daylight time. */
1112 pc.local_time_zone_table[0].value = -1;
1113 pc.local_time_zone_table[1].name = 0;
1116 if (yyparse (&pc) != 0)
1119 if (pc.timespec_seen)
1121 *result = pc.seconds;
1125 if (1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen
1126 || 1 < (pc.local_zones_seen + pc.zones_seen)
1127 || (pc.local_zones_seen && 1 < pc.local_isdst))
1130 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE + pc.rel_year;
1131 tm.tm_mon = pc.month - 1 + pc.rel_month;
1132 tm.tm_mday = pc.day + pc.rel_day;
1133 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1135 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1138 tm.tm_min = pc.minutes;
1139 tm.tm_sec = pc.seconds.tv_sec;
1143 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1144 pc.seconds.tv_nsec = 0;
1147 /* Let mktime deduce tm_isdst if we have an absolute time stamp,
1148 or if the relative time stamp mentions days, months, or years. */
1149 if (pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day
1150 | pc.rel_month | pc.rel_year)
1153 /* But if the input explicitly specifies local time with or without
1154 DST, give mktime that information. */
1155 if (pc.local_zones_seen)
1156 tm.tm_isdst = pc.local_isdst;
1160 Start = mktime (&tm);
1162 if (Start == (time_t) -1)
1165 /* Guard against falsely reporting errors near the time_t boundaries
1166 when parsing times in other time zones. For example, if the min
1167 time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
1168 of UTC, then the min localtime value is 1970-01-01 08:00:00; if
1169 we apply mktime to 1970-01-01 00:00:00 we will get an error, so
1170 we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
1171 zone by 24 hours to compensate. This algorithm assumes that
1172 there is no DST transition within a day of the time_t boundaries. */
1176 if (tm.tm_year <= EPOCH_YEAR - TM_YEAR_BASE)
1179 pc.time_zone += 24 * 60;
1184 pc.time_zone -= 24 * 60;
1186 Start = mktime (&tm);
1189 if (Start == (time_t) -1)
1193 if (pc.days_seen && ! pc.dates_seen)
1195 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1196 + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
1198 Start = mktime (&tm);
1199 if (Start == (time_t) -1)
1205 long int delta = pc.time_zone * 60;
1207 #ifdef HAVE_TM_GMTOFF
1208 delta -= tm.tm_gmtoff;
1211 struct tm const *gmt = gmtime (&t);
1214 delta -= tm_diff (&tm, gmt);
1217 if ((Start < t1) != (delta < 0))
1218 return false; /* time_t overflow */
1222 /* Add relative hours, minutes, and seconds. Ignore leap seconds;
1223 i.e. "+ 10 minutes" means 600 seconds, even if one of them is a
1224 leap second. Typically this is not what the user wants, but it's
1225 too hard to do it the other way, because the time zone indicator
1226 must be applied before relative times, and if mktime is applied
1227 again the time zone will be lost. */
1229 long int sum_ns = pc.seconds.tv_nsec + pc.rel_ns;
1230 long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
1232 long int d1 = 60 * 60 * pc.rel_hour;
1233 time_t t1 = t0 + d1;
1234 long int d2 = 60 * pc.rel_minutes;
1235 time_t t2 = t1 + d2;
1236 long int d3 = pc.rel_seconds;
1237 time_t t3 = t2 + d3;
1238 long int d4 = (sum_ns - normalized_ns) / BILLION;
1239 time_t t4 = t3 + d4;
1241 if ((d1 / (60 * 60) ^ pc.rel_hour)
1242 | (d2 / 60 ^ pc.rel_minutes)
1243 | ((t1 < t0) ^ (d1 < 0))
1244 | ((t2 < t1) ^ (d2 < 0))
1245 | ((t3 < t2) ^ (d3 < 0))
1246 | ((t4 < t3) ^ (d4 < 0)))
1249 result->tv_sec = t4;
1250 result->tv_nsec = normalized_ns;
1260 main (int ac, char **av)
1264 printf ("Enter date, or blank line to exit.\n\t> ");
1267 buff[BUFSIZ - 1] = 0;
1268 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1271 struct tm const *tm;
1272 if (! get_date (&d, buff, NULL))
1273 printf ("Bad format - couldn't convert.\n");
1274 else if (! (tm = localtime (&d.tv_sec)))
1276 long int sec = d.tv_sec;
1277 printf ("localtime (%ld) failed\n", sec);
1282 printf ("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1283 tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
1284 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);
1291 #endif /* defined TEST */