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 #define EPOCH_YEAR 1970
88 #define TM_YEAR_BASE 1900
90 #define HOUR(x) ((x) * 60)
92 /* An integer value, and the number of digits in its textual
100 /* An entry in the lexical lookup table. */
108 /* Meridian: am, pm, or 24-hour style. */
109 enum { MERam, MERpm, MER24 };
111 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
113 /* Information passed to and from the parser. */
116 /* The input string remaining to be parsed. */
119 /* N, if this is the Nth Tuesday. */
120 long int day_ordinal;
122 /* Day of week; Sunday is 0. */
125 /* tm_isdst flag for the local zone. */
128 /* Time zone, in minutes east of UTC. */
131 /* Style used for time. */
134 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
140 struct timespec seconds; /* includes nanoseconds */
142 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
147 long int rel_minutes;
148 long int rel_seconds;
151 /* Counts of nonterminals of various flavors parsed so far. */
155 size_t local_zones_seen;
160 /* Table of local time zone abbrevations, terminated by a null entry. */
161 table local_time_zone_table[3];
165 static int yylex (union YYSTYPE *, parser_control *);
166 static int yyerror (parser_control *, char *);
170 /* We want a reentrant parser, even if the TZ manipulation and the calls to
171 localtime and gmtime are not reentrant. */
173 %parse-param { parser_control *pc }
174 %lex-param { parser_control *pc }
176 /* This grammar has 13 shift/reduce conflicts. */
183 struct timespec timespec;
188 %token <intval> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN
189 %token <intval> tMINUTE_UNIT tMONTH tMONTH_UNIT tSEC_UNIT tYEAR_UNIT tZONE
191 %token <textintval> tSNUMBER tUNUMBER
192 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
194 %type <intval> o_merid
195 %type <timespec> seconds signed_seconds unsigned_seconds
208 pc->timespec_seen = true;
219 { pc->times_seen++; }
221 { pc->local_zones_seen++; }
223 { pc->zones_seen++; }
225 { pc->dates_seen++; }
238 pc->seconds.tv_sec = 0;
239 pc->seconds.tv_nsec = 0;
242 | tUNUMBER ':' tUNUMBER o_merid
245 pc->minutes = $3.value;
246 pc->seconds.tv_sec = 0;
247 pc->seconds.tv_nsec = 0;
250 | tUNUMBER ':' tUNUMBER tSNUMBER
253 pc->minutes = $3.value;
254 pc->seconds.tv_sec = 0;
255 pc->seconds.tv_nsec = 0;
256 pc->meridian = MER24;
258 pc->time_zone = $4.value % 100 + ($4.value / 100) * 60;
260 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
263 pc->minutes = $3.value;
267 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER
270 pc->minutes = $3.value;
272 pc->meridian = MER24;
274 pc->time_zone = $6.value % 100 + ($6.value / 100) * 60;
280 { pc->local_isdst = $1; }
282 { pc->local_isdst = $1 < 0 ? 1 : $1 + 1; }
287 { pc->time_zone = $1; }
289 { pc->time_zone = $1 + 60; }
291 { pc->time_zone = $1 + 60; }
307 pc->day_ordinal = $1.value;
313 tUNUMBER '/' tUNUMBER
315 pc->month = $1.value;
318 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
320 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
321 otherwise as MM/DD/YY.
322 The goal in recognizing YYYY/MM/DD is solely to support legacy
323 machine-generated dates like those in an RCS log listing. If
324 you want portability, use the ISO 8601 format. */
328 pc->month = $3.value;
333 pc->month = $1.value;
338 | tUNUMBER tSNUMBER tSNUMBER
340 /* ISO 8601 format. YYYY-MM-DD. */
342 pc->month = -$2.value;
345 | tUNUMBER tMONTH tSNUMBER
347 /* e.g. 17-JUN-1992. */
350 pc->year.value = -$3.value;
351 pc->year.digits = $3.digits;
353 | tMONTH tSNUMBER tSNUMBER
355 /* e.g. JUN-17-1992. */
358 pc->year.value = -$3.value;
359 pc->year.digits = $3.digits;
366 | tMONTH tUNUMBER ',' tUNUMBER
377 | tUNUMBER tMONTH tUNUMBER
388 pc->rel_ns = -pc->rel_ns;
389 pc->rel_seconds = -pc->rel_seconds;
390 pc->rel_minutes = -pc->rel_minutes;
391 pc->rel_hour = -pc->rel_hour;
392 pc->rel_day = -pc->rel_day;
393 pc->rel_month = -pc->rel_month;
394 pc->rel_year = -pc->rel_year;
401 { pc->rel_year += $1.value * $2; }
402 | tSNUMBER tYEAR_UNIT
403 { pc->rel_year += $1.value * $2; }
405 { pc->rel_year += $1; }
406 | tUNUMBER tMONTH_UNIT
407 { pc->rel_month += $1.value * $2; }
408 | tSNUMBER tMONTH_UNIT
409 { pc->rel_month += $1.value * $2; }
411 { pc->rel_month += $1; }
413 { pc->rel_day += $1.value * $2; }
415 { pc->rel_day += $1.value * $2; }
417 { pc->rel_day += $1; }
418 | tUNUMBER tHOUR_UNIT
419 { pc->rel_hour += $1.value * $2; }
420 | tSNUMBER tHOUR_UNIT
421 { pc->rel_hour += $1.value * $2; }
423 { pc->rel_hour += $1; }
424 | tUNUMBER tMINUTE_UNIT
425 { pc->rel_minutes += $1.value * $2; }
426 | tSNUMBER tMINUTE_UNIT
427 { pc->rel_minutes += $1.value * $2; }
429 { pc->rel_minutes += $1; }
431 { pc->rel_seconds += $1.value * $2; }
433 { pc->rel_seconds += $1.value * $2; }
434 | tSDECIMAL_NUMBER tSEC_UNIT
435 { pc->rel_seconds += $1.tv_sec * $2; pc->rel_ns += $1.tv_nsec * $2; }
436 | tUDECIMAL_NUMBER tSEC_UNIT
437 { pc->rel_seconds += $1.tv_sec * $2; pc->rel_ns += $1.tv_nsec * $2; }
439 { pc->rel_seconds += $1; }
442 seconds: signed_seconds | unsigned_seconds;
447 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
453 { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
460 && ! pc->rels_seen && (pc->times_seen || 2 < $1.digits))
467 pc->day = $1.value % 100;
468 pc->month = ($1.value / 100) % 100;
469 pc->year.value = $1.value / 10000;
470 pc->year.digits = $1.digits - 4;
482 pc->hour = $1.value / 100;
483 pc->minutes = $1.value % 100;
485 pc->seconds.tv_sec = 0;
486 pc->seconds.tv_nsec = 0;
487 pc->meridian = MER24;
502 static table const meridian_table[] =
504 { "AM", tMERIDIAN, MERam },
505 { "A.M.", tMERIDIAN, MERam },
506 { "PM", tMERIDIAN, MERpm },
507 { "P.M.", tMERIDIAN, MERpm },
511 static table const dst_table[] =
516 static table const month_and_day_table[] =
518 { "JANUARY", tMONTH, 1 },
519 { "FEBRUARY", tMONTH, 2 },
520 { "MARCH", tMONTH, 3 },
521 { "APRIL", tMONTH, 4 },
522 { "MAY", tMONTH, 5 },
523 { "JUNE", tMONTH, 6 },
524 { "JULY", tMONTH, 7 },
525 { "AUGUST", tMONTH, 8 },
526 { "SEPTEMBER",tMONTH, 9 },
527 { "SEPT", tMONTH, 9 },
528 { "OCTOBER", tMONTH, 10 },
529 { "NOVEMBER", tMONTH, 11 },
530 { "DECEMBER", tMONTH, 12 },
531 { "SUNDAY", tDAY, 0 },
532 { "MONDAY", tDAY, 1 },
533 { "TUESDAY", tDAY, 2 },
535 { "WEDNESDAY",tDAY, 3 },
536 { "WEDNES", tDAY, 3 },
537 { "THURSDAY", tDAY, 4 },
539 { "THURS", tDAY, 4 },
540 { "FRIDAY", tDAY, 5 },
541 { "SATURDAY", tDAY, 6 },
545 static table const time_units_table[] =
547 { "YEAR", tYEAR_UNIT, 1 },
548 { "MONTH", tMONTH_UNIT, 1 },
549 { "FORTNIGHT",tDAY_UNIT, 14 },
550 { "WEEK", tDAY_UNIT, 7 },
551 { "DAY", tDAY_UNIT, 1 },
552 { "HOUR", tHOUR_UNIT, 1 },
553 { "MINUTE", tMINUTE_UNIT, 1 },
554 { "MIN", tMINUTE_UNIT, 1 },
555 { "SECOND", tSEC_UNIT, 1 },
556 { "SEC", tSEC_UNIT, 1 },
560 /* Assorted relative-time words. */
561 static table const relative_time_table[] =
563 { "TOMORROW", tDAY_UNIT, 1 },
564 { "YESTERDAY",tDAY_UNIT, -1 },
565 { "TODAY", tDAY_UNIT, 0 },
566 { "NOW", tDAY_UNIT, 0 },
567 { "LAST", tUNUMBER, -1 },
568 { "THIS", tUNUMBER, 0 },
569 { "NEXT", tUNUMBER, 1 },
570 { "FIRST", tUNUMBER, 1 },
571 /*{ "SECOND", tUNUMBER, 2 }, */
572 { "THIRD", tUNUMBER, 3 },
573 { "FOURTH", tUNUMBER, 4 },
574 { "FIFTH", tUNUMBER, 5 },
575 { "SIXTH", tUNUMBER, 6 },
576 { "SEVENTH", tUNUMBER, 7 },
577 { "EIGHTH", tUNUMBER, 8 },
578 { "NINTH", tUNUMBER, 9 },
579 { "TENTH", tUNUMBER, 10 },
580 { "ELEVENTH", tUNUMBER, 11 },
581 { "TWELFTH", tUNUMBER, 12 },
586 /* The time zone table. This table is necessarily incomplete, as time
587 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
588 as Eastern time in Australia, not as US Eastern Standard Time.
589 You cannot rely on getdate to handle arbitrary time zone
590 abbreviations; use numeric abbreviations like `-0500' instead. */
591 static table const time_zone_table[] =
593 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
594 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
595 { "UTC", tZONE, HOUR ( 0) },
596 { "WET", tZONE, HOUR ( 0) }, /* Western European */
597 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
598 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
599 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
600 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
601 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
602 { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
603 { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
604 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
605 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
606 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
607 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
608 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
609 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
610 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
611 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
612 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
613 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
614 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
615 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
616 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
617 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
618 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
619 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
620 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
621 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
622 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
623 { "CET", tZONE, HOUR ( 1) }, /* Central European */
624 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
625 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
626 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
627 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
628 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
629 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
630 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
631 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
632 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
633 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
634 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
635 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
636 { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
637 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
638 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
639 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
640 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
641 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
642 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
646 /* Military time zone table. */
647 static table const military_table[] =
649 { "A", tZONE, -HOUR ( 1) },
650 { "B", tZONE, -HOUR ( 2) },
651 { "C", tZONE, -HOUR ( 3) },
652 { "D", tZONE, -HOUR ( 4) },
653 { "E", tZONE, -HOUR ( 5) },
654 { "F", tZONE, -HOUR ( 6) },
655 { "G", tZONE, -HOUR ( 7) },
656 { "H", tZONE, -HOUR ( 8) },
657 { "I", tZONE, -HOUR ( 9) },
658 { "K", tZONE, -HOUR (10) },
659 { "L", tZONE, -HOUR (11) },
660 { "M", tZONE, -HOUR (12) },
661 { "N", tZONE, HOUR ( 1) },
662 { "O", tZONE, HOUR ( 2) },
663 { "P", tZONE, HOUR ( 3) },
664 { "Q", tZONE, HOUR ( 4) },
665 { "R", tZONE, HOUR ( 5) },
666 { "S", tZONE, HOUR ( 6) },
667 { "T", tZONE, HOUR ( 7) },
668 { "U", tZONE, HOUR ( 8) },
669 { "V", tZONE, HOUR ( 9) },
670 { "W", tZONE, HOUR (10) },
671 { "X", tZONE, HOUR (11) },
672 { "Y", tZONE, HOUR (12) },
673 { "Z", tZONE, HOUR ( 0) },
680 to_hour (long int hours, int meridian)
684 default: /* Pacify GCC. */
686 return 0 <= hours && hours < 24 ? hours : -1;
688 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
690 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
695 to_year (textint textyear)
697 long int year = textyear.value;
702 /* XPG4 suggests that years 00-68 map to 2000-2068, and
703 years 69-99 map to 1969-1999. */
704 else if (textyear.digits == 2)
705 year += year < 69 ? 2000 : 1900;
711 lookup_zone (parser_control const *pc, char const *name)
715 /* Try local zone abbreviations first; they're more likely to be right. */
716 for (tp = pc->local_time_zone_table; tp->name; tp++)
717 if (strcmp (name, tp->name) == 0)
720 for (tp = time_zone_table; tp->name; tp++)
721 if (strcmp (name, tp->name) == 0)
728 /* Yield the difference between *A and *B,
729 measured in seconds, ignoring leap seconds.
730 The body of this function is taken directly from the GNU C Library;
731 see src/strftime.c. */
733 tm_diff (struct tm const *a, struct tm const *b)
735 /* Compute intervening leap days correctly even if year is negative.
736 Take care to avoid int overflow in leap day calculations. */
737 int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3);
738 int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3);
739 int a100 = a4 / 25 - (a4 % 25 < 0);
740 int b100 = b4 / 25 - (b4 % 25 < 0);
741 int a400 = a100 >> 2;
742 int b400 = b100 >> 2;
743 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
744 long int ayear = a->tm_year;
745 long int years = ayear - b->tm_year;
746 long int days = (365 * years + intervening_leap_days
747 + (a->tm_yday - b->tm_yday));
748 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
749 + (a->tm_min - b->tm_min))
750 + (a->tm_sec - b->tm_sec));
752 #endif /* ! HAVE_TM_GMTOFF */
755 lookup_word (parser_control const *pc, char *word)
764 /* Make it uppercase. */
765 for (p = word; *p; p++)
767 unsigned char ch = *p;
772 for (tp = meridian_table; tp->name; tp++)
773 if (strcmp (word, tp->name) == 0)
776 /* See if we have an abbreviation for a month. */
777 wordlen = strlen (word);
778 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
780 for (tp = month_and_day_table; tp->name; tp++)
781 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
784 if ((tp = lookup_zone (pc, word)))
787 if (strcmp (word, dst_table[0].name) == 0)
790 for (tp = time_units_table; tp->name; tp++)
791 if (strcmp (word, tp->name) == 0)
794 /* Strip off any plural and try the units table again. */
795 if (word[wordlen - 1] == 'S')
797 word[wordlen - 1] = '\0';
798 for (tp = time_units_table; tp->name; tp++)
799 if (strcmp (word, tp->name) == 0)
801 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
804 for (tp = relative_time_table; tp->name; tp++)
805 if (strcmp (word, tp->name) == 0)
808 /* Military time zones. */
810 for (tp = military_table; tp->name; tp++)
811 if (word[0] == tp->name[0])
814 /* Drop out any periods and try the time zone table again. */
815 for (period_found = false, p = q = word; (*p = *q); q++)
820 if (period_found && (tp = lookup_zone (pc, word)))
827 yylex (YYSTYPE *lvalp, parser_control *pc)
834 while (c = *pc->input, ISSPACE (c))
837 if (ISDIGIT (c) || c == '-' || c == '+')
841 unsigned long int value;
842 if (c == '-' || c == '+')
844 sign = c == '-' ? -1 : 1;
845 while (c = *++pc->input, ISSPACE (c))
848 /* skip the '-' sign */
854 for (value = 0; ; value *= 10)
856 unsigned long int value1 = value + (c - '0');
863 if (ULONG_MAX / 10 < value)
866 if ((c == '.' || c == ',') && ISDIGIT (p[1]))
871 unsigned long int value1;
873 /* Check for overflow when converting value to time_t. */
891 /* Accumulate fraction, to ns precision. */
894 for (digits = 2; digits <= LOG10_BILLION; digits++)
901 /* Skip excess digits, truncating toward -Infinity. */
903 for (; ISDIGIT (*p); p++)
912 /* Adjust to the timespec convention, which is that
913 tv_nsec is always a positive offset even if tv_sec is
923 lvalp->timespec.tv_sec = s;
924 lvalp->timespec.tv_nsec = ns;
926 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
932 lvalp->textintval.value = - value;
933 if (0 < lvalp->textintval.value)
938 lvalp->textintval.value = value;
939 if (lvalp->textintval.value < 0)
942 lvalp->textintval.digits = p - pc->input;
944 return sign ? tSNUMBER : tUNUMBER;
956 if (p < buff + sizeof buff - 1)
960 while (ISALPHA (c) || c == '.');
963 tp = lookup_word (pc, buff);
966 lvalp->intval = tp->value;
987 /* Do nothing if the parser reports an error. */
989 yyerror (parser_control *pc ATTRIBUTE_UNUSED, char *s ATTRIBUTE_UNUSED)
994 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
995 passing it to mktime, return true if it's OK that mktime returned T.
996 It's not OK if *TM0 has out-of-range members. */
999 mktime_ok (struct tm const *tm0, struct tm const *tm1, time_t t)
1001 if (t == (time_t) -1)
1003 /* Guard against falsely reporting an error when parsing a time
1004 stamp that happens to equal (time_t) -1, on a host that
1005 supports such a time stamp. */
1006 tm1 = localtime (&t);
1011 return ! ((tm0->tm_sec ^ tm1->tm_sec)
1012 | (tm0->tm_min ^ tm1->tm_min)
1013 | (tm0->tm_hour ^ tm1->tm_hour)
1014 | (tm0->tm_mday ^ tm1->tm_mday)
1015 | (tm0->tm_mon ^ tm1->tm_mon)
1016 | (tm0->tm_year ^ tm1->tm_year));
1019 /* A reasonable upper bound for the size of ordinary TZ strings.
1020 Use heap allocation if TZ's length exceeds this. */
1021 enum { TZBUFSIZE = 100 };
1023 /* Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
1026 get_tz (char tzbuf[TZBUFSIZE])
1028 char *tz = getenv ("TZ");
1031 size_t tzsize = strlen (tz) + 1;
1032 tz = (tzsize <= TZBUFSIZE
1033 ? memcpy (tzbuf, tz, tzsize)
1034 : xmemdup (tz, tzsize));
1039 /* Parse a date/time string, storing the resulting time value into *RESULT.
1040 The string itself is pointed to by P. Return true if successful.
1041 P can be an incomplete or relative time specification; if so, use
1042 *NOW as the basis for the returned time. */
1044 get_date (struct timespec *result, char const *p, struct timespec const *now)
1048 struct tm const *tmp;
1052 struct timespec gettime_buffer;
1054 bool tz_was_altered = false;
1056 char tz0buf[TZBUFSIZE];
1061 if (gettime (&gettime_buffer) != 0)
1063 now = &gettime_buffer;
1066 Start = now->tv_sec;
1067 Start_ns = now->tv_nsec;
1069 tmp = localtime (&now->tv_sec);
1073 while (c = *p, ISSPACE (c))
1076 if (strncmp (p, "TZ=\"", 4) == 0)
1078 char const *tzbase = p + 4;
1082 for (s = tzbase; *s; s++, tzsize++)
1086 if (! (*s == '\\' || *s == '"'))
1093 char tz1buf[TZBUFSIZE];
1094 bool large_tz = TZBUFSIZE < tzsize;
1096 tz0 = get_tz (tz0buf);
1097 z = tz1 = large_tz ? xmalloc (tzsize) : tz1buf;
1098 for (s = tzbase; *s != '"'; s++)
1099 *z++ = *(s += *s == '\\');
1101 setenv_ok = setenv ("TZ", tz1, 1) == 0;
1106 tz_was_altered = true;
1112 pc.year.value = tmp->tm_year;
1113 pc.year.value += TM_YEAR_BASE;
1115 pc.month = tmp->tm_mon + 1;
1116 pc.day = tmp->tm_mday;
1117 pc.hour = tmp->tm_hour;
1118 pc.minutes = tmp->tm_min;
1119 pc.seconds.tv_sec = tmp->tm_sec;
1120 pc.seconds.tv_nsec = Start_ns;
1121 tm.tm_isdst = tmp->tm_isdst;
1123 pc.meridian = MER24;
1131 pc.timespec_seen = false;
1136 pc.local_zones_seen = 0;
1139 #if HAVE_STRUCT_TM_TM_ZONE
1140 pc.local_time_zone_table[0].name = tmp->tm_zone;
1141 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1142 pc.local_time_zone_table[0].value = tmp->tm_isdst;
1143 pc.local_time_zone_table[1].name = NULL;
1145 /* Probe the names used in the next three calendar quarters, looking
1146 for a tm_isdst different from the one we already have. */
1149 for (quarter = 1; quarter <= 3; quarter++)
1151 time_t probe = Start + quarter * (90 * 24 * 60 * 60);
1152 struct tm const *probe_tm = localtime (&probe);
1153 if (probe_tm && probe_tm->tm_zone
1154 && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
1157 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
1158 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1159 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
1160 pc.local_time_zone_table[2].name = NULL;
1170 extern char *tzname[];
1173 for (i = 0; i < 2; i++)
1175 pc.local_time_zone_table[i].name = tzname[i];
1176 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1177 pc.local_time_zone_table[i].value = i;
1179 pc.local_time_zone_table[i].name = NULL;
1182 pc.local_time_zone_table[0].name = NULL;
1186 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1187 && ! strcmp (pc.local_time_zone_table[0].name,
1188 pc.local_time_zone_table[1].name))
1190 /* This locale uses the same abbrevation for standard and
1191 daylight times. So if we see that abbreviation, we don't
1192 know whether it's daylight time. */
1193 pc.local_time_zone_table[0].value = -1;
1194 pc.local_time_zone_table[1].name = NULL;
1197 if (yyparse (&pc) != 0)
1200 if (pc.timespec_seen)
1201 *result = pc.seconds;
1204 if (1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen
1205 || 1 < (pc.local_zones_seen + pc.zones_seen)
1206 || (pc.local_zones_seen && 1 < pc.local_isdst))
1209 tm.tm_year = to_year (pc.year) - TM_YEAR_BASE;
1210 tm.tm_mon = pc.month - 1;
1211 tm.tm_mday = pc.day;
1212 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1214 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1217 tm.tm_min = pc.minutes;
1218 tm.tm_sec = pc.seconds.tv_sec;
1222 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1223 pc.seconds.tv_nsec = 0;
1226 /* Let mktime deduce tm_isdst if we have an absolute time stamp. */
1227 if (pc.dates_seen | pc.days_seen | pc.times_seen)
1230 /* But if the input explicitly specifies local time with or without
1231 DST, give mktime that information. */
1232 if (pc.local_zones_seen)
1233 tm.tm_isdst = pc.local_isdst;
1237 Start = mktime (&tm);
1239 if (! mktime_ok (&tm0, &tm, Start))
1241 if (! pc.zones_seen)
1245 /* Guard against falsely reporting errors near the time_t
1246 boundaries when parsing times in other time zones. For
1247 example, suppose the input string "1969-12-31 23:00:00 -0100",
1248 the current time zone is 8 hours ahead of UTC, and the min
1249 time_t value is 1970-01-01 00:00:00 UTC. Then the min
1250 localtime value is 1970-01-01 08:00:00, and mktime will
1251 therefore fail on 1969-12-31 23:00:00. To work around the
1252 problem, set the time zone to 1 hour behind UTC temporarily
1253 by setting TZ="XXX1:00" and try mktime again. */
1255 long int time_zone = pc.time_zone;
1256 long int abs_time_zone = time_zone < 0 ? - time_zone : time_zone;
1257 long int abs_time_zone_hour = abs_time_zone / 60;
1258 int abs_time_zone_min = abs_time_zone % 60;
1259 char tz1buf[sizeof "XXX+0:00"
1260 + sizeof pc.time_zone * CHAR_BIT / 3];
1261 if (!tz_was_altered)
1262 tz0 = get_tz (tz0buf);
1263 sprintf (tz1buf, "XXX%s%ld:%02d", "-" + (time_zone < 0),
1264 abs_time_zone_hour, abs_time_zone_min);
1265 if (setenv ("TZ", tz1buf, 1) != 0)
1267 tz_was_altered = true;
1269 Start = mktime (&tm);
1270 if (! mktime_ok (&tm0, &tm, Start))
1275 if (pc.days_seen && ! pc.dates_seen)
1277 tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1278 + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
1280 Start = mktime (&tm);
1281 if (Start == (time_t) -1)
1287 long int delta = pc.time_zone * 60;
1289 #ifdef HAVE_TM_GMTOFF
1290 delta -= tm.tm_gmtoff;
1293 struct tm const *gmt = gmtime (&t);
1296 delta -= tm_diff (&tm, gmt);
1299 if ((Start < t1) != (delta < 0))
1300 goto fail; /* time_t overflow */
1304 /* Add relative date. */
1305 if (pc.rel_year | pc.rel_month | pc.rel_day)
1307 int year = tm.tm_year + pc.rel_year;
1308 int month = tm.tm_mon + pc.rel_month;
1309 int day = tm.tm_mday + pc.rel_day;
1310 if (((year < tm.tm_year) ^ (pc.rel_year < 0))
1311 | ((month < tm.tm_mon) ^ (pc.rel_month < 0))
1312 | ((day < tm.tm_mday) ^ (pc.rel_day < 0)))
1317 Start = mktime (&tm);
1318 if (Start == (time_t) -1)
1322 /* Add relative hours, minutes, and seconds. On hosts that support
1323 leap seconds, ignore the possibility of leap seconds; e.g.,
1324 "+ 10 minutes" adds 600 seconds, even if one of them is a
1325 leap second. Typically this is not what the user wants, but it's
1326 too hard to do it the other way, because the time zone indicator
1327 must be applied before relative times, and if mktime is applied
1328 again the time zone will be lost. */
1330 long int sum_ns = pc.seconds.tv_nsec + pc.rel_ns;
1331 long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
1333 long int d1 = 60 * 60 * pc.rel_hour;
1334 time_t t1 = t0 + d1;
1335 long int d2 = 60 * pc.rel_minutes;
1336 time_t t2 = t1 + d2;
1337 long int d3 = pc.rel_seconds;
1338 time_t t3 = t2 + d3;
1339 long int d4 = (sum_ns - normalized_ns) / BILLION;
1340 time_t t4 = t3 + d4;
1342 if ((d1 / (60 * 60) ^ pc.rel_hour)
1343 | (d2 / 60 ^ pc.rel_minutes)
1344 | ((t1 < t0) ^ (d1 < 0))
1345 | ((t2 < t1) ^ (d2 < 0))
1346 | ((t3 < t2) ^ (d3 < 0))
1347 | ((t4 < t3) ^ (d4 < 0)))
1350 result->tv_sec = t4;
1351 result->tv_nsec = normalized_ns;
1361 ok &= (tz0 ? setenv ("TZ", tz0, 1) : unsetenv ("TZ")) == 0;
1370 main (int ac, char **av)
1374 printf ("Enter date, or blank line to exit.\n\t> ");
1377 buff[BUFSIZ - 1] = '\0';
1378 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1381 struct tm const *tm;
1382 if (! get_date (&d, buff, NULL))
1383 printf ("Bad format - couldn't convert.\n");
1384 else if (! (tm = localtime (&d.tv_sec)))
1386 long int sec = d.tv_sec;
1387 printf ("localtime (%ld) failed\n", sec);
1392 printf ("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1393 tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
1394 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);