2 /* Parse a string into an internal time stamp.
4 Copyright (C) 1999-2000, 2002-2011 Free Software Foundation, Inc.
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>. */
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
35 #include "parse-datetime.h"
41 /* There's no need to extend the stack, so there's no need to involve
43 #define YYSTACK_USE_ALLOCA 0
45 /* Tell Bison how much stack space is needed. 20 should be plenty for
46 this grammar, which is not right recursive. Beware setting it too
47 high, since that might cause problems on machines whose
48 implementations have lame stack-overflow checking. */
50 #define YYINITDEPTH YYMAXDEPTH
52 /* Since the code of parse-datetime.y is not included in the Emacs executable
53 itself, there is no need to #define static in this file. Even if
54 the code were included in the Emacs executable, it probably
55 wouldn't do any harm to #undef it here; this will only cause
56 problems if we try to write to a static variable, which I don't
57 think this code needs to do. */
70 /* Bison's skeleton tests _STDLIB_H, while some stdlib.h headers
71 use _STDLIB_H_ as witness. Map the latter to the one bison uses. */
72 /* FIXME: this is temporary. Remove when we have a mechanism to ensure
73 that the version we're using is fixed, too. */
79 /* ISDIGIT differs from isdigit, as follows:
80 - Its arg may be any int or unsigned int; it need not be an unsigned char
82 - It's typically faster.
83 POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
84 isdigit unless it's important to use the locale's definition
85 of `digit' even when the host does not conform to POSIX. */
86 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
88 /* Shift A right by B bits portably, by dividing A by 2**B and
89 truncating towards minus infinity. A and B should be free of side
90 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
91 INT_BITS is the number of useful bits in an int. GNU code can
92 assume that INT_BITS is at least 32.
94 ISO C99 says that A >> B is implementation-defined if A < 0. Some
95 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
96 right in the usual way when A < 0, so SHR falls back on division if
97 ordinary A >> B doesn't seem to be the usual signed shift. */
101 : (a
) / (1 << (b
)) - ((a
) %
(1 << (b
)) < 0))
103 #define EPOCH_YEAR 1970
104 #define TM_YEAR_BASE 1900
106 #define HOUR(x) ((x) * 60)
108 /* long_time_t is a signed integer type that contains all time_t values. */
109 verify
(TYPE_IS_INTEGER
(time_t));
110 #if TIME_T_FITS_IN_LONG_INT
111 typedef
long int long_time_t
;
113 typedef
time_t long_time_t
;
116 /* Lots of this code assumes time_t and time_t-like values fit into
118 verify
(TYPE_MINIMUM
(long_time_t
) <= TYPE_MINIMUM
(time_t)
119 && TYPE_MAXIMUM
(time_t) <= TYPE_MAXIMUM
(long_time_t
));
121 /* FIXME: It also assumes that signed integer overflow silently wraps around,
122 but this is not true any more with recent versions of GCC 4. */
124 /* An integer value, and the number of digits in its textual
133 /* An entry in the lexical lookup table. */
141 /* Meridian: am, pm, or 24-hour style. */
142 enum { MERam
, MERpm
, MER24
};
144 enum { BILLION
= 1000000000, LOG10_BILLION
= 9 };
146 /* Relative times. */
149 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
159 #if HAVE_COMPOUND_LITERALS
160 # define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
162 static relative_time
const RELATIVE_TIME_0
;
165 /* Information passed to and from the parser. */
168 /* The input string remaining to be parsed. */
171 /* N, if this is the Nth Tuesday. */
172 long int day_ordinal
;
174 /* Day of week; Sunday is 0. */
177 /* tm_isdst flag for the local zone. */
180 /* Time zone, in minutes east of UTC. */
183 /* Style used for time. */
186 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
192 struct timespec seconds
; /* includes nanoseconds */
194 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
197 /* Presence or counts of nonterminals of various flavors parsed so far. */
202 size_t local_zones_seen
;
207 /* Table of local time zone abbrevations, terminated by a null entry. */
208 table local_time_zone_table
[3];
212 static int yylex (union YYSTYPE *, parser_control
*);
213 static int yyerror (parser_control
const *, char const *);
214 static long int time_zone_hhmm
(parser_control
*, textint
, long int);
216 /* Extract into *PC any date and time info from a string of digits
217 of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
220 digits_to_date_time
(parser_control
*pc
, textint text_int
)
222 if
(pc
->dates_seen
&& ! pc
->year.digits
223 && ! pc
->rels_seen
&& (pc
->times_seen ||
2 < text_int.digits
))
227 if
(4 < text_int.digits
)
230 pc
->day
= text_int.value %
100;
231 pc
->month
= (text_int.value
/ 100) %
100;
232 pc
->year.value
= text_int.value
/ 10000;
233 pc
->year.digits
= text_int.digits
- 4;
238 if
(text_int.digits
<= 2)
240 pc
->hour
= text_int.value
;
245 pc
->hour
= text_int.value
/ 100;
246 pc
->minutes
= text_int.value %
100;
248 pc
->seconds.tv_sec
= 0;
249 pc
->seconds.tv_nsec
= 0;
250 pc
->meridian
= MER24
;
255 /* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1). */
257 apply_relative_time
(parser_control
*pc
, relative_time rel
, int factor
)
259 pc
->rel.ns
+= factor
* rel.ns
;
260 pc
->rel.seconds
+= factor
* rel.seconds
;
261 pc
->rel.minutes
+= factor
* rel.minutes
;
262 pc
->rel.hour
+= factor
* rel.hour
;
263 pc
->rel.day
+= factor
* rel.day
;
264 pc
->rel.month
+= factor
* rel.month
;
265 pc
->rel.year
+= factor
* rel.year
;
266 pc
->rels_seen
= true
;
269 /* Set PC-> hour, minutes, seconds and nanoseconds members from arguments. */
271 set_hhmmss
(parser_control
*pc
, long int hour
, long int minutes
,
272 time_t sec
, long int nsec
)
275 pc
->minutes
= minutes
;
276 pc
->seconds.tv_sec
= sec
;
277 pc
->seconds.tv_nsec
= nsec
;
282 /* We want a reentrant parser, even if the TZ manipulation and the calls to
283 localtime and gmtime are not reentrant. */
285 %parse
-param
{ parser_control
*pc
}
286 %lex
-param
{ parser_control
*pc
}
288 /* This grammar has 20 shift/reduce conflicts. */
295 struct timespec timespec
;
301 %token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
302 %token
<intval
> tDAY_UNIT tDAY_SHIFT
304 %token
<intval
> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
305 %token
<intval
> tMONTH tORDINAL tZONE
307 %token
<textintval
> tSNUMBER tUNUMBER
308 %token
<timespec
> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
310 %type
<intval
> o_colon_minutes o_merid
311 %type
<timespec
> seconds signed_seconds unsigned_seconds
313 %type
<rel
> relunit relunit_snumber dayshift
326 pc
->timespec_seen
= true
;
337 { pc
->times_seen
++; }
339 { pc
->local_zones_seen
++; }
341 { pc
->zones_seen
++; }
343 { pc
->dates_seen
++; }
354 set_hhmmss
(pc
, $1.value
, 0, 0, 0);
357 | tUNUMBER
':' tUNUMBER o_merid
359 set_hhmmss
(pc
, $1.value
, $3.value
, 0, 0);
362 | tUNUMBER
':' tUNUMBER tSNUMBER o_colon_minutes
364 set_hhmmss
(pc
, $1.value
, $3.value
, 0, 0);
365 pc
->meridian
= MER24
;
367 pc
->time_zone
= time_zone_hhmm
(pc
, $4, $5);
369 | tUNUMBER
':' tUNUMBER
':' unsigned_seconds o_merid
371 set_hhmmss
(pc
, $1.value
, $3.value
, $5.tv_sec
, $5.tv_nsec
);
374 | tUNUMBER
':' tUNUMBER
':' unsigned_seconds tSNUMBER o_colon_minutes
376 set_hhmmss
(pc
, $1.value
, $3.value
, $5.tv_sec
, $5.tv_nsec
);
377 pc
->meridian
= MER24
;
379 pc
->time_zone
= time_zone_hhmm
(pc
, $6, $7);
386 pc
->local_isdst
= $1;
387 pc
->dsts_seen
+= (0 < $1);
392 pc
->dsts_seen
+= (0 < $1) + 1;
398 { pc
->time_zone
= $1; }
399 | tZONE relunit_snumber
400 { pc
->time_zone
= $1;
401 apply_relative_time
(pc
, $2, 1); }
402 | tZONE tSNUMBER o_colon_minutes
403 { pc
->time_zone
= $1 + time_zone_hhmm
(pc
, $2, $3); }
405 { pc
->time_zone
= $1 + 60; }
407 { pc
->time_zone
= $1 + 60; }
423 pc
->day_ordinal
= $1;
428 pc
->day_ordinal
= $1.value
;
434 tUNUMBER
'/' tUNUMBER
436 pc
->month
= $1.value
;
439 | tUNUMBER
'/' tUNUMBER
'/' tUNUMBER
441 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
442 otherwise as MM/DD/YY.
443 The goal in recognizing YYYY/MM/DD is solely to support legacy
444 machine-generated dates like those in an RCS log listing. If
445 you want portability, use the ISO 8601 format. */
449 pc
->month
= $3.value
;
454 pc
->month
= $1.value
;
459 | tUNUMBER tSNUMBER tSNUMBER
461 /* ISO 8601 format. YYYY-MM-DD. */
463 pc
->month
= -$2.value
;
466 | tUNUMBER tMONTH tSNUMBER
468 /* e.g. 17-JUN-1992. */
471 pc
->year.value
= -$3.value
;
472 pc
->year.digits
= $3.digits
;
474 | tMONTH tSNUMBER tSNUMBER
476 /* e.g. JUN-17-1992. */
479 pc
->year.value
= -$3.value
;
480 pc
->year.digits
= $3.digits
;
487 | tMONTH tUNUMBER
',' tUNUMBER
498 | tUNUMBER tMONTH tUNUMBER
508 { apply_relative_time
(pc
, $1, -1); }
510 { apply_relative_time
(pc
, $1, 1); }
512 { apply_relative_time
(pc
, $1, 1); }
517 { $$
= RELATIVE_TIME_0
; $$.year
= $1; }
518 | tUNUMBER tYEAR_UNIT
519 { $$
= RELATIVE_TIME_0
; $$.year
= $1.value
; }
521 { $$
= RELATIVE_TIME_0
; $$.year
= 1; }
522 | tORDINAL tMONTH_UNIT
523 { $$
= RELATIVE_TIME_0
; $$.month
= $1; }
524 | tUNUMBER tMONTH_UNIT
525 { $$
= RELATIVE_TIME_0
; $$.month
= $1.value
; }
527 { $$
= RELATIVE_TIME_0
; $$.month
= 1; }
529 { $$
= RELATIVE_TIME_0
; $$.day
= $1 * $2; }
531 { $$
= RELATIVE_TIME_0
; $$.day
= $1.value
* $2; }
533 { $$
= RELATIVE_TIME_0
; $$.day
= $1; }
534 | tORDINAL tHOUR_UNIT
535 { $$
= RELATIVE_TIME_0
; $$.hour
= $1; }
536 | tUNUMBER tHOUR_UNIT
537 { $$
= RELATIVE_TIME_0
; $$.hour
= $1.value
; }
539 { $$
= RELATIVE_TIME_0
; $$.hour
= 1; }
540 | tORDINAL tMINUTE_UNIT
541 { $$
= RELATIVE_TIME_0
; $$.minutes
= $1; }
542 | tUNUMBER tMINUTE_UNIT
543 { $$
= RELATIVE_TIME_0
; $$.minutes
= $1.value
; }
545 { $$
= RELATIVE_TIME_0
; $$.minutes
= 1; }
547 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1; }
549 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1.value
; }
550 | tSDECIMAL_NUMBER tSEC_UNIT
551 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1.tv_sec
; $$.ns
= $1.tv_nsec
; }
552 | tUDECIMAL_NUMBER tSEC_UNIT
553 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1.tv_sec
; $$.ns
= $1.tv_nsec
; }
555 { $$
= RELATIVE_TIME_0
; $$.seconds
= 1; }
561 { $$
= RELATIVE_TIME_0
; $$.year
= $1.value
; }
562 | tSNUMBER tMONTH_UNIT
563 { $$
= RELATIVE_TIME_0
; $$.month
= $1.value
; }
565 { $$
= RELATIVE_TIME_0
; $$.day
= $1.value
* $2; }
566 | tSNUMBER tHOUR_UNIT
567 { $$
= RELATIVE_TIME_0
; $$.hour
= $1.value
; }
568 | tSNUMBER tMINUTE_UNIT
569 { $$
= RELATIVE_TIME_0
; $$.minutes
= $1.value
; }
571 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1.value
; }
576 { $$
= RELATIVE_TIME_0
; $$.day
= $1; }
579 seconds: signed_seconds | unsigned_seconds
;
584 { $$.tv_sec
= $1.value
; $$.tv_nsec
= 0; }
590 { $$.tv_sec
= $1.value
; $$.tv_nsec
= 0; }
595 { digits_to_date_time
(pc
, $1); }
599 tUNUMBER relunit_snumber
601 /* Hybrid all-digit and relative offset, so that we accept e.g.,
602 "YYYYMMDD +N days" as well as "YYYYMMDD N days". */
603 digits_to_date_time
(pc
, $1);
604 apply_relative_time
(pc
, $2, 1);
624 static table
const meridian_table
[] =
626 { "AM", tMERIDIAN
, MERam
},
627 { "A.M.", tMERIDIAN
, MERam
},
628 { "PM", tMERIDIAN
, MERpm
},
629 { "P.M.", tMERIDIAN
, MERpm
},
633 static table
const dst_table
[] =
638 static table
const month_and_day_table
[] =
640 { "JANUARY", tMONTH
, 1 },
641 { "FEBRUARY", tMONTH
, 2 },
642 { "MARCH", tMONTH
, 3 },
643 { "APRIL", tMONTH
, 4 },
644 { "MAY", tMONTH
, 5 },
645 { "JUNE", tMONTH
, 6 },
646 { "JULY", tMONTH
, 7 },
647 { "AUGUST", tMONTH
, 8 },
648 { "SEPTEMBER",tMONTH
, 9 },
649 { "SEPT", tMONTH
, 9 },
650 { "OCTOBER", tMONTH
, 10 },
651 { "NOVEMBER", tMONTH
, 11 },
652 { "DECEMBER", tMONTH
, 12 },
653 { "SUNDAY", tDAY
, 0 },
654 { "MONDAY", tDAY
, 1 },
655 { "TUESDAY", tDAY
, 2 },
657 { "WEDNESDAY",tDAY
, 3 },
658 { "WEDNES", tDAY
, 3 },
659 { "THURSDAY", tDAY
, 4 },
661 { "THURS", tDAY
, 4 },
662 { "FRIDAY", tDAY
, 5 },
663 { "SATURDAY", tDAY
, 6 },
667 static table
const time_units_table
[] =
669 { "YEAR", tYEAR_UNIT
, 1 },
670 { "MONTH", tMONTH_UNIT
, 1 },
671 { "FORTNIGHT",tDAY_UNIT
, 14 },
672 { "WEEK", tDAY_UNIT
, 7 },
673 { "DAY", tDAY_UNIT
, 1 },
674 { "HOUR", tHOUR_UNIT
, 1 },
675 { "MINUTE", tMINUTE_UNIT
, 1 },
676 { "MIN", tMINUTE_UNIT
, 1 },
677 { "SECOND", tSEC_UNIT
, 1 },
678 { "SEC", tSEC_UNIT
, 1 },
682 /* Assorted relative-time words. */
683 static table
const relative_time_table
[] =
685 { "TOMORROW", tDAY_SHIFT
, 1 },
686 { "YESTERDAY",tDAY_SHIFT
, -1 },
687 { "TODAY", tDAY_SHIFT
, 0 },
688 { "NOW", tDAY_SHIFT
, 0 },
689 { "LAST", tORDINAL
, -1 },
690 { "THIS", tORDINAL
, 0 },
691 { "NEXT", tORDINAL
, 1 },
692 { "FIRST", tORDINAL
, 1 },
693 /*{ "SECOND", tORDINAL, 2 }, */
694 { "THIRD", tORDINAL
, 3 },
695 { "FOURTH", tORDINAL
, 4 },
696 { "FIFTH", tORDINAL
, 5 },
697 { "SIXTH", tORDINAL
, 6 },
698 { "SEVENTH", tORDINAL
, 7 },
699 { "EIGHTH", tORDINAL
, 8 },
700 { "NINTH", tORDINAL
, 9 },
701 { "TENTH", tORDINAL
, 10 },
702 { "ELEVENTH", tORDINAL
, 11 },
703 { "TWELFTH", tORDINAL
, 12 },
708 /* The universal time zone table. These labels can be used even for
709 time stamps that would not otherwise be valid, e.g., GMT time
710 stamps in London during summer. */
711 static table
const universal_time_zone_table
[] =
713 { "GMT", tZONE
, HOUR
( 0) }, /* Greenwich Mean */
714 { "UT", tZONE
, HOUR
( 0) }, /* Universal (Coordinated) */
715 { "UTC", tZONE
, HOUR
( 0) },
719 /* The time zone table. This table is necessarily incomplete, as time
720 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
721 as Eastern time in Australia, not as US Eastern Standard Time.
722 You cannot rely on parse_datetime to handle arbitrary time zone
723 abbreviations; use numeric abbreviations like `-0500' instead. */
724 static table
const time_zone_table
[] =
726 { "WET", tZONE
, HOUR
( 0) }, /* Western European */
727 { "WEST", tDAYZONE
, HOUR
( 0) }, /* Western European Summer */
728 { "BST", tDAYZONE
, HOUR
( 0) }, /* British Summer */
729 { "ART", tZONE
, -HOUR
( 3) }, /* Argentina */
730 { "BRT", tZONE
, -HOUR
( 3) }, /* Brazil */
731 { "BRST", tDAYZONE
, -HOUR
( 3) }, /* Brazil Summer */
732 { "NST", tZONE
, -(HOUR
( 3) + 30) }, /* Newfoundland Standard */
733 { "NDT", tDAYZONE
,-(HOUR
( 3) + 30) }, /* Newfoundland Daylight */
734 { "AST", tZONE
, -HOUR
( 4) }, /* Atlantic Standard */
735 { "ADT", tDAYZONE
, -HOUR
( 4) }, /* Atlantic Daylight */
736 { "CLT", tZONE
, -HOUR
( 4) }, /* Chile */
737 { "CLST", tDAYZONE
, -HOUR
( 4) }, /* Chile Summer */
738 { "EST", tZONE
, -HOUR
( 5) }, /* Eastern Standard */
739 { "EDT", tDAYZONE
, -HOUR
( 5) }, /* Eastern Daylight */
740 { "CST", tZONE
, -HOUR
( 6) }, /* Central Standard */
741 { "CDT", tDAYZONE
, -HOUR
( 6) }, /* Central Daylight */
742 { "MST", tZONE
, -HOUR
( 7) }, /* Mountain Standard */
743 { "MDT", tDAYZONE
, -HOUR
( 7) }, /* Mountain Daylight */
744 { "PST", tZONE
, -HOUR
( 8) }, /* Pacific Standard */
745 { "PDT", tDAYZONE
, -HOUR
( 8) }, /* Pacific Daylight */
746 { "AKST", tZONE
, -HOUR
( 9) }, /* Alaska Standard */
747 { "AKDT", tDAYZONE
, -HOUR
( 9) }, /* Alaska Daylight */
748 { "HST", tZONE
, -HOUR
(10) }, /* Hawaii Standard */
749 { "HAST", tZONE
, -HOUR
(10) }, /* Hawaii-Aleutian Standard */
750 { "HADT", tDAYZONE
, -HOUR
(10) }, /* Hawaii-Aleutian Daylight */
751 { "SST", tZONE
, -HOUR
(12) }, /* Samoa Standard */
752 { "WAT", tZONE
, HOUR
( 1) }, /* West Africa */
753 { "CET", tZONE
, HOUR
( 1) }, /* Central European */
754 { "CEST", tDAYZONE
, HOUR
( 1) }, /* Central European Summer */
755 { "MET", tZONE
, HOUR
( 1) }, /* Middle European */
756 { "MEZ", tZONE
, HOUR
( 1) }, /* Middle European */
757 { "MEST", tDAYZONE
, HOUR
( 1) }, /* Middle European Summer */
758 { "MESZ", tDAYZONE
, HOUR
( 1) }, /* Middle European Summer */
759 { "EET", tZONE
, HOUR
( 2) }, /* Eastern European */
760 { "EEST", tDAYZONE
, HOUR
( 2) }, /* Eastern European Summer */
761 { "CAT", tZONE
, HOUR
( 2) }, /* Central Africa */
762 { "SAST", tZONE
, HOUR
( 2) }, /* South Africa Standard */
763 { "EAT", tZONE
, HOUR
( 3) }, /* East Africa */
764 { "MSK", tZONE
, HOUR
( 3) }, /* Moscow */
765 { "MSD", tDAYZONE
, HOUR
( 3) }, /* Moscow Daylight */
766 { "IST", tZONE
, (HOUR
( 5) + 30) }, /* India Standard */
767 { "SGT", tZONE
, HOUR
( 8) }, /* Singapore */
768 { "KST", tZONE
, HOUR
( 9) }, /* Korea Standard */
769 { "JST", tZONE
, HOUR
( 9) }, /* Japan Standard */
770 { "GST", tZONE
, HOUR
(10) }, /* Guam Standard */
771 { "NZST", tZONE
, HOUR
(12) }, /* New Zealand Standard */
772 { "NZDT", tDAYZONE
, HOUR
(12) }, /* New Zealand Daylight */
776 /* Military time zone table. */
777 static table
const military_table
[] =
779 { "A", tZONE
, -HOUR
( 1) },
780 { "B", tZONE
, -HOUR
( 2) },
781 { "C", tZONE
, -HOUR
( 3) },
782 { "D", tZONE
, -HOUR
( 4) },
783 { "E", tZONE
, -HOUR
( 5) },
784 { "F", tZONE
, -HOUR
( 6) },
785 { "G", tZONE
, -HOUR
( 7) },
786 { "H", tZONE
, -HOUR
( 8) },
787 { "I", tZONE
, -HOUR
( 9) },
788 { "K", tZONE
, -HOUR
(10) },
789 { "L", tZONE
, -HOUR
(11) },
790 { "M", tZONE
, -HOUR
(12) },
791 { "N", tZONE
, HOUR
( 1) },
792 { "O", tZONE
, HOUR
( 2) },
793 { "P", tZONE
, HOUR
( 3) },
794 { "Q", tZONE
, HOUR
( 4) },
795 { "R", tZONE
, HOUR
( 5) },
796 { "S", tZONE
, HOUR
( 6) },
797 { "T", tZONE
, HOUR
( 7) },
798 { "U", tZONE
, HOUR
( 8) },
799 { "V", tZONE
, HOUR
( 9) },
800 { "W", tZONE
, HOUR
(10) },
801 { "X", tZONE
, HOUR
(11) },
802 { "Y", tZONE
, HOUR
(12) },
803 { "Z", tZONE
, HOUR
( 0) },
809 /* Convert a time zone expressed as HH:MM into an integer count of
810 minutes. If MM is negative, then S is of the form HHMM and needs
811 to be picked apart; otherwise, S is of the form HH. As specified in
812 http://www.opengroup.org/susv3xbd/xbd_chap08.html#tag_08_03, allow
813 only valid TZ range, and consider first two digits as hours, if no
814 minutes specified. */
817 time_zone_hhmm
(parser_control
*pc
, textint s
, long int mm
)
821 /* If the length of S is 1 or 2 and no minutes are specified,
822 interpret it as a number of hours. */
823 if
(s.digits
<= 2 && mm
< 0)
827 n_minutes
= (s.value
/ 100) * 60 + s.value %
100;
829 n_minutes
= s.value
* 60 + (s.negative ?
-mm
: mm
);
831 /* If the absolute number of minutes is larger than 24 hours,
832 arrange to reject it by incrementing pc->zones_seen. Thus,
833 we allow only values in the range UTC-24:00 to UTC+24:00. */
834 if
(24 * 60 < abs
(n_minutes
))
841 to_hour
(long int hours
, int meridian
)
845 default
: /* Pacify GCC. */
847 return
0 <= hours
&& hours
< 24 ? hours
: -1;
849 return
0 < hours
&& hours
< 12 ? hours
: hours
== 12 ?
0 : -1;
851 return
0 < hours
&& hours
< 12 ? hours
+ 12 : hours
== 12 ?
12 : -1;
856 to_year
(textint textyear
)
858 long int year
= textyear.value
;
863 /* XPG4 suggests that years 00-68 map to 2000-2068, and
864 years 69-99 map to 1969-1999. */
865 else if
(textyear.digits
== 2)
866 year
+= year
< 69 ?
2000 : 1900;
872 lookup_zone
(parser_control
const *pc
, char const *name
)
876 for
(tp
= universal_time_zone_table
; tp
->name
; tp
++)
877 if
(strcmp
(name
, tp
->name
) == 0)
880 /* Try local zone abbreviations before those in time_zone_table, as
881 the local ones are more likely to be right. */
882 for
(tp
= pc
->local_time_zone_table
; tp
->name
; tp
++)
883 if
(strcmp
(name
, tp
->name
) == 0)
886 for
(tp
= time_zone_table
; tp
->name
; tp
++)
887 if
(strcmp
(name
, tp
->name
) == 0)
894 /* Yield the difference between *A and *B,
895 measured in seconds, ignoring leap seconds.
896 The body of this function is taken directly from the GNU C Library;
897 see src/strftime.c. */
899 tm_diff
(struct tm
const *a
, struct tm
const *b
)
901 /* Compute intervening leap days correctly even if year is negative.
902 Take care to avoid int overflow in leap day calculations. */
903 int a4
= SHR
(a
->tm_year
, 2) + SHR
(TM_YEAR_BASE
, 2) - ! (a
->tm_year
& 3);
904 int b4
= SHR
(b
->tm_year
, 2) + SHR
(TM_YEAR_BASE
, 2) - ! (b
->tm_year
& 3);
905 int a100
= a4
/ 25 - (a4 %
25 < 0);
906 int b100
= b4
/ 25 - (b4 %
25 < 0);
907 int a400
= SHR
(a100
, 2);
908 int b400
= SHR
(b100
, 2);
909 int intervening_leap_days
= (a4
- b4
) - (a100
- b100
) + (a400
- b400
);
910 long int ayear
= a
->tm_year
;
911 long int years
= ayear
- b
->tm_year
;
912 long int days
= (365 * years
+ intervening_leap_days
913 + (a
->tm_yday
- b
->tm_yday
));
914 return
(60 * (60 * (24 * days
+ (a
->tm_hour
- b
->tm_hour
))
915 + (a
->tm_min
- b
->tm_min
))
916 + (a
->tm_sec
- b
->tm_sec
));
918 #endif /* ! HAVE_TM_GMTOFF */
921 lookup_word
(parser_control
const *pc
, char *word
)
930 /* Make it uppercase. */
931 for
(p
= word
; *p
; p
++)
933 unsigned char ch
= *p
;
937 for
(tp
= meridian_table
; tp
->name
; tp
++)
938 if
(strcmp
(word
, tp
->name
) == 0)
941 /* See if we have an abbreviation for a month. */
942 wordlen
= strlen
(word
);
943 abbrev
= wordlen
== 3 ||
(wordlen
== 4 && word
[3] == '.');
945 for
(tp
= month_and_day_table
; tp
->name
; tp
++)
946 if
((abbrev ? strncmp
(word
, tp
->name
, 3) : strcmp
(word
, tp
->name
)) == 0)
949 if
((tp
= lookup_zone
(pc
, word
)))
952 if
(strcmp
(word
, dst_table
[0].name
) == 0)
955 for
(tp
= time_units_table
; tp
->name
; tp
++)
956 if
(strcmp
(word
, tp
->name
) == 0)
959 /* Strip off any plural and try the units table again. */
960 if
(word
[wordlen
- 1] == 'S')
962 word
[wordlen
- 1] = '\0';
963 for
(tp
= time_units_table
; tp
->name
; tp
++)
964 if
(strcmp
(word
, tp
->name
) == 0)
966 word
[wordlen
- 1] = 'S'; /* For "this" in relative_time_table. */
969 for
(tp
= relative_time_table
; tp
->name
; tp
++)
970 if
(strcmp
(word
, tp
->name
) == 0)
973 /* Military time zones. */
975 for
(tp
= military_table
; tp
->name
; tp
++)
976 if
(word
[0] == tp
->name
[0])
979 /* Drop out any periods and try the time zone table again. */
980 for
(period_found
= false
, p
= q
= word
; (*p
= *q
); q
++)
985 if
(period_found
&& (tp
= lookup_zone
(pc
, word
)))
992 yylex (YYSTYPE *lvalp
, parser_control
*pc
)
999 while
(c
= *pc
->input
, c_isspace
(c
))
1002 if
(ISDIGIT
(c
) || c
== '-' || c
== '+')
1006 unsigned long int value
;
1007 if
(c
== '-' || c
== '+')
1009 sign
= c
== '-' ?
-1 : 1;
1010 while
(c
= *++pc
->input
, c_isspace
(c
))
1013 /* skip the '-' sign */
1019 for
(value
= 0; ; value
*= 10)
1021 unsigned long int value1
= value
+ (c
- '0');
1028 if
(ULONG_MAX
/ 10 < value
)
1031 if
((c
== '.' || c
== ',') && ISDIGIT
(p
[1]))
1036 unsigned long int value1
;
1038 /* Check for overflow when converting value to time_t. */
1053 if
(value
!= value1
)
1056 /* Accumulate fraction, to ns precision. */
1059 for
(digits
= 2; digits
<= LOG10_BILLION
; digits
++)
1066 /* Skip excess digits, truncating toward -Infinity. */
1068 for
(; ISDIGIT
(*p
); p
++)
1074 while
(ISDIGIT
(*p
))
1077 /* Adjust to the timespec convention, which is that
1078 tv_nsec is always a positive offset even if tv_sec is
1088 lvalp
->timespec.tv_sec
= s
;
1089 lvalp
->timespec.tv_nsec
= ns
;
1091 return sign ? tSDECIMAL_NUMBER
: tUDECIMAL_NUMBER
;
1095 lvalp
->textintval.negative
= sign
< 0;
1098 lvalp
->textintval.value
= - value
;
1099 if
(0 < lvalp
->textintval.value
)
1104 lvalp
->textintval.value
= value
;
1105 if
(lvalp
->textintval.value
< 0)
1108 lvalp
->textintval.digits
= p
- pc
->input
;
1110 return sign ? tSNUMBER
: tUNUMBER
;
1122 if
(p
< buff
+ sizeof buff
- 1)
1126 while
(c_isalpha
(c
) || c
== '.');
1129 tp
= lookup_word
(pc
, buff
);
1132 lvalp
->intval
= tp
->value
;
1137 return
*pc
->input
++;
1153 /* Do nothing if the parser reports an error. */
1155 yyerror (parser_control
const *pc _GL_UNUSED
,
1156 char const *s _GL_UNUSED
)
1161 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1162 passing it to mktime, return true if it's OK that mktime returned T.
1163 It's not OK if *TM0 has out-of-range members. */
1166 mktime_ok
(struct tm
const *tm0
, struct tm
const *tm1
, time_t t
)
1168 if
(t
== (time_t) -1)
1170 /* Guard against falsely reporting an error when parsing a time
1171 stamp that happens to equal (time_t) -1, on a host that
1172 supports such a time stamp. */
1173 tm1
= localtime
(&t
);
1178 return
! ((tm0
->tm_sec ^ tm1
->tm_sec
)
1179 |
(tm0
->tm_min ^ tm1
->tm_min
)
1180 |
(tm0
->tm_hour ^ tm1
->tm_hour
)
1181 |
(tm0
->tm_mday ^ tm1
->tm_mday
)
1182 |
(tm0
->tm_mon ^ tm1
->tm_mon
)
1183 |
(tm0
->tm_year ^ tm1
->tm_year
));
1186 /* A reasonable upper bound for the size of ordinary TZ strings.
1187 Use heap allocation if TZ's length exceeds this. */
1188 enum { TZBUFSIZE
= 100 };
1190 /* Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
1193 get_tz
(char tzbuf
[TZBUFSIZE
])
1195 char *tz
= getenv
("TZ");
1198 size_t tzsize
= strlen
(tz
) + 1;
1199 tz
= (tzsize
<= TZBUFSIZE
1200 ? memcpy
(tzbuf
, tz
, tzsize
)
1201 : xmemdup
(tz
, tzsize
));
1206 /* Parse a date/time string, storing the resulting time value into *RESULT.
1207 The string itself is pointed to by P. Return true if successful.
1208 P can be an incomplete or relative time specification; if so, use
1209 *NOW as the basis for the returned time. */
1211 parse_datetime
(struct timespec
*result
, char const *p
,
1212 struct timespec
const *now
)
1216 struct tm
const *tmp
;
1220 struct timespec gettime_buffer
;
1222 bool tz_was_altered
= false
;
1224 char tz0buf
[TZBUFSIZE
];
1229 gettime
(&gettime_buffer
);
1230 now
= &gettime_buffer
;
1233 Start
= now
->tv_sec
;
1234 Start_ns
= now
->tv_nsec
;
1236 tmp
= localtime
(&now
->tv_sec
);
1240 while
(c
= *p
, c_isspace
(c
))
1243 if
(strncmp
(p
, "TZ=\"", 4) == 0)
1245 char const *tzbase
= p
+ 4;
1249 for
(s
= tzbase
; *s
; s
++, tzsize
++)
1253 if
(! (*s
== '\\' ||
*s
== '"'))
1260 char tz1buf
[TZBUFSIZE
];
1261 bool large_tz
= TZBUFSIZE
< tzsize
;
1263 /* Free tz0, in case this is the 2nd or subsequent time through. */
1265 tz0
= get_tz
(tz0buf
);
1266 z
= tz1
= large_tz ? xmalloc
(tzsize
) : tz1buf
;
1267 for
(s
= tzbase
; *s
!= '"'; s
++)
1268 *z
++ = *(s
+= *s
== '\\');
1270 setenv_ok
= setenv
("TZ", tz1
, 1) == 0;
1275 tz_was_altered
= true
;
1280 /* As documented, be careful to treat the empty string just like
1281 a date string of "0". Without this, an empty string would be
1282 declared invalid when parsed during a DST transition. */
1287 pc.year.value
= tmp
->tm_year
;
1288 pc.year.value
+= TM_YEAR_BASE
;
1290 pc.month
= tmp
->tm_mon
+ 1;
1291 pc.day
= tmp
->tm_mday
;
1292 pc.hour
= tmp
->tm_hour
;
1293 pc.minutes
= tmp
->tm_min
;
1294 pc.seconds.tv_sec
= tmp
->tm_sec
;
1295 pc.seconds.tv_nsec
= Start_ns
;
1296 tm.tm_isdst
= tmp
->tm_isdst
;
1298 pc.meridian
= MER24
;
1299 pc.rel
= RELATIVE_TIME_0
;
1300 pc.timespec_seen
= false
;
1301 pc.rels_seen
= false
;
1305 pc.local_zones_seen
= 0;
1309 #if HAVE_STRUCT_TM_TM_ZONE
1310 pc.local_time_zone_table
[0].name
= tmp
->tm_zone
;
1311 pc.local_time_zone_table
[0].type
= tLOCAL_ZONE
;
1312 pc.local_time_zone_table
[0].value
= tmp
->tm_isdst
;
1313 pc.local_time_zone_table
[1].name
= NULL
;
1315 /* Probe the names used in the next three calendar quarters, looking
1316 for a tm_isdst different from the one we already have. */
1319 for
(quarter
= 1; quarter
<= 3; quarter
++)
1321 time_t probe
= Start
+ quarter
* (90 * 24 * 60 * 60);
1322 struct tm
const *probe_tm
= localtime
(&probe
);
1323 if
(probe_tm
&& probe_tm
->tm_zone
1324 && probe_tm
->tm_isdst
!= pc.local_time_zone_table
[0].value
)
1327 pc.local_time_zone_table
[1].name
= probe_tm
->tm_zone
;
1328 pc.local_time_zone_table
[1].type
= tLOCAL_ZONE
;
1329 pc.local_time_zone_table
[1].value
= probe_tm
->tm_isdst
;
1330 pc.local_time_zone_table
[2].name
= NULL
;
1339 # if !HAVE_DECL_TZNAME
1340 extern
char *tzname
[];
1343 for
(i
= 0; i
< 2; i
++)
1345 pc.local_time_zone_table
[i
].name
= tzname
[i
];
1346 pc.local_time_zone_table
[i
].type
= tLOCAL_ZONE
;
1347 pc.local_time_zone_table
[i
].value
= i
;
1349 pc.local_time_zone_table
[i
].name
= NULL
;
1352 pc.local_time_zone_table
[0].name
= NULL
;
1356 if
(pc.local_time_zone_table
[0].name
&& pc.local_time_zone_table
[1].name
1357 && ! strcmp
(pc.local_time_zone_table
[0].name
,
1358 pc.local_time_zone_table
[1].name
))
1360 /* This locale uses the same abbrevation for standard and
1361 daylight times. So if we see that abbreviation, we don't
1362 know whether it's daylight time. */
1363 pc.local_time_zone_table
[0].value
= -1;
1364 pc.local_time_zone_table
[1].name
= NULL
;
1367 if
(yyparse (&pc
) != 0)
1370 if
(pc.timespec_seen
)
1371 *result
= pc.seconds
;
1374 if
(1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
1375 |
(pc.local_zones_seen
+ pc.zones_seen
)))
1378 tm.tm_year
= to_year
(pc.year
) - TM_YEAR_BASE
;
1379 tm.tm_mon
= pc.month
- 1;
1380 tm.tm_mday
= pc.day
;
1381 if
(pc.times_seen ||
(pc.rels_seen
&& ! pc.dates_seen
&& ! pc.days_seen
))
1383 tm.tm_hour
= to_hour
(pc.hour
, pc.meridian
);
1386 tm.tm_min
= pc.minutes
;
1387 tm.tm_sec
= pc.seconds.tv_sec
;
1391 tm.tm_hour
= tm.tm_min
= tm.tm_sec
= 0;
1392 pc.seconds.tv_nsec
= 0;
1395 /* Let mktime deduce tm_isdst if we have an absolute time stamp. */
1396 if
(pc.dates_seen | pc.days_seen | pc.times_seen
)
1399 /* But if the input explicitly specifies local time with or without
1400 DST, give mktime that information. */
1401 if
(pc.local_zones_seen
)
1402 tm.tm_isdst
= pc.local_isdst
;
1406 Start
= mktime
(&tm
);
1408 if
(! mktime_ok
(&tm0
, &tm
, Start
))
1410 if
(! pc.zones_seen
)
1414 /* Guard against falsely reporting errors near the time_t
1415 boundaries when parsing times in other time zones. For
1416 example, suppose the input string "1969-12-31 23:00:00 -0100",
1417 the current time zone is 8 hours ahead of UTC, and the min
1418 time_t value is 1970-01-01 00:00:00 UTC. Then the min
1419 localtime value is 1970-01-01 08:00:00, and mktime will
1420 therefore fail on 1969-12-31 23:00:00. To work around the
1421 problem, set the time zone to 1 hour behind UTC temporarily
1422 by setting TZ="XXX1:00" and try mktime again. */
1424 long int time_zone
= pc.time_zone
;
1425 long int abs_time_zone
= time_zone
< 0 ?
- time_zone
: time_zone
;
1426 long int abs_time_zone_hour
= abs_time_zone
/ 60;
1427 int abs_time_zone_min
= abs_time_zone %
60;
1428 char tz1buf
[sizeof
"XXX+0:00"
1429 + sizeof pc.time_zone
* CHAR_BIT
/ 3];
1430 if
(!tz_was_altered
)
1431 tz0
= get_tz
(tz0buf
);
1432 sprintf
(tz1buf
, "XXX%s%ld:%02d", "-" + (time_zone
< 0),
1433 abs_time_zone_hour
, abs_time_zone_min
);
1434 if
(setenv
("TZ", tz1buf
, 1) != 0)
1436 tz_was_altered
= true
;
1438 Start
= mktime
(&tm
);
1439 if
(! mktime_ok
(&tm0
, &tm
, Start
))
1444 if
(pc.days_seen
&& ! pc.dates_seen
)
1446 tm.tm_mday
+= ((pc.day_number
- tm.tm_wday
+ 7) %
7
1447 + 7 * (pc.day_ordinal
1448 - (0 < pc.day_ordinal
1449 && tm.tm_wday
!= pc.day_number
)));
1451 Start
= mktime
(&tm
);
1452 if
(Start
== (time_t) -1)
1456 /* Add relative date. */
1457 if
(pc.rel.year | pc.rel.month | pc.rel.day
)
1459 int year
= tm.tm_year
+ pc.rel.year
;
1460 int month
= tm.tm_mon
+ pc.rel.month
;
1461 int day
= tm.tm_mday
+ pc.rel.day
;
1462 if
(((year
< tm.tm_year
) ^
(pc.rel.year
< 0))
1463 |
((month
< tm.tm_mon
) ^
(pc.rel.month
< 0))
1464 |
((day
< tm.tm_mday
) ^
(pc.rel.day
< 0)))
1469 tm.tm_hour
= tm0.tm_hour
;
1470 tm.tm_min
= tm0.tm_min
;
1471 tm.tm_sec
= tm0.tm_sec
;
1472 tm.tm_isdst
= tm0.tm_isdst
;
1473 Start
= mktime
(&tm
);
1474 if
(Start
== (time_t) -1)
1478 /* The only "output" of this if-block is an updated Start value,
1479 so this block must follow others that clobber Start. */
1482 long int delta
= pc.time_zone
* 60;
1484 #ifdef HAVE_TM_GMTOFF
1485 delta
-= tm.tm_gmtoff
;
1488 struct tm
const *gmt
= gmtime
(&t
);
1491 delta
-= tm_diff
(&tm
, gmt
);
1494 if
((Start
< t1
) != (delta
< 0))
1495 goto fail
; /* time_t overflow */
1499 /* Add relative hours, minutes, and seconds. On hosts that support
1500 leap seconds, ignore the possibility of leap seconds; e.g.,
1501 "+ 10 minutes" adds 600 seconds, even if one of them is a
1502 leap second. Typically this is not what the user wants, but it's
1503 too hard to do it the other way, because the time zone indicator
1504 must be applied before relative times, and if mktime is applied
1505 again the time zone will be lost. */
1507 long int sum_ns
= pc.seconds.tv_nsec
+ pc.rel.ns
;
1508 long int normalized_ns
= (sum_ns % BILLION
+ BILLION
) % BILLION
;
1510 long int d1
= 60 * 60 * pc.rel.hour
;
1511 time_t t1
= t0
+ d1
;
1512 long int d2
= 60 * pc.rel.minutes
;
1513 time_t t2
= t1
+ d2
;
1514 long_time_t d3
= pc.rel.seconds
;
1515 long_time_t t3
= t2
+ d3
;
1516 long int d4
= (sum_ns
- normalized_ns
) / BILLION
;
1517 long_time_t t4
= t3
+ d4
;
1520 if
((d1
/ (60 * 60) ^ pc.rel.hour
)
1521 |
(d2
/ 60 ^ pc.rel.minutes
)
1522 |
((t1
< t0
) ^
(d1
< 0))
1523 |
((t2
< t1
) ^
(d2
< 0))
1524 |
((t3
< t2
) ^
(d3
< 0))
1525 |
((t4
< t3
) ^
(d4
< 0))
1529 result
->tv_sec
= t5
;
1530 result
->tv_nsec
= normalized_ns
;
1540 ok
&= (tz0 ? setenv
("TZ", tz0
, 1) : unsetenv
("TZ")) == 0;
1549 main
(int ac
, char **av
)
1553 printf
("Enter date, or blank line to exit.\n\t> ");
1556 buff
[BUFSIZ
- 1] = '\0';
1557 while
(fgets
(buff
, BUFSIZ
- 1, stdin
) && buff
[0])
1560 struct tm
const *tm
;
1561 if
(! parse_datetime
(&d
, buff
, NULL
))
1562 printf
("Bad format - couldn't convert.\n");
1563 else if
(! (tm
= localtime
(&d.tv_sec
)))
1565 long int sec
= d.tv_sec
;
1566 printf
("localtime (%ld) failed\n", sec
);
1571 printf
("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1572 tm
->tm_year
+ 1900L, tm
->tm_mon
+ 1, tm
->tm_mday
,
1573 tm
->tm_hour
, tm
->tm_min
, tm
->tm_sec
, ns
);