*-map tests: Fix compilation error.
[gnulib.git] / lib / parse-datetime.y
blobb264bb7fbbd4ecac46b45d41024cd72686c57ad2
1 %{
2 /* Parse a string into an internal timestamp.
4 Copyright (C) 1999-2000, 2002-2019 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 <https://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 Assaf Gordon <assafgordon@gmail.com> in 2016 to add
25 debug output.
27 Modified by Paul Eggert <eggert@twinsun.com> in 1999 to do the
28 right thing about local DST. Also modified by Paul Eggert
29 <eggert@cs.ucla.edu> in 2004 to support nanosecond-resolution
30 timestamps, in 2004 to support TZ strings in dates, and in 2017 to
31 check for integer overflow and to support longer-than-'long'
32 'time_t' and 'tv_nsec'. */
34 #include <config.h>
36 #include "parse-datetime.h"
38 #include "intprops.h"
39 #include "timespec.h"
40 #include "verify.h"
41 #include "strftime.h"
43 /* There's no need to extend the stack, so there's no need to involve
44 alloca. */
45 #define YYSTACK_USE_ALLOCA 0
47 /* Tell Bison how much stack space is needed. 20 should be plenty for
48 this grammar, which is not right recursive. Beware setting it too
49 high, since that might cause problems on machines whose
50 implementations have lame stack-overflow checking. */
51 #define YYMAXDEPTH 20
52 #define YYINITDEPTH YYMAXDEPTH
54 /* Since the code of parse-datetime.y is not included in the Emacs executable
55 itself, there is no need to #define static in this file. Even if
56 the code were included in the Emacs executable, it probably
57 wouldn't do any harm to #undef it here; this will only cause
58 problems if we try to write to a static variable, which I don't
59 think this code needs to do. */
60 #ifdef emacs
61 # undef static
62 #endif
64 #include <inttypes.h>
65 #include <c-ctype.h>
66 #include <limits.h>
67 #include <stdarg.h>
68 #include <stdio.h>
69 #include <stdlib.h>
70 #include <string.h>
72 #include "gettext.h"
74 #define _(str) gettext (str)
76 /* Bison's skeleton tests _STDLIB_H, while some stdlib.h headers
77 use _STDLIB_H_ as witness. Map the latter to the one bison uses. */
78 /* FIXME: this is temporary. Remove when we have a mechanism to ensure
79 that the version we're using is fixed, too. */
80 #ifdef _STDLIB_H_
81 # undef _STDLIB_H
82 # define _STDLIB_H 1
83 #endif
85 /* The __attribute__ feature is available in gcc versions 2.5 and later.
86 The __-protected variants of the attributes 'format' and 'printf' are
87 accepted by gcc versions 2.6.4 (effectively 2.7) and later.
88 Enable _GL_ATTRIBUTE_FORMAT only if these are supported too, because
89 gnulib and libintl do '#define printf __printf__' when they override
90 the 'printf' function. */
91 #if 2 < __GNUC__ + (7 <= __GNUC_MINOR__)
92 # define _GL_ATTRIBUTE_FORMAT(spec) __attribute__ ((__format__ spec))
93 #else
94 # define _GL_ATTRIBUTE_FORMAT(spec) /* empty */
95 #endif
97 /* Shift A right by B bits portably, by dividing A by 2**B and
98 truncating towards minus infinity. A and B should be free of side
99 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
100 INT_BITS is the number of useful bits in an int. GNU code can
101 assume that INT_BITS is at least 32.
103 ISO C99 says that A >> B is implementation-defined if A < 0. Some
104 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
105 right in the usual way when A < 0, so SHR falls back on division if
106 ordinary A >> B doesn't seem to be the usual signed shift. */
107 #define SHR(a, b) \
108 (-1 >> 1 == -1 \
109 ? (a) >> (b) \
110 : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
112 #define HOUR(x) (60 * 60 * (x))
114 #define STREQ(a, b) (strcmp (a, b) == 0)
116 /* Verify that time_t is an integer as POSIX requires, and that every
117 time_t value fits in intmax_t. Please file a bug report if these
118 assumptions are false on your platform. */
119 verify (TYPE_IS_INTEGER (time_t));
120 verify (!TYPE_SIGNED (time_t) || INTMAX_MIN <= TYPE_MINIMUM (time_t));
121 verify (TYPE_MAXIMUM (time_t) <= INTMAX_MAX);
123 /* True if N is out of range for time_t. */
124 static bool
125 time_overflow (intmax_t n)
127 return ! ((TYPE_SIGNED (time_t) ? TYPE_MINIMUM (time_t) <= n : 0 <= n)
128 && n <= TYPE_MAXIMUM (time_t));
131 /* Convert a possibly-signed character to an unsigned character. This is
132 a bit safer than casting to unsigned char, since it catches some type
133 errors that the cast doesn't. */
134 static unsigned char to_uchar (char ch) { return ch; }
136 static void _GL_ATTRIBUTE_FORMAT ((__printf__, 1, 2))
137 dbg_printf (char const *msg, ...)
139 va_list args;
140 /* TODO: use gnulib's 'program_name' instead? */
141 fputs ("date: ", stderr);
143 va_start (args, msg);
144 vfprintf (stderr, msg, args);
145 va_end (args);
149 /* An integer value, and the number of digits in its textual
150 representation. */
151 typedef struct
153 bool negative;
154 intmax_t value;
155 ptrdiff_t digits;
156 } textint;
158 /* An entry in the lexical lookup table. */
159 typedef struct
161 char const *name;
162 int type;
163 int value;
164 } table;
166 /* Meridian: am, pm, or 24-hour style. */
167 enum { MERam, MERpm, MER24 };
169 /* A reasonable upper bound for the buffer used in debug output. */
170 enum { DBGBUFSIZE = 100 };
172 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
174 /* Relative times. */
175 typedef struct
177 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
178 intmax_t year;
179 intmax_t month;
180 intmax_t day;
181 intmax_t hour;
182 intmax_t minutes;
183 intmax_t seconds;
184 int ns;
185 } relative_time;
187 #if HAVE_COMPOUND_LITERALS
188 # define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
189 #else
190 static relative_time const RELATIVE_TIME_0;
191 #endif
193 /* Information passed to and from the parser. */
194 typedef struct
196 /* The input string remaining to be parsed. */
197 const char *input;
199 /* N, if this is the Nth Tuesday. */
200 intmax_t day_ordinal;
202 /* Day of week; Sunday is 0. */
203 int day_number;
205 /* tm_isdst flag for the local zone. */
206 int local_isdst;
208 /* Time zone, in seconds east of UT. */
209 int time_zone;
211 /* Style used for time. */
212 int meridian;
214 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
215 textint year;
216 intmax_t month;
217 intmax_t day;
218 intmax_t hour;
219 intmax_t minutes;
220 struct timespec seconds; /* includes nanoseconds */
222 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
223 relative_time rel;
225 /* Presence or counts of nonterminals of various flavors parsed so far. */
226 bool timespec_seen;
227 bool rels_seen;
228 ptrdiff_t dates_seen;
229 ptrdiff_t days_seen;
230 ptrdiff_t local_zones_seen;
231 ptrdiff_t dsts_seen;
232 ptrdiff_t times_seen;
233 ptrdiff_t zones_seen;
234 bool year_seen;
236 /* Print debugging output to stderr. */
237 bool parse_datetime_debug;
239 /* Which of the 'seen' parts have been printed when debugging. */
240 bool debug_dates_seen;
241 bool debug_days_seen;
242 bool debug_local_zones_seen;
243 bool debug_times_seen;
244 bool debug_zones_seen;
245 bool debug_year_seen;
247 /* The user specified explicit ordinal day value. */
248 bool debug_ordinal_day_seen;
250 /* Table of local time zone abbreviations, terminated by a null entry. */
251 table local_time_zone_table[3];
252 } parser_control;
254 union YYSTYPE;
255 static int yylex (union YYSTYPE *, parser_control *);
256 static int yyerror (parser_control const *, char const *);
257 static bool time_zone_hhmm (parser_control *, textint, intmax_t);
259 /* Extract into *PC any date and time info from a string of digits
260 of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
261 YYYY, ...). */
262 static void
263 digits_to_date_time (parser_control *pc, textint text_int)
265 if (pc->dates_seen && ! pc->year.digits
266 && ! pc->rels_seen && (pc->times_seen || 2 < text_int.digits))
268 pc->year_seen = true;
269 pc->year = text_int;
271 else
273 if (4 < text_int.digits)
275 pc->dates_seen++;
276 pc->day = text_int.value % 100;
277 pc->month = (text_int.value / 100) % 100;
278 pc->year.value = text_int.value / 10000;
279 pc->year.digits = text_int.digits - 4;
281 else
283 pc->times_seen++;
284 if (text_int.digits <= 2)
286 pc->hour = text_int.value;
287 pc->minutes = 0;
289 else
291 pc->hour = text_int.value / 100;
292 pc->minutes = text_int.value % 100;
294 pc->seconds.tv_sec = 0;
295 pc->seconds.tv_nsec = 0;
296 pc->meridian = MER24;
301 /* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1). Return true
302 if successful, false if an overflow occurred. */
303 static bool
304 apply_relative_time (parser_control *pc, relative_time rel, int factor)
306 if (factor < 0
307 ? (INT_SUBTRACT_WRAPV (pc->rel.ns, rel.ns, &pc->rel.ns)
308 | INT_SUBTRACT_WRAPV (pc->rel.seconds, rel.seconds, &pc->rel.seconds)
309 | INT_SUBTRACT_WRAPV (pc->rel.minutes, rel.minutes, &pc->rel.minutes)
310 | INT_SUBTRACT_WRAPV (pc->rel.hour, rel.hour, &pc->rel.hour)
311 | INT_SUBTRACT_WRAPV (pc->rel.day, rel.day, &pc->rel.day)
312 | INT_SUBTRACT_WRAPV (pc->rel.month, rel.month, &pc->rel.month)
313 | INT_SUBTRACT_WRAPV (pc->rel.year, rel.year, &pc->rel.year))
314 : (INT_ADD_WRAPV (pc->rel.ns, rel.ns, &pc->rel.ns)
315 | INT_ADD_WRAPV (pc->rel.seconds, rel.seconds, &pc->rel.seconds)
316 | INT_ADD_WRAPV (pc->rel.minutes, rel.minutes, &pc->rel.minutes)
317 | INT_ADD_WRAPV (pc->rel.hour, rel.hour, &pc->rel.hour)
318 | INT_ADD_WRAPV (pc->rel.day, rel.day, &pc->rel.day)
319 | INT_ADD_WRAPV (pc->rel.month, rel.month, &pc->rel.month)
320 | INT_ADD_WRAPV (pc->rel.year, rel.year, &pc->rel.year)))
321 return false;
322 pc->rels_seen = true;
323 return true;
326 /* Set PC-> hour, minutes, seconds and nanoseconds members from arguments. */
327 static void
328 set_hhmmss (parser_control *pc, intmax_t hour, intmax_t minutes,
329 time_t sec, int nsec)
331 pc->hour = hour;
332 pc->minutes = minutes;
333 pc->seconds.tv_sec = sec;
334 pc->seconds.tv_nsec = nsec;
337 /* Return a textual representation of the day ordinal/number values
338 in the parser_control struct (e.g., "last wed", "this tues", "thu"). */
339 static const char *
340 str_days (parser_control *pc, char *buffer, int n)
342 /* TODO: use relative_time_table for reverse lookup. */
343 static char const ordinal_values[][11] = {
344 "last",
345 "this",
346 "next/first",
347 "(SECOND)", /* SECOND is commented out in relative_time_table. */
348 "third",
349 "fourth",
350 "fifth",
351 "sixth",
352 "seventh",
353 "eight",
354 "ninth",
355 "tenth",
356 "eleventh",
357 "twelfth"
360 static char const days_values[][4] = {
361 "Sun",
362 "Mon",
363 "Tue",
364 "Wed",
365 "Thu",
366 "Fri",
367 "Sat"
370 int len;
372 /* Don't add an ordinal prefix if the user didn't specify it
373 (e.g., "this wed" vs "wed"). */
374 if (pc->debug_ordinal_day_seen)
376 /* Use word description if possible (e.g., -1 = last, 3 = third). */
377 len = (-1 <= pc->day_ordinal && pc->day_ordinal <= 12
378 ? snprintf (buffer, n, "%s", ordinal_values[pc->day_ordinal + 1])
379 : snprintf (buffer, n, "%"PRIdMAX, pc->day_ordinal));
381 else
383 buffer[0] = '\0';
384 len = 0;
387 /* Add the day name */
388 if (0 <= pc->day_number && pc->day_number <= 6 && 0 <= len && len < n)
389 snprintf (buffer + len, n - len, &" %s"[len == 0],
390 days_values[pc->day_number]);
391 else
393 /* invalid day_number value - should never happen */
395 return buffer;
398 /* Convert a time zone to its string representation. */
400 enum { TIME_ZONE_BUFSIZE = INT_STRLEN_BOUND (intmax_t) + sizeof ":MM:SS" } ;
402 static char const *
403 time_zone_str (int time_zone, char time_zone_buf[TIME_ZONE_BUFSIZE])
405 char *p = time_zone_buf;
406 char sign = time_zone < 0 ? '-' : '+';
407 int hour = abs (time_zone / (60 * 60));
408 p += sprintf (time_zone_buf, "%c%02d", sign, hour);
409 int offset_from_hour = abs (time_zone % (60 * 60));
410 if (offset_from_hour != 0)
412 int mm = offset_from_hour / 60;
413 int ss = offset_from_hour % 60;
414 *p++ = ':';
415 *p++ = '0' + mm / 10;
416 *p++ = '0' + mm % 10;
417 if (ss)
419 *p++ = ':';
420 *p++ = '0' + ss / 10;
421 *p++ = '0' + ss % 10;
423 *p = '\0';
425 return time_zone_buf;
428 /* debugging: print the current time in the parser_control structure.
429 The parser will increment "*_seen" members for those which were parsed.
430 This function will print only newly seen parts. */
431 static void
432 debug_print_current_time (char const *item, parser_control *pc)
434 bool space = false;
436 if (!pc->parse_datetime_debug)
437 return;
439 /* no newline, more items printed below */
440 dbg_printf (_("parsed %s part: "), item);
442 if (pc->dates_seen && !pc->debug_dates_seen)
444 /*TODO: use pc->year.negative? */
445 fprintf (stderr, "(Y-M-D) %04"PRIdMAX"-%02"PRIdMAX"-%02"PRIdMAX,
446 pc->year.value, pc->month, pc->day);
447 pc->debug_dates_seen = true;
448 space = true;
451 if (pc->year_seen != pc->debug_year_seen)
453 if (space)
454 fputc (' ', stderr);
455 fprintf (stderr, _("year: %04"PRIdMAX), pc->year.value);
457 pc->debug_year_seen = pc->year_seen;
458 space = true;
461 if (pc->times_seen && !pc->debug_times_seen)
463 intmax_t sec = pc->seconds.tv_sec;
464 fprintf (stderr, &" %02"PRIdMAX":%02"PRIdMAX":%02"PRIdMAX[!space],
465 pc->hour, pc->minutes, sec);
466 if (pc->seconds.tv_nsec != 0)
468 int nsec = pc->seconds.tv_nsec;
469 fprintf (stderr, ".%09d", nsec);
471 if (pc->meridian == MERpm)
472 fputs ("pm", stderr);
474 pc->debug_times_seen = true;
475 space = true;
478 if (pc->days_seen && !pc->debug_days_seen)
480 if (space)
481 fputc (' ', stderr);
482 char tmp[DBGBUFSIZE];
483 fprintf (stderr, _("%s (day ordinal=%"PRIdMAX" number=%d)"),
484 str_days (pc, tmp, sizeof tmp),
485 pc->day_ordinal, pc->day_number);
486 pc->debug_days_seen = true;
487 space = true;
490 /* local zone strings only change the DST settings,
491 not the timezone value. If seen, inform about the DST. */
492 if (pc->local_zones_seen && !pc->debug_local_zones_seen)
494 fprintf (stderr, &" isdst=%d%s"[!space],
495 pc->local_isdst, pc->dsts_seen ? " DST" : "");
496 pc->debug_local_zones_seen = true;
497 space = true;
500 if (pc->zones_seen && !pc->debug_zones_seen)
502 char time_zone_buf[TIME_ZONE_BUFSIZE];
503 fprintf (stderr, &" UTC%s"[!space],
504 time_zone_str (pc->time_zone, time_zone_buf));
505 pc->debug_zones_seen = true;
506 space = true;
509 if (pc->timespec_seen)
511 intmax_t sec = pc->seconds.tv_sec;
512 if (space)
513 fputc (' ', stderr);
514 fprintf (stderr, _("number of seconds: %"PRIdMAX), sec);
517 fputc ('\n', stderr);
520 /* Debugging: print the current relative values. */
522 static bool
523 print_rel_part (bool space, intmax_t val, char const *name)
525 if (val == 0)
526 return space;
527 fprintf (stderr, &" %+"PRIdMAX" %s"[!space], val, name);
528 return true;
531 static void
532 debug_print_relative_time (char const *item, parser_control const *pc)
534 bool space = false;
536 if (!pc->parse_datetime_debug)
537 return;
539 /* no newline, more items printed below */
540 dbg_printf (_("parsed %s part: "), item);
542 if (pc->rel.year == 0 && pc->rel.month == 0 && pc->rel.day == 0
543 && pc->rel.hour == 0 && pc->rel.minutes == 0 && pc->rel.seconds == 0
544 && pc->rel.ns == 0)
546 /* Special case: relative time of this/today/now */
547 fputs (_("today/this/now\n"), stderr);
548 return;
551 space = print_rel_part (space, pc->rel.year, "year(s)");
552 space = print_rel_part (space, pc->rel.month, "month(s)");
553 space = print_rel_part (space, pc->rel.day, "day(s)");
554 space = print_rel_part (space, pc->rel.hour, "hour(s)");
555 space = print_rel_part (space, pc->rel.minutes, "minutes");
556 space = print_rel_part (space, pc->rel.seconds, "seconds");
557 print_rel_part (space, pc->rel.ns, "nanoseconds");
559 fputc ('\n', stderr);
566 /* We want a reentrant parser, even if the TZ manipulation and the calls to
567 localtime and gmtime are not reentrant. */
568 %pure-parser
569 %parse-param { parser_control *pc }
570 %lex-param { parser_control *pc }
572 /* This grammar has 31 shift/reduce conflicts. */
573 %expect 31
575 %union
577 intmax_t intval;
578 textint textintval;
579 struct timespec timespec;
580 relative_time rel;
583 %token <intval> tAGO
584 %token tDST
586 %token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
587 %token <intval> tDAY_UNIT tDAY_SHIFT
589 %token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
590 %token <intval> tMONTH tORDINAL tZONE
592 %token <textintval> tSNUMBER tUNUMBER
593 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
595 %type <intval> o_colon_minutes
596 %type <timespec> seconds signed_seconds unsigned_seconds
598 %type <rel> relunit relunit_snumber dayshift
602 spec:
603 timespec
604 | items
607 timespec:
608 '@' seconds
610 pc->seconds = $2;
611 pc->timespec_seen = true;
612 debug_print_current_time (_("number of seconds"), pc);
616 items:
617 /* empty */
618 | items item
621 item:
622 datetime
624 pc->times_seen++; pc->dates_seen++;
625 debug_print_current_time (_("datetime"), pc);
627 | time
629 pc->times_seen++;
630 debug_print_current_time (_("time"), pc);
632 | local_zone
634 pc->local_zones_seen++;
635 debug_print_current_time (_("local_zone"), pc);
637 | zone
639 pc->zones_seen++;
640 debug_print_current_time (_("zone"), pc);
642 | date
644 pc->dates_seen++;
645 debug_print_current_time (_("date"), pc);
647 | day
649 pc->days_seen++;
650 debug_print_current_time (_("day"), pc);
652 | rel
654 debug_print_relative_time (_("relative"), pc);
656 | number
658 debug_print_current_time (_("number"), pc);
660 | hybrid
662 debug_print_relative_time (_("hybrid"), pc);
666 datetime:
667 iso_8601_datetime
670 iso_8601_datetime:
671 iso_8601_date 'T' iso_8601_time
674 time:
675 tUNUMBER tMERIDIAN
677 set_hhmmss (pc, $1.value, 0, 0, 0);
678 pc->meridian = $2;
680 | tUNUMBER ':' tUNUMBER tMERIDIAN
682 set_hhmmss (pc, $1.value, $3.value, 0, 0);
683 pc->meridian = $4;
685 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tMERIDIAN
687 set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
688 pc->meridian = $6;
690 | iso_8601_time
693 iso_8601_time:
694 tUNUMBER zone_offset
696 set_hhmmss (pc, $1.value, 0, 0, 0);
697 pc->meridian = MER24;
699 | tUNUMBER ':' tUNUMBER o_zone_offset
701 set_hhmmss (pc, $1.value, $3.value, 0, 0);
702 pc->meridian = MER24;
704 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_zone_offset
706 set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
707 pc->meridian = MER24;
711 o_zone_offset:
712 /* empty */
713 | zone_offset
716 zone_offset:
717 tSNUMBER o_colon_minutes
719 pc->zones_seen++;
720 if (! time_zone_hhmm (pc, $1, $2)) YYABORT;
724 /* Local zone strings affect only the DST setting, and take effect
725 only if the current TZ setting is relevant.
727 Example 1:
728 'EEST' is parsed as tLOCAL_ZONE, as it relates to the effective TZ:
729 TZ='Europe/Helsinki' date -d '2016-06-30 EEST'
731 Example 2:
732 'EEST' is parsed as tDAYZONE:
733 TZ='Asia/Tokyo' date -d '2016-06-30 EEST'
735 This is implemented by probing the next three calendar quarters
736 of the effective timezone and looking for DST changes -
737 if found, the timezone name (EEST) is inserted into
738 the lexical lookup table with type tLOCAL_ZONE.
739 (Search for 'quarter' comment in 'parse_datetime2'.)
741 local_zone:
742 tLOCAL_ZONE
743 { pc->local_isdst = $1; }
744 | tLOCAL_ZONE tDST
746 pc->local_isdst = 1;
747 pc->dsts_seen++;
751 /* Note 'T' is a special case, as it is used as the separator in ISO
752 8601 date and time of day representation. */
753 zone:
754 tZONE
755 { pc->time_zone = $1; }
756 | 'T'
757 { pc->time_zone = HOUR (7); }
758 | tZONE relunit_snumber
759 { pc->time_zone = $1;
760 if (! apply_relative_time (pc, $2, 1)) YYABORT;
761 debug_print_relative_time (_("relative"), pc);
763 | 'T' relunit_snumber
764 { pc->time_zone = HOUR (7);
765 if (! apply_relative_time (pc, $2, 1)) YYABORT;
766 debug_print_relative_time (_("relative"), pc);
768 | tZONE tSNUMBER o_colon_minutes
769 { if (! time_zone_hhmm (pc, $2, $3)) YYABORT;
770 if (INT_ADD_WRAPV (pc->time_zone, $1, &pc->time_zone)) YYABORT; }
771 | tDAYZONE
772 { pc->time_zone = $1 + 60 * 60; }
773 | tZONE tDST
774 { pc->time_zone = $1 + 60 * 60; }
777 day:
778 tDAY
780 pc->day_ordinal = 0;
781 pc->day_number = $1;
783 | tDAY ','
785 pc->day_ordinal = 0;
786 pc->day_number = $1;
788 | tORDINAL tDAY
790 pc->day_ordinal = $1;
791 pc->day_number = $2;
792 pc->debug_ordinal_day_seen = true;
794 | tUNUMBER tDAY
796 pc->day_ordinal = $1.value;
797 pc->day_number = $2;
798 pc->debug_ordinal_day_seen = true;
802 date:
803 tUNUMBER '/' tUNUMBER
805 pc->month = $1.value;
806 pc->day = $3.value;
808 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
810 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
811 otherwise as MM/DD/YY.
812 The goal in recognizing YYYY/MM/DD is solely to support legacy
813 machine-generated dates like those in an RCS log listing. If
814 you want portability, use the ISO 8601 format. */
815 if (4 <= $1.digits)
817 if (pc->parse_datetime_debug)
819 intmax_t digits = $1.digits;
820 dbg_printf (_("warning: value %"PRIdMAX" has %"PRIdMAX" digits. "
821 "Assuming YYYY/MM/DD\n"),
822 $1.value, digits);
825 pc->year = $1;
826 pc->month = $3.value;
827 pc->day = $5.value;
829 else
831 if (pc->parse_datetime_debug)
832 dbg_printf (_("warning: value %"PRIdMAX" has less than 4 digits. "
833 "Assuming MM/DD/YY[YY]\n"),
834 $1.value);
836 pc->month = $1.value;
837 pc->day = $3.value;
838 pc->year = $5;
841 | tUNUMBER tMONTH tSNUMBER
843 /* E.g., 17-JUN-1992. */
844 pc->day = $1.value;
845 pc->month = $2;
846 if (INT_SUBTRACT_WRAPV (0, $3.value, &pc->year.value)) YYABORT;
847 pc->year.digits = $3.digits;
849 | tMONTH tSNUMBER tSNUMBER
851 /* E.g., JUN-17-1992. */
852 pc->month = $1;
853 if (INT_SUBTRACT_WRAPV (0, $2.value, &pc->day)) YYABORT;
854 if (INT_SUBTRACT_WRAPV (0, $3.value, &pc->year.value)) YYABORT;
855 pc->year.digits = $3.digits;
857 | tMONTH tUNUMBER
859 pc->month = $1;
860 pc->day = $2.value;
862 | tMONTH tUNUMBER ',' tUNUMBER
864 pc->month = $1;
865 pc->day = $2.value;
866 pc->year = $4;
868 | tUNUMBER tMONTH
870 pc->day = $1.value;
871 pc->month = $2;
873 | tUNUMBER tMONTH tUNUMBER
875 pc->day = $1.value;
876 pc->month = $2;
877 pc->year = $3;
879 | iso_8601_date
882 iso_8601_date:
883 tUNUMBER tSNUMBER tSNUMBER
885 /* ISO 8601 format. YYYY-MM-DD. */
886 pc->year = $1;
887 if (INT_SUBTRACT_WRAPV (0, $2.value, &pc->month)) YYABORT;
888 if (INT_SUBTRACT_WRAPV (0, $3.value, &pc->day)) YYABORT;
892 rel:
893 relunit tAGO
894 { if (! apply_relative_time (pc, $1, $2)) YYABORT; }
895 | relunit
896 { if (! apply_relative_time (pc, $1, 1)) YYABORT; }
897 | dayshift
898 { if (! apply_relative_time (pc, $1, 1)) YYABORT; }
901 relunit:
902 tORDINAL tYEAR_UNIT
903 { $$ = RELATIVE_TIME_0; $$.year = $1; }
904 | tUNUMBER tYEAR_UNIT
905 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
906 | tYEAR_UNIT
907 { $$ = RELATIVE_TIME_0; $$.year = 1; }
908 | tORDINAL tMONTH_UNIT
909 { $$ = RELATIVE_TIME_0; $$.month = $1; }
910 | tUNUMBER tMONTH_UNIT
911 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
912 | tMONTH_UNIT
913 { $$ = RELATIVE_TIME_0; $$.month = 1; }
914 | tORDINAL tDAY_UNIT
915 { $$ = RELATIVE_TIME_0;
916 if (INT_MULTIPLY_WRAPV ($1, $2, &$$.day)) YYABORT; }
917 | tUNUMBER tDAY_UNIT
918 { $$ = RELATIVE_TIME_0;
919 if (INT_MULTIPLY_WRAPV ($1.value, $2, &$$.day)) YYABORT; }
920 | tDAY_UNIT
921 { $$ = RELATIVE_TIME_0; $$.day = $1; }
922 | tORDINAL tHOUR_UNIT
923 { $$ = RELATIVE_TIME_0; $$.hour = $1; }
924 | tUNUMBER tHOUR_UNIT
925 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
926 | tHOUR_UNIT
927 { $$ = RELATIVE_TIME_0; $$.hour = 1; }
928 | tORDINAL tMINUTE_UNIT
929 { $$ = RELATIVE_TIME_0; $$.minutes = $1; }
930 | tUNUMBER tMINUTE_UNIT
931 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
932 | tMINUTE_UNIT
933 { $$ = RELATIVE_TIME_0; $$.minutes = 1; }
934 | tORDINAL tSEC_UNIT
935 { $$ = RELATIVE_TIME_0; $$.seconds = $1; }
936 | tUNUMBER tSEC_UNIT
937 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
938 | tSDECIMAL_NUMBER tSEC_UNIT
939 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
940 | tUDECIMAL_NUMBER tSEC_UNIT
941 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
942 | tSEC_UNIT
943 { $$ = RELATIVE_TIME_0; $$.seconds = 1; }
944 | relunit_snumber
947 relunit_snumber:
948 tSNUMBER tYEAR_UNIT
949 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
950 | tSNUMBER tMONTH_UNIT
951 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
952 | tSNUMBER tDAY_UNIT
953 { $$ = RELATIVE_TIME_0;
954 if (INT_MULTIPLY_WRAPV ($1.value, $2, &$$.day)) YYABORT; }
955 | tSNUMBER tHOUR_UNIT
956 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
957 | tSNUMBER tMINUTE_UNIT
958 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
959 | tSNUMBER tSEC_UNIT
960 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
963 dayshift:
964 tDAY_SHIFT
965 { $$ = RELATIVE_TIME_0; $$.day = $1; }
968 seconds: signed_seconds | unsigned_seconds;
970 signed_seconds:
971 tSDECIMAL_NUMBER
972 | tSNUMBER
973 { if (time_overflow ($1.value)) YYABORT;
974 $$.tv_sec = $1.value; $$.tv_nsec = 0; }
977 unsigned_seconds:
978 tUDECIMAL_NUMBER
979 | tUNUMBER
980 { if (time_overflow ($1.value)) YYABORT;
981 $$.tv_sec = $1.value; $$.tv_nsec = 0; }
984 number:
985 tUNUMBER
986 { digits_to_date_time (pc, $1); }
989 hybrid:
990 tUNUMBER relunit_snumber
992 /* Hybrid all-digit and relative offset, so that we accept e.g.,
993 "YYYYMMDD +N days" as well as "YYYYMMDD N days". */
994 digits_to_date_time (pc, $1);
995 if (! apply_relative_time (pc, $2, 1)) YYABORT;
999 o_colon_minutes:
1000 /* empty */
1001 { $$ = -1; }
1002 | ':' tUNUMBER
1003 { $$ = $2.value; }
1008 static table const meridian_table[] =
1010 { "AM", tMERIDIAN, MERam },
1011 { "A.M.", tMERIDIAN, MERam },
1012 { "PM", tMERIDIAN, MERpm },
1013 { "P.M.", tMERIDIAN, MERpm },
1014 { NULL, 0, 0 }
1017 static table const dst_table[] =
1019 { "DST", tDST, 0 }
1022 static table const month_and_day_table[] =
1024 { "JANUARY", tMONTH, 1 },
1025 { "FEBRUARY", tMONTH, 2 },
1026 { "MARCH", tMONTH, 3 },
1027 { "APRIL", tMONTH, 4 },
1028 { "MAY", tMONTH, 5 },
1029 { "JUNE", tMONTH, 6 },
1030 { "JULY", tMONTH, 7 },
1031 { "AUGUST", tMONTH, 8 },
1032 { "SEPTEMBER",tMONTH, 9 },
1033 { "SEPT", tMONTH, 9 },
1034 { "OCTOBER", tMONTH, 10 },
1035 { "NOVEMBER", tMONTH, 11 },
1036 { "DECEMBER", tMONTH, 12 },
1037 { "SUNDAY", tDAY, 0 },
1038 { "MONDAY", tDAY, 1 },
1039 { "TUESDAY", tDAY, 2 },
1040 { "TUES", tDAY, 2 },
1041 { "WEDNESDAY",tDAY, 3 },
1042 { "WEDNES", tDAY, 3 },
1043 { "THURSDAY", tDAY, 4 },
1044 { "THUR", tDAY, 4 },
1045 { "THURS", tDAY, 4 },
1046 { "FRIDAY", tDAY, 5 },
1047 { "SATURDAY", tDAY, 6 },
1048 { NULL, 0, 0 }
1051 static table const time_units_table[] =
1053 { "YEAR", tYEAR_UNIT, 1 },
1054 { "MONTH", tMONTH_UNIT, 1 },
1055 { "FORTNIGHT",tDAY_UNIT, 14 },
1056 { "WEEK", tDAY_UNIT, 7 },
1057 { "DAY", tDAY_UNIT, 1 },
1058 { "HOUR", tHOUR_UNIT, 1 },
1059 { "MINUTE", tMINUTE_UNIT, 1 },
1060 { "MIN", tMINUTE_UNIT, 1 },
1061 { "SECOND", tSEC_UNIT, 1 },
1062 { "SEC", tSEC_UNIT, 1 },
1063 { NULL, 0, 0 }
1066 /* Assorted relative-time words. */
1067 static table const relative_time_table[] =
1069 { "TOMORROW", tDAY_SHIFT, 1 },
1070 { "YESTERDAY",tDAY_SHIFT, -1 },
1071 { "TODAY", tDAY_SHIFT, 0 },
1072 { "NOW", tDAY_SHIFT, 0 },
1073 { "LAST", tORDINAL, -1 },
1074 { "THIS", tORDINAL, 0 },
1075 { "NEXT", tORDINAL, 1 },
1076 { "FIRST", tORDINAL, 1 },
1077 /*{ "SECOND", tORDINAL, 2 }, */
1078 { "THIRD", tORDINAL, 3 },
1079 { "FOURTH", tORDINAL, 4 },
1080 { "FIFTH", tORDINAL, 5 },
1081 { "SIXTH", tORDINAL, 6 },
1082 { "SEVENTH", tORDINAL, 7 },
1083 { "EIGHTH", tORDINAL, 8 },
1084 { "NINTH", tORDINAL, 9 },
1085 { "TENTH", tORDINAL, 10 },
1086 { "ELEVENTH", tORDINAL, 11 },
1087 { "TWELFTH", tORDINAL, 12 },
1088 { "AGO", tAGO, -1 },
1089 { "HENCE", tAGO, 1 },
1090 { NULL, 0, 0 }
1093 /* The universal time zone table. These labels can be used even for
1094 timestamps that would not otherwise be valid, e.g., GMT timestamps
1095 oin London during summer. */
1096 static table const universal_time_zone_table[] =
1098 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
1099 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
1100 { "UTC", tZONE, HOUR ( 0) },
1101 { NULL, 0, 0 }
1104 /* The time zone table. This table is necessarily incomplete, as time
1105 zone abbreviations are ambiguous; e.g., Australians interpret "EST"
1106 as Eastern time in Australia, not as US Eastern Standard Time.
1107 You cannot rely on parse_datetime to handle arbitrary time zone
1108 abbreviations; use numeric abbreviations like "-0500" instead. */
1109 static table const time_zone_table[] =
1111 { "WET", tZONE, HOUR ( 0) }, /* Western European */
1112 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
1113 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
1114 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
1115 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
1116 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
1117 { "NST", tZONE, -(HOUR ( 3) + 30 * 60) }, /* Newfoundland Standard */
1118 { "NDT", tDAYZONE,-(HOUR ( 3) + 30 * 60) }, /* Newfoundland Daylight */
1119 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
1120 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
1121 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
1122 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
1123 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
1124 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
1125 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
1126 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
1127 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
1128 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
1129 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
1130 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
1131 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
1132 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
1133 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
1134 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
1135 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
1136 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
1137 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
1138 { "CET", tZONE, HOUR ( 1) }, /* Central European */
1139 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
1140 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
1141 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
1142 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
1143 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
1144 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
1145 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
1146 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
1147 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
1148 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
1149 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
1150 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
1151 { "IST", tZONE, (HOUR ( 5) + 30 * 60) }, /* India Standard */
1152 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
1153 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
1154 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
1155 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
1156 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
1157 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
1158 { NULL, 0, 0 }
1161 /* Military time zone table.
1163 Note 'T' is a special case, as it is used as the separator in ISO
1164 8601 date and time of day representation. */
1165 static table const military_table[] =
1167 { "A", tZONE, -HOUR ( 1) },
1168 { "B", tZONE, -HOUR ( 2) },
1169 { "C", tZONE, -HOUR ( 3) },
1170 { "D", tZONE, -HOUR ( 4) },
1171 { "E", tZONE, -HOUR ( 5) },
1172 { "F", tZONE, -HOUR ( 6) },
1173 { "G", tZONE, -HOUR ( 7) },
1174 { "H", tZONE, -HOUR ( 8) },
1175 { "I", tZONE, -HOUR ( 9) },
1176 { "K", tZONE, -HOUR (10) },
1177 { "L", tZONE, -HOUR (11) },
1178 { "M", tZONE, -HOUR (12) },
1179 { "N", tZONE, HOUR ( 1) },
1180 { "O", tZONE, HOUR ( 2) },
1181 { "P", tZONE, HOUR ( 3) },
1182 { "Q", tZONE, HOUR ( 4) },
1183 { "R", tZONE, HOUR ( 5) },
1184 { "S", tZONE, HOUR ( 6) },
1185 { "T", 'T', 0 },
1186 { "U", tZONE, HOUR ( 8) },
1187 { "V", tZONE, HOUR ( 9) },
1188 { "W", tZONE, HOUR (10) },
1189 { "X", tZONE, HOUR (11) },
1190 { "Y", tZONE, HOUR (12) },
1191 { "Z", tZONE, HOUR ( 0) },
1192 { NULL, 0, 0 }
1197 /* Convert a time zone expressed as HH:MM into an integer count of
1198 seconds. If MM is negative, then S is of the form HHMM and needs
1199 to be picked apart; otherwise, S is of the form HH. As specified in
1200 http://www.opengroup.org/susv3xbd/xbd_chap08.html#tag_08_03, allow
1201 only valid TZ range, and consider first two digits as hours, if no
1202 minutes specified. Return true if successful. */
1204 static bool
1205 time_zone_hhmm (parser_control *pc, textint s, intmax_t mm)
1207 intmax_t n_minutes;
1208 bool overflow = false;
1210 /* If the length of S is 1 or 2 and no minutes are specified,
1211 interpret it as a number of hours. */
1212 if (s.digits <= 2 && mm < 0)
1213 s.value *= 100;
1215 if (mm < 0)
1216 n_minutes = (s.value / 100) * 60 + s.value % 100;
1217 else
1219 overflow |= INT_MULTIPLY_WRAPV (s.value, 60, &n_minutes);
1220 overflow |= (s.negative
1221 ? INT_SUBTRACT_WRAPV (n_minutes, mm, &n_minutes)
1222 : INT_ADD_WRAPV (n_minutes, mm, &n_minutes));
1225 if (overflow || ! (-24 * 60 <= n_minutes && n_minutes <= 24 * 60))
1226 return false;
1227 pc->time_zone = n_minutes * 60;
1228 return true;
1231 static int
1232 to_hour (intmax_t hours, int meridian)
1234 switch (meridian)
1236 default: /* Pacify GCC. */
1237 case MER24:
1238 return 0 <= hours && hours < 24 ? hours : -1;
1239 case MERam:
1240 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
1241 case MERpm:
1242 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
1246 enum { TM_YEAR_BASE = 1900 };
1247 enum { TM_YEAR_BUFSIZE = INT_BUFSIZE_BOUND (int) + 1 };
1249 /* Convert TM_YEAR, a year minus 1900, to a string that is numerically
1250 correct even if subtracting 1900 would overflow. */
1252 static char const *
1253 tm_year_str (int tm_year, char buf[TM_YEAR_BUFSIZE])
1255 verify (TM_YEAR_BASE % 100 == 0);
1256 sprintf (buf, &"-%02d%02d"[-TM_YEAR_BASE <= tm_year],
1257 abs (tm_year / 100 + TM_YEAR_BASE / 100),
1258 abs (tm_year % 100));
1259 return buf;
1262 /* Convert a text year number to a year minus 1900, working correctly
1263 even if the input is in the range INT_MAX .. INT_MAX + 1900 - 1. */
1265 static bool
1266 to_tm_year (textint textyear, bool debug, int *tm_year)
1268 intmax_t year = textyear.value;
1270 /* XPG4 suggests that years 00-68 map to 2000-2068, and
1271 years 69-99 map to 1969-1999. */
1272 if (0 <= year && textyear.digits == 2)
1274 year += year < 69 ? 2000 : 1900;
1275 if (debug)
1276 dbg_printf (_("warning: adjusting year value %"PRIdMAX
1277 " to %"PRIdMAX"\n"),
1278 textyear.value, year);
1281 if (year < 0
1282 ? INT_SUBTRACT_WRAPV (-TM_YEAR_BASE, year, tm_year)
1283 : INT_SUBTRACT_WRAPV (year, TM_YEAR_BASE, tm_year))
1285 if (debug)
1286 dbg_printf (_("error: out-of-range year %"PRIdMAX"\n"), year);
1287 return false;
1290 return true;
1293 static table const * _GL_ATTRIBUTE_PURE
1294 lookup_zone (parser_control const *pc, char const *name)
1296 table const *tp;
1298 for (tp = universal_time_zone_table; tp->name; tp++)
1299 if (strcmp (name, tp->name) == 0)
1300 return tp;
1302 /* Try local zone abbreviations before those in time_zone_table, as
1303 the local ones are more likely to be right. */
1304 for (tp = pc->local_time_zone_table; tp->name; tp++)
1305 if (strcmp (name, tp->name) == 0)
1306 return tp;
1308 for (tp = time_zone_table; tp->name; tp++)
1309 if (strcmp (name, tp->name) == 0)
1310 return tp;
1312 return NULL;
1315 #if ! HAVE_TM_GMTOFF
1316 /* Yield the difference between *A and *B,
1317 measured in seconds, ignoring leap seconds.
1318 The body of this function is taken directly from the GNU C Library;
1319 see strftime.c. */
1320 static int
1321 tm_diff (const struct tm *a, const struct tm *b)
1323 /* Compute intervening leap days correctly even if year is negative.
1324 Take care to avoid int overflow in leap day calculations,
1325 but it's OK to assume that A and B are close to each other. */
1326 int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
1327 int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
1328 int a100 = a4 / 25 - (a4 % 25 < 0);
1329 int b100 = b4 / 25 - (b4 % 25 < 0);
1330 int a400 = SHR (a100, 2);
1331 int b400 = SHR (b100, 2);
1332 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
1333 int years = a->tm_year - b->tm_year;
1334 int days = (365 * years + intervening_leap_days
1335 + (a->tm_yday - b->tm_yday));
1336 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
1337 + (a->tm_min - b->tm_min))
1338 + (a->tm_sec - b->tm_sec));
1340 #endif /* ! HAVE_TM_GMTOFF */
1342 static table const *
1343 lookup_word (parser_control const *pc, char *word)
1345 char *p;
1346 char *q;
1347 ptrdiff_t wordlen;
1348 table const *tp;
1349 bool period_found;
1350 bool abbrev;
1352 /* Make it uppercase. */
1353 for (p = word; *p; p++)
1354 *p = c_toupper (to_uchar (*p));
1356 for (tp = meridian_table; tp->name; tp++)
1357 if (strcmp (word, tp->name) == 0)
1358 return tp;
1360 /* See if we have an abbreviation for a month. */
1361 wordlen = strlen (word);
1362 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
1364 for (tp = month_and_day_table; tp->name; tp++)
1365 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
1366 return tp;
1368 if ((tp = lookup_zone (pc, word)))
1369 return tp;
1371 if (strcmp (word, dst_table[0].name) == 0)
1372 return dst_table;
1374 for (tp = time_units_table; tp->name; tp++)
1375 if (strcmp (word, tp->name) == 0)
1376 return tp;
1378 /* Strip off any plural and try the units table again. */
1379 if (word[wordlen - 1] == 'S')
1381 word[wordlen - 1] = '\0';
1382 for (tp = time_units_table; tp->name; tp++)
1383 if (strcmp (word, tp->name) == 0)
1384 return tp;
1385 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
1388 for (tp = relative_time_table; tp->name; tp++)
1389 if (strcmp (word, tp->name) == 0)
1390 return tp;
1392 /* Military time zones. */
1393 if (wordlen == 1)
1394 for (tp = military_table; tp->name; tp++)
1395 if (word[0] == tp->name[0])
1396 return tp;
1398 /* Drop out any periods and try the time zone table again. */
1399 for (period_found = false, p = q = word; (*p = *q); q++)
1400 if (*q == '.')
1401 period_found = true;
1402 else
1403 p++;
1404 if (period_found && (tp = lookup_zone (pc, word)))
1405 return tp;
1407 return NULL;
1410 static int
1411 yylex (union YYSTYPE *lvalp, parser_control *pc)
1413 unsigned char c;
1415 for (;;)
1417 while (c = *pc->input, c_isspace (c))
1418 pc->input++;
1420 if (c_isdigit (c) || c == '-' || c == '+')
1422 char const *p;
1423 int sign;
1424 intmax_t value = 0;
1425 if (c == '-' || c == '+')
1427 sign = c == '-' ? -1 : 1;
1428 while (c = *++pc->input, c_isspace (c))
1429 continue;
1430 if (! c_isdigit (c))
1431 /* skip the '-' sign */
1432 continue;
1434 else
1435 sign = 0;
1436 p = pc->input;
1440 if (INT_MULTIPLY_WRAPV (value, 10, &value))
1441 return '?';
1442 if (INT_ADD_WRAPV (value, sign < 0 ? '0' - c : c - '0', &value))
1443 return '?';
1444 c = *++p;
1446 while (c_isdigit (c));
1448 if ((c == '.' || c == ',') && c_isdigit (p[1]))
1450 time_t s;
1451 int ns;
1452 int digits;
1454 if (time_overflow (value))
1455 return '?';
1456 s = value;
1458 /* Accumulate fraction, to ns precision. */
1459 p++;
1460 ns = *p++ - '0';
1461 for (digits = 2; digits <= LOG10_BILLION; digits++)
1463 ns *= 10;
1464 if (c_isdigit (*p))
1465 ns += *p++ - '0';
1468 /* Skip excess digits, truncating toward -Infinity. */
1469 if (sign < 0)
1470 for (; c_isdigit (*p); p++)
1471 if (*p != '0')
1473 ns++;
1474 break;
1476 while (c_isdigit (*p))
1477 p++;
1479 /* Adjust to the timespec convention, which is that
1480 tv_nsec is always a positive offset even if tv_sec is
1481 negative. */
1482 if (sign < 0 && ns)
1484 if (s == TYPE_MINIMUM (time_t))
1485 return '?';
1486 s--;
1487 ns = BILLION - ns;
1490 lvalp->timespec.tv_sec = s;
1491 lvalp->timespec.tv_nsec = ns;
1492 pc->input = p;
1493 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
1495 else
1497 lvalp->textintval.negative = sign < 0;
1498 lvalp->textintval.value = value;
1499 lvalp->textintval.digits = p - pc->input;
1500 pc->input = p;
1501 return sign ? tSNUMBER : tUNUMBER;
1505 if (c_isalpha (c))
1507 char buff[20];
1508 char *p = buff;
1509 table const *tp;
1513 if (p < buff + sizeof buff - 1)
1514 *p++ = c;
1515 c = *++pc->input;
1517 while (c_isalpha (c) || c == '.');
1519 *p = '\0';
1520 tp = lookup_word (pc, buff);
1521 if (! tp)
1523 if (pc->parse_datetime_debug)
1524 dbg_printf (_("error: unknown word '%s'\n"), buff);
1525 return '?';
1527 lvalp->intval = tp->value;
1528 return tp->type;
1531 if (c != '(')
1532 return to_uchar (*pc->input++);
1534 ptrdiff_t count = 0;
1537 c = *pc->input++;
1538 if (c == '\0')
1539 return c;
1540 if (c == '(')
1541 count++;
1542 else if (c == ')')
1543 count--;
1545 while (count != 0);
1549 /* Do nothing if the parser reports an error. */
1550 static int
1551 yyerror (parser_control const *pc _GL_UNUSED,
1552 char const *s _GL_UNUSED)
1554 return 0;
1557 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1558 passing it to mktime_z, return true if it's OK. It's not OK if
1559 mktime failed or if *TM0 has out-of-range mainline members.
1560 The caller should set TM1->tm_wday to -1 before calling mktime,
1561 as a negative tm_wday is how mktime failure is inferred. */
1563 static bool
1564 mktime_ok (struct tm const *tm0, struct tm const *tm1)
1566 if (tm1->tm_wday < 0)
1567 return false;
1569 return ! ((tm0->tm_sec ^ tm1->tm_sec)
1570 | (tm0->tm_min ^ tm1->tm_min)
1571 | (tm0->tm_hour ^ tm1->tm_hour)
1572 | (tm0->tm_mday ^ tm1->tm_mday)
1573 | (tm0->tm_mon ^ tm1->tm_mon)
1574 | (tm0->tm_year ^ tm1->tm_year));
1577 /* Debugging: format a 'struct tm' into a buffer, taking the parser's
1578 timezone information into account (if pc != NULL). */
1579 static char const *
1580 debug_strfdatetime (struct tm const *tm, parser_control const *pc,
1581 char *buf, int n)
1583 /* TODO:
1584 1. find an optimal way to print date string in a clear and unambiguous
1585 format. Currently, always add '(Y-M-D)' prefix.
1586 Consider '2016y01m10d' or 'year(2016) month(01) day(10)'.
1588 If the user needs debug printing, it means he/she already having
1589 issues with the parsing - better to avoid formats that could
1590 be mis-interpreted (e.g., just YYYY-MM-DD).
1592 2. Can strftime be used instead?
1593 depends if it is portable and can print invalid dates on all systems.
1595 3. Print timezone information ?
1597 4. Print DST information ?
1599 5. Print nanosecond information ?
1601 NOTE:
1602 Printed date/time values might not be valid, e.g., '2016-02-31'
1603 or '2016-19-2016' . These are the values as parsed from the user
1604 string, before validation.
1606 int m = nstrftime (buf, n, "(Y-M-D) %Y-%m-%d %H:%M:%S", tm, 0, 0);
1608 /* If parser_control information was provided (for timezone),
1609 and there's enough space in the buffer, add timezone info. */
1610 if (pc && m < n && pc->zones_seen)
1612 int tz = pc->time_zone;
1614 /* Account for DST if tLOCAL_ZONE was seen. */
1615 if (pc->local_zones_seen && !pc->zones_seen && 0 < pc->local_isdst)
1616 tz += 60 * 60;
1618 char time_zone_buf[TIME_ZONE_BUFSIZE];
1619 snprintf (&buf[m], n - m, " TZ=%s", time_zone_str (tz, time_zone_buf));
1621 return buf;
1624 static char const *
1625 debug_strfdate (struct tm const *tm, char *buf, int n)
1627 char tm_year_buf[TM_YEAR_BUFSIZE];
1628 snprintf (buf, n, "(Y-M-D) %s-%02d-%02d",
1629 tm_year_str (tm->tm_year, tm_year_buf),
1630 tm->tm_mon + 1, tm->tm_mday);
1631 return buf;
1634 static char const *
1635 debug_strftime (struct tm const *tm, char *buf, int n)
1637 snprintf (buf, n, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
1638 return buf;
1641 /* If mktime_ok failed, display the failed time values,
1642 and provide possible hints. Example output:
1644 date: error: invalid date/time value:
1645 date: user provided time: '(Y-M-D) 2006-04-02 02:45:00'
1646 date: normalized time: '(Y-M-D) 2006-04-02 03:45:00'
1647 date: __
1648 date: possible reasons:
1649 date: non-existing due to daylight-saving time;
1650 date: numeric values overflow;
1651 date: missing timezone;
1653 static void
1654 debug_mktime_not_ok (struct tm const *tm0, struct tm const *tm1,
1655 parser_control const *pc, bool time_zone_seen)
1657 /* TODO: handle t == -1 (as in 'mktime_ok'). */
1658 char tmp[DBGBUFSIZE];
1659 int i;
1660 const bool eq_sec = (tm0->tm_sec == tm1->tm_sec);
1661 const bool eq_min = (tm0->tm_min == tm1->tm_min);
1662 const bool eq_hour = (tm0->tm_hour == tm1->tm_hour);
1663 const bool eq_mday = (tm0->tm_mday == tm1->tm_mday);
1664 const bool eq_month = (tm0->tm_mon == tm1->tm_mon);
1665 const bool eq_year = (tm0->tm_year == tm1->tm_year);
1667 const bool dst_shift = eq_sec && eq_min && !eq_hour
1668 && eq_mday && eq_month && eq_year;
1670 if (!pc->parse_datetime_debug)
1671 return;
1673 dbg_printf (_("error: invalid date/time value:\n"));
1674 dbg_printf (_(" user provided time: '%s'\n"),
1675 debug_strfdatetime (tm0, pc, tmp, sizeof tmp));
1676 dbg_printf (_(" normalized time: '%s'\n"),
1677 debug_strfdatetime (tm1, pc, tmp, sizeof tmp));
1678 /* The format must be aligned with debug_strfdatetime and the two
1679 DEBUG statements above. This string is not translated. */
1680 i = snprintf (tmp, sizeof tmp,
1681 " %4s %2s %2s %2s %2s %2s",
1682 eq_year ? "" : "----",
1683 eq_month ? "" : "--",
1684 eq_mday ? "" : "--",
1685 eq_hour ? "" : "--",
1686 eq_min ? "" : "--",
1687 eq_sec ? "" : "--");
1688 /* Trim trailing whitespace. */
1689 if (0 <= i)
1691 if (sizeof tmp - 1 < i)
1692 i = sizeof tmp - 1;
1693 while (0 < i && tmp[i - 1] == ' ')
1694 --i;
1695 tmp[i] = '\0';
1697 dbg_printf ("%s\n", tmp);
1699 dbg_printf (_(" possible reasons:\n"));
1700 if (dst_shift)
1701 dbg_printf (_(" non-existing due to daylight-saving time;\n"));
1702 if (!eq_mday && !eq_month)
1703 dbg_printf (_(" invalid day/month combination;\n"));
1704 dbg_printf (_(" numeric values overflow;\n"));
1705 dbg_printf (" %s\n", (time_zone_seen ? _("incorrect timezone")
1706 : _("missing timezone")));
1709 /* The original interface: run with debug=false and the default timezone. */
1710 bool
1711 parse_datetime (struct timespec *result, char const *p,
1712 struct timespec const *now)
1714 char const *tzstring = getenv ("TZ");
1715 timezone_t tz = tzalloc (tzstring);
1716 if (!tz)
1717 return false;
1718 bool ok = parse_datetime2 (result, p, now, 0, tz, tzstring);
1719 tzfree (tz);
1720 return ok;
1723 /* Parse a date/time string, storing the resulting time value into *RESULT.
1724 The string itself is pointed to by P. Return true if successful.
1725 P can be an incomplete or relative time specification; if so, use
1726 *NOW as the basis for the returned time. Default to timezone
1727 TZDEFAULT, which corresponds to tzalloc (TZSTRING). */
1728 bool
1729 parse_datetime2 (struct timespec *result, char const *p,
1730 struct timespec const *now, unsigned int flags,
1731 timezone_t tzdefault, char const *tzstring)
1733 struct tm tm;
1734 struct tm tm0;
1735 char time_zone_buf[TIME_ZONE_BUFSIZE];
1736 char dbg_tm[DBGBUFSIZE];
1737 bool ok = false;
1738 char const *input_sentinel = p + strlen (p);
1739 char *tz1alloc = NULL;
1741 /* A reasonable upper bound for the size of ordinary TZ strings.
1742 Use heap allocation if TZ's length exceeds this. */
1743 enum { TZBUFSIZE = 100 };
1744 char tz1buf[TZBUFSIZE];
1746 struct timespec gettime_buffer;
1747 if (! now)
1749 gettime (&gettime_buffer);
1750 now = &gettime_buffer;
1753 time_t Start = now->tv_sec;
1754 int Start_ns = now->tv_nsec;
1756 unsigned char c;
1757 while (c = *p, c_isspace (c))
1758 p++;
1760 timezone_t tz = tzdefault;
1762 /* Store a local copy prior to first "goto". Without this, a prior use
1763 below of RELATIVE_TIME_0 on the RHS might translate to an assignment-
1764 to-temporary, which would trigger a -Wjump-misses-init warning. */
1765 const relative_time rel_time_0 = RELATIVE_TIME_0;
1767 if (strncmp (p, "TZ=\"", 4) == 0)
1769 char const *tzbase = p + 4;
1770 ptrdiff_t tzsize = 1;
1771 char const *s;
1773 for (s = tzbase; *s; s++, tzsize++)
1774 if (*s == '\\')
1776 s++;
1777 if (! (*s == '\\' || *s == '"'))
1778 break;
1780 else if (*s == '"')
1782 timezone_t tz1;
1783 char *tz1string = tz1buf;
1784 char *z;
1785 if (TZBUFSIZE < tzsize)
1787 tz1alloc = malloc (tzsize);
1788 if (!tz1alloc)
1789 goto fail;
1790 tz1string = tz1alloc;
1792 z = tz1string;
1793 for (s = tzbase; *s != '"'; s++)
1794 *z++ = *(s += *s == '\\');
1795 *z = '\0';
1796 tz1 = tzalloc (tz1string);
1797 if (!tz1)
1798 goto fail;
1799 tz = tz1;
1800 tzstring = tz1string;
1802 p = s + 1;
1803 while (c = *p, c_isspace (c))
1804 p++;
1806 break;
1810 struct tm tmp;
1811 if (! localtime_rz (tz, &now->tv_sec, &tmp))
1812 goto fail;
1814 /* As documented, be careful to treat the empty string just like
1815 a date string of "0". Without this, an empty string would be
1816 declared invalid when parsed during a DST transition. */
1817 if (*p == '\0')
1818 p = "0";
1820 parser_control pc;
1821 pc.input = p;
1822 pc.parse_datetime_debug = (flags & PARSE_DATETIME_DEBUG) != 0;
1823 if (INT_ADD_WRAPV (tmp.tm_year, TM_YEAR_BASE, &pc.year.value))
1825 if (pc.parse_datetime_debug)
1826 dbg_printf (_("error: initial year out of range\n"));
1827 goto fail;
1829 pc.year.digits = 0;
1830 pc.month = tmp.tm_mon + 1;
1831 pc.day = tmp.tm_mday;
1832 pc.hour = tmp.tm_hour;
1833 pc.minutes = tmp.tm_min;
1834 pc.seconds.tv_sec = tmp.tm_sec;
1835 pc.seconds.tv_nsec = Start_ns;
1836 tm.tm_isdst = tmp.tm_isdst;
1838 pc.meridian = MER24;
1839 pc.rel = rel_time_0;
1840 pc.timespec_seen = false;
1841 pc.rels_seen = false;
1842 pc.dates_seen = 0;
1843 pc.days_seen = 0;
1844 pc.times_seen = 0;
1845 pc.local_zones_seen = 0;
1846 pc.dsts_seen = 0;
1847 pc.zones_seen = 0;
1848 pc.year_seen = false;
1849 pc.debug_dates_seen = false;
1850 pc.debug_days_seen = false;
1851 pc.debug_times_seen = false;
1852 pc.debug_local_zones_seen = false;
1853 pc.debug_zones_seen = false;
1854 pc.debug_year_seen = false;
1855 pc.debug_ordinal_day_seen = false;
1857 #if HAVE_STRUCT_TM_TM_ZONE
1858 pc.local_time_zone_table[0].name = tmp.tm_zone;
1859 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1860 pc.local_time_zone_table[0].value = tmp.tm_isdst;
1861 pc.local_time_zone_table[1].name = NULL;
1863 /* Probe the names used in the next three calendar quarters, looking
1864 for a tm_isdst different from the one we already have. */
1866 int quarter;
1867 for (quarter = 1; quarter <= 3; quarter++)
1869 intmax_t iprobe;
1870 if (INT_ADD_WRAPV (Start, quarter * (90 * 24 * 60 * 60), &iprobe)
1871 || time_overflow (iprobe))
1872 break;
1873 time_t probe = iprobe;
1874 struct tm probe_tm;
1875 if (localtime_rz (tz, &probe, &probe_tm) && probe_tm.tm_zone
1876 && probe_tm.tm_isdst != pc.local_time_zone_table[0].value)
1879 pc.local_time_zone_table[1].name = probe_tm.tm_zone;
1880 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1881 pc.local_time_zone_table[1].value = probe_tm.tm_isdst;
1882 pc.local_time_zone_table[2].name = NULL;
1884 break;
1888 #else
1889 #if HAVE_TZNAME
1891 # if !HAVE_DECL_TZNAME
1892 extern char *tzname[];
1893 # endif
1894 int i;
1895 for (i = 0; i < 2; i++)
1897 pc.local_time_zone_table[i].name = tzname[i];
1898 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1899 pc.local_time_zone_table[i].value = i;
1901 pc.local_time_zone_table[i].name = NULL;
1903 #else
1904 pc.local_time_zone_table[0].name = NULL;
1905 #endif
1906 #endif
1908 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1909 && ! strcmp (pc.local_time_zone_table[0].name,
1910 pc.local_time_zone_table[1].name))
1912 /* This locale uses the same abbreviation for standard and
1913 daylight times. So if we see that abbreviation, we don't
1914 know whether it's daylight time. */
1915 pc.local_time_zone_table[0].value = -1;
1916 pc.local_time_zone_table[1].name = NULL;
1919 if (yyparse (&pc) != 0)
1921 if (pc.parse_datetime_debug)
1922 dbg_printf ((input_sentinel <= pc.input
1923 ? _("error: parsing failed\n")
1924 : _("error: parsing failed, stopped at '%s'\n")),
1925 pc.input);
1926 goto fail;
1930 /* Determine effective timezone source. */
1932 if (pc.parse_datetime_debug)
1934 dbg_printf (_("input timezone: "));
1936 if (pc.timespec_seen)
1937 fprintf (stderr, _("'@timespec' - always UTC"));
1938 else if (pc.zones_seen)
1939 fprintf (stderr, _("parsed date/time string"));
1940 else if (tzstring)
1942 if (tz != tzdefault)
1943 fprintf (stderr, _("TZ=\"%s\" in date string"), tzstring);
1944 else if (STREQ (tzstring, "UTC0"))
1946 /* Special case: 'date -u' sets TZ="UTC0". */
1947 fprintf (stderr, _("TZ=\"UTC0\" environment value or -u"));
1949 else
1950 fprintf (stderr, _("TZ=\"%s\" environment value"), tzstring);
1952 else
1953 fprintf (stderr, _("system default"));
1955 /* Account for DST changes if tLOCAL_ZONE was seen.
1956 local timezone only changes DST and is relative to the
1957 default timezone.*/
1958 if (pc.local_zones_seen && !pc.zones_seen && 0 < pc.local_isdst)
1959 fprintf (stderr, ", dst");
1961 if (pc.zones_seen)
1962 fprintf (stderr, " (%s)", time_zone_str (pc.time_zone, time_zone_buf));
1964 fputc ('\n', stderr);
1967 if (pc.timespec_seen)
1968 *result = pc.seconds;
1969 else
1971 if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
1972 | (pc.local_zones_seen + pc.zones_seen)))
1974 if (pc.parse_datetime_debug)
1976 if (pc.times_seen > 1)
1977 dbg_printf ("error: seen multiple time parts\n");
1978 if (pc.dates_seen > 1)
1979 dbg_printf ("error: seen multiple date parts\n");
1980 if (pc.days_seen > 1)
1981 dbg_printf ("error: seen multiple days parts\n");
1982 if (pc.dsts_seen > 1)
1983 dbg_printf ("error: seen multiple daylight-saving parts\n");
1984 if ((pc.local_zones_seen + pc.zones_seen) > 1)
1985 dbg_printf ("error: seen multiple time-zone parts\n");
1987 goto fail;
1990 if (! to_tm_year (pc.year, pc.parse_datetime_debug, &tm.tm_year)
1991 || INT_ADD_WRAPV (pc.month, -1, &tm.tm_mon)
1992 || INT_ADD_WRAPV (pc.day, 0, &tm.tm_mday))
1994 if (pc.parse_datetime_debug)
1995 dbg_printf (_("error: year, month, or day overflow\n"));
1996 goto fail;
1998 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
2000 tm.tm_hour = to_hour (pc.hour, pc.meridian);
2001 if (tm.tm_hour < 0)
2003 char const *mrd = (pc.meridian == MERam ? "am"
2004 : pc.meridian == MERpm ?"pm" : "");
2005 if (pc.parse_datetime_debug)
2006 dbg_printf (_("error: invalid hour %"PRIdMAX"%s\n"),
2007 pc.hour, mrd);
2008 goto fail;
2010 tm.tm_min = pc.minutes;
2011 tm.tm_sec = pc.seconds.tv_sec;
2012 if (pc.parse_datetime_debug)
2013 dbg_printf ((pc.times_seen
2014 ? _("using specified time as starting value: '%s'\n")
2015 : _("using current time as starting value: '%s'\n")),
2016 debug_strftime (&tm, dbg_tm, sizeof dbg_tm));
2018 else
2020 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
2021 pc.seconds.tv_nsec = 0;
2022 if (pc.parse_datetime_debug)
2023 dbg_printf ("warning: using midnight as starting time: 00:00:00\n");
2026 /* Let mktime deduce tm_isdst if we have an absolute timestamp. */
2027 if (pc.dates_seen | pc.days_seen | pc.times_seen)
2028 tm.tm_isdst = -1;
2030 /* But if the input explicitly specifies local time with or without
2031 DST, give mktime that information. */
2032 if (pc.local_zones_seen)
2033 tm.tm_isdst = pc.local_isdst;
2035 tm0.tm_sec = tm.tm_sec;
2036 tm0.tm_min = tm.tm_min;
2037 tm0.tm_hour = tm.tm_hour;
2038 tm0.tm_mday = tm.tm_mday;
2039 tm0.tm_mon = tm.tm_mon;
2040 tm0.tm_year = tm.tm_year;
2041 tm0.tm_isdst = tm.tm_isdst;
2042 tm.tm_wday = -1;
2044 Start = mktime_z (tz, &tm);
2046 if (! mktime_ok (&tm0, &tm))
2048 bool repaired = false;
2049 bool time_zone_seen = pc.zones_seen != 0;
2050 if (time_zone_seen)
2052 /* Guard against falsely reporting errors near the time_t
2053 boundaries when parsing times in other time zones. For
2054 example, suppose the input string "1969-12-31 23:00:00 -0100",
2055 the current time zone is 8 hours ahead of UTC, and the min
2056 time_t value is 1970-01-01 00:00:00 UTC. Then the min
2057 localtime value is 1970-01-01 08:00:00, and mktime will
2058 therefore fail on 1969-12-31 23:00:00. To work around the
2059 problem, set the time zone to 1 hour behind UTC temporarily
2060 by setting TZ="XXX1:00" and try mktime again. */
2062 char tz2buf[sizeof "XXX" - 1 + TIME_ZONE_BUFSIZE];
2063 tz2buf[0] = tz2buf[1] = tz2buf[2] = 'X';
2064 time_zone_str (pc.time_zone, &tz2buf[3]);
2065 timezone_t tz2 = tzalloc (tz2buf);
2066 if (!tz2)
2068 if (pc.parse_datetime_debug)
2069 dbg_printf (_("error: tzalloc (\"%s\") failed\n"), tz2buf);
2070 goto fail;
2072 tm.tm_sec = tm0.tm_sec;
2073 tm.tm_min = tm0.tm_min;
2074 tm.tm_hour = tm0.tm_hour;
2075 tm.tm_mday = tm0.tm_mday;
2076 tm.tm_mon = tm0.tm_mon;
2077 tm.tm_year = tm0.tm_year;
2078 tm.tm_isdst = tm0.tm_isdst;
2079 tm.tm_wday = -1;
2080 Start = mktime_z (tz2, &tm);
2081 repaired = mktime_ok (&tm0, &tm);
2082 tzfree (tz2);
2085 if (! repaired)
2087 debug_mktime_not_ok (&tm0, &tm, &pc, time_zone_seen);
2088 goto fail;
2092 char dbg_ord[DBGBUFSIZE];
2094 if (pc.days_seen && ! pc.dates_seen)
2096 intmax_t dayincr;
2097 if (INT_MULTIPLY_WRAPV ((pc.day_ordinal
2098 - (0 < pc.day_ordinal
2099 && tm.tm_wday != pc.day_number)),
2100 7, &dayincr)
2101 || INT_ADD_WRAPV ((pc.day_number - tm.tm_wday + 7) % 7,
2102 dayincr, &dayincr)
2103 || INT_ADD_WRAPV (dayincr, tm.tm_mday, &tm.tm_mday))
2104 Start = -1;
2105 else
2107 tm.tm_isdst = -1;
2108 Start = mktime_z (tz, &tm);
2111 if (Start == (time_t) -1)
2113 if (pc.parse_datetime_debug)
2114 dbg_printf (_("error: day '%s' "
2115 "(day ordinal=%"PRIdMAX" number=%d) "
2116 "resulted in an invalid date: '%s'\n"),
2117 str_days (&pc, dbg_ord, sizeof dbg_ord),
2118 pc.day_ordinal, pc.day_number,
2119 debug_strfdatetime (&tm, &pc, dbg_tm,
2120 sizeof dbg_tm));
2121 goto fail;
2124 if (pc.parse_datetime_debug)
2125 dbg_printf (_("new start date: '%s' is '%s'\n"),
2126 str_days (&pc, dbg_ord, sizeof dbg_ord),
2127 debug_strfdatetime (&tm, &pc, dbg_tm, sizeof dbg_tm));
2131 if (pc.parse_datetime_debug)
2133 if (!pc.dates_seen && !pc.days_seen)
2134 dbg_printf (_("using current date as starting value: '%s'\n"),
2135 debug_strfdate (&tm, dbg_tm, sizeof dbg_tm));
2137 if (pc.days_seen && pc.dates_seen)
2138 dbg_printf (_("warning: day (%s) ignored when explicit dates "
2139 "are given\n"),
2140 str_days (&pc, dbg_ord, sizeof dbg_ord));
2142 dbg_printf (_("starting date/time: '%s'\n"),
2143 debug_strfdatetime (&tm, &pc, dbg_tm, sizeof dbg_tm));
2146 /* Add relative date. */
2147 if (pc.rel.year | pc.rel.month | pc.rel.day)
2149 if (pc.parse_datetime_debug)
2151 if ((pc.rel.year != 0 || pc.rel.month != 0) && tm.tm_mday != 15)
2152 dbg_printf (_("warning: when adding relative months/years, "
2153 "it is recommended to specify the 15th of the "
2154 "months\n"));
2156 if (pc.rel.day != 0 && tm.tm_hour != 12)
2157 dbg_printf (_("warning: when adding relative days, "
2158 "it is recommended to specify noon\n"));
2161 int year, month, day;
2162 if (INT_ADD_WRAPV (tm.tm_year, pc.rel.year, &year)
2163 || INT_ADD_WRAPV (tm.tm_mon, pc.rel.month, &month)
2164 || INT_ADD_WRAPV (tm.tm_mday, pc.rel.day, &day))
2166 if (pc.parse_datetime_debug)
2167 dbg_printf (_("error: %s:%d\n"), __FILE__, __LINE__);
2168 goto fail;
2170 tm.tm_year = year;
2171 tm.tm_mon = month;
2172 tm.tm_mday = day;
2173 tm.tm_hour = tm0.tm_hour;
2174 tm.tm_min = tm0.tm_min;
2175 tm.tm_sec = tm0.tm_sec;
2176 tm.tm_isdst = tm0.tm_isdst;
2177 Start = mktime_z (tz, &tm);
2178 if (Start == (time_t) -1)
2180 if (pc.parse_datetime_debug)
2181 dbg_printf (_("error: adding relative date resulted "
2182 "in an invalid date: '%s'\n"),
2183 debug_strfdatetime (&tm, &pc, dbg_tm,
2184 sizeof dbg_tm));
2185 goto fail;
2188 if (pc.parse_datetime_debug)
2190 dbg_printf (_("after date adjustment "
2191 "(%+"PRIdMAX" years, %+"PRIdMAX" months, "
2192 "%+"PRIdMAX" days),\n"),
2193 pc.rel.year, pc.rel.month, pc.rel.day);
2194 dbg_printf (_(" new date/time = '%s'\n"),
2195 debug_strfdatetime (&tm, &pc, dbg_tm,
2196 sizeof dbg_tm));
2198 /* Warn about crossing DST due to time adjustment.
2199 Example: https://bugs.gnu.org/8357
2200 env TZ=Europe/Helsinki \
2201 date --debug \
2202 -d 'Mon Mar 28 00:36:07 2011 EEST 1 day ago'
2204 This case is different than DST changes due to time adjustment,
2205 i.e., "1 day ago" vs "24 hours ago" are calculated in different
2206 places.
2208 'tm0.tm_isdst' contains the DST of the input date,
2209 'tm.tm_isdst' is the normalized result after calling
2210 mktime (&tm).
2212 if (tm0.tm_isdst != -1 && tm.tm_isdst != tm0.tm_isdst)
2213 dbg_printf (_("warning: daylight saving time changed after "
2214 "date adjustment\n"));
2216 /* Warn if the user did not ask to adjust days but mday changed,
2218 user did not ask to adjust months/days but the month changed.
2220 Example for first case:
2221 2016-05-31 + 1 month => 2016-06-31 => 2016-07-01.
2222 User asked to adjust month, but the day changed from 31 to 01.
2224 Example for second case:
2225 2016-02-29 + 1 year => 2017-02-29 => 2017-03-01.
2226 User asked to adjust year, but the month changed from 02 to 03.
2228 if (pc.rel.day == 0
2229 && (tm.tm_mday != day
2230 || (pc.rel.month == 0 && tm.tm_mon != month)))
2232 dbg_printf (_("warning: month/year adjustment resulted in "
2233 "shifted dates:\n"));
2234 char tm_year_buf[TM_YEAR_BUFSIZE];
2235 dbg_printf (_(" adjusted Y M D: %s %02d %02d\n"),
2236 tm_year_str (year, tm_year_buf), month + 1, day);
2237 dbg_printf (_(" normalized Y M D: %s %02d %02d\n"),
2238 tm_year_str (tm.tm_year, tm_year_buf),
2239 tm.tm_mon + 1, tm.tm_mday);
2245 /* The only "output" of this if-block is an updated Start value,
2246 so this block must follow others that clobber Start. */
2247 if (pc.zones_seen)
2249 intmax_t delta = pc.time_zone, t1;
2250 bool overflow = false;
2251 #ifdef HAVE_TM_GMTOFF
2252 long int utcoff = tm.tm_gmtoff;
2253 #else
2254 time_t t = Start;
2255 struct tm gmt;
2256 int utcoff = (gmtime_r (&t, &gmt)
2257 ? tm_diff (&tm, &gmt)
2258 : (overflow = true, 0));
2259 #endif
2260 overflow |= INT_SUBTRACT_WRAPV (delta, utcoff, &delta);
2261 overflow |= INT_SUBTRACT_WRAPV (Start, delta, &t1);
2262 if (overflow || time_overflow (t1))
2264 if (pc.parse_datetime_debug)
2265 dbg_printf (_("error: timezone %d caused time_t overflow\n"),
2266 pc.time_zone);
2267 goto fail;
2269 Start = t1;
2272 if (pc.parse_datetime_debug)
2274 intmax_t Starti = Start;
2275 dbg_printf (_("'%s' = %"PRIdMAX" epoch-seconds\n"),
2276 debug_strfdatetime (&tm, &pc, dbg_tm, sizeof dbg_tm),
2277 Starti);
2281 /* Add relative hours, minutes, and seconds. On hosts that support
2282 leap seconds, ignore the possibility of leap seconds; e.g.,
2283 "+ 10 minutes" adds 600 seconds, even if one of them is a
2284 leap second. Typically this is not what the user wants, but it's
2285 too hard to do it the other way, because the time zone indicator
2286 must be applied before relative times, and if mktime is applied
2287 again the time zone will be lost. */
2289 intmax_t orig_ns = pc.seconds.tv_nsec;
2290 intmax_t sum_ns = orig_ns + pc.rel.ns;
2291 int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
2292 int d4 = (sum_ns - normalized_ns) / BILLION;
2293 intmax_t d1, t1, d2, t2, t3, t4;
2294 if (INT_MULTIPLY_WRAPV (pc.rel.hour, 60 * 60, &d1)
2295 || INT_ADD_WRAPV (Start, d1, &t1)
2296 || INT_MULTIPLY_WRAPV (pc.rel.minutes, 60, &d2)
2297 || INT_ADD_WRAPV (t1, d2, &t2)
2298 || INT_ADD_WRAPV (t2, pc.rel.seconds, &t3)
2299 || INT_ADD_WRAPV (t3, d4, &t4)
2300 || time_overflow (t4))
2302 if (pc.parse_datetime_debug)
2303 dbg_printf (_("error: adding relative time caused an "
2304 "overflow\n"));
2305 goto fail;
2308 result->tv_sec = t4;
2309 result->tv_nsec = normalized_ns;
2311 if (pc.parse_datetime_debug
2312 && (pc.rel.hour | pc.rel.minutes | pc.rel.seconds | pc.rel.ns))
2314 dbg_printf (_("after time adjustment (%+"PRIdMAX" hours, "
2315 "%+"PRIdMAX" minutes, "
2316 "%+"PRIdMAX" seconds, %+d ns),\n"),
2317 pc.rel.hour, pc.rel.minutes, pc.rel.seconds,
2318 pc.rel.ns);
2319 dbg_printf (_(" new time = %"PRIdMAX" epoch-seconds\n"), t4);
2321 /* Warn about crossing DST due to time adjustment.
2322 Example: https://bugs.gnu.org/8357
2323 env TZ=Europe/Helsinki \
2324 date --debug \
2325 -d 'Mon Mar 28 00:36:07 2011 EEST 24 hours ago'
2327 This case is different than DST changes due to days adjustment,
2328 i.e., "1 day ago" vs "24 hours ago" are calculated in different
2329 places.
2331 'tm.tm_isdst' contains the date after date adjustment. */
2332 struct tm lmt;
2333 if (tm.tm_isdst != -1 && localtime_rz (tz, &result->tv_sec, &lmt)
2334 && tm.tm_isdst != lmt.tm_isdst)
2335 dbg_printf (_("warning: daylight saving time changed after "
2336 "time adjustment\n"));
2341 if (pc.parse_datetime_debug)
2343 /* Special case: using 'date -u' simply set TZ=UTC0 */
2344 if (! tzstring)
2345 dbg_printf (_("timezone: system default\n"));
2346 else if (STREQ (tzstring, "UTC0"))
2347 dbg_printf (_("timezone: Universal Time\n"));
2348 else
2349 dbg_printf (_("timezone: TZ=\"%s\" environment value\n"), tzstring);
2351 intmax_t sec = result->tv_sec;
2352 int nsec = result->tv_nsec;
2353 dbg_printf (_("final: %"PRIdMAX".%09d (epoch-seconds)\n"),
2354 sec, nsec);
2356 struct tm gmt, lmt;
2357 bool got_utc = !!gmtime_r (&result->tv_sec, &gmt);
2358 if (got_utc)
2359 dbg_printf (_("final: %s (UTC)\n"),
2360 debug_strfdatetime (&gmt, NULL,
2361 dbg_tm, sizeof dbg_tm));
2362 if (localtime_rz (tz, &result->tv_sec, &lmt))
2364 #ifdef HAVE_TM_GMTOFF
2365 bool got_utcoff = true;
2366 long int utcoff = lmt.tm_gmtoff;
2367 #else
2368 bool got_utcoff = got_utc;
2369 int utcoff;
2370 if (got_utcoff)
2371 utcoff = tm_diff (&lmt, &gmt);
2372 #endif
2373 if (got_utcoff)
2374 dbg_printf (_("final: %s (UTC%s)\n"),
2375 debug_strfdatetime (&lmt, NULL, dbg_tm, sizeof dbg_tm),
2376 time_zone_str (utcoff, time_zone_buf));
2377 else
2378 dbg_printf (_("final: %s (unknown time zone offset)\n"),
2379 debug_strfdatetime (&lmt, NULL, dbg_tm, sizeof dbg_tm));
2383 ok = true;
2385 fail:
2386 if (tz != tzdefault)
2387 tzfree (tz);
2388 free (tz1alloc);
2389 return ok;
2392 #if TEST
2395 main (int ac, char **av)
2397 char buff[BUFSIZ];
2399 printf ("Enter date, or blank line to exit.\n\t> ");
2400 fflush (stdout);
2402 buff[BUFSIZ - 1] = '\0';
2403 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
2405 struct timespec d;
2406 struct tm const *tm;
2407 if (! parse_datetime (&d, buff, NULL))
2408 printf ("Bad format - couldn't convert.\n");
2409 else if (! (tm = localtime (&d.tv_sec)))
2411 intmax_t sec = d.tv_sec;
2412 printf ("localtime (%"PRIdMAX") failed\n", sec);
2414 else
2416 int ns = d.tv_nsec;
2417 char tm_year_buf[TM_YEAR_BUFSIZE];
2418 printf ("%s-%02d-%02d %02d:%02d:%02d.%09d\n",
2419 tm_year_str (tm->tm_year, tm_year_buf),
2420 tm->tm_mon + 1, tm->tm_mday,
2421 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);
2423 printf ("\t> ");
2424 fflush (stdout);
2426 return 0;
2428 #endif /* TEST */