parse-datetime: Use idx_t for nonnegative ptrdiff_t variables.
[gnulib.git] / lib / parse-datetime.y
blob2b56db440b75b72d9c3dcc1bf119214879624e3b
1 %{
2 /* Parse a string into an internal timestamp.
4 Copyright (C) 1999-2000, 2002-2020 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 and 2020 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 "idx.h"
39 #include "intprops.h"
40 #include "timespec.h"
41 #include "verify.h"
42 #include "strftime.h"
44 /* There's no need to extend the stack, so there's no need to involve
45 alloca. */
46 #define YYSTACK_USE_ALLOCA 0
48 /* Tell Bison how much stack space is needed. 20 should be plenty for
49 this grammar, which is not right recursive. Beware setting it too
50 high, since that might cause problems on machines whose
51 implementations have lame stack-overflow checking. */
52 #define YYMAXDEPTH 20
53 #define YYINITDEPTH YYMAXDEPTH
55 /* Since the code of parse-datetime.y is not included in the Emacs executable
56 itself, there is no need to #define static in this file. Even if
57 the code were included in the Emacs executable, it probably
58 wouldn't do any harm to #undef it here; this will only cause
59 problems if we try to write to a static variable, which I don't
60 think this code needs to do. */
61 #ifdef emacs
62 # undef static
63 #endif
65 #include <inttypes.h>
66 #include <c-ctype.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 /* Shift A right by B bits portably, by dividing A by 2**B and
86 truncating towards minus infinity. A and B should be free of side
87 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
88 INT_BITS is the number of useful bits in an int. GNU code can
89 assume that INT_BITS is at least 32.
91 ISO C99 says that A >> B is implementation-defined if A < 0. Some
92 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
93 right in the usual way when A < 0, so SHR falls back on division if
94 ordinary A >> B doesn't seem to be the usual signed shift. */
95 #define SHR(a, b) \
96 (-1 >> 1 == -1 \
97 ? (a) >> (b) \
98 : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
100 #define HOUR(x) (60 * 60 * (x))
102 #define STREQ(a, b) (strcmp (a, b) == 0)
104 /* Verify that time_t is an integer as POSIX requires, and that every
105 time_t value fits in intmax_t. Please file a bug report if these
106 assumptions are false on your platform. */
107 verify (TYPE_IS_INTEGER (time_t));
108 verify (!TYPE_SIGNED (time_t) || INTMAX_MIN <= TYPE_MINIMUM (time_t));
109 verify (TYPE_MAXIMUM (time_t) <= INTMAX_MAX);
111 /* True if N is out of range for time_t. */
112 static bool
113 time_overflow (intmax_t n)
115 return ! ((TYPE_SIGNED (time_t) ? TYPE_MINIMUM (time_t) <= n : 0 <= n)
116 && n <= TYPE_MAXIMUM (time_t));
119 /* Convert a possibly-signed character to an unsigned character. This is
120 a bit safer than casting to unsigned char, since it catches some type
121 errors that the cast doesn't. */
122 static unsigned char to_uchar (char ch) { return ch; }
124 static void _GL_ATTRIBUTE_FORMAT ((__printf__, 1, 2))
125 dbg_printf (char const *msg, ...)
127 va_list args;
128 /* TODO: use gnulib's 'program_name' instead? */
129 fputs ("date: ", stderr);
131 va_start (args, msg);
132 vfprintf (stderr, msg, args);
133 va_end (args);
137 /* An integer value, and the number of digits in its textual
138 representation. */
139 typedef struct
141 bool negative;
142 intmax_t value;
143 idx_t digits;
144 } textint;
146 /* An entry in the lexical lookup table. */
147 typedef struct
149 char const *name;
150 int type;
151 int value;
152 } table;
154 /* Meridian: am, pm, or 24-hour style. */
155 enum { MERam, MERpm, MER24 };
157 /* A reasonable upper bound for the buffer used in debug output. */
158 enum { DBGBUFSIZE = 100 };
160 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
162 /* Relative times. */
163 typedef struct
165 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
166 intmax_t year;
167 intmax_t month;
168 intmax_t day;
169 intmax_t hour;
170 intmax_t minutes;
171 intmax_t seconds;
172 int ns;
173 } relative_time;
175 #if HAVE_COMPOUND_LITERALS
176 # define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
177 #else
178 static relative_time const RELATIVE_TIME_0;
179 #endif
181 /* Information passed to and from the parser. */
182 typedef struct
184 /* The input string remaining to be parsed. */
185 const char *input;
187 /* N, if this is the Nth Tuesday. */
188 intmax_t day_ordinal;
190 /* Day of week; Sunday is 0. */
191 int day_number;
193 /* tm_isdst flag for the local zone. */
194 int local_isdst;
196 /* Time zone, in seconds east of UT. */
197 int time_zone;
199 /* Style used for time. */
200 int meridian;
202 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
203 textint year;
204 intmax_t month;
205 intmax_t day;
206 intmax_t hour;
207 intmax_t minutes;
208 struct timespec seconds; /* includes nanoseconds */
210 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
211 relative_time rel;
213 /* Presence or counts of nonterminals of various flavors parsed so far. */
214 bool timespec_seen;
215 bool rels_seen;
216 idx_t dates_seen;
217 idx_t days_seen;
218 idx_t local_zones_seen;
219 idx_t dsts_seen;
220 idx_t times_seen;
221 idx_t zones_seen;
222 bool year_seen;
224 /* Print debugging output to stderr. */
225 bool parse_datetime_debug;
227 /* Which of the 'seen' parts have been printed when debugging. */
228 bool debug_dates_seen;
229 bool debug_days_seen;
230 bool debug_local_zones_seen;
231 bool debug_times_seen;
232 bool debug_zones_seen;
233 bool debug_year_seen;
235 /* The user specified explicit ordinal day value. */
236 bool debug_ordinal_day_seen;
238 /* Table of local time zone abbreviations, terminated by a null entry. */
239 table local_time_zone_table[3];
240 } parser_control;
242 union YYSTYPE;
243 static int yylex (union YYSTYPE *, parser_control *);
244 static int yyerror (parser_control const *, char const *);
245 static bool time_zone_hhmm (parser_control *, textint, intmax_t);
247 /* Extract into *PC any date and time info from a string of digits
248 of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
249 YYYY, ...). */
250 static void
251 digits_to_date_time (parser_control *pc, textint text_int)
253 if (pc->dates_seen && ! pc->year.digits
254 && ! pc->rels_seen && (pc->times_seen || 2 < text_int.digits))
256 pc->year_seen = true;
257 pc->year = text_int;
259 else
261 if (4 < text_int.digits)
263 pc->dates_seen++;
264 pc->day = text_int.value % 100;
265 pc->month = (text_int.value / 100) % 100;
266 pc->year.value = text_int.value / 10000;
267 pc->year.digits = text_int.digits - 4;
269 else
271 pc->times_seen++;
272 if (text_int.digits <= 2)
274 pc->hour = text_int.value;
275 pc->minutes = 0;
277 else
279 pc->hour = text_int.value / 100;
280 pc->minutes = text_int.value % 100;
282 pc->seconds.tv_sec = 0;
283 pc->seconds.tv_nsec = 0;
284 pc->meridian = MER24;
289 /* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1). Return true
290 if successful, false if an overflow occurred. */
291 static bool
292 apply_relative_time (parser_control *pc, relative_time rel, int factor)
294 if (factor < 0
295 ? (INT_SUBTRACT_WRAPV (pc->rel.ns, rel.ns, &pc->rel.ns)
296 | INT_SUBTRACT_WRAPV (pc->rel.seconds, rel.seconds, &pc->rel.seconds)
297 | INT_SUBTRACT_WRAPV (pc->rel.minutes, rel.minutes, &pc->rel.minutes)
298 | INT_SUBTRACT_WRAPV (pc->rel.hour, rel.hour, &pc->rel.hour)
299 | INT_SUBTRACT_WRAPV (pc->rel.day, rel.day, &pc->rel.day)
300 | INT_SUBTRACT_WRAPV (pc->rel.month, rel.month, &pc->rel.month)
301 | INT_SUBTRACT_WRAPV (pc->rel.year, rel.year, &pc->rel.year))
302 : (INT_ADD_WRAPV (pc->rel.ns, rel.ns, &pc->rel.ns)
303 | INT_ADD_WRAPV (pc->rel.seconds, rel.seconds, &pc->rel.seconds)
304 | INT_ADD_WRAPV (pc->rel.minutes, rel.minutes, &pc->rel.minutes)
305 | INT_ADD_WRAPV (pc->rel.hour, rel.hour, &pc->rel.hour)
306 | INT_ADD_WRAPV (pc->rel.day, rel.day, &pc->rel.day)
307 | INT_ADD_WRAPV (pc->rel.month, rel.month, &pc->rel.month)
308 | INT_ADD_WRAPV (pc->rel.year, rel.year, &pc->rel.year)))
309 return false;
310 pc->rels_seen = true;
311 return true;
314 /* Set PC-> hour, minutes, seconds and nanoseconds members from arguments. */
315 static void
316 set_hhmmss (parser_control *pc, intmax_t hour, intmax_t minutes,
317 time_t sec, int nsec)
319 pc->hour = hour;
320 pc->minutes = minutes;
321 pc->seconds.tv_sec = sec;
322 pc->seconds.tv_nsec = nsec;
325 /* Return a textual representation of the day ordinal/number values
326 in the parser_control struct (e.g., "last wed", "this tues", "thu"). */
327 static const char *
328 str_days (parser_control *pc, char *buffer, int n)
330 /* TODO: use relative_time_table for reverse lookup. */
331 static char const ordinal_values[][11] = {
332 "last",
333 "this",
334 "next/first",
335 "(SECOND)", /* SECOND is commented out in relative_time_table. */
336 "third",
337 "fourth",
338 "fifth",
339 "sixth",
340 "seventh",
341 "eight",
342 "ninth",
343 "tenth",
344 "eleventh",
345 "twelfth"
348 static char const days_values[][4] = {
349 "Sun",
350 "Mon",
351 "Tue",
352 "Wed",
353 "Thu",
354 "Fri",
355 "Sat"
358 int len;
360 /* Don't add an ordinal prefix if the user didn't specify it
361 (e.g., "this wed" vs "wed"). */
362 if (pc->debug_ordinal_day_seen)
364 /* Use word description if possible (e.g., -1 = last, 3 = third). */
365 len = (-1 <= pc->day_ordinal && pc->day_ordinal <= 12
366 ? snprintf (buffer, n, "%s", ordinal_values[pc->day_ordinal + 1])
367 : snprintf (buffer, n, "%"PRIdMAX, pc->day_ordinal));
369 else
371 buffer[0] = '\0';
372 len = 0;
375 /* Add the day name */
376 if (0 <= pc->day_number && pc->day_number <= 6 && 0 <= len && len < n)
377 snprintf (buffer + len, n - len, &" %s"[len == 0],
378 days_values[pc->day_number]);
379 else
381 /* invalid day_number value - should never happen */
383 return buffer;
386 /* Convert a time zone to its string representation. */
388 enum { TIME_ZONE_BUFSIZE = INT_STRLEN_BOUND (intmax_t) + sizeof ":MM:SS" } ;
390 static char const *
391 time_zone_str (int time_zone, char time_zone_buf[TIME_ZONE_BUFSIZE])
393 char *p = time_zone_buf;
394 char sign = time_zone < 0 ? '-' : '+';
395 int hour = abs (time_zone / (60 * 60));
396 p += sprintf (time_zone_buf, "%c%02d", sign, hour);
397 int offset_from_hour = abs (time_zone % (60 * 60));
398 if (offset_from_hour != 0)
400 int mm = offset_from_hour / 60;
401 int ss = offset_from_hour % 60;
402 *p++ = ':';
403 *p++ = '0' + mm / 10;
404 *p++ = '0' + mm % 10;
405 if (ss)
407 *p++ = ':';
408 *p++ = '0' + ss / 10;
409 *p++ = '0' + ss % 10;
411 *p = '\0';
413 return time_zone_buf;
416 /* debugging: print the current time in the parser_control structure.
417 The parser will increment "*_seen" members for those which were parsed.
418 This function will print only newly seen parts. */
419 static void
420 debug_print_current_time (char const *item, parser_control *pc)
422 bool space = false;
424 if (!pc->parse_datetime_debug)
425 return;
427 /* no newline, more items printed below */
428 dbg_printf (_("parsed %s part: "), item);
430 if (pc->dates_seen && !pc->debug_dates_seen)
432 /*TODO: use pc->year.negative? */
433 fprintf (stderr, "(Y-M-D) %04"PRIdMAX"-%02"PRIdMAX"-%02"PRIdMAX,
434 pc->year.value, pc->month, pc->day);
435 pc->debug_dates_seen = true;
436 space = true;
439 if (pc->year_seen != pc->debug_year_seen)
441 if (space)
442 fputc (' ', stderr);
443 fprintf (stderr, _("year: %04"PRIdMAX), pc->year.value);
445 pc->debug_year_seen = pc->year_seen;
446 space = true;
449 if (pc->times_seen && !pc->debug_times_seen)
451 intmax_t sec = pc->seconds.tv_sec;
452 fprintf (stderr, &" %02"PRIdMAX":%02"PRIdMAX":%02"PRIdMAX[!space],
453 pc->hour, pc->minutes, sec);
454 if (pc->seconds.tv_nsec != 0)
456 int nsec = pc->seconds.tv_nsec;
457 fprintf (stderr, ".%09d", nsec);
459 if (pc->meridian == MERpm)
460 fputs ("pm", stderr);
462 pc->debug_times_seen = true;
463 space = true;
466 if (pc->days_seen && !pc->debug_days_seen)
468 if (space)
469 fputc (' ', stderr);
470 char tmp[DBGBUFSIZE];
471 fprintf (stderr, _("%s (day ordinal=%"PRIdMAX" number=%d)"),
472 str_days (pc, tmp, sizeof tmp),
473 pc->day_ordinal, pc->day_number);
474 pc->debug_days_seen = true;
475 space = true;
478 /* local zone strings only change the DST settings,
479 not the timezone value. If seen, inform about the DST. */
480 if (pc->local_zones_seen && !pc->debug_local_zones_seen)
482 fprintf (stderr, &" isdst=%d%s"[!space],
483 pc->local_isdst, pc->dsts_seen ? " DST" : "");
484 pc->debug_local_zones_seen = true;
485 space = true;
488 if (pc->zones_seen && !pc->debug_zones_seen)
490 char time_zone_buf[TIME_ZONE_BUFSIZE];
491 fprintf (stderr, &" UTC%s"[!space],
492 time_zone_str (pc->time_zone, time_zone_buf));
493 pc->debug_zones_seen = true;
494 space = true;
497 if (pc->timespec_seen)
499 intmax_t sec = pc->seconds.tv_sec;
500 if (space)
501 fputc (' ', stderr);
502 fprintf (stderr, _("number of seconds: %"PRIdMAX), sec);
505 fputc ('\n', stderr);
508 /* Debugging: print the current relative values. */
510 static bool
511 print_rel_part (bool space, intmax_t val, char const *name)
513 if (val == 0)
514 return space;
515 fprintf (stderr, &" %+"PRIdMAX" %s"[!space], val, name);
516 return true;
519 static void
520 debug_print_relative_time (char const *item, parser_control const *pc)
522 bool space = false;
524 if (!pc->parse_datetime_debug)
525 return;
527 /* no newline, more items printed below */
528 dbg_printf (_("parsed %s part: "), item);
530 if (pc->rel.year == 0 && pc->rel.month == 0 && pc->rel.day == 0
531 && pc->rel.hour == 0 && pc->rel.minutes == 0 && pc->rel.seconds == 0
532 && pc->rel.ns == 0)
534 /* Special case: relative time of this/today/now */
535 fputs (_("today/this/now\n"), stderr);
536 return;
539 space = print_rel_part (space, pc->rel.year, "year(s)");
540 space = print_rel_part (space, pc->rel.month, "month(s)");
541 space = print_rel_part (space, pc->rel.day, "day(s)");
542 space = print_rel_part (space, pc->rel.hour, "hour(s)");
543 space = print_rel_part (space, pc->rel.minutes, "minutes");
544 space = print_rel_part (space, pc->rel.seconds, "seconds");
545 print_rel_part (space, pc->rel.ns, "nanoseconds");
547 fputc ('\n', stderr);
554 /* We want a reentrant parser, even if the TZ manipulation and the calls to
555 localtime and gmtime are not reentrant. */
556 %define api.pure
557 %parse-param { parser_control *pc }
558 %lex-param { parser_control *pc }
560 /* This grammar has 31 shift/reduce conflicts. */
561 %expect 31
563 %union
565 intmax_t intval;
566 textint textintval;
567 struct timespec timespec;
568 relative_time rel;
571 %token <intval> tAGO
572 %token tDST
574 %token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
575 %token <intval> tDAY_UNIT tDAY_SHIFT
577 %token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
578 %token <intval> tMONTH tORDINAL tZONE
580 %token <textintval> tSNUMBER tUNUMBER
581 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
583 %type <intval> o_colon_minutes
584 %type <timespec> seconds signed_seconds unsigned_seconds
586 %type <rel> relunit relunit_snumber dayshift
590 spec:
591 timespec
592 | items
595 timespec:
596 '@' seconds
598 pc->seconds = $2;
599 pc->timespec_seen = true;
600 debug_print_current_time (_("number of seconds"), pc);
604 items:
605 /* empty */
606 | items item
609 item:
610 datetime
612 pc->times_seen++; pc->dates_seen++;
613 debug_print_current_time (_("datetime"), pc);
615 | time
617 pc->times_seen++;
618 debug_print_current_time (_("time"), pc);
620 | local_zone
622 pc->local_zones_seen++;
623 debug_print_current_time (_("local_zone"), pc);
625 | zone
627 pc->zones_seen++;
628 debug_print_current_time (_("zone"), pc);
630 | date
632 pc->dates_seen++;
633 debug_print_current_time (_("date"), pc);
635 | day
637 pc->days_seen++;
638 debug_print_current_time (_("day"), pc);
640 | rel
642 debug_print_relative_time (_("relative"), pc);
644 | number
646 debug_print_current_time (_("number"), pc);
648 | hybrid
650 debug_print_relative_time (_("hybrid"), pc);
654 datetime:
655 iso_8601_datetime
658 iso_8601_datetime:
659 iso_8601_date 'T' iso_8601_time
662 time:
663 tUNUMBER tMERIDIAN
665 set_hhmmss (pc, $1.value, 0, 0, 0);
666 pc->meridian = $2;
668 | tUNUMBER ':' tUNUMBER tMERIDIAN
670 set_hhmmss (pc, $1.value, $3.value, 0, 0);
671 pc->meridian = $4;
673 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tMERIDIAN
675 set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
676 pc->meridian = $6;
678 | iso_8601_time
681 iso_8601_time:
682 tUNUMBER zone_offset
684 set_hhmmss (pc, $1.value, 0, 0, 0);
685 pc->meridian = MER24;
687 | tUNUMBER ':' tUNUMBER o_zone_offset
689 set_hhmmss (pc, $1.value, $3.value, 0, 0);
690 pc->meridian = MER24;
692 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_zone_offset
694 set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
695 pc->meridian = MER24;
699 o_zone_offset:
700 /* empty */
701 | zone_offset
704 zone_offset:
705 tSNUMBER o_colon_minutes
707 pc->zones_seen++;
708 if (! time_zone_hhmm (pc, $1, $2)) YYABORT;
712 /* Local zone strings affect only the DST setting, and take effect
713 only if the current TZ setting is relevant.
715 Example 1:
716 'EEST' is parsed as tLOCAL_ZONE, as it relates to the effective TZ:
717 TZ='Europe/Helsinki' date -d '2016-06-30 EEST'
719 Example 2:
720 'EEST' is parsed as tDAYZONE:
721 TZ='Asia/Tokyo' date -d '2016-06-30 EEST'
723 This is implemented by probing the next three calendar quarters
724 of the effective timezone and looking for DST changes -
725 if found, the timezone name (EEST) is inserted into
726 the lexical lookup table with type tLOCAL_ZONE.
727 (Search for 'quarter' comment in 'parse_datetime2'.)
729 local_zone:
730 tLOCAL_ZONE
731 { pc->local_isdst = $1; }
732 | tLOCAL_ZONE tDST
734 pc->local_isdst = 1;
735 pc->dsts_seen++;
739 /* Note 'T' is a special case, as it is used as the separator in ISO
740 8601 date and time of day representation. */
741 zone:
742 tZONE
743 { pc->time_zone = $1; }
744 | 'T'
745 { pc->time_zone = -HOUR (7); }
746 | tZONE relunit_snumber
747 { pc->time_zone = $1;
748 if (! apply_relative_time (pc, $2, 1)) YYABORT;
749 debug_print_relative_time (_("relative"), pc);
751 | 'T' relunit_snumber
752 { pc->time_zone = -HOUR (7);
753 if (! apply_relative_time (pc, $2, 1)) YYABORT;
754 debug_print_relative_time (_("relative"), pc);
756 | tZONE tSNUMBER o_colon_minutes
757 { if (! time_zone_hhmm (pc, $2, $3)) YYABORT;
758 if (INT_ADD_WRAPV (pc->time_zone, $1, &pc->time_zone)) YYABORT; }
759 | tDAYZONE
760 { pc->time_zone = $1 + 60 * 60; }
761 | tZONE tDST
762 { pc->time_zone = $1 + 60 * 60; }
765 day:
766 tDAY
768 pc->day_ordinal = 0;
769 pc->day_number = $1;
771 | tDAY ','
773 pc->day_ordinal = 0;
774 pc->day_number = $1;
776 | tORDINAL tDAY
778 pc->day_ordinal = $1;
779 pc->day_number = $2;
780 pc->debug_ordinal_day_seen = true;
782 | tUNUMBER tDAY
784 pc->day_ordinal = $1.value;
785 pc->day_number = $2;
786 pc->debug_ordinal_day_seen = true;
790 date:
791 tUNUMBER '/' tUNUMBER
793 pc->month = $1.value;
794 pc->day = $3.value;
796 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
798 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
799 otherwise as MM/DD/YY.
800 The goal in recognizing YYYY/MM/DD is solely to support legacy
801 machine-generated dates like those in an RCS log listing. If
802 you want portability, use the ISO 8601 format. */
803 if (4 <= $1.digits)
805 if (pc->parse_datetime_debug)
807 intmax_t digits = $1.digits;
808 dbg_printf (_("warning: value %"PRIdMAX" has %"PRIdMAX" digits. "
809 "Assuming YYYY/MM/DD\n"),
810 $1.value, digits);
813 pc->year = $1;
814 pc->month = $3.value;
815 pc->day = $5.value;
817 else
819 if (pc->parse_datetime_debug)
820 dbg_printf (_("warning: value %"PRIdMAX" has less than 4 digits. "
821 "Assuming MM/DD/YY[YY]\n"),
822 $1.value);
824 pc->month = $1.value;
825 pc->day = $3.value;
826 pc->year = $5;
829 | tUNUMBER tMONTH tSNUMBER
831 /* E.g., 17-JUN-1992. */
832 pc->day = $1.value;
833 pc->month = $2;
834 if (INT_SUBTRACT_WRAPV (0, $3.value, &pc->year.value)) YYABORT;
835 pc->year.digits = $3.digits;
837 | tMONTH tSNUMBER tSNUMBER
839 /* E.g., JUN-17-1992. */
840 pc->month = $1;
841 if (INT_SUBTRACT_WRAPV (0, $2.value, &pc->day)) YYABORT;
842 if (INT_SUBTRACT_WRAPV (0, $3.value, &pc->year.value)) YYABORT;
843 pc->year.digits = $3.digits;
845 | tMONTH tUNUMBER
847 pc->month = $1;
848 pc->day = $2.value;
850 | tMONTH tUNUMBER ',' tUNUMBER
852 pc->month = $1;
853 pc->day = $2.value;
854 pc->year = $4;
856 | tUNUMBER tMONTH
858 pc->day = $1.value;
859 pc->month = $2;
861 | tUNUMBER tMONTH tUNUMBER
863 pc->day = $1.value;
864 pc->month = $2;
865 pc->year = $3;
867 | iso_8601_date
870 iso_8601_date:
871 tUNUMBER tSNUMBER tSNUMBER
873 /* ISO 8601 format. YYYY-MM-DD. */
874 pc->year = $1;
875 if (INT_SUBTRACT_WRAPV (0, $2.value, &pc->month)) YYABORT;
876 if (INT_SUBTRACT_WRAPV (0, $3.value, &pc->day)) YYABORT;
880 rel:
881 relunit tAGO
882 { if (! apply_relative_time (pc, $1, $2)) YYABORT; }
883 | relunit
884 { if (! apply_relative_time (pc, $1, 1)) YYABORT; }
885 | dayshift
886 { if (! apply_relative_time (pc, $1, 1)) YYABORT; }
889 relunit:
890 tORDINAL tYEAR_UNIT
891 { $$ = RELATIVE_TIME_0; $$.year = $1; }
892 | tUNUMBER tYEAR_UNIT
893 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
894 | tYEAR_UNIT
895 { $$ = RELATIVE_TIME_0; $$.year = 1; }
896 | tORDINAL tMONTH_UNIT
897 { $$ = RELATIVE_TIME_0; $$.month = $1; }
898 | tUNUMBER tMONTH_UNIT
899 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
900 | tMONTH_UNIT
901 { $$ = RELATIVE_TIME_0; $$.month = 1; }
902 | tORDINAL tDAY_UNIT
903 { $$ = RELATIVE_TIME_0;
904 if (INT_MULTIPLY_WRAPV ($1, $2, &$$.day)) YYABORT; }
905 | tUNUMBER tDAY_UNIT
906 { $$ = RELATIVE_TIME_0;
907 if (INT_MULTIPLY_WRAPV ($1.value, $2, &$$.day)) YYABORT; }
908 | tDAY_UNIT
909 { $$ = RELATIVE_TIME_0; $$.day = $1; }
910 | tORDINAL tHOUR_UNIT
911 { $$ = RELATIVE_TIME_0; $$.hour = $1; }
912 | tUNUMBER tHOUR_UNIT
913 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
914 | tHOUR_UNIT
915 { $$ = RELATIVE_TIME_0; $$.hour = 1; }
916 | tORDINAL tMINUTE_UNIT
917 { $$ = RELATIVE_TIME_0; $$.minutes = $1; }
918 | tUNUMBER tMINUTE_UNIT
919 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
920 | tMINUTE_UNIT
921 { $$ = RELATIVE_TIME_0; $$.minutes = 1; }
922 | tORDINAL tSEC_UNIT
923 { $$ = RELATIVE_TIME_0; $$.seconds = $1; }
924 | tUNUMBER tSEC_UNIT
925 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
926 | tSDECIMAL_NUMBER tSEC_UNIT
927 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
928 | tUDECIMAL_NUMBER tSEC_UNIT
929 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
930 | tSEC_UNIT
931 { $$ = RELATIVE_TIME_0; $$.seconds = 1; }
932 | relunit_snumber
935 relunit_snumber:
936 tSNUMBER tYEAR_UNIT
937 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
938 | tSNUMBER tMONTH_UNIT
939 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
940 | tSNUMBER tDAY_UNIT
941 { $$ = RELATIVE_TIME_0;
942 if (INT_MULTIPLY_WRAPV ($1.value, $2, &$$.day)) YYABORT; }
943 | tSNUMBER tHOUR_UNIT
944 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
945 | tSNUMBER tMINUTE_UNIT
946 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
947 | tSNUMBER tSEC_UNIT
948 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
951 dayshift:
952 tDAY_SHIFT
953 { $$ = RELATIVE_TIME_0; $$.day = $1; }
956 seconds: signed_seconds | unsigned_seconds;
958 signed_seconds:
959 tSDECIMAL_NUMBER
960 | tSNUMBER
961 { if (time_overflow ($1.value)) YYABORT;
962 $$.tv_sec = $1.value; $$.tv_nsec = 0; }
965 unsigned_seconds:
966 tUDECIMAL_NUMBER
967 | tUNUMBER
968 { if (time_overflow ($1.value)) YYABORT;
969 $$.tv_sec = $1.value; $$.tv_nsec = 0; }
972 number:
973 tUNUMBER
974 { digits_to_date_time (pc, $1); }
977 hybrid:
978 tUNUMBER relunit_snumber
980 /* Hybrid all-digit and relative offset, so that we accept e.g.,
981 "YYYYMMDD +N days" as well as "YYYYMMDD N days". */
982 digits_to_date_time (pc, $1);
983 if (! apply_relative_time (pc, $2, 1)) YYABORT;
987 o_colon_minutes:
988 /* empty */
989 { $$ = -1; }
990 | ':' tUNUMBER
991 { $$ = $2.value; }
996 static table const meridian_table[] =
998 { "AM", tMERIDIAN, MERam },
999 { "A.M.", tMERIDIAN, MERam },
1000 { "PM", tMERIDIAN, MERpm },
1001 { "P.M.", tMERIDIAN, MERpm },
1002 { NULL, 0, 0 }
1005 static table const dst_table[] =
1007 { "DST", tDST, 0 }
1010 static table const month_and_day_table[] =
1012 { "JANUARY", tMONTH, 1 },
1013 { "FEBRUARY", tMONTH, 2 },
1014 { "MARCH", tMONTH, 3 },
1015 { "APRIL", tMONTH, 4 },
1016 { "MAY", tMONTH, 5 },
1017 { "JUNE", tMONTH, 6 },
1018 { "JULY", tMONTH, 7 },
1019 { "AUGUST", tMONTH, 8 },
1020 { "SEPTEMBER",tMONTH, 9 },
1021 { "SEPT", tMONTH, 9 },
1022 { "OCTOBER", tMONTH, 10 },
1023 { "NOVEMBER", tMONTH, 11 },
1024 { "DECEMBER", tMONTH, 12 },
1025 { "SUNDAY", tDAY, 0 },
1026 { "MONDAY", tDAY, 1 },
1027 { "TUESDAY", tDAY, 2 },
1028 { "TUES", tDAY, 2 },
1029 { "WEDNESDAY",tDAY, 3 },
1030 { "WEDNES", tDAY, 3 },
1031 { "THURSDAY", tDAY, 4 },
1032 { "THUR", tDAY, 4 },
1033 { "THURS", tDAY, 4 },
1034 { "FRIDAY", tDAY, 5 },
1035 { "SATURDAY", tDAY, 6 },
1036 { NULL, 0, 0 }
1039 static table const time_units_table[] =
1041 { "YEAR", tYEAR_UNIT, 1 },
1042 { "MONTH", tMONTH_UNIT, 1 },
1043 { "FORTNIGHT",tDAY_UNIT, 14 },
1044 { "WEEK", tDAY_UNIT, 7 },
1045 { "DAY", tDAY_UNIT, 1 },
1046 { "HOUR", tHOUR_UNIT, 1 },
1047 { "MINUTE", tMINUTE_UNIT, 1 },
1048 { "MIN", tMINUTE_UNIT, 1 },
1049 { "SECOND", tSEC_UNIT, 1 },
1050 { "SEC", tSEC_UNIT, 1 },
1051 { NULL, 0, 0 }
1054 /* Assorted relative-time words. */
1055 static table const relative_time_table[] =
1057 { "TOMORROW", tDAY_SHIFT, 1 },
1058 { "YESTERDAY",tDAY_SHIFT, -1 },
1059 { "TODAY", tDAY_SHIFT, 0 },
1060 { "NOW", tDAY_SHIFT, 0 },
1061 { "LAST", tORDINAL, -1 },
1062 { "THIS", tORDINAL, 0 },
1063 { "NEXT", tORDINAL, 1 },
1064 { "FIRST", tORDINAL, 1 },
1065 /*{ "SECOND", tORDINAL, 2 }, */
1066 { "THIRD", tORDINAL, 3 },
1067 { "FOURTH", tORDINAL, 4 },
1068 { "FIFTH", tORDINAL, 5 },
1069 { "SIXTH", tORDINAL, 6 },
1070 { "SEVENTH", tORDINAL, 7 },
1071 { "EIGHTH", tORDINAL, 8 },
1072 { "NINTH", tORDINAL, 9 },
1073 { "TENTH", tORDINAL, 10 },
1074 { "ELEVENTH", tORDINAL, 11 },
1075 { "TWELFTH", tORDINAL, 12 },
1076 { "AGO", tAGO, -1 },
1077 { "HENCE", tAGO, 1 },
1078 { NULL, 0, 0 }
1081 /* The universal time zone table. These labels can be used even for
1082 timestamps that would not otherwise be valid, e.g., GMT timestamps
1083 oin London during summer. */
1084 static table const universal_time_zone_table[] =
1086 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
1087 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
1088 { "UTC", tZONE, HOUR ( 0) },
1089 { NULL, 0, 0 }
1092 /* The time zone table. This table is necessarily incomplete, as time
1093 zone abbreviations are ambiguous; e.g., Australians interpret "EST"
1094 as Eastern time in Australia, not as US Eastern Standard Time.
1095 You cannot rely on parse_datetime to handle arbitrary time zone
1096 abbreviations; use numeric abbreviations like "-0500" instead. */
1097 static table const time_zone_table[] =
1099 { "WET", tZONE, HOUR ( 0) }, /* Western European */
1100 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
1101 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
1102 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
1103 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
1104 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
1105 { "NST", tZONE, -(HOUR ( 3) + 30 * 60) }, /* Newfoundland Standard */
1106 { "NDT", tDAYZONE,-(HOUR ( 3) + 30 * 60) }, /* Newfoundland Daylight */
1107 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
1108 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
1109 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
1110 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
1111 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
1112 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
1113 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
1114 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
1115 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
1116 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
1117 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
1118 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
1119 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
1120 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
1121 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
1122 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
1123 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
1124 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
1125 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
1126 { "CET", tZONE, HOUR ( 1) }, /* Central European */
1127 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
1128 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
1129 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
1130 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
1131 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
1132 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
1133 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
1134 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
1135 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
1136 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
1137 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
1138 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
1139 { "IST", tZONE, (HOUR ( 5) + 30 * 60) }, /* India Standard */
1140 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
1141 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
1142 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
1143 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
1144 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
1145 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
1146 { NULL, 0, 0 }
1149 /* Military time zone table.
1151 RFC 822 got these backwards, but RFC 5322 makes the incorrect
1152 treatment optional, so do them the right way here.
1154 Note 'T' is a special case, as it is used as the separator in ISO
1155 8601 date and time of day representation. */
1156 static table const military_table[] =
1158 { "A", tZONE, HOUR ( 1) },
1159 { "B", tZONE, HOUR ( 2) },
1160 { "C", tZONE, HOUR ( 3) },
1161 { "D", tZONE, HOUR ( 4) },
1162 { "E", tZONE, HOUR ( 5) },
1163 { "F", tZONE, HOUR ( 6) },
1164 { "G", tZONE, HOUR ( 7) },
1165 { "H", tZONE, HOUR ( 8) },
1166 { "I", tZONE, HOUR ( 9) },
1167 { "K", tZONE, HOUR (10) },
1168 { "L", tZONE, HOUR (11) },
1169 { "M", tZONE, HOUR (12) },
1170 { "N", tZONE, -HOUR ( 1) },
1171 { "O", tZONE, -HOUR ( 2) },
1172 { "P", tZONE, -HOUR ( 3) },
1173 { "Q", tZONE, -HOUR ( 4) },
1174 { "R", tZONE, -HOUR ( 5) },
1175 { "S", tZONE, -HOUR ( 6) },
1176 { "T", 'T', 0 },
1177 { "U", tZONE, -HOUR ( 8) },
1178 { "V", tZONE, -HOUR ( 9) },
1179 { "W", tZONE, -HOUR (10) },
1180 { "X", tZONE, -HOUR (11) },
1181 { "Y", tZONE, -HOUR (12) },
1182 { "Z", tZONE, HOUR ( 0) },
1183 { NULL, 0, 0 }
1188 /* Convert a time zone expressed as HH:MM into an integer count of
1189 seconds. If MM is negative, then S is of the form HHMM and needs
1190 to be picked apart; otherwise, S is of the form HH. As specified in
1191 https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03, allow
1192 only valid TZ range, and consider first two digits as hours, if no
1193 minutes specified. Return true if successful. */
1195 static bool
1196 time_zone_hhmm (parser_control *pc, textint s, intmax_t mm)
1198 intmax_t n_minutes;
1199 bool overflow = false;
1201 /* If the length of S is 1 or 2 and no minutes are specified,
1202 interpret it as a number of hours. */
1203 if (s.digits <= 2 && mm < 0)
1204 s.value *= 100;
1206 if (mm < 0)
1207 n_minutes = (s.value / 100) * 60 + s.value % 100;
1208 else
1210 overflow |= INT_MULTIPLY_WRAPV (s.value, 60, &n_minutes);
1211 overflow |= (s.negative
1212 ? INT_SUBTRACT_WRAPV (n_minutes, mm, &n_minutes)
1213 : INT_ADD_WRAPV (n_minutes, mm, &n_minutes));
1216 if (overflow || ! (-24 * 60 <= n_minutes && n_minutes <= 24 * 60))
1217 return false;
1218 pc->time_zone = n_minutes * 60;
1219 return true;
1222 static int
1223 to_hour (intmax_t hours, int meridian)
1225 switch (meridian)
1227 default: /* Pacify GCC. */
1228 case MER24:
1229 return 0 <= hours && hours < 24 ? hours : -1;
1230 case MERam:
1231 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
1232 case MERpm:
1233 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
1237 enum { TM_YEAR_BASE = 1900 };
1238 enum { TM_YEAR_BUFSIZE = INT_BUFSIZE_BOUND (int) + 1 };
1240 /* Convert TM_YEAR, a year minus 1900, to a string that is numerically
1241 correct even if subtracting 1900 would overflow. */
1243 static char const *
1244 tm_year_str (int tm_year, char buf[TM_YEAR_BUFSIZE])
1246 verify (TM_YEAR_BASE % 100 == 0);
1247 sprintf (buf, &"-%02d%02d"[-TM_YEAR_BASE <= tm_year],
1248 abs (tm_year / 100 + TM_YEAR_BASE / 100),
1249 abs (tm_year % 100));
1250 return buf;
1253 /* Convert a text year number to a year minus 1900, working correctly
1254 even if the input is in the range INT_MAX .. INT_MAX + 1900 - 1. */
1256 static bool
1257 to_tm_year (textint textyear, bool debug, int *tm_year)
1259 intmax_t year = textyear.value;
1261 /* XPG4 suggests that years 00-68 map to 2000-2068, and
1262 years 69-99 map to 1969-1999. */
1263 if (0 <= year && textyear.digits == 2)
1265 year += year < 69 ? 2000 : 1900;
1266 if (debug)
1267 dbg_printf (_("warning: adjusting year value %"PRIdMAX
1268 " to %"PRIdMAX"\n"),
1269 textyear.value, year);
1272 if (year < 0
1273 ? INT_SUBTRACT_WRAPV (-TM_YEAR_BASE, year, tm_year)
1274 : INT_SUBTRACT_WRAPV (year, TM_YEAR_BASE, tm_year))
1276 if (debug)
1277 dbg_printf (_("error: out-of-range year %"PRIdMAX"\n"), year);
1278 return false;
1281 return true;
1284 static table const * _GL_ATTRIBUTE_PURE
1285 lookup_zone (parser_control const *pc, char const *name)
1287 table const *tp;
1289 for (tp = universal_time_zone_table; tp->name; tp++)
1290 if (strcmp (name, tp->name) == 0)
1291 return tp;
1293 /* Try local zone abbreviations before those in time_zone_table, as
1294 the local ones are more likely to be right. */
1295 for (tp = pc->local_time_zone_table; tp->name; tp++)
1296 if (strcmp (name, tp->name) == 0)
1297 return tp;
1299 for (tp = time_zone_table; tp->name; tp++)
1300 if (strcmp (name, tp->name) == 0)
1301 return tp;
1303 return NULL;
1306 #if ! HAVE_TM_GMTOFF
1307 /* Yield the difference between *A and *B,
1308 measured in seconds, ignoring leap seconds.
1309 The body of this function is taken directly from the GNU C Library;
1310 see strftime.c. */
1311 static int
1312 tm_diff (const struct tm *a, const struct tm *b)
1314 /* Compute intervening leap days correctly even if year is negative.
1315 Take care to avoid int overflow in leap day calculations,
1316 but it's OK to assume that A and B are close to each other. */
1317 int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
1318 int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
1319 int a100 = a4 / 25 - (a4 % 25 < 0);
1320 int b100 = b4 / 25 - (b4 % 25 < 0);
1321 int a400 = SHR (a100, 2);
1322 int b400 = SHR (b100, 2);
1323 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
1324 int years = a->tm_year - b->tm_year;
1325 int days = (365 * years + intervening_leap_days
1326 + (a->tm_yday - b->tm_yday));
1327 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
1328 + (a->tm_min - b->tm_min))
1329 + (a->tm_sec - b->tm_sec));
1331 #endif /* ! HAVE_TM_GMTOFF */
1333 static table const *
1334 lookup_word (parser_control const *pc, char *word)
1336 char *p;
1337 char *q;
1338 idx_t wordlen;
1339 table const *tp;
1340 bool period_found;
1341 bool abbrev;
1343 /* Make it uppercase. */
1344 for (p = word; *p; p++)
1345 *p = c_toupper (to_uchar (*p));
1347 for (tp = meridian_table; tp->name; tp++)
1348 if (strcmp (word, tp->name) == 0)
1349 return tp;
1351 /* See if we have an abbreviation for a month. */
1352 wordlen = strlen (word);
1353 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
1355 for (tp = month_and_day_table; tp->name; tp++)
1356 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
1357 return tp;
1359 if ((tp = lookup_zone (pc, word)))
1360 return tp;
1362 if (strcmp (word, dst_table[0].name) == 0)
1363 return dst_table;
1365 for (tp = time_units_table; tp->name; tp++)
1366 if (strcmp (word, tp->name) == 0)
1367 return tp;
1369 /* Strip off any plural and try the units table again. */
1370 if (word[wordlen - 1] == 'S')
1372 word[wordlen - 1] = '\0';
1373 for (tp = time_units_table; tp->name; tp++)
1374 if (strcmp (word, tp->name) == 0)
1375 return tp;
1376 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
1379 for (tp = relative_time_table; tp->name; tp++)
1380 if (strcmp (word, tp->name) == 0)
1381 return tp;
1383 /* Military time zones. */
1384 if (wordlen == 1)
1385 for (tp = military_table; tp->name; tp++)
1386 if (word[0] == tp->name[0])
1387 return tp;
1389 /* Drop out any periods and try the time zone table again. */
1390 for (period_found = false, p = q = word; (*p = *q); q++)
1391 if (*q == '.')
1392 period_found = true;
1393 else
1394 p++;
1395 if (period_found && (tp = lookup_zone (pc, word)))
1396 return tp;
1398 return NULL;
1401 static int
1402 yylex (union YYSTYPE *lvalp, parser_control *pc)
1404 unsigned char c;
1406 for (;;)
1408 while (c = *pc->input, c_isspace (c))
1409 pc->input++;
1411 if (c_isdigit (c) || c == '-' || c == '+')
1413 char const *p = pc->input;
1414 int sign;
1415 if (c == '-' || c == '+')
1417 sign = c == '-' ? -1 : 1;
1418 while (c = *(pc->input = ++p), c_isspace (c))
1419 continue;
1420 if (! c_isdigit (c))
1421 /* skip the '-' sign */
1422 continue;
1424 else
1425 sign = 0;
1427 time_t value = 0;
1430 if (INT_MULTIPLY_WRAPV (value, 10, &value))
1431 return '?';
1432 if (INT_ADD_WRAPV (value, sign < 0 ? '0' - c : c - '0', &value))
1433 return '?';
1434 c = *++p;
1436 while (c_isdigit (c));
1438 if ((c == '.' || c == ',') && c_isdigit (p[1]))
1440 time_t s = value;
1441 int digits;
1443 /* Accumulate fraction, to ns precision. */
1444 p++;
1445 int ns = *p++ - '0';
1446 for (digits = 2; digits <= LOG10_BILLION; digits++)
1448 ns *= 10;
1449 if (c_isdigit (*p))
1450 ns += *p++ - '0';
1453 /* Skip excess digits, truncating toward -Infinity. */
1454 if (sign < 0)
1455 for (; c_isdigit (*p); p++)
1456 if (*p != '0')
1458 ns++;
1459 break;
1461 while (c_isdigit (*p))
1462 p++;
1464 /* Adjust to the timespec convention, which is that
1465 tv_nsec is always a positive offset even if tv_sec is
1466 negative. */
1467 if (sign < 0 && ns)
1469 if (INT_SUBTRACT_WRAPV (s, 1, &s))
1470 return '?';
1471 ns = BILLION - ns;
1474 lvalp->timespec.tv_sec = s;
1475 lvalp->timespec.tv_nsec = ns;
1476 pc->input = p;
1477 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
1479 else
1481 lvalp->textintval.negative = sign < 0;
1482 lvalp->textintval.value = value;
1483 lvalp->textintval.digits = p - pc->input;
1484 pc->input = p;
1485 return sign ? tSNUMBER : tUNUMBER;
1489 if (c_isalpha (c))
1491 char buff[20];
1492 char *p = buff;
1493 table const *tp;
1497 if (p < buff + sizeof buff - 1)
1498 *p++ = c;
1499 c = *++pc->input;
1501 while (c_isalpha (c) || c == '.');
1503 *p = '\0';
1504 tp = lookup_word (pc, buff);
1505 if (! tp)
1507 if (pc->parse_datetime_debug)
1508 dbg_printf (_("error: unknown word '%s'\n"), buff);
1509 return '?';
1511 lvalp->intval = tp->value;
1512 return tp->type;
1515 if (c != '(')
1516 return to_uchar (*pc->input++);
1518 idx_t count = 0;
1521 c = *pc->input++;
1522 if (c == '\0')
1523 return c;
1524 if (c == '(')
1525 count++;
1526 else if (c == ')')
1527 count--;
1529 while (count != 0);
1533 /* Do nothing if the parser reports an error. */
1534 static int
1535 yyerror (parser_control const *pc _GL_UNUSED,
1536 char const *s _GL_UNUSED)
1538 return 0;
1541 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1542 passing it to mktime_z, return true if it's OK. It's not OK if
1543 mktime failed or if *TM0 has out-of-range mainline members.
1544 The caller should set TM1->tm_wday to -1 before calling mktime,
1545 as a negative tm_wday is how mktime failure is inferred. */
1547 static bool
1548 mktime_ok (struct tm const *tm0, struct tm const *tm1)
1550 if (tm1->tm_wday < 0)
1551 return false;
1553 return ! ((tm0->tm_sec ^ tm1->tm_sec)
1554 | (tm0->tm_min ^ tm1->tm_min)
1555 | (tm0->tm_hour ^ tm1->tm_hour)
1556 | (tm0->tm_mday ^ tm1->tm_mday)
1557 | (tm0->tm_mon ^ tm1->tm_mon)
1558 | (tm0->tm_year ^ tm1->tm_year));
1561 /* Debugging: format a 'struct tm' into a buffer, taking the parser's
1562 timezone information into account (if pc != NULL). */
1563 static char const *
1564 debug_strfdatetime (struct tm const *tm, parser_control const *pc,
1565 char *buf, int n)
1567 /* TODO:
1568 1. find an optimal way to print date string in a clear and unambiguous
1569 format. Currently, always add '(Y-M-D)' prefix.
1570 Consider '2016y01m10d' or 'year(2016) month(01) day(10)'.
1572 If the user needs debug printing, it means he/she already having
1573 issues with the parsing - better to avoid formats that could
1574 be mis-interpreted (e.g., just YYYY-MM-DD).
1576 2. Can strftime be used instead?
1577 depends if it is portable and can print invalid dates on all systems.
1579 3. Print timezone information ?
1581 4. Print DST information ?
1583 5. Print nanosecond information ?
1585 NOTE:
1586 Printed date/time values might not be valid, e.g., '2016-02-31'
1587 or '2016-19-2016' . These are the values as parsed from the user
1588 string, before validation.
1590 int m = nstrftime (buf, n, "(Y-M-D) %Y-%m-%d %H:%M:%S", tm, 0, 0);
1592 /* If parser_control information was provided (for timezone),
1593 and there's enough space in the buffer, add timezone info. */
1594 if (pc && m < n && pc->zones_seen)
1596 int tz = pc->time_zone;
1598 /* Account for DST if tLOCAL_ZONE was seen. */
1599 if (pc->local_zones_seen && !pc->zones_seen && 0 < pc->local_isdst)
1600 tz += 60 * 60;
1602 char time_zone_buf[TIME_ZONE_BUFSIZE];
1603 snprintf (&buf[m], n - m, " TZ=%s", time_zone_str (tz, time_zone_buf));
1605 return buf;
1608 static char const *
1609 debug_strfdate (struct tm const *tm, char *buf, int n)
1611 char tm_year_buf[TM_YEAR_BUFSIZE];
1612 snprintf (buf, n, "(Y-M-D) %s-%02d-%02d",
1613 tm_year_str (tm->tm_year, tm_year_buf),
1614 tm->tm_mon + 1, tm->tm_mday);
1615 return buf;
1618 static char const *
1619 debug_strftime (struct tm const *tm, char *buf, int n)
1621 snprintf (buf, n, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
1622 return buf;
1625 /* If mktime_ok failed, display the failed time values,
1626 and provide possible hints. Example output:
1628 date: error: invalid date/time value:
1629 date: user provided time: '(Y-M-D) 2006-04-02 02:45:00'
1630 date: normalized time: '(Y-M-D) 2006-04-02 03:45:00'
1631 date: __
1632 date: possible reasons:
1633 date: non-existing due to daylight-saving time;
1634 date: numeric values overflow;
1635 date: missing timezone;
1637 static void
1638 debug_mktime_not_ok (struct tm const *tm0, struct tm const *tm1,
1639 parser_control const *pc, bool time_zone_seen)
1641 /* TODO: handle t == -1 (as in 'mktime_ok'). */
1642 char tmp[DBGBUFSIZE];
1643 int i;
1644 const bool eq_sec = (tm0->tm_sec == tm1->tm_sec);
1645 const bool eq_min = (tm0->tm_min == tm1->tm_min);
1646 const bool eq_hour = (tm0->tm_hour == tm1->tm_hour);
1647 const bool eq_mday = (tm0->tm_mday == tm1->tm_mday);
1648 const bool eq_month = (tm0->tm_mon == tm1->tm_mon);
1649 const bool eq_year = (tm0->tm_year == tm1->tm_year);
1651 const bool dst_shift = eq_sec && eq_min && !eq_hour
1652 && eq_mday && eq_month && eq_year;
1654 if (!pc->parse_datetime_debug)
1655 return;
1657 dbg_printf (_("error: invalid date/time value:\n"));
1658 dbg_printf (_(" user provided time: '%s'\n"),
1659 debug_strfdatetime (tm0, pc, tmp, sizeof tmp));
1660 dbg_printf (_(" normalized time: '%s'\n"),
1661 debug_strfdatetime (tm1, pc, tmp, sizeof tmp));
1662 /* The format must be aligned with debug_strfdatetime and the two
1663 DEBUG statements above. This string is not translated. */
1664 i = snprintf (tmp, sizeof tmp,
1665 " %4s %2s %2s %2s %2s %2s",
1666 eq_year ? "" : "----",
1667 eq_month ? "" : "--",
1668 eq_mday ? "" : "--",
1669 eq_hour ? "" : "--",
1670 eq_min ? "" : "--",
1671 eq_sec ? "" : "--");
1672 /* Trim trailing whitespace. */
1673 if (0 <= i)
1675 if (sizeof tmp - 1 < i)
1676 i = sizeof tmp - 1;
1677 while (0 < i && tmp[i - 1] == ' ')
1678 --i;
1679 tmp[i] = '\0';
1681 dbg_printf ("%s\n", tmp);
1683 dbg_printf (_(" possible reasons:\n"));
1684 if (dst_shift)
1685 dbg_printf (_(" non-existing due to daylight-saving time;\n"));
1686 if (!eq_mday && !eq_month)
1687 dbg_printf (_(" invalid day/month combination;\n"));
1688 dbg_printf (_(" numeric values overflow;\n"));
1689 dbg_printf (" %s\n", (time_zone_seen ? _("incorrect timezone")
1690 : _("missing timezone")));
1693 /* The original interface: run with debug=false and the default timezone. */
1694 bool
1695 parse_datetime (struct timespec *result, char const *p,
1696 struct timespec const *now)
1698 char const *tzstring = getenv ("TZ");
1699 timezone_t tz = tzalloc (tzstring);
1700 if (!tz)
1701 return false;
1702 bool ok = parse_datetime2 (result, p, now, 0, tz, tzstring);
1703 tzfree (tz);
1704 return ok;
1707 /* Parse a date/time string, storing the resulting time value into *RESULT.
1708 The string itself is pointed to by P. Return true if successful.
1709 P can be an incomplete or relative time specification; if so, use
1710 *NOW as the basis for the returned time. Default to timezone
1711 TZDEFAULT, which corresponds to tzalloc (TZSTRING). */
1712 bool
1713 parse_datetime2 (struct timespec *result, char const *p,
1714 struct timespec const *now, unsigned int flags,
1715 timezone_t tzdefault, char const *tzstring)
1717 struct tm tm;
1718 struct tm tm0;
1719 char time_zone_buf[TIME_ZONE_BUFSIZE];
1720 char dbg_tm[DBGBUFSIZE];
1721 bool ok = false;
1722 char const *input_sentinel = p + strlen (p);
1723 char *tz1alloc = NULL;
1725 /* A reasonable upper bound for the size of ordinary TZ strings.
1726 Use heap allocation if TZ's length exceeds this. */
1727 enum { TZBUFSIZE = 100 };
1728 char tz1buf[TZBUFSIZE];
1730 struct timespec gettime_buffer;
1731 if (! now)
1733 gettime (&gettime_buffer);
1734 now = &gettime_buffer;
1737 time_t Start = now->tv_sec;
1738 int Start_ns = now->tv_nsec;
1740 unsigned char c;
1741 while (c = *p, c_isspace (c))
1742 p++;
1744 timezone_t tz = tzdefault;
1746 /* Store a local copy prior to first "goto". Without this, a prior use
1747 below of RELATIVE_TIME_0 on the RHS might translate to an assignment-
1748 to-temporary, which would trigger a -Wjump-misses-init warning. */
1749 const relative_time rel_time_0 = RELATIVE_TIME_0;
1751 if (strncmp (p, "TZ=\"", 4) == 0)
1753 char const *tzbase = p + 4;
1754 idx_t tzsize = 1;
1755 char const *s;
1757 for (s = tzbase; *s; s++, tzsize++)
1758 if (*s == '\\')
1760 s++;
1761 if (! (*s == '\\' || *s == '"'))
1762 break;
1764 else if (*s == '"')
1766 timezone_t tz1;
1767 char *tz1string = tz1buf;
1768 char *z;
1769 if (TZBUFSIZE < tzsize)
1771 tz1alloc = malloc (tzsize);
1772 if (!tz1alloc)
1773 goto fail;
1774 tz1string = tz1alloc;
1776 z = tz1string;
1777 for (s = tzbase; *s != '"'; s++)
1778 *z++ = *(s += *s == '\\');
1779 *z = '\0';
1780 tz1 = tzalloc (tz1string);
1781 if (!tz1)
1782 goto fail;
1783 tz = tz1;
1784 tzstring = tz1string;
1786 p = s + 1;
1787 while (c = *p, c_isspace (c))
1788 p++;
1790 break;
1794 struct tm tmp;
1795 if (! localtime_rz (tz, &now->tv_sec, &tmp))
1796 goto fail;
1798 /* As documented, be careful to treat the empty string just like
1799 a date string of "0". Without this, an empty string would be
1800 declared invalid when parsed during a DST transition. */
1801 if (*p == '\0')
1802 p = "0";
1804 parser_control pc;
1805 pc.input = p;
1806 pc.parse_datetime_debug = (flags & PARSE_DATETIME_DEBUG) != 0;
1807 if (INT_ADD_WRAPV (tmp.tm_year, TM_YEAR_BASE, &pc.year.value))
1809 if (pc.parse_datetime_debug)
1810 dbg_printf (_("error: initial year out of range\n"));
1811 goto fail;
1813 pc.year.digits = 0;
1814 pc.month = tmp.tm_mon + 1;
1815 pc.day = tmp.tm_mday;
1816 pc.hour = tmp.tm_hour;
1817 pc.minutes = tmp.tm_min;
1818 pc.seconds.tv_sec = tmp.tm_sec;
1819 pc.seconds.tv_nsec = Start_ns;
1820 tm.tm_isdst = tmp.tm_isdst;
1822 pc.meridian = MER24;
1823 pc.rel = rel_time_0;
1824 pc.timespec_seen = false;
1825 pc.rels_seen = false;
1826 pc.dates_seen = 0;
1827 pc.days_seen = 0;
1828 pc.times_seen = 0;
1829 pc.local_zones_seen = 0;
1830 pc.dsts_seen = 0;
1831 pc.zones_seen = 0;
1832 pc.year_seen = false;
1833 pc.debug_dates_seen = false;
1834 pc.debug_days_seen = false;
1835 pc.debug_times_seen = false;
1836 pc.debug_local_zones_seen = false;
1837 pc.debug_zones_seen = false;
1838 pc.debug_year_seen = false;
1839 pc.debug_ordinal_day_seen = false;
1841 #if HAVE_STRUCT_TM_TM_ZONE
1842 pc.local_time_zone_table[0].name = tmp.tm_zone;
1843 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1844 pc.local_time_zone_table[0].value = tmp.tm_isdst;
1845 pc.local_time_zone_table[1].name = NULL;
1847 /* Probe the names used in the next three calendar quarters, looking
1848 for a tm_isdst different from the one we already have. */
1850 int quarter;
1851 for (quarter = 1; quarter <= 3; quarter++)
1853 time_t probe;
1854 if (INT_ADD_WRAPV (Start, quarter * (90 * 24 * 60 * 60), &probe))
1855 break;
1856 struct tm probe_tm;
1857 if (localtime_rz (tz, &probe, &probe_tm) && probe_tm.tm_zone
1858 && probe_tm.tm_isdst != pc.local_time_zone_table[0].value)
1861 pc.local_time_zone_table[1].name = probe_tm.tm_zone;
1862 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1863 pc.local_time_zone_table[1].value = probe_tm.tm_isdst;
1864 pc.local_time_zone_table[2].name = NULL;
1866 break;
1870 #else
1871 #if HAVE_TZNAME
1873 # if !HAVE_DECL_TZNAME
1874 extern char *tzname[];
1875 # endif
1876 int i;
1877 for (i = 0; i < 2; i++)
1879 pc.local_time_zone_table[i].name = tzname[i];
1880 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1881 pc.local_time_zone_table[i].value = i;
1883 pc.local_time_zone_table[i].name = NULL;
1885 #else
1886 pc.local_time_zone_table[0].name = NULL;
1887 #endif
1888 #endif
1890 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1891 && ! strcmp (pc.local_time_zone_table[0].name,
1892 pc.local_time_zone_table[1].name))
1894 /* This locale uses the same abbreviation for standard and
1895 daylight times. So if we see that abbreviation, we don't
1896 know whether it's daylight time. */
1897 pc.local_time_zone_table[0].value = -1;
1898 pc.local_time_zone_table[1].name = NULL;
1901 if (yyparse (&pc) != 0)
1903 if (pc.parse_datetime_debug)
1904 dbg_printf ((input_sentinel <= pc.input
1905 ? _("error: parsing failed\n")
1906 : _("error: parsing failed, stopped at '%s'\n")),
1907 pc.input);
1908 goto fail;
1912 /* Determine effective timezone source. */
1914 if (pc.parse_datetime_debug)
1916 dbg_printf (_("input timezone: "));
1918 if (pc.timespec_seen)
1919 fprintf (stderr, _("'@timespec' - always UTC"));
1920 else if (pc.zones_seen)
1921 fprintf (stderr, _("parsed date/time string"));
1922 else if (tzstring)
1924 if (tz != tzdefault)
1925 fprintf (stderr, _("TZ=\"%s\" in date string"), tzstring);
1926 else if (STREQ (tzstring, "UTC0"))
1928 /* Special case: 'date -u' sets TZ="UTC0". */
1929 fprintf (stderr, _("TZ=\"UTC0\" environment value or -u"));
1931 else
1932 fprintf (stderr, _("TZ=\"%s\" environment value"), tzstring);
1934 else
1935 fprintf (stderr, _("system default"));
1937 /* Account for DST changes if tLOCAL_ZONE was seen.
1938 local timezone only changes DST and is relative to the
1939 default timezone.*/
1940 if (pc.local_zones_seen && !pc.zones_seen && 0 < pc.local_isdst)
1941 fprintf (stderr, ", dst");
1943 if (pc.zones_seen)
1944 fprintf (stderr, " (%s)", time_zone_str (pc.time_zone, time_zone_buf));
1946 fputc ('\n', stderr);
1949 if (pc.timespec_seen)
1950 *result = pc.seconds;
1951 else
1953 if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
1954 | (pc.local_zones_seen + pc.zones_seen)))
1956 if (pc.parse_datetime_debug)
1958 if (pc.times_seen > 1)
1959 dbg_printf ("error: seen multiple time parts\n");
1960 if (pc.dates_seen > 1)
1961 dbg_printf ("error: seen multiple date parts\n");
1962 if (pc.days_seen > 1)
1963 dbg_printf ("error: seen multiple days parts\n");
1964 if (pc.dsts_seen > 1)
1965 dbg_printf ("error: seen multiple daylight-saving parts\n");
1966 if ((pc.local_zones_seen + pc.zones_seen) > 1)
1967 dbg_printf ("error: seen multiple time-zone parts\n");
1969 goto fail;
1972 if (! to_tm_year (pc.year, pc.parse_datetime_debug, &tm.tm_year)
1973 || INT_ADD_WRAPV (pc.month, -1, &tm.tm_mon)
1974 || INT_ADD_WRAPV (pc.day, 0, &tm.tm_mday))
1976 if (pc.parse_datetime_debug)
1977 dbg_printf (_("error: year, month, or day overflow\n"));
1978 goto fail;
1980 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1982 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1983 if (tm.tm_hour < 0)
1985 char const *mrd = (pc.meridian == MERam ? "am"
1986 : pc.meridian == MERpm ?"pm" : "");
1987 if (pc.parse_datetime_debug)
1988 dbg_printf (_("error: invalid hour %"PRIdMAX"%s\n"),
1989 pc.hour, mrd);
1990 goto fail;
1992 tm.tm_min = pc.minutes;
1993 tm.tm_sec = pc.seconds.tv_sec;
1994 if (pc.parse_datetime_debug)
1995 dbg_printf ((pc.times_seen
1996 ? _("using specified time as starting value: '%s'\n")
1997 : _("using current time as starting value: '%s'\n")),
1998 debug_strftime (&tm, dbg_tm, sizeof dbg_tm));
2000 else
2002 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
2003 pc.seconds.tv_nsec = 0;
2004 if (pc.parse_datetime_debug)
2005 dbg_printf ("warning: using midnight as starting time: 00:00:00\n");
2008 /* Let mktime deduce tm_isdst if we have an absolute timestamp. */
2009 if (pc.dates_seen | pc.days_seen | pc.times_seen)
2010 tm.tm_isdst = -1;
2012 /* But if the input explicitly specifies local time with or without
2013 DST, give mktime that information. */
2014 if (pc.local_zones_seen)
2015 tm.tm_isdst = pc.local_isdst;
2017 tm0.tm_sec = tm.tm_sec;
2018 tm0.tm_min = tm.tm_min;
2019 tm0.tm_hour = tm.tm_hour;
2020 tm0.tm_mday = tm.tm_mday;
2021 tm0.tm_mon = tm.tm_mon;
2022 tm0.tm_year = tm.tm_year;
2023 tm0.tm_isdst = tm.tm_isdst;
2024 tm.tm_wday = -1;
2026 Start = mktime_z (tz, &tm);
2028 if (! mktime_ok (&tm0, &tm))
2030 bool repaired = false;
2031 bool time_zone_seen = pc.zones_seen != 0;
2032 if (time_zone_seen)
2034 /* Guard against falsely reporting errors near the time_t
2035 boundaries when parsing times in other time zones. For
2036 example, suppose the input string "1969-12-31 23:00:00 -0100",
2037 the current time zone is 8 hours ahead of UTC, and the min
2038 time_t value is 1970-01-01 00:00:00 UTC. Then the min
2039 localtime value is 1970-01-01 08:00:00, and mktime will
2040 therefore fail on 1969-12-31 23:00:00. To work around the
2041 problem, set the time zone to 1 hour behind UTC temporarily
2042 by setting TZ="XXX1:00" and try mktime again. */
2044 char tz2buf[sizeof "XXX" - 1 + TIME_ZONE_BUFSIZE];
2045 tz2buf[0] = tz2buf[1] = tz2buf[2] = 'X';
2046 time_zone_str (pc.time_zone, &tz2buf[3]);
2047 timezone_t tz2 = tzalloc (tz2buf);
2048 if (!tz2)
2050 if (pc.parse_datetime_debug)
2051 dbg_printf (_("error: tzalloc (\"%s\") failed\n"), tz2buf);
2052 goto fail;
2054 tm.tm_sec = tm0.tm_sec;
2055 tm.tm_min = tm0.tm_min;
2056 tm.tm_hour = tm0.tm_hour;
2057 tm.tm_mday = tm0.tm_mday;
2058 tm.tm_mon = tm0.tm_mon;
2059 tm.tm_year = tm0.tm_year;
2060 tm.tm_isdst = tm0.tm_isdst;
2061 tm.tm_wday = -1;
2062 Start = mktime_z (tz2, &tm);
2063 repaired = mktime_ok (&tm0, &tm);
2064 tzfree (tz2);
2067 if (! repaired)
2069 debug_mktime_not_ok (&tm0, &tm, &pc, time_zone_seen);
2070 goto fail;
2074 char dbg_ord[DBGBUFSIZE];
2076 if (pc.days_seen && ! pc.dates_seen)
2078 intmax_t dayincr;
2079 if (INT_MULTIPLY_WRAPV ((pc.day_ordinal
2080 - (0 < pc.day_ordinal
2081 && tm.tm_wday != pc.day_number)),
2082 7, &dayincr)
2083 || INT_ADD_WRAPV ((pc.day_number - tm.tm_wday + 7) % 7,
2084 dayincr, &dayincr)
2085 || INT_ADD_WRAPV (dayincr, tm.tm_mday, &tm.tm_mday))
2086 Start = -1;
2087 else
2089 tm.tm_isdst = -1;
2090 Start = mktime_z (tz, &tm);
2093 if (Start == (time_t) -1)
2095 if (pc.parse_datetime_debug)
2096 dbg_printf (_("error: day '%s' "
2097 "(day ordinal=%"PRIdMAX" number=%d) "
2098 "resulted in an invalid date: '%s'\n"),
2099 str_days (&pc, dbg_ord, sizeof dbg_ord),
2100 pc.day_ordinal, pc.day_number,
2101 debug_strfdatetime (&tm, &pc, dbg_tm,
2102 sizeof dbg_tm));
2103 goto fail;
2106 if (pc.parse_datetime_debug)
2107 dbg_printf (_("new start date: '%s' is '%s'\n"),
2108 str_days (&pc, dbg_ord, sizeof dbg_ord),
2109 debug_strfdatetime (&tm, &pc, dbg_tm, sizeof dbg_tm));
2113 if (pc.parse_datetime_debug)
2115 if (!pc.dates_seen && !pc.days_seen)
2116 dbg_printf (_("using current date as starting value: '%s'\n"),
2117 debug_strfdate (&tm, dbg_tm, sizeof dbg_tm));
2119 if (pc.days_seen && pc.dates_seen)
2120 dbg_printf (_("warning: day (%s) ignored when explicit dates "
2121 "are given\n"),
2122 str_days (&pc, dbg_ord, sizeof dbg_ord));
2124 dbg_printf (_("starting date/time: '%s'\n"),
2125 debug_strfdatetime (&tm, &pc, dbg_tm, sizeof dbg_tm));
2128 /* Add relative date. */
2129 if (pc.rel.year | pc.rel.month | pc.rel.day)
2131 if (pc.parse_datetime_debug)
2133 if ((pc.rel.year != 0 || pc.rel.month != 0) && tm.tm_mday != 15)
2134 dbg_printf (_("warning: when adding relative months/years, "
2135 "it is recommended to specify the 15th of the "
2136 "months\n"));
2138 if (pc.rel.day != 0 && tm.tm_hour != 12)
2139 dbg_printf (_("warning: when adding relative days, "
2140 "it is recommended to specify noon\n"));
2143 int year, month, day;
2144 if (INT_ADD_WRAPV (tm.tm_year, pc.rel.year, &year)
2145 || INT_ADD_WRAPV (tm.tm_mon, pc.rel.month, &month)
2146 || INT_ADD_WRAPV (tm.tm_mday, pc.rel.day, &day))
2148 if (pc.parse_datetime_debug)
2149 dbg_printf (_("error: %s:%d\n"), __FILE__, __LINE__);
2150 goto fail;
2152 tm.tm_year = year;
2153 tm.tm_mon = month;
2154 tm.tm_mday = day;
2155 tm.tm_hour = tm0.tm_hour;
2156 tm.tm_min = tm0.tm_min;
2157 tm.tm_sec = tm0.tm_sec;
2158 tm.tm_isdst = tm0.tm_isdst;
2159 Start = mktime_z (tz, &tm);
2160 if (Start == (time_t) -1)
2162 if (pc.parse_datetime_debug)
2163 dbg_printf (_("error: adding relative date resulted "
2164 "in an invalid date: '%s'\n"),
2165 debug_strfdatetime (&tm, &pc, dbg_tm,
2166 sizeof dbg_tm));
2167 goto fail;
2170 if (pc.parse_datetime_debug)
2172 dbg_printf (_("after date adjustment "
2173 "(%+"PRIdMAX" years, %+"PRIdMAX" months, "
2174 "%+"PRIdMAX" days),\n"),
2175 pc.rel.year, pc.rel.month, pc.rel.day);
2176 dbg_printf (_(" new date/time = '%s'\n"),
2177 debug_strfdatetime (&tm, &pc, dbg_tm,
2178 sizeof dbg_tm));
2180 /* Warn about crossing DST due to time adjustment.
2181 Example: https://bugs.gnu.org/8357
2182 env TZ=Europe/Helsinki \
2183 date --debug \
2184 -d 'Mon Mar 28 00:36:07 2011 EEST 1 day ago'
2186 This case is different than DST changes due to time adjustment,
2187 i.e., "1 day ago" vs "24 hours ago" are calculated in different
2188 places.
2190 'tm0.tm_isdst' contains the DST of the input date,
2191 'tm.tm_isdst' is the normalized result after calling
2192 mktime (&tm).
2194 if (tm0.tm_isdst != -1 && tm.tm_isdst != tm0.tm_isdst)
2195 dbg_printf (_("warning: daylight saving time changed after "
2196 "date adjustment\n"));
2198 /* Warn if the user did not ask to adjust days but mday changed,
2200 user did not ask to adjust months/days but the month changed.
2202 Example for first case:
2203 2016-05-31 + 1 month => 2016-06-31 => 2016-07-01.
2204 User asked to adjust month, but the day changed from 31 to 01.
2206 Example for second case:
2207 2016-02-29 + 1 year => 2017-02-29 => 2017-03-01.
2208 User asked to adjust year, but the month changed from 02 to 03.
2210 if (pc.rel.day == 0
2211 && (tm.tm_mday != day
2212 || (pc.rel.month == 0 && tm.tm_mon != month)))
2214 dbg_printf (_("warning: month/year adjustment resulted in "
2215 "shifted dates:\n"));
2216 char tm_year_buf[TM_YEAR_BUFSIZE];
2217 dbg_printf (_(" adjusted Y M D: %s %02d %02d\n"),
2218 tm_year_str (year, tm_year_buf), month + 1, day);
2219 dbg_printf (_(" normalized Y M D: %s %02d %02d\n"),
2220 tm_year_str (tm.tm_year, tm_year_buf),
2221 tm.tm_mon + 1, tm.tm_mday);
2227 /* The only "output" of this if-block is an updated Start value,
2228 so this block must follow others that clobber Start. */
2229 if (pc.zones_seen)
2231 bool overflow = false;
2232 #ifdef HAVE_TM_GMTOFF
2233 long int utcoff = tm.tm_gmtoff;
2234 #else
2235 time_t t = Start;
2236 struct tm gmt;
2237 int utcoff = (gmtime_r (&t, &gmt)
2238 ? tm_diff (&tm, &gmt)
2239 : (overflow = true, 0));
2240 #endif
2241 intmax_t delta;
2242 overflow |= INT_SUBTRACT_WRAPV (pc.time_zone, utcoff, &delta);
2243 time_t t1;
2244 overflow |= INT_SUBTRACT_WRAPV (Start, delta, &t1);
2245 if (overflow)
2247 if (pc.parse_datetime_debug)
2248 dbg_printf (_("error: timezone %d caused time_t overflow\n"),
2249 pc.time_zone);
2250 goto fail;
2252 Start = t1;
2255 if (pc.parse_datetime_debug)
2257 intmax_t Starti = Start;
2258 dbg_printf (_("'%s' = %"PRIdMAX" epoch-seconds\n"),
2259 debug_strfdatetime (&tm, &pc, dbg_tm, sizeof dbg_tm),
2260 Starti);
2264 /* Add relative hours, minutes, and seconds. On hosts that support
2265 leap seconds, ignore the possibility of leap seconds; e.g.,
2266 "+ 10 minutes" adds 600 seconds, even if one of them is a
2267 leap second. Typically this is not what the user wants, but it's
2268 too hard to do it the other way, because the time zone indicator
2269 must be applied before relative times, and if mktime is applied
2270 again the time zone will be lost. */
2272 intmax_t orig_ns = pc.seconds.tv_nsec;
2273 intmax_t sum_ns = orig_ns + pc.rel.ns;
2274 int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
2275 int d4 = (sum_ns - normalized_ns) / BILLION;
2276 intmax_t d1, t1, d2, t2, t3;
2277 time_t t4;
2278 if (INT_MULTIPLY_WRAPV (pc.rel.hour, 60 * 60, &d1)
2279 || INT_ADD_WRAPV (Start, d1, &t1)
2280 || INT_MULTIPLY_WRAPV (pc.rel.minutes, 60, &d2)
2281 || INT_ADD_WRAPV (t1, d2, &t2)
2282 || INT_ADD_WRAPV (t2, pc.rel.seconds, &t3)
2283 || INT_ADD_WRAPV (t3, d4, &t4))
2285 if (pc.parse_datetime_debug)
2286 dbg_printf (_("error: adding relative time caused an "
2287 "overflow\n"));
2288 goto fail;
2291 result->tv_sec = t4;
2292 result->tv_nsec = normalized_ns;
2294 if (pc.parse_datetime_debug
2295 && (pc.rel.hour | pc.rel.minutes | pc.rel.seconds | pc.rel.ns))
2297 dbg_printf (_("after time adjustment (%+"PRIdMAX" hours, "
2298 "%+"PRIdMAX" minutes, "
2299 "%+"PRIdMAX" seconds, %+d ns),\n"),
2300 pc.rel.hour, pc.rel.minutes, pc.rel.seconds,
2301 pc.rel.ns);
2302 intmax_t t4i = t4;
2303 dbg_printf (_(" new time = %"PRIdMAX" epoch-seconds\n"), t4i);
2305 /* Warn about crossing DST due to time adjustment.
2306 Example: https://bugs.gnu.org/8357
2307 env TZ=Europe/Helsinki \
2308 date --debug \
2309 -d 'Mon Mar 28 00:36:07 2011 EEST 24 hours ago'
2311 This case is different than DST changes due to days adjustment,
2312 i.e., "1 day ago" vs "24 hours ago" are calculated in different
2313 places.
2315 'tm.tm_isdst' contains the date after date adjustment. */
2316 struct tm lmt;
2317 if (tm.tm_isdst != -1 && localtime_rz (tz, &result->tv_sec, &lmt)
2318 && tm.tm_isdst != lmt.tm_isdst)
2319 dbg_printf (_("warning: daylight saving time changed after "
2320 "time adjustment\n"));
2325 if (pc.parse_datetime_debug)
2327 /* Special case: using 'date -u' simply set TZ=UTC0 */
2328 if (! tzstring)
2329 dbg_printf (_("timezone: system default\n"));
2330 else if (STREQ (tzstring, "UTC0"))
2331 dbg_printf (_("timezone: Universal Time\n"));
2332 else
2333 dbg_printf (_("timezone: TZ=\"%s\" environment value\n"), tzstring);
2335 intmax_t sec = result->tv_sec;
2336 int nsec = result->tv_nsec;
2337 dbg_printf (_("final: %"PRIdMAX".%09d (epoch-seconds)\n"),
2338 sec, nsec);
2340 struct tm gmt, lmt;
2341 bool got_utc = !!gmtime_r (&result->tv_sec, &gmt);
2342 if (got_utc)
2343 dbg_printf (_("final: %s (UTC)\n"),
2344 debug_strfdatetime (&gmt, NULL,
2345 dbg_tm, sizeof dbg_tm));
2346 if (localtime_rz (tz, &result->tv_sec, &lmt))
2348 #ifdef HAVE_TM_GMTOFF
2349 bool got_utcoff = true;
2350 long int utcoff = lmt.tm_gmtoff;
2351 #else
2352 bool got_utcoff = got_utc;
2353 int utcoff;
2354 if (got_utcoff)
2355 utcoff = tm_diff (&lmt, &gmt);
2356 #endif
2357 if (got_utcoff)
2358 dbg_printf (_("final: %s (UTC%s)\n"),
2359 debug_strfdatetime (&lmt, NULL, dbg_tm, sizeof dbg_tm),
2360 time_zone_str (utcoff, time_zone_buf));
2361 else
2362 dbg_printf (_("final: %s (unknown time zone offset)\n"),
2363 debug_strfdatetime (&lmt, NULL, dbg_tm, sizeof dbg_tm));
2367 ok = true;
2369 fail:
2370 if (tz != tzdefault)
2371 tzfree (tz);
2372 free (tz1alloc);
2373 return ok;
2376 #if TEST
2379 main (int ac, char **av)
2381 char buff[BUFSIZ];
2383 printf ("Enter date, or blank line to exit.\n\t> ");
2384 fflush (stdout);
2386 buff[BUFSIZ - 1] = '\0';
2387 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
2389 struct timespec d;
2390 struct tm const *tm;
2391 if (! parse_datetime (&d, buff, NULL))
2392 printf ("Bad format - couldn't convert.\n");
2393 else if (! (tm = localtime (&d.tv_sec)))
2395 intmax_t sec = d.tv_sec;
2396 printf ("localtime (%"PRIdMAX") failed\n", sec);
2398 else
2400 int ns = d.tv_nsec;
2401 char tm_year_buf[TM_YEAR_BUFSIZE];
2402 printf ("%s-%02d-%02d %02d:%02d:%02d.%09d\n",
2403 tm_year_str (tm->tm_year, tm_year_buf),
2404 tm->tm_mon + 1, tm->tm_mday,
2405 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);
2407 printf ("\t> ");
2408 fflush (stdout);
2410 return 0;
2412 #endif /* TEST */