2 /* Parse a string into an internal time stamp.
3 Copyright (C) 1999, 2000, 2002, 2003, 2004 Free Software Foundation, Inc.
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2, or (at your option)
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software Foundation,
17 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
19 /* Originally written by Steven M. Bellovin <smb@research.att.com> while
20 at the University of North Carolina at Chapel Hill. Later tweaked by
21 a couple of people on Usenet. Completely overhauled by Rich $alz
22 <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
24 Modified by Paul Eggert <eggert@twinsun.com> in August 1999 to do
25 the right thing about local DST. Also modified by Paul Eggert
26 <eggert@cs.ucla.edu> in February 2004 to support
27 nanosecond-resolution time stamps, and in October 2004 to support
28 TZ strings in dates. */
30 /* FIXME: Check for arithmetic overflow in all cases, not just
41 /* Since the code of getdate.y is not included in the Emacs executable
42 itself, there is no need to #define static in this file. Even if
43 the code were included in the Emacs executable, it probably
44 wouldn't do any harm to #undef it here; this will only cause
45 problems if we try to write to a static variable, which I don't
46 think this code needs to do. */
59 #if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII)
60 # define IN_CTYPE_DOMAIN(c) 1
62 # define IN_CTYPE_DOMAIN(c) isascii (c)
65 #define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c))
66 #define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c))
67 #define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c))
69 /* ISDIGIT differs from isdigit, 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 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)
78 #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
79 # define __attribute__(x)
82 #ifndef ATTRIBUTE_UNUSED
83 # define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
86 #define EPOCH_YEAR 1970
87 #define TM_YEAR_BASE 1900
89 #define HOUR(x) ((x) * 60)
91 /* An integer value, and the number of digits in its textual
99 /* An entry in the lexical lookup table. */
107 /* Meridian: am, pm, or 24-hour style. */
108 enum { MERam, MERpm, MER24 };
110 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
112 /* Information passed to and from the parser. */
115 /* The input string remaining to be parsed. */
118 /* N, if this is the Nth Tuesday. */
119 long int day_ordinal;
121 /* Day of week; Sunday is 0. */
124 /* tm_isdst flag for the local zone. */
127 /* Time zone, in minutes east of UTC. */
130 /* Style used for time. */
133 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
139 struct timespec seconds; /* includes nanoseconds */
141 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
146 long int rel_minutes;
147 long int rel_seconds;
150 /* Counts of nonterminals of various flavors parsed so far. */
154 size_t local_zones_seen;
159 /* Table of local time zone abbrevations, terminated by a null entry. */
160 table local_time_zone_table[3];
164 static int yylex (union YYSTYPE *, parser_control *);
165 static int yyerror (parser_control *, char *);
169 /* We want a reentrant parser, even if the TZ manipulation and the calls to
170 localtime and gmtime are not reentrant. */
172 %parse-param { parser_control *pc }
173 %lex-param { parser_control *pc }
175 /* This grammar has 13 shift/reduce conflicts. */
182 struct timespec timespec;
187 %token <intval> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN
188 %token <intval> tMINUTE_UNIT tMONTH tMONTH_UNIT tSEC_UNIT tYEAR_UNIT tZONE
190 %token <textintval> tSNUMBER tUNUMBER
191 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
193 %type <intval> o_merid
194 %type <timespec> seconds signed_seconds unsigned_seconds
207 pc->timespec_seen = true;
218 { pc->times_seen++; }
220 { pc->local_zones_seen++; }
222 { pc->zones_seen++; }
224 { pc->dates_seen++; }
237 pc->seconds.tv_sec = 0;
238 pc->seconds.tv_nsec = 0;
241 | tUNUMBER ':' tUNUMBER o_merid
244 pc->minutes = $3.value;
245 pc->seconds.tv_sec = 0;
246 pc->seconds.tv_nsec = 0;
249 | tUNUMBER ':' tUNUMBER tSNUMBER
252 pc->minutes = $3.value;
253 pc->seconds.tv_sec = 0;
254 pc->seconds.tv_nsec = 0;
255 pc->meridian = MER24;
257 pc->time_zone = $4.value % 100 + ($4.value / 100) * 60;
259 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
262 pc->minutes = $3.value;
266 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER
269 pc->minutes = $3.value;
271 pc->meridian = MER24;
273 pc->time_zone = $6.value % 100 + ($6.value / 100) * 60;
279 { pc->local_isdst = $1; }
281 { pc->local_isdst = $1 < 0 ? 1 : $1 + 1; }
286 { pc->time_zone = $1; }
288 { pc->time_zone = $1 + 60; }
290 { pc->time_zone = $1 + 60; }
306 pc->day_ordinal = $1.value;
312 tUNUMBER '/' tUNUMBER
314 pc->month = $1.value;
317 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
319 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
320 otherwise as MM/DD/YY.
321 The goal in recognizing YYYY/MM/DD is solely to support legacy
322 machine-generated dates like those in an RCS log listing. If
323 you want portability, use the ISO 8601 format. */
327 pc->month = $3.value;
332 pc->month = $1.value;
337 | tUNUMBER tSNUMBER tSNUMBER
339 /* ISO 8601 format. YYYY-MM-DD. */
341 pc->month = -$2.value;
344 | tUNUMBER tMONTH tSNUMBER
346 /* e.g. 17-JUN-1992. */
349 pc->year.value = -$3.value;
350 pc->year.digits = $3.digits;
352 | tMONTH tSNUMBER tSNUMBER
354 /* e.g. JUN-17-1992. */
357 pc->year.value = -$3.value;
358 pc->year.digits = $3.digits;
365 | tMONTH tUNUMBER ',' tUNUMBER
376 | tUNUMBER tMONTH tUNUMBER
387 pc->rel_ns = -pc->rel_ns;
388 pc->rel_seconds = -pc->rel_seconds;
389 pc->rel_minutes = -pc->rel_minutes;
390 pc->rel_hour = -pc->rel_hour;
391 pc->rel_day = -pc->rel_day;
392 pc->rel_month = -pc->rel_month;
393 pc->rel_year = -pc->rel_year;
400 { pc->rel_year += $1.value * $2; }
401 | tSNUMBER tYEAR_UNIT
402 { pc->rel_year += $1.value * $2; }
404 { pc->rel_year += $1; }
405 | tUNUMBER tMONTH_UNIT
406 { pc->rel_month += $1.value * $2; }
407 | tSNUMBER tMONTH_UNIT
408 { pc->rel_month += $1.value * $2; }
410 { pc->rel_month += $1; }
412 { pc->rel_day += $1.value * $2; }
414 { pc->rel_day += $1.value * $2; }
416 { pc->rel_day += $1; }
417 | tUNUMBER tHOUR_UNIT
418 { pc->rel_hour += $1.value * $2; }
419 | tSNUMBER tHOUR_UNIT
420 { pc->rel_hour += $1.value * $2; }
422 { pc->rel_hour += $1; }
423 | tUNUMBER tMINUTE_UNIT
424 { pc->rel_minutes += $1.value * $2; }
425 | tSNUMBER tMINUTE_UNIT
426 { pc->rel_minutes += $1.value * $2; }
428 { pc->rel_minutes += $1; }
430 { pc->rel_seconds += $1.value * $2; }
432 { pc->rel_seconds += $1.value * $2; }
433 | tSDECIMAL_NUMBER tSEC_UNIT
434 { pc->rel_seconds += $1.tv_sec * $2; pc->rel_ns += $1.tv_nsec * $2; }
435 | tUDECIMAL_NUMBER tSEC_UNIT
436 { pc->rel_seconds += $1.tv_sec * $2; pc->rel_ns += $1.tv_nsec * $2; }
438 { pc->rel_seconds += $1; }
441 seconds: signed_seconds | unsigned_seconds;
446 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
452 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
459 && ! pc->rels_seen && (pc->times_seen || 2 < $1.digits))
466 pc->day = $1.value % 100;
467 pc->month = ($1.value / 100) % 100;
468 pc->year.value = $1.value / 10000;
469 pc->year.digits = $1.digits - 4;
481 pc->hour = $1.value / 100;
482 pc->minutes = $1.value % 100;
484 pc->seconds.tv_sec = 0;
485 pc->seconds.tv_nsec = 0;
486 pc->meridian = MER24;
501 static table const meridian_table[] =
503 { "AM", tMERIDIAN, MERam },
504 { "A.M.", tMERIDIAN, MERam },
505 { "PM", tMERIDIAN, MERpm },
506 { "P.M.", tMERIDIAN, MERpm },
510 static table const dst_table[] =
515 static table const month_and_day_table[] =
517 { "JANUARY", tMONTH, 1 },
518 { "FEBRUARY", tMONTH, 2 },
519 { "MARCH", tMONTH, 3 },
520 { "APRIL", tMONTH, 4 },
521 { "MAY", tMONTH, 5 },
522 { "JUNE", tMONTH, 6 },
523 { "JULY", tMONTH, 7 },
524 { "AUGUST", tMONTH, 8 },
525 { "SEPTEMBER",tMONTH, 9 },
526 { "SEPT", tMONTH, 9 },
527 { "OCTOBER", tMONTH, 10 },
528 { "NOVEMBER", tMONTH, 11 },
529 { "DECEMBER", tMONTH, 12 },
530 { "SUNDAY", tDAY, 0 },
531 { "MONDAY", tDAY, 1 },
532 { "TUESDAY", tDAY, 2 },
534 { "WEDNESDAY",tDAY, 3 },
535 { "WEDNES", tDAY, 3 },
536 { "THURSDAY", tDAY, 4 },
538 { "THURS", tDAY, 4 },
539 { "FRIDAY", tDAY, 5 },
540 { "SATURDAY", tDAY, 6 },
544 static table const time_units_table[] =
546 { "YEAR", tYEAR_UNIT, 1 },
547 { "MONTH", tMONTH_UNIT, 1 },
548 { "FORTNIGHT",tDAY_UNIT, 14 },
549 { "WEEK", tDAY_UNIT, 7 },
550 { "DAY", tDAY_UNIT, 1 },
551 { "HOUR", tHOUR_UNIT, 1 },
552 { "MINUTE", tMINUTE_UNIT, 1 },
553 { "MIN", tMINUTE_UNIT, 1 },
554 { "SECOND", tSEC_UNIT, 1 },
555 { "SEC", tSEC_UNIT, 1 },
559 /* Assorted relative-time words. */
560 static table const relative_time_table[] =
562 { "TOMORROW", tDAY_UNIT, 1 },
563 { "YESTERDAY",tDAY_UNIT, -1 },
564 { "TODAY", tDAY_UNIT, 0 },
565 { "NOW", tDAY_UNIT, 0 },
566 { "LAST", tUNUMBER, -1 },
567 { "THIS", tUNUMBER, 0 },
568 { "NEXT", tUNUMBER, 1 },
569 { "FIRST", tUNUMBER, 1 },
570 /*{ "SECOND", tUNUMBER, 2 }, */
571 { "THIRD", tUNUMBER, 3 },
572 { "FOURTH", tUNUMBER, 4 },
573 { "FIFTH", tUNUMBER, 5 },
574 { "SIXTH", tUNUMBER, 6 },
575 { "SEVENTH", tUNUMBER, 7 },
576 { "EIGHTH", tUNUMBER, 8 },
577 { "NINTH", tUNUMBER, 9 },
578 { "TENTH", tUNUMBER, 10 },
579 { "ELEVENTH", tUNUMBER, 11 },
580 { "TWELFTH", tUNUMBER, 12 },
585 /* The time zone table. This table is necessarily incomplete, as time
586 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
587 as Eastern time in Australia, not as US Eastern Standard Time.
588 You cannot rely on getdate to handle arbitrary time zone
589 abbreviations; use numeric abbreviations like `-0500' instead. */
590 static table const time_zone_table[] =
592 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
593 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
594 { "UTC", tZONE, HOUR ( 0) },
595 { "WET", tZONE, HOUR ( 0) }, /* Western European */
596 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
597 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
598 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
599 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
600 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
601 { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
602 { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
603 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
604 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
605 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
606 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
607 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
608 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
609 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
610 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
611 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
612 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
613 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
614 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
615 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
616 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
617 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
618 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
619 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
620 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
621 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
622 { "CET", tZONE, HOUR ( 1) }, /* Central European */
623 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
624 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
625 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
626 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
627 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
628 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
629 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
630 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
631 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
632 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
633 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
634 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
635 { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
636 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
637 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
638 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
639 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
640 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
641 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
645 /* Military time zone table. */
646 static table const military_table[] =
648 { "A", tZONE, -HOUR ( 1) },
649 { "B", tZONE, -HOUR ( 2) },
650 { "C", tZONE, -HOUR ( 3) },
651 { "D", tZONE, -HOUR ( 4) },
652 { "E", tZONE, -HOUR ( 5) },
653 { "F", tZONE, -HOUR ( 6) },
654 { "G", tZONE, -HOUR ( 7) },
655 { "H", tZONE, -HOUR ( 8) },
656 { "I", tZONE, -HOUR ( 9) },
657 { "K", tZONE, -HOUR (10) },
658 { "L", tZONE, -HOUR (11) },
659 { "M", tZONE, -HOUR (12) },
660 { "N", tZONE, HOUR ( 1) },
661 { "O", tZONE, HOUR ( 2) },
662 { "P", tZONE, HOUR ( 3) },
663 { "Q", tZONE, HOUR ( 4) },
664 { "R", tZONE, HOUR ( 5) },
665 { "S", tZONE, HOUR ( 6) },
666 { "T", tZONE, HOUR ( 7) },
667 { "U", tZONE, HOUR ( 8) },
668 { "V", tZONE, HOUR ( 9) },
669 { "W", tZONE, HOUR (10) },
670 { "X", tZONE, HOUR (11) },
671 { "Y", tZONE, HOUR (12) },
672 { "Z", tZONE, HOUR ( 0) },
679 to_hour (long int hours, int meridian)
683 default: /* Pacify GCC. */
685 return 0 <= hours && hours < 24 ? hours : -1;
687 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
689 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
694 to_year (textint textyear)
696 long int year = textyear.value;
701 /* XPG4 suggests that years 00-68 map to 2000-2068, and
702 years 69-99 map to 1969-1999. */
703 else if (textyear.digits == 2)
704 year += year < 69 ? 2000 : 1900;
710 lookup_zone (parser_control const *pc, char const *name)
714 /* Try local zone abbreviations first; they're more likely to be right. */
715 for (tp = pc->local_time_zone_table; tp->name; tp++)
716 if (strcmp (name, tp->name) == 0)
719 for (tp = time_zone_table; tp->name; tp++)
720 if (strcmp (name, tp->name) == 0)
727 /* Yield the difference between *A and *B,
728 measured in seconds, ignoring leap seconds.
729 The body of this function is taken directly from the GNU C Library;
730 see src/strftime.c. */
732 tm_diff (struct tm const *a, struct tm const *b)
734 /* Compute intervening leap days correctly even if year is negative.
735 Take care to avoid int overflow in leap day calculations. */
736 int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3);
737 int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3);
738 int a100 = a4 / 25 - (a4 % 25 < 0);
739 int b100 = b4 / 25 - (b4 % 25 < 0);
740 int a400 = a100 >> 2;
741 int b400 = b100 >> 2;
742 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
743 long int ayear = a->tm_year;
744 long int years = ayear - b->tm_year;
745 long int days = (365 * years + intervening_leap_days
746 + (a->tm_yday - b->tm_yday));
747 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
748 + (a->tm_min - b->tm_min))
749 + (a->tm_sec - b->tm_sec));
751 #endif /* ! HAVE_TM_GMTOFF */
754 lookup_word (parser_control const *pc, char *word)
763 /* Make it uppercase. */
764 for (p = word; *p; p++)
766 unsigned char ch = *p;
771 for (tp = meridian_table; tp->name; tp++)
772 if (strcmp (word, tp->name) == 0)
775 /* See if we have an abbreviation for a month. */
776 wordlen = strlen (word);
777 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
779 for (tp = month_and_day_table; tp->name; tp++)
780 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
783 if ((tp = lookup_zone (pc, word)))
786 if (strcmp (word, dst_table[0].name) == 0)
789 for (tp = time_units_table; tp->name; tp++)
790 if (strcmp (word, tp->name) == 0)
793 /* Strip off any plural and try the units table again. */
794 if (word[wordlen - 1] == 'S')
796 word[wordlen - 1] = '\0';
797 for (tp = time_units_table; tp->name; tp++)
798 if (strcmp (word, tp->name) == 0)
800 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
803 for (tp = relative_time_table; tp->name; tp++)
804 if (strcmp (word, tp->name) == 0)
807 /* Military time zones. */
809 for (tp = military_table; tp->name; tp++)
810 if (word[0] == tp->name[0])
813 /* Drop out any periods and try the time zone table again. */
814 for (period_found = false, p = q = word; (*p = *q); q++)
819 if (period_found && (tp = lookup_zone (pc, word)))
826 yylex (YYSTYPE *lvalp, parser_control *pc)
833 while (c = *pc->input, ISSPACE (c))
836 if (ISDIGIT (c) || c == '-' || c == '+')
840 unsigned long int value;
841 if (c == '-' || c == '+')
843 sign = c == '-' ? -1 : 1;
844 while (c = *++pc->input, ISSPACE (c))
847 /* skip the '-' sign */
853 for (value = 0; ; value *= 10)
855 unsigned long int value1 = value + (c - '0');
862 if (ULONG_MAX / 10 < value)
865 if ((c == '.' || c == ',') && ISDIGIT (p[1]))
870 unsigned long int value1;
872 /* Check for overflow when converting value to time_t. */
890 /* Accumulate fraction, to ns precision. */
893 for (digits = 2; digits <= LOG10_BILLION; digits++)
900 /* Skip excess digits, truncating toward -Infinity. */
902 for (; ISDIGIT (*p); p++)
911 /* Adjust to the timespec convention, which is that
912 tv_nsec is always a positive offset even if tv_sec is
922 lvalp->timespec.tv_sec = s;
923 lvalp->timespec.tv_nsec = ns;
925 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
931 lvalp->textintval.value = - value;
932 if (0 < lvalp->textintval.value)
937 lvalp->textintval.value = value;
938 if (lvalp->textintval.value < 0)
941 lvalp->textintval.digits = p - pc->input;
943 return sign ? tSNUMBER : tUNUMBER;
955 if (p < buff + sizeof buff - 1)
959 while (ISALPHA (c) || c == '.');
962 tp = lookup_word (pc, buff);
965 lvalp->intval = tp->value;
986 /* Do nothing if the parser reports an error. */
988 yyerror (parser_control *pc ATTRIBUTE_UNUSED, char *s ATTRIBUTE_UNUSED)
993 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
994 passing it to mktime, return true if it's OK that mktime returned T.
995 It's not OK if *TM0 has out-of-range members. */
998 mktime_ok (struct tm const *tm0, struct tm const *tm1, time_t t)
1000 if (t == (time_t) -1)
1002 /* Guard against falsely reporting an error when parsing a time
1003 stamp that happens to equal (time_t) -1, on a host that
1004 supports such a time stamp. */
1005 tm1 = localtime (&t);
1010 return ! ((tm0->tm_sec ^ tm1->tm_sec)
1011 | (tm0->tm_min ^ tm1->tm_min)
1012 | (tm0->tm_hour ^ tm1->tm_hour)
1013 | (tm0->tm_mday ^ tm1->tm_mday)
1014 | (tm0->tm_mon ^ tm1->tm_mon)
1015 | (tm0->tm_year ^ tm1->tm_year));
1018 /* A reasonable upper bound for the size of ordinary TZ strings.
1019 Use heap allocation if TZ's length exceeds this. */
1020 enum { TZBUFSIZE = 100 };
1022 /* Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
1025 get_tz (char tzbuf[TZBUFSIZE])
1027 char *tz = getenv ("TZ");
1030 size_t tzsize = strlen (tz) + 1;
1031 tz = (tzsize <= TZBUFSIZE
1032 ? memcpy (tzbuf, tz, tzsize)
1033 : xmemdup (tz, tzsize));
1038 /* Parse a date/time string, storing the resulting time value into *RESULT.
1039 The string itself is pointed to by P. Return true if successful.
1040 P can be an incomplete or relative time specification; if so, use
1041 *NOW as the basis for the returned time. */
1043 get_date (struct timespec *result, char const *p, struct timespec const *now)
1047 struct tm const *tmp;
1051 struct timespec gettime_buffer;
1053 bool tz_was_altered = false;
1055 char tz0buf[TZBUFSIZE];
1060 if (gettime (&gettime_buffer) != 0)
1062 now = &gettime_buffer;
1065 Start = now->tv_sec;
1066 Start_ns = now->tv_nsec;
1068 tmp = localtime (&now->tv_sec);
1072 while (c = *p, ISSPACE (c))
1075 if (strncmp (p, "TZ=\"", 4) == 0)
1077 char const *tzbase = p + 4;
1081 for (s = tzbase; *s; s++, tzsize++)
1085 if (! (*s == '\\' || *s == '"'))
1092 char tz1buf[TZBUFSIZE];
1093 bool large_tz = TZBUFSIZE < tzsize;
1095 tz0 = get_tz (tz0buf);
1096 z = tz1 = large_tz ? xmalloc (tzsize) : tz1buf;
1097 for (s = tzbase; *s != '"'; s++)
1098 *z++ = *(s += *s == '\\');
1100 setenv_ok = setenv ("TZ", tz1, 1) == 0;
1105 tz_was_altered = true;
1111 pc.year.value = tmp->tm_year;
1112 pc.year.value += TM_YEAR_BASE;
1114 pc.month = tmp->tm_mon + 1;
1115 pc.day = tmp->tm_mday;
1116 pc.hour = tmp->tm_hour;
1117 pc.minutes = tmp->tm_min;
1118 pc.seconds.tv_sec = tmp->tm_sec;
1119 pc.seconds.tv_nsec = Start_ns;
1120 tm.tm_isdst = tmp->tm_isdst;
1122 pc.meridian = MER24;
1130 pc.timespec_seen = false;
1135 pc.local_zones_seen = 0;
1138 #if HAVE_STRUCT_TM_TM_ZONE
1139 pc.local_time_zone_table[0].name = tmp->tm_zone;
1140 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1141 pc.local_time_zone_table[0].value = tmp->tm_isdst;
1142 pc.local_time_zone_table[1].name = NULL;
1144 /* Probe the names used in the next three calendar quarters, looking
1145 for a tm_isdst different from the one we already have. */
1148 for (quarter = 1; quarter <= 3; quarter++)
1150 time_t probe = Start + quarter * (90 * 24 * 60 * 60);
1151 struct tm const *probe_tm = localtime (&probe);
1152 if (probe_tm && probe_tm->tm_zone
1153 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
1156 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
1157 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1158 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
1159 pc.local_time_zone_table[2].name = NULL;
1169 extern char *tzname[];
1172 for (i = 0; i < 2; i++)
1174 pc.local_time_zone_table[i].name = tzname[i];
1175 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1176 pc.local_time_zone_table[i].value = i;
1178 pc.local_time_zone_table[i].name = NULL;
1181 pc.local_time_zone_table[0].name = NULL;
1185 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1186 && ! strcmp (pc.local_time_zone_table[0].name,
1187 pc.local_time_zone_table[1].name))
1189 /* This locale uses the same abbrevation for standard and
1190 daylight times. So if we see that abbreviation, we don't
1191 know whether it's daylight time. */
1192 pc.local_time_zone_table[0].value = -1;
1193 pc.local_time_zone_table[1].name = NULL;
1196 if (yyparse (&pc) != 0)
1199 if (pc.timespec_seen)
1200 *result = pc.seconds;
1203 if (1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen
1204 || 1 < (pc.local_zones_seen + pc.zones_seen)
1205 || (pc.local_zones_seen && 1 < pc.local_isdst))
1208 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
1209 tm.tm_mon = pc.month - 1;
1210 tm.tm_mday = pc.day;
1211 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1213 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1216 tm.tm_min = pc.minutes;
1217 tm.tm_sec = pc.seconds.tv_sec;
1221 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1222 pc.seconds.tv_nsec = 0;
1225 /* Let mktime deduce tm_isdst if we have an absolute time stamp. */
1226 if (pc.dates_seen | pc.days_seen | pc.times_seen)
1229 /* But if the input explicitly specifies local time with or without
1230 DST, give mktime that information. */
1231 if (pc.local_zones_seen)
1232 tm.tm_isdst = pc.local_isdst;
1236 Start = mktime (&tm);
1238 if (! mktime_ok (&tm0, &tm, Start))
1240 if (! pc.zones_seen)
1244 /* Guard against falsely reporting errors near the time_t
1245 boundaries when parsing times in other time zones. For
1246 example, suppose the input string "1969-12-31 23:00:00 -0100",
1247 the current time zone is 8 hours ahead of UTC, and the min
1248 time_t value is 1970-01-01 00:00:00 UTC. Then the min
1249 localtime value is 1970-01-01 08:00:00, and mktime will
1250 therefore fail on 1969-12-31 23:00:00. To work around the
1251 problem, set the time zone to 1 hour behind UTC temporarily
1252 by setting TZ="XXX1:00" and try mktime again. */
1254 long int time_zone = pc.time_zone;
1255 long int abs_time_zone = time_zone < 0 ? - time_zone : time_zone;
1256 long int abs_time_zone_hour = abs_time_zone / 60;
1257 int abs_time_zone_min = abs_time_zone % 60;
1258 char tz1buf[sizeof "XXX+0:00"
1259 + sizeof pc.time_zone * CHAR_BIT / 3];
1260 if (!tz_was_altered)
1261 tz0 = get_tz (tz0buf);
1262 sprintf (tz1buf, "XXX%s%ld:%02d", "-" + (time_zone < 0),
1263 abs_time_zone_hour, abs_time_zone_min);
1264 if (setenv ("TZ", tz1buf, 1) != 0)
1266 tz_was_altered = true;
1268 Start = mktime (&tm);
1269 if (! mktime_ok (&tm0, &tm, Start))
1274 if (pc.days_seen && ! pc.dates_seen)
1276 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1277 + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
1279 Start = mktime (&tm);
1280 if (Start == (time_t) -1)
1286 long int delta = pc.time_zone * 60;
1288 #ifdef HAVE_TM_GMTOFF
1289 delta -= tm.tm_gmtoff;
1292 struct tm const *gmt = gmtime (&t);
1295 delta -= tm_diff (&tm, gmt);
1298 if ((Start < t1) != (delta < 0))
1299 goto fail; /* time_t overflow */
1303 /* Add relative date. */
1304 if (pc.rel_year | pc.rel_month | pc.rel_day)
1306 int year = tm.tm_year + pc.rel_year;
1307 int month = tm.tm_mon + pc.rel_month;
1308 int day = tm.tm_mday + pc.rel_day;
1309 if (((year < tm.tm_year) ^ (pc.rel_year < 0))
1310 | (month < tm.tm_mon) ^ (pc.rel_month < 0)
1311 | (day < tm.tm_mday) ^ (pc.rel_day < 0))
1316 Start = mktime (&tm);
1317 if (Start == (time_t) -1)
1321 /* Add relative hours, minutes, and seconds. On hosts that support
1322 leap seconds, ignore the possibility of leap seconds; e.g.,
1323 "+ 10 minutes" adds 600 seconds, even if one of them is a
1324 leap second. Typically this is not what the user wants, but it's
1325 too hard to do it the other way, because the time zone indicator
1326 must be applied before relative times, and if mktime is applied
1327 again the time zone will be lost. */
1329 long int sum_ns = pc.seconds.tv_nsec + pc.rel_ns;
1330 long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
1332 long int d1 = 60 * 60 * pc.rel_hour;
1333 time_t t1 = t0 + d1;
1334 long int d2 = 60 * pc.rel_minutes;
1335 time_t t2 = t1 + d2;
1336 long int d3 = pc.rel_seconds;
1337 time_t t3 = t2 + d3;
1338 long int d4 = (sum_ns - normalized_ns) / BILLION;
1339 time_t t4 = t3 + d4;
1341 if ((d1 / (60 * 60) ^ pc.rel_hour)
1342 | (d2 / 60 ^ pc.rel_minutes)
1343 | ((t1 < t0) ^ (d1 < 0))
1344 | ((t2 < t1) ^ (d2 < 0))
1345 | ((t3 < t2) ^ (d3 < 0))
1346 | ((t4 < t3) ^ (d4 < 0)))
1349 result->tv_sec = t4;
1350 result->tv_nsec = normalized_ns;
1360 ok &= (tz0 ? setenv ("TZ", tz0, 1) : unsetenv ("TZ")) == 0;
1371 main (int ac, char **av)
1375 printf ("Enter date, or blank line to exit.\n\t> ");
1378 buff[BUFSIZ - 1] = '\0';
1379 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1382 struct tm const *tm;
1383 if (! get_date (&d, buff, NULL))
1384 printf ("Bad format - couldn't convert.\n");
1385 else if (! (tm = localtime (&d.tv_sec)))
1387 long int sec = d.tv_sec;
1388 printf ("localtime (%ld) failed\n", sec);
1393 printf ("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1394 tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
1395 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);