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. */
60 #if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII)
61 # define IN_CTYPE_DOMAIN(c) 1
63 # define IN_CTYPE_DOMAIN(c) isascii (c)
66 #define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c))
67 #define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c))
68 #define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c))
70 /* ISDIGIT differs from isdigit, as follows:
71 - Its arg may be any int or unsigned int; it need not be an unsigned char.
72 - It's guaranteed to evaluate its argument exactly once.
73 - It's typically faster.
74 POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
75 isdigit unless it's important to use the locale's definition
76 of `digit' even when the host does not conform to POSIX. */
77 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
79 #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
80 # define __attribute__(x)
83 #ifndef ATTRIBUTE_UNUSED
84 # define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
87 /* Shift A right by B bits portably, by dividing A by 2**B and
88 truncating towards minus infinity. A and B should be free of side
89 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
90 INT_BITS is the number of useful bits in an int. GNU code can
91 assume that INT_BITS is at least 32.
93 ISO C99 says that A >> B is implementation-defined if A < 0. Some
94 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
95 right in the usual way when A < 0, so SHR falls back on division if
96 ordinary A >> B doesn't seem to be the usual signed shift. */
100 : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
102 #define EPOCH_YEAR 1970
103 #define TM_YEAR_BASE 1900
105 #define HOUR(x) ((x) * 60)
107 /* An integer value, and the number of digits in its textual
115 /* An entry in the lexical lookup table. */
123 /* Meridian: am, pm, or 24-hour style. */
124 enum { MERam, MERpm, MER24 };
126 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
128 /* Information passed to and from the parser. */
131 /* The input string remaining to be parsed. */
134 /* N, if this is the Nth Tuesday. */
135 long int day_ordinal;
137 /* Day of week; Sunday is 0. */
140 /* tm_isdst flag for the local zone. */
143 /* Time zone, in minutes east of UTC. */
146 /* Style used for time. */
149 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
155 struct timespec seconds; /* includes nanoseconds */
157 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
162 long int rel_minutes;
163 long int rel_seconds;
166 /* Counts of nonterminals of various flavors parsed so far. */
170 size_t local_zones_seen;
175 /* Table of local time zone abbrevations, terminated by a null entry. */
176 table local_time_zone_table[3];
180 static int yylex (union YYSTYPE *, parser_control *);
181 static int yyerror (parser_control *, char *);
185 /* We want a reentrant parser, even if the TZ manipulation and the calls to
186 localtime and gmtime are not reentrant. */
188 %parse-param { parser_control *pc }
189 %lex-param { parser_control *pc }
191 /* This grammar has 13 shift/reduce conflicts. */
198 struct timespec timespec;
203 %token <intval> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN
204 %token <intval> tMINUTE_UNIT tMONTH tMONTH_UNIT tSEC_UNIT tYEAR_UNIT tZONE
206 %token <textintval> tSNUMBER tUNUMBER
207 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
209 %type <intval> o_merid
210 %type <timespec> seconds signed_seconds unsigned_seconds
223 pc->timespec_seen = true;
234 { pc->times_seen++; }
236 { pc->local_zones_seen++; }
238 { pc->zones_seen++; }
240 { pc->dates_seen++; }
253 pc->seconds.tv_sec = 0;
254 pc->seconds.tv_nsec = 0;
257 | tUNUMBER ':' tUNUMBER o_merid
260 pc->minutes = $3.value;
261 pc->seconds.tv_sec = 0;
262 pc->seconds.tv_nsec = 0;
265 | tUNUMBER ':' tUNUMBER tSNUMBER
268 pc->minutes = $3.value;
269 pc->seconds.tv_sec = 0;
270 pc->seconds.tv_nsec = 0;
271 pc->meridian = MER24;
273 pc->time_zone = $4.value % 100 + ($4.value / 100) * 60;
275 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
278 pc->minutes = $3.value;
282 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER
285 pc->minutes = $3.value;
287 pc->meridian = MER24;
289 pc->time_zone = $6.value % 100 + ($6.value / 100) * 60;
295 { pc->local_isdst = $1; }
297 { pc->local_isdst = $1 < 0 ? 1 : $1 + 1; }
302 { pc->time_zone = $1; }
304 { pc->time_zone = $1 + 60; }
306 { pc->time_zone = $1 + 60; }
322 pc->day_ordinal = $1.value;
328 tUNUMBER '/' tUNUMBER
330 pc->month = $1.value;
333 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
335 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
336 otherwise as MM/DD/YY.
337 The goal in recognizing YYYY/MM/DD is solely to support legacy
338 machine-generated dates like those in an RCS log listing. If
339 you want portability, use the ISO 8601 format. */
343 pc->month = $3.value;
348 pc->month = $1.value;
353 | tUNUMBER tSNUMBER tSNUMBER
355 /* ISO 8601 format. YYYY-MM-DD. */
357 pc->month = -$2.value;
360 | tUNUMBER tMONTH tSNUMBER
362 /* e.g. 17-JUN-1992. */
365 pc->year.value = -$3.value;
366 pc->year.digits = $3.digits;
368 | tMONTH tSNUMBER tSNUMBER
370 /* e.g. JUN-17-1992. */
373 pc->year.value = -$3.value;
374 pc->year.digits = $3.digits;
381 | tMONTH tUNUMBER ',' tUNUMBER
392 | tUNUMBER tMONTH tUNUMBER
403 pc->rel_ns = -pc->rel_ns;
404 pc->rel_seconds = -pc->rel_seconds;
405 pc->rel_minutes = -pc->rel_minutes;
406 pc->rel_hour = -pc->rel_hour;
407 pc->rel_day = -pc->rel_day;
408 pc->rel_month = -pc->rel_month;
409 pc->rel_year = -pc->rel_year;
416 { pc->rel_year += $1.value * $2; }
417 | tSNUMBER tYEAR_UNIT
418 { pc->rel_year += $1.value * $2; }
420 { pc->rel_year += $1; }
421 | tUNUMBER tMONTH_UNIT
422 { pc->rel_month += $1.value * $2; }
423 | tSNUMBER tMONTH_UNIT
424 { pc->rel_month += $1.value * $2; }
426 { pc->rel_month += $1; }
428 { pc->rel_day += $1.value * $2; }
430 { pc->rel_day += $1.value * $2; }
432 { pc->rel_day += $1; }
433 | tUNUMBER tHOUR_UNIT
434 { pc->rel_hour += $1.value * $2; }
435 | tSNUMBER tHOUR_UNIT
436 { pc->rel_hour += $1.value * $2; }
438 { pc->rel_hour += $1; }
439 | tUNUMBER tMINUTE_UNIT
440 { pc->rel_minutes += $1.value * $2; }
441 | tSNUMBER tMINUTE_UNIT
442 { pc->rel_minutes += $1.value * $2; }
444 { pc->rel_minutes += $1; }
446 { pc->rel_seconds += $1.value * $2; }
448 { pc->rel_seconds += $1.value * $2; }
449 | tSDECIMAL_NUMBER tSEC_UNIT
450 { pc->rel_seconds += $1.tv_sec * $2; pc->rel_ns += $1.tv_nsec * $2; }
451 | tUDECIMAL_NUMBER tSEC_UNIT
452 { pc->rel_seconds += $1.tv_sec * $2; pc->rel_ns += $1.tv_nsec * $2; }
454 { pc->rel_seconds += $1; }
457 seconds: signed_seconds | unsigned_seconds;
462 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
468 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
475 && ! pc->rels_seen && (pc->times_seen || 2 < $1.digits))
482 pc->day = $1.value % 100;
483 pc->month = ($1.value / 100) % 100;
484 pc->year.value = $1.value / 10000;
485 pc->year.digits = $1.digits - 4;
497 pc->hour = $1.value / 100;
498 pc->minutes = $1.value % 100;
500 pc->seconds.tv_sec = 0;
501 pc->seconds.tv_nsec = 0;
502 pc->meridian = MER24;
517 static table const meridian_table[] =
519 { "AM", tMERIDIAN, MERam },
520 { "A.M.", tMERIDIAN, MERam },
521 { "PM", tMERIDIAN, MERpm },
522 { "P.M.", tMERIDIAN, MERpm },
526 static table const dst_table[] =
531 static table const month_and_day_table[] =
533 { "JANUARY", tMONTH, 1 },
534 { "FEBRUARY", tMONTH, 2 },
535 { "MARCH", tMONTH, 3 },
536 { "APRIL", tMONTH, 4 },
537 { "MAY", tMONTH, 5 },
538 { "JUNE", tMONTH, 6 },
539 { "JULY", tMONTH, 7 },
540 { "AUGUST", tMONTH, 8 },
541 { "SEPTEMBER",tMONTH, 9 },
542 { "SEPT", tMONTH, 9 },
543 { "OCTOBER", tMONTH, 10 },
544 { "NOVEMBER", tMONTH, 11 },
545 { "DECEMBER", tMONTH, 12 },
546 { "SUNDAY", tDAY, 0 },
547 { "MONDAY", tDAY, 1 },
548 { "TUESDAY", tDAY, 2 },
550 { "WEDNESDAY",tDAY, 3 },
551 { "WEDNES", tDAY, 3 },
552 { "THURSDAY", tDAY, 4 },
554 { "THURS", tDAY, 4 },
555 { "FRIDAY", tDAY, 5 },
556 { "SATURDAY", tDAY, 6 },
560 static table const time_units_table[] =
562 { "YEAR", tYEAR_UNIT, 1 },
563 { "MONTH", tMONTH_UNIT, 1 },
564 { "FORTNIGHT",tDAY_UNIT, 14 },
565 { "WEEK", tDAY_UNIT, 7 },
566 { "DAY", tDAY_UNIT, 1 },
567 { "HOUR", tHOUR_UNIT, 1 },
568 { "MINUTE", tMINUTE_UNIT, 1 },
569 { "MIN", tMINUTE_UNIT, 1 },
570 { "SECOND", tSEC_UNIT, 1 },
571 { "SEC", tSEC_UNIT, 1 },
575 /* Assorted relative-time words. */
576 static table const relative_time_table[] =
578 { "TOMORROW", tDAY_UNIT, 1 },
579 { "YESTERDAY",tDAY_UNIT, -1 },
580 { "TODAY", tDAY_UNIT, 0 },
581 { "NOW", tDAY_UNIT, 0 },
582 { "LAST", tUNUMBER, -1 },
583 { "THIS", tUNUMBER, 0 },
584 { "NEXT", tUNUMBER, 1 },
585 { "FIRST", tUNUMBER, 1 },
586 /*{ "SECOND", tUNUMBER, 2 }, */
587 { "THIRD", tUNUMBER, 3 },
588 { "FOURTH", tUNUMBER, 4 },
589 { "FIFTH", tUNUMBER, 5 },
590 { "SIXTH", tUNUMBER, 6 },
591 { "SEVENTH", tUNUMBER, 7 },
592 { "EIGHTH", tUNUMBER, 8 },
593 { "NINTH", tUNUMBER, 9 },
594 { "TENTH", tUNUMBER, 10 },
595 { "ELEVENTH", tUNUMBER, 11 },
596 { "TWELFTH", tUNUMBER, 12 },
601 /* The time zone table. This table is necessarily incomplete, as time
602 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
603 as Eastern time in Australia, not as US Eastern Standard Time.
604 You cannot rely on getdate to handle arbitrary time zone
605 abbreviations; use numeric abbreviations like `-0500' instead. */
606 static table const time_zone_table[] =
608 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
609 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
610 { "UTC", tZONE, HOUR ( 0) },
611 { "WET", tZONE, HOUR ( 0) }, /* Western European */
612 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
613 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
614 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
615 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
616 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
617 { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
618 { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
619 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
620 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
621 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
622 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
623 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
624 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
625 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
626 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
627 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
628 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
629 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
630 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
631 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
632 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
633 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
634 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
635 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
636 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
637 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
638 { "CET", tZONE, HOUR ( 1) }, /* Central European */
639 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
640 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
641 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
642 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
643 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
644 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
645 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
646 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
647 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
648 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
649 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
650 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
651 { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
652 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
653 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
654 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
655 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
656 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
657 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
661 /* Military time zone table. */
662 static table const military_table[] =
664 { "A", tZONE, -HOUR ( 1) },
665 { "B", tZONE, -HOUR ( 2) },
666 { "C", tZONE, -HOUR ( 3) },
667 { "D", tZONE, -HOUR ( 4) },
668 { "E", tZONE, -HOUR ( 5) },
669 { "F", tZONE, -HOUR ( 6) },
670 { "G", tZONE, -HOUR ( 7) },
671 { "H", tZONE, -HOUR ( 8) },
672 { "I", tZONE, -HOUR ( 9) },
673 { "K", tZONE, -HOUR (10) },
674 { "L", tZONE, -HOUR (11) },
675 { "M", tZONE, -HOUR (12) },
676 { "N", tZONE, HOUR ( 1) },
677 { "O", tZONE, HOUR ( 2) },
678 { "P", tZONE, HOUR ( 3) },
679 { "Q", tZONE, HOUR ( 4) },
680 { "R", tZONE, HOUR ( 5) },
681 { "S", tZONE, HOUR ( 6) },
682 { "T", tZONE, HOUR ( 7) },
683 { "U", tZONE, HOUR ( 8) },
684 { "V", tZONE, HOUR ( 9) },
685 { "W", tZONE, HOUR (10) },
686 { "X", tZONE, HOUR (11) },
687 { "Y", tZONE, HOUR (12) },
688 { "Z", tZONE, HOUR ( 0) },
695 to_hour (long int hours, int meridian)
699 default: /* Pacify GCC. */
701 return 0 <= hours && hours < 24 ? hours : -1;
703 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
705 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
710 to_year (textint textyear)
712 long int year = textyear.value;
717 /* XPG4 suggests that years 00-68 map to 2000-2068, and
718 years 69-99 map to 1969-1999. */
719 else if (textyear.digits == 2)
720 year += year < 69 ? 2000 : 1900;
726 lookup_zone (parser_control const *pc, char const *name)
730 /* Try local zone abbreviations first; they're more likely to be right. */
731 for (tp = pc->local_time_zone_table; tp->name; tp++)
732 if (strcmp (name, tp->name) == 0)
735 for (tp = time_zone_table; tp->name; tp++)
736 if (strcmp (name, tp->name) == 0)
743 /* Yield the difference between *A and *B,
744 measured in seconds, ignoring leap seconds.
745 The body of this function is taken directly from the GNU C Library;
746 see src/strftime.c. */
748 tm_diff (struct tm const *a, struct tm const *b)
750 /* Compute intervening leap days correctly even if year is negative.
751 Take care to avoid int overflow in leap day calculations. */
752 int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
753 int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
754 int a100 = a4 / 25 - (a4 % 25 < 0);
755 int b100 = b4 / 25 - (b4 % 25 < 0);
756 int a400 = SHR (a100, 2);
757 int b400 = SHR (b100, 2);
758 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
759 long int ayear = a->tm_year;
760 long int years = ayear - b->tm_year;
761 long int days = (365 * years + intervening_leap_days
762 + (a->tm_yday - b->tm_yday));
763 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
764 + (a->tm_min - b->tm_min))
765 + (a->tm_sec - b->tm_sec));
767 #endif /* ! HAVE_TM_GMTOFF */
770 lookup_word (parser_control const *pc, char *word)
779 /* Make it uppercase. */
780 for (p = word; *p; p++)
782 unsigned char ch = *p;
787 for (tp = meridian_table; tp->name; tp++)
788 if (strcmp (word, tp->name) == 0)
791 /* See if we have an abbreviation for a month. */
792 wordlen = strlen (word);
793 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
795 for (tp = month_and_day_table; tp->name; tp++)
796 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
799 if ((tp = lookup_zone (pc, word)))
802 if (strcmp (word, dst_table[0].name) == 0)
805 for (tp = time_units_table; tp->name; tp++)
806 if (strcmp (word, tp->name) == 0)
809 /* Strip off any plural and try the units table again. */
810 if (word[wordlen - 1] == 'S')
812 word[wordlen - 1] = '\0';
813 for (tp = time_units_table; tp->name; tp++)
814 if (strcmp (word, tp->name) == 0)
816 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
819 for (tp = relative_time_table; tp->name; tp++)
820 if (strcmp (word, tp->name) == 0)
823 /* Military time zones. */
825 for (tp = military_table; tp->name; tp++)
826 if (word[0] == tp->name[0])
829 /* Drop out any periods and try the time zone table again. */
830 for (period_found = false, p = q = word; (*p = *q); q++)
835 if (period_found && (tp = lookup_zone (pc, word)))
842 yylex (YYSTYPE *lvalp, parser_control *pc)
849 while (c = *pc->input, ISSPACE (c))
852 if (ISDIGIT (c) || c == '-' || c == '+')
856 unsigned long int value;
857 if (c == '-' || c == '+')
859 sign = c == '-' ? -1 : 1;
860 while (c = *++pc->input, ISSPACE (c))
863 /* skip the '-' sign */
869 for (value = 0; ; value *= 10)
871 unsigned long int value1 = value + (c - '0');
878 if (ULONG_MAX / 10 < value)
881 if ((c == '.' || c == ',') && ISDIGIT (p[1]))
886 unsigned long int value1;
888 /* Check for overflow when converting value to time_t. */
906 /* Accumulate fraction, to ns precision. */
909 for (digits = 2; digits <= LOG10_BILLION; digits++)
916 /* Skip excess digits, truncating toward -Infinity. */
918 for (; ISDIGIT (*p); p++)
927 /* Adjust to the timespec convention, which is that
928 tv_nsec is always a positive offset even if tv_sec is
938 lvalp->timespec.tv_sec = s;
939 lvalp->timespec.tv_nsec = ns;
941 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
947 lvalp->textintval.value = - value;
948 if (0 < lvalp->textintval.value)
953 lvalp->textintval.value = value;
954 if (lvalp->textintval.value < 0)
957 lvalp->textintval.digits = p - pc->input;
959 return sign ? tSNUMBER : tUNUMBER;
971 if (p < buff + sizeof buff - 1)
975 while (ISALPHA (c) || c == '.');
978 tp = lookup_word (pc, buff);
981 lvalp->intval = tp->value;
1002 /* Do nothing if the parser reports an error. */
1004 yyerror (parser_control *pc ATTRIBUTE_UNUSED, char *s ATTRIBUTE_UNUSED)
1009 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1010 passing it to mktime, return true if it's OK that mktime returned T.
1011 It's not OK if *TM0 has out-of-range members. */
1014 mktime_ok (struct tm const *tm0, struct tm const *tm1, time_t t)
1016 if (t == (time_t) -1)
1018 /* Guard against falsely reporting an error when parsing a time
1019 stamp that happens to equal (time_t) -1, on a host that
1020 supports such a time stamp. */
1021 tm1 = localtime (&t);
1026 return ! ((tm0->tm_sec ^ tm1->tm_sec)
1027 | (tm0->tm_min ^ tm1->tm_min)
1028 | (tm0->tm_hour ^ tm1->tm_hour)
1029 | (tm0->tm_mday ^ tm1->tm_mday)
1030 | (tm0->tm_mon ^ tm1->tm_mon)
1031 | (tm0->tm_year ^ tm1->tm_year));
1034 /* A reasonable upper bound for the size of ordinary TZ strings.
1035 Use heap allocation if TZ's length exceeds this. */
1036 enum { TZBUFSIZE = 100 };
1038 /* Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
1041 get_tz (char tzbuf[TZBUFSIZE])
1043 char *tz = getenv ("TZ");
1046 size_t tzsize = strlen (tz) + 1;
1047 tz = (tzsize <= TZBUFSIZE
1048 ? memcpy (tzbuf, tz, tzsize)
1049 : xmemdup (tz, tzsize));
1054 /* Parse a date/time string, storing the resulting time value into *RESULT.
1055 The string itself is pointed to by P. Return true if successful.
1056 P can be an incomplete or relative time specification; if so, use
1057 *NOW as the basis for the returned time. */
1059 get_date (struct timespec *result, char const *p, struct timespec const *now)
1063 struct tm const *tmp;
1067 struct timespec gettime_buffer;
1069 bool tz_was_altered = false;
1071 char tz0buf[TZBUFSIZE];
1076 if (gettime (&gettime_buffer) != 0)
1078 now = &gettime_buffer;
1081 Start = now->tv_sec;
1082 Start_ns = now->tv_nsec;
1084 tmp = localtime (&now->tv_sec);
1088 while (c = *p, ISSPACE (c))
1091 if (strncmp (p, "TZ=\"", 4) == 0)
1093 char const *tzbase = p + 4;
1097 for (s = tzbase; *s; s++, tzsize++)
1101 if (! (*s == '\\' || *s == '"'))
1108 char tz1buf[TZBUFSIZE];
1109 bool large_tz = TZBUFSIZE < tzsize;
1111 tz0 = get_tz (tz0buf);
1112 z = tz1 = large_tz ? xmalloc (tzsize) : tz1buf;
1113 for (s = tzbase; *s != '"'; s++)
1114 *z++ = *(s += *s == '\\');
1116 setenv_ok = setenv ("TZ", tz1, 1) == 0;
1121 tz_was_altered = true;
1127 pc.year.value = tmp->tm_year;
1128 pc.year.value += TM_YEAR_BASE;
1130 pc.month = tmp->tm_mon + 1;
1131 pc.day = tmp->tm_mday;
1132 pc.hour = tmp->tm_hour;
1133 pc.minutes = tmp->tm_min;
1134 pc.seconds.tv_sec = tmp->tm_sec;
1135 pc.seconds.tv_nsec = Start_ns;
1136 tm.tm_isdst = tmp->tm_isdst;
1138 pc.meridian = MER24;
1146 pc.timespec_seen = false;
1151 pc.local_zones_seen = 0;
1154 #if HAVE_STRUCT_TM_TM_ZONE
1155 pc.local_time_zone_table[0].name = tmp->tm_zone;
1156 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1157 pc.local_time_zone_table[0].value = tmp->tm_isdst;
1158 pc.local_time_zone_table[1].name = NULL;
1160 /* Probe the names used in the next three calendar quarters, looking
1161 for a tm_isdst different from the one we already have. */
1164 for (quarter = 1; quarter <= 3; quarter++)
1166 time_t probe = Start + quarter * (90 * 24 * 60 * 60);
1167 struct tm const *probe_tm = localtime (&probe);
1168 if (probe_tm && probe_tm->tm_zone
1169 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
1172 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
1173 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1174 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
1175 pc.local_time_zone_table[2].name = NULL;
1185 extern char *tzname[];
1188 for (i = 0; i < 2; i++)
1190 pc.local_time_zone_table[i].name = tzname[i];
1191 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1192 pc.local_time_zone_table[i].value = i;
1194 pc.local_time_zone_table[i].name = NULL;
1197 pc.local_time_zone_table[0].name = NULL;
1201 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1202 && ! strcmp (pc.local_time_zone_table[0].name,
1203 pc.local_time_zone_table[1].name))
1205 /* This locale uses the same abbrevation for standard and
1206 daylight times. So if we see that abbreviation, we don't
1207 know whether it's daylight time. */
1208 pc.local_time_zone_table[0].value = -1;
1209 pc.local_time_zone_table[1].name = NULL;
1212 if (yyparse (&pc) != 0)
1215 if (pc.timespec_seen)
1216 *result = pc.seconds;
1219 if (1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen
1220 || 1 < (pc.local_zones_seen + pc.zones_seen)
1221 || (pc.local_zones_seen && 1 < pc.local_isdst))
1224 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
1225 tm.tm_mon = pc.month - 1;
1226 tm.tm_mday = pc.day;
1227 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1229 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1232 tm.tm_min = pc.minutes;
1233 tm.tm_sec = pc.seconds.tv_sec;
1237 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1238 pc.seconds.tv_nsec = 0;
1241 /* Let mktime deduce tm_isdst if we have an absolute time stamp. */
1242 if (pc.dates_seen | pc.days_seen | pc.times_seen)
1245 /* But if the input explicitly specifies local time with or without
1246 DST, give mktime that information. */
1247 if (pc.local_zones_seen)
1248 tm.tm_isdst = pc.local_isdst;
1252 Start = mktime (&tm);
1254 if (! mktime_ok (&tm0, &tm, Start))
1256 if (! pc.zones_seen)
1260 /* Guard against falsely reporting errors near the time_t
1261 boundaries when parsing times in other time zones. For
1262 example, suppose the input string "1969-12-31 23:00:00 -0100",
1263 the current time zone is 8 hours ahead of UTC, and the min
1264 time_t value is 1970-01-01 00:00:00 UTC. Then the min
1265 localtime value is 1970-01-01 08:00:00, and mktime will
1266 therefore fail on 1969-12-31 23:00:00. To work around the
1267 problem, set the time zone to 1 hour behind UTC temporarily
1268 by setting TZ="XXX1:00" and try mktime again. */
1270 long int time_zone = pc.time_zone;
1271 long int abs_time_zone = time_zone < 0 ? - time_zone : time_zone;
1272 long int abs_time_zone_hour = abs_time_zone / 60;
1273 int abs_time_zone_min = abs_time_zone % 60;
1274 char tz1buf[sizeof "XXX+0:00"
1275 + sizeof pc.time_zone * CHAR_BIT / 3];
1276 if (!tz_was_altered)
1277 tz0 = get_tz (tz0buf);
1278 sprintf (tz1buf, "XXX%s%ld:%02d", "-" + (time_zone < 0),
1279 abs_time_zone_hour, abs_time_zone_min);
1280 if (setenv ("TZ", tz1buf, 1) != 0)
1282 tz_was_altered = true;
1284 Start = mktime (&tm);
1285 if (! mktime_ok (&tm0, &tm, Start))
1290 if (pc.days_seen && ! pc.dates_seen)
1292 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1293 + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
1295 Start = mktime (&tm);
1296 if (Start == (time_t) -1)
1302 long int delta = pc.time_zone * 60;
1304 #ifdef HAVE_TM_GMTOFF
1305 delta -= tm.tm_gmtoff;
1308 struct tm const *gmt = gmtime (&t);
1311 delta -= tm_diff (&tm, gmt);
1314 if ((Start < t1) != (delta < 0))
1315 goto fail; /* time_t overflow */
1319 /* Add relative date. */
1320 if (pc.rel_year | pc.rel_month | pc.rel_day)
1322 int year = tm.tm_year + pc.rel_year;
1323 int month = tm.tm_mon + pc.rel_month;
1324 int day = tm.tm_mday + pc.rel_day;
1325 if (((year < tm.tm_year) ^ (pc.rel_year < 0))
1326 | ((month < tm.tm_mon) ^ (pc.rel_month < 0))
1327 | ((day < tm.tm_mday) ^ (pc.rel_day < 0)))
1332 Start = mktime (&tm);
1333 if (Start == (time_t) -1)
1337 /* Add relative hours, minutes, and seconds. On hosts that support
1338 leap seconds, ignore the possibility of leap seconds; e.g.,
1339 "+ 10 minutes" adds 600 seconds, even if one of them is a
1340 leap second. Typically this is not what the user wants, but it's
1341 too hard to do it the other way, because the time zone indicator
1342 must be applied before relative times, and if mktime is applied
1343 again the time zone will be lost. */
1345 long int sum_ns = pc.seconds.tv_nsec + pc.rel_ns;
1346 long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
1348 long int d1 = 60 * 60 * pc.rel_hour;
1349 time_t t1 = t0 + d1;
1350 long int d2 = 60 * pc.rel_minutes;
1351 time_t t2 = t1 + d2;
1352 long int d3 = pc.rel_seconds;
1353 time_t t3 = t2 + d3;
1354 long int d4 = (sum_ns - normalized_ns) / BILLION;
1355 time_t t4 = t3 + d4;
1357 if ((d1 / (60 * 60) ^ pc.rel_hour)
1358 | (d2 / 60 ^ pc.rel_minutes)
1359 | ((t1 < t0) ^ (d1 < 0))
1360 | ((t2 < t1) ^ (d2 < 0))
1361 | ((t3 < t2) ^ (d3 < 0))
1362 | ((t4 < t3) ^ (d4 < 0)))
1365 result->tv_sec = t4;
1366 result->tv_nsec = normalized_ns;
1376 ok &= (tz0 ? setenv ("TZ", tz0, 1) : unsetenv ("TZ")) == 0;
1385 main (int ac, char **av)
1389 printf ("Enter date, or blank line to exit.\n\t> ");
1392 buff[BUFSIZ - 1] = '\0';
1393 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1396 struct tm const *tm;
1397 if (! get_date (&d, buff, NULL))
1398 printf ("Bad format - couldn't convert.\n");
1399 else if (! (tm = localtime (&d.tv_sec)))
1401 long int sec = d.tv_sec;
1402 printf ("localtime (%ld) failed\n", sec);
1407 printf ("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1408 tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
1409 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);