Fix last ChangeLog entry.
[gnulib.git] / lib / parse-datetime.y
blob0c624674223c0603d2a759b7be7238227d8eec92
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 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 /* 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 ptrdiff_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 ptrdiff_t dates_seen;
217 ptrdiff_t days_seen;
218 ptrdiff_t local_zones_seen;
219 ptrdiff_t dsts_seen;
220 ptrdiff_t times_seen;
221 ptrdiff_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 ptrdiff_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;
1414 int sign;
1415 intmax_t value = 0;
1416 if (c == '-' || c == '+')
1418 sign = c == '-' ? -1 : 1;
1419 while (c = *++pc->input, c_isspace (c))
1420 continue;
1421 if (! c_isdigit (c))
1422 /* skip the '-' sign */
1423 continue;
1425 else
1426 sign = 0;
1427 p = pc->input;
1431 if (INT_MULTIPLY_WRAPV (value, 10, &value))
1432 return '?';
1433 if (INT_ADD_WRAPV (value, sign < 0 ? '0' - c : c - '0', &value))
1434 return '?';
1435 c = *++p;
1437 while (c_isdigit (c));
1439 if ((c == '.' || c == ',') && c_isdigit (p[1]))
1441 time_t s;
1442 int ns;
1443 int digits;
1445 if (time_overflow (value))
1446 return '?';
1447 s = value;
1449 /* Accumulate fraction, to ns precision. */
1450 p++;
1451 ns = *p++ - '0';
1452 for (digits = 2; digits <= LOG10_BILLION; digits++)
1454 ns *= 10;
1455 if (c_isdigit (*p))
1456 ns += *p++ - '0';
1459 /* Skip excess digits, truncating toward -Infinity. */
1460 if (sign < 0)
1461 for (; c_isdigit (*p); p++)
1462 if (*p != '0')
1464 ns++;
1465 break;
1467 while (c_isdigit (*p))
1468 p++;
1470 /* Adjust to the timespec convention, which is that
1471 tv_nsec is always a positive offset even if tv_sec is
1472 negative. */
1473 if (sign < 0 && ns)
1475 if (s == TYPE_MINIMUM (time_t))
1476 return '?';
1477 s--;
1478 ns = BILLION - ns;
1481 lvalp->timespec.tv_sec = s;
1482 lvalp->timespec.tv_nsec = ns;
1483 pc->input = p;
1484 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
1486 else
1488 lvalp->textintval.negative = sign < 0;
1489 lvalp->textintval.value = value;
1490 lvalp->textintval.digits = p - pc->input;
1491 pc->input = p;
1492 return sign ? tSNUMBER : tUNUMBER;
1496 if (c_isalpha (c))
1498 char buff[20];
1499 char *p = buff;
1500 table const *tp;
1504 if (p < buff + sizeof buff - 1)
1505 *p++ = c;
1506 c = *++pc->input;
1508 while (c_isalpha (c) || c == '.');
1510 *p = '\0';
1511 tp = lookup_word (pc, buff);
1512 if (! tp)
1514 if (pc->parse_datetime_debug)
1515 dbg_printf (_("error: unknown word '%s'\n"), buff);
1516 return '?';
1518 lvalp->intval = tp->value;
1519 return tp->type;
1522 if (c != '(')
1523 return to_uchar (*pc->input++);
1525 ptrdiff_t count = 0;
1528 c = *pc->input++;
1529 if (c == '\0')
1530 return c;
1531 if (c == '(')
1532 count++;
1533 else if (c == ')')
1534 count--;
1536 while (count != 0);
1540 /* Do nothing if the parser reports an error. */
1541 static int
1542 yyerror (parser_control const *pc _GL_UNUSED,
1543 char const *s _GL_UNUSED)
1545 return 0;
1548 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1549 passing it to mktime_z, return true if it's OK. It's not OK if
1550 mktime failed or if *TM0 has out-of-range mainline members.
1551 The caller should set TM1->tm_wday to -1 before calling mktime,
1552 as a negative tm_wday is how mktime failure is inferred. */
1554 static bool
1555 mktime_ok (struct tm const *tm0, struct tm const *tm1)
1557 if (tm1->tm_wday < 0)
1558 return false;
1560 return ! ((tm0->tm_sec ^ tm1->tm_sec)
1561 | (tm0->tm_min ^ tm1->tm_min)
1562 | (tm0->tm_hour ^ tm1->tm_hour)
1563 | (tm0->tm_mday ^ tm1->tm_mday)
1564 | (tm0->tm_mon ^ tm1->tm_mon)
1565 | (tm0->tm_year ^ tm1->tm_year));
1568 /* Debugging: format a 'struct tm' into a buffer, taking the parser's
1569 timezone information into account (if pc != NULL). */
1570 static char const *
1571 debug_strfdatetime (struct tm const *tm, parser_control const *pc,
1572 char *buf, int n)
1574 /* TODO:
1575 1. find an optimal way to print date string in a clear and unambiguous
1576 format. Currently, always add '(Y-M-D)' prefix.
1577 Consider '2016y01m10d' or 'year(2016) month(01) day(10)'.
1579 If the user needs debug printing, it means he/she already having
1580 issues with the parsing - better to avoid formats that could
1581 be mis-interpreted (e.g., just YYYY-MM-DD).
1583 2. Can strftime be used instead?
1584 depends if it is portable and can print invalid dates on all systems.
1586 3. Print timezone information ?
1588 4. Print DST information ?
1590 5. Print nanosecond information ?
1592 NOTE:
1593 Printed date/time values might not be valid, e.g., '2016-02-31'
1594 or '2016-19-2016' . These are the values as parsed from the user
1595 string, before validation.
1597 int m = nstrftime (buf, n, "(Y-M-D) %Y-%m-%d %H:%M:%S", tm, 0, 0);
1599 /* If parser_control information was provided (for timezone),
1600 and there's enough space in the buffer, add timezone info. */
1601 if (pc && m < n && pc->zones_seen)
1603 int tz = pc->time_zone;
1605 /* Account for DST if tLOCAL_ZONE was seen. */
1606 if (pc->local_zones_seen && !pc->zones_seen && 0 < pc->local_isdst)
1607 tz += 60 * 60;
1609 char time_zone_buf[TIME_ZONE_BUFSIZE];
1610 snprintf (&buf[m], n - m, " TZ=%s", time_zone_str (tz, time_zone_buf));
1612 return buf;
1615 static char const *
1616 debug_strfdate (struct tm const *tm, char *buf, int n)
1618 char tm_year_buf[TM_YEAR_BUFSIZE];
1619 snprintf (buf, n, "(Y-M-D) %s-%02d-%02d",
1620 tm_year_str (tm->tm_year, tm_year_buf),
1621 tm->tm_mon + 1, tm->tm_mday);
1622 return buf;
1625 static char const *
1626 debug_strftime (struct tm const *tm, char *buf, int n)
1628 snprintf (buf, n, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
1629 return buf;
1632 /* If mktime_ok failed, display the failed time values,
1633 and provide possible hints. Example output:
1635 date: error: invalid date/time value:
1636 date: user provided time: '(Y-M-D) 2006-04-02 02:45:00'
1637 date: normalized time: '(Y-M-D) 2006-04-02 03:45:00'
1638 date: __
1639 date: possible reasons:
1640 date: non-existing due to daylight-saving time;
1641 date: numeric values overflow;
1642 date: missing timezone;
1644 static void
1645 debug_mktime_not_ok (struct tm const *tm0, struct tm const *tm1,
1646 parser_control const *pc, bool time_zone_seen)
1648 /* TODO: handle t == -1 (as in 'mktime_ok'). */
1649 char tmp[DBGBUFSIZE];
1650 int i;
1651 const bool eq_sec = (tm0->tm_sec == tm1->tm_sec);
1652 const bool eq_min = (tm0->tm_min == tm1->tm_min);
1653 const bool eq_hour = (tm0->tm_hour == tm1->tm_hour);
1654 const bool eq_mday = (tm0->tm_mday == tm1->tm_mday);
1655 const bool eq_month = (tm0->tm_mon == tm1->tm_mon);
1656 const bool eq_year = (tm0->tm_year == tm1->tm_year);
1658 const bool dst_shift = eq_sec && eq_min && !eq_hour
1659 && eq_mday && eq_month && eq_year;
1661 if (!pc->parse_datetime_debug)
1662 return;
1664 dbg_printf (_("error: invalid date/time value:\n"));
1665 dbg_printf (_(" user provided time: '%s'\n"),
1666 debug_strfdatetime (tm0, pc, tmp, sizeof tmp));
1667 dbg_printf (_(" normalized time: '%s'\n"),
1668 debug_strfdatetime (tm1, pc, tmp, sizeof tmp));
1669 /* The format must be aligned with debug_strfdatetime and the two
1670 DEBUG statements above. This string is not translated. */
1671 i = snprintf (tmp, sizeof tmp,
1672 " %4s %2s %2s %2s %2s %2s",
1673 eq_year ? "" : "----",
1674 eq_month ? "" : "--",
1675 eq_mday ? "" : "--",
1676 eq_hour ? "" : "--",
1677 eq_min ? "" : "--",
1678 eq_sec ? "" : "--");
1679 /* Trim trailing whitespace. */
1680 if (0 <= i)
1682 if (sizeof tmp - 1 < i)
1683 i = sizeof tmp - 1;
1684 while (0 < i && tmp[i - 1] == ' ')
1685 --i;
1686 tmp[i] = '\0';
1688 dbg_printf ("%s\n", tmp);
1690 dbg_printf (_(" possible reasons:\n"));
1691 if (dst_shift)
1692 dbg_printf (_(" non-existing due to daylight-saving time;\n"));
1693 if (!eq_mday && !eq_month)
1694 dbg_printf (_(" invalid day/month combination;\n"));
1695 dbg_printf (_(" numeric values overflow;\n"));
1696 dbg_printf (" %s\n", (time_zone_seen ? _("incorrect timezone")
1697 : _("missing timezone")));
1700 /* The original interface: run with debug=false and the default timezone. */
1701 bool
1702 parse_datetime (struct timespec *result, char const *p,
1703 struct timespec const *now)
1705 char const *tzstring = getenv ("TZ");
1706 timezone_t tz = tzalloc (tzstring);
1707 if (!tz)
1708 return false;
1709 bool ok = parse_datetime2 (result, p, now, 0, tz, tzstring);
1710 tzfree (tz);
1711 return ok;
1714 /* Parse a date/time string, storing the resulting time value into *RESULT.
1715 The string itself is pointed to by P. Return true if successful.
1716 P can be an incomplete or relative time specification; if so, use
1717 *NOW as the basis for the returned time. Default to timezone
1718 TZDEFAULT, which corresponds to tzalloc (TZSTRING). */
1719 bool
1720 parse_datetime2 (struct timespec *result, char const *p,
1721 struct timespec const *now, unsigned int flags,
1722 timezone_t tzdefault, char const *tzstring)
1724 struct tm tm;
1725 struct tm tm0;
1726 char time_zone_buf[TIME_ZONE_BUFSIZE];
1727 char dbg_tm[DBGBUFSIZE];
1728 bool ok = false;
1729 char const *input_sentinel = p + strlen (p);
1730 char *tz1alloc = NULL;
1732 /* A reasonable upper bound for the size of ordinary TZ strings.
1733 Use heap allocation if TZ's length exceeds this. */
1734 enum { TZBUFSIZE = 100 };
1735 char tz1buf[TZBUFSIZE];
1737 struct timespec gettime_buffer;
1738 if (! now)
1740 gettime (&gettime_buffer);
1741 now = &gettime_buffer;
1744 time_t Start = now->tv_sec;
1745 int Start_ns = now->tv_nsec;
1747 unsigned char c;
1748 while (c = *p, c_isspace (c))
1749 p++;
1751 timezone_t tz = tzdefault;
1753 /* Store a local copy prior to first "goto". Without this, a prior use
1754 below of RELATIVE_TIME_0 on the RHS might translate to an assignment-
1755 to-temporary, which would trigger a -Wjump-misses-init warning. */
1756 const relative_time rel_time_0 = RELATIVE_TIME_0;
1758 if (strncmp (p, "TZ=\"", 4) == 0)
1760 char const *tzbase = p + 4;
1761 ptrdiff_t tzsize = 1;
1762 char const *s;
1764 for (s = tzbase; *s; s++, tzsize++)
1765 if (*s == '\\')
1767 s++;
1768 if (! (*s == '\\' || *s == '"'))
1769 break;
1771 else if (*s == '"')
1773 timezone_t tz1;
1774 char *tz1string = tz1buf;
1775 char *z;
1776 if (TZBUFSIZE < tzsize)
1778 tz1alloc = malloc (tzsize);
1779 if (!tz1alloc)
1780 goto fail;
1781 tz1string = tz1alloc;
1783 z = tz1string;
1784 for (s = tzbase; *s != '"'; s++)
1785 *z++ = *(s += *s == '\\');
1786 *z = '\0';
1787 tz1 = tzalloc (tz1string);
1788 if (!tz1)
1789 goto fail;
1790 tz = tz1;
1791 tzstring = tz1string;
1793 p = s + 1;
1794 while (c = *p, c_isspace (c))
1795 p++;
1797 break;
1801 struct tm tmp;
1802 if (! localtime_rz (tz, &now->tv_sec, &tmp))
1803 goto fail;
1805 /* As documented, be careful to treat the empty string just like
1806 a date string of "0". Without this, an empty string would be
1807 declared invalid when parsed during a DST transition. */
1808 if (*p == '\0')
1809 p = "0";
1811 parser_control pc;
1812 pc.input = p;
1813 pc.parse_datetime_debug = (flags & PARSE_DATETIME_DEBUG) != 0;
1814 if (INT_ADD_WRAPV (tmp.tm_year, TM_YEAR_BASE, &pc.year.value))
1816 if (pc.parse_datetime_debug)
1817 dbg_printf (_("error: initial year out of range\n"));
1818 goto fail;
1820 pc.year.digits = 0;
1821 pc.month = tmp.tm_mon + 1;
1822 pc.day = tmp.tm_mday;
1823 pc.hour = tmp.tm_hour;
1824 pc.minutes = tmp.tm_min;
1825 pc.seconds.tv_sec = tmp.tm_sec;
1826 pc.seconds.tv_nsec = Start_ns;
1827 tm.tm_isdst = tmp.tm_isdst;
1829 pc.meridian = MER24;
1830 pc.rel = rel_time_0;
1831 pc.timespec_seen = false;
1832 pc.rels_seen = false;
1833 pc.dates_seen = 0;
1834 pc.days_seen = 0;
1835 pc.times_seen = 0;
1836 pc.local_zones_seen = 0;
1837 pc.dsts_seen = 0;
1838 pc.zones_seen = 0;
1839 pc.year_seen = false;
1840 pc.debug_dates_seen = false;
1841 pc.debug_days_seen = false;
1842 pc.debug_times_seen = false;
1843 pc.debug_local_zones_seen = false;
1844 pc.debug_zones_seen = false;
1845 pc.debug_year_seen = false;
1846 pc.debug_ordinal_day_seen = false;
1848 #if HAVE_STRUCT_TM_TM_ZONE
1849 pc.local_time_zone_table[0].name = tmp.tm_zone;
1850 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1851 pc.local_time_zone_table[0].value = tmp.tm_isdst;
1852 pc.local_time_zone_table[1].name = NULL;
1854 /* Probe the names used in the next three calendar quarters, looking
1855 for a tm_isdst different from the one we already have. */
1857 int quarter;
1858 for (quarter = 1; quarter <= 3; quarter++)
1860 intmax_t iprobe;
1861 if (INT_ADD_WRAPV (Start, quarter * (90 * 24 * 60 * 60), &iprobe)
1862 || time_overflow (iprobe))
1863 break;
1864 time_t probe = iprobe;
1865 struct tm probe_tm;
1866 if (localtime_rz (tz, &probe, &probe_tm) && probe_tm.tm_zone
1867 && probe_tm.tm_isdst != pc.local_time_zone_table[0].value)
1870 pc.local_time_zone_table[1].name = probe_tm.tm_zone;
1871 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1872 pc.local_time_zone_table[1].value = probe_tm.tm_isdst;
1873 pc.local_time_zone_table[2].name = NULL;
1875 break;
1879 #else
1880 #if HAVE_TZNAME
1882 # if !HAVE_DECL_TZNAME
1883 extern char *tzname[];
1884 # endif
1885 int i;
1886 for (i = 0; i < 2; i++)
1888 pc.local_time_zone_table[i].name = tzname[i];
1889 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1890 pc.local_time_zone_table[i].value = i;
1892 pc.local_time_zone_table[i].name = NULL;
1894 #else
1895 pc.local_time_zone_table[0].name = NULL;
1896 #endif
1897 #endif
1899 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1900 && ! strcmp (pc.local_time_zone_table[0].name,
1901 pc.local_time_zone_table[1].name))
1903 /* This locale uses the same abbreviation for standard and
1904 daylight times. So if we see that abbreviation, we don't
1905 know whether it's daylight time. */
1906 pc.local_time_zone_table[0].value = -1;
1907 pc.local_time_zone_table[1].name = NULL;
1910 if (yyparse (&pc) != 0)
1912 if (pc.parse_datetime_debug)
1913 dbg_printf ((input_sentinel <= pc.input
1914 ? _("error: parsing failed\n")
1915 : _("error: parsing failed, stopped at '%s'\n")),
1916 pc.input);
1917 goto fail;
1921 /* Determine effective timezone source. */
1923 if (pc.parse_datetime_debug)
1925 dbg_printf (_("input timezone: "));
1927 if (pc.timespec_seen)
1928 fprintf (stderr, _("'@timespec' - always UTC"));
1929 else if (pc.zones_seen)
1930 fprintf (stderr, _("parsed date/time string"));
1931 else if (tzstring)
1933 if (tz != tzdefault)
1934 fprintf (stderr, _("TZ=\"%s\" in date string"), tzstring);
1935 else if (STREQ (tzstring, "UTC0"))
1937 /* Special case: 'date -u' sets TZ="UTC0". */
1938 fprintf (stderr, _("TZ=\"UTC0\" environment value or -u"));
1940 else
1941 fprintf (stderr, _("TZ=\"%s\" environment value"), tzstring);
1943 else
1944 fprintf (stderr, _("system default"));
1946 /* Account for DST changes if tLOCAL_ZONE was seen.
1947 local timezone only changes DST and is relative to the
1948 default timezone.*/
1949 if (pc.local_zones_seen && !pc.zones_seen && 0 < pc.local_isdst)
1950 fprintf (stderr, ", dst");
1952 if (pc.zones_seen)
1953 fprintf (stderr, " (%s)", time_zone_str (pc.time_zone, time_zone_buf));
1955 fputc ('\n', stderr);
1958 if (pc.timespec_seen)
1959 *result = pc.seconds;
1960 else
1962 if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
1963 | (pc.local_zones_seen + pc.zones_seen)))
1965 if (pc.parse_datetime_debug)
1967 if (pc.times_seen > 1)
1968 dbg_printf ("error: seen multiple time parts\n");
1969 if (pc.dates_seen > 1)
1970 dbg_printf ("error: seen multiple date parts\n");
1971 if (pc.days_seen > 1)
1972 dbg_printf ("error: seen multiple days parts\n");
1973 if (pc.dsts_seen > 1)
1974 dbg_printf ("error: seen multiple daylight-saving parts\n");
1975 if ((pc.local_zones_seen + pc.zones_seen) > 1)
1976 dbg_printf ("error: seen multiple time-zone parts\n");
1978 goto fail;
1981 if (! to_tm_year (pc.year, pc.parse_datetime_debug, &tm.tm_year)
1982 || INT_ADD_WRAPV (pc.month, -1, &tm.tm_mon)
1983 || INT_ADD_WRAPV (pc.day, 0, &tm.tm_mday))
1985 if (pc.parse_datetime_debug)
1986 dbg_printf (_("error: year, month, or day overflow\n"));
1987 goto fail;
1989 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1991 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1992 if (tm.tm_hour < 0)
1994 char const *mrd = (pc.meridian == MERam ? "am"
1995 : pc.meridian == MERpm ?"pm" : "");
1996 if (pc.parse_datetime_debug)
1997 dbg_printf (_("error: invalid hour %"PRIdMAX"%s\n"),
1998 pc.hour, mrd);
1999 goto fail;
2001 tm.tm_min = pc.minutes;
2002 tm.tm_sec = pc.seconds.tv_sec;
2003 if (pc.parse_datetime_debug)
2004 dbg_printf ((pc.times_seen
2005 ? _("using specified time as starting value: '%s'\n")
2006 : _("using current time as starting value: '%s'\n")),
2007 debug_strftime (&tm, dbg_tm, sizeof dbg_tm));
2009 else
2011 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
2012 pc.seconds.tv_nsec = 0;
2013 if (pc.parse_datetime_debug)
2014 dbg_printf ("warning: using midnight as starting time: 00:00:00\n");
2017 /* Let mktime deduce tm_isdst if we have an absolute timestamp. */
2018 if (pc.dates_seen | pc.days_seen | pc.times_seen)
2019 tm.tm_isdst = -1;
2021 /* But if the input explicitly specifies local time with or without
2022 DST, give mktime that information. */
2023 if (pc.local_zones_seen)
2024 tm.tm_isdst = pc.local_isdst;
2026 tm0.tm_sec = tm.tm_sec;
2027 tm0.tm_min = tm.tm_min;
2028 tm0.tm_hour = tm.tm_hour;
2029 tm0.tm_mday = tm.tm_mday;
2030 tm0.tm_mon = tm.tm_mon;
2031 tm0.tm_year = tm.tm_year;
2032 tm0.tm_isdst = tm.tm_isdst;
2033 tm.tm_wday = -1;
2035 Start = mktime_z (tz, &tm);
2037 if (! mktime_ok (&tm0, &tm))
2039 bool repaired = false;
2040 bool time_zone_seen = pc.zones_seen != 0;
2041 if (time_zone_seen)
2043 /* Guard against falsely reporting errors near the time_t
2044 boundaries when parsing times in other time zones. For
2045 example, suppose the input string "1969-12-31 23:00:00 -0100",
2046 the current time zone is 8 hours ahead of UTC, and the min
2047 time_t value is 1970-01-01 00:00:00 UTC. Then the min
2048 localtime value is 1970-01-01 08:00:00, and mktime will
2049 therefore fail on 1969-12-31 23:00:00. To work around the
2050 problem, set the time zone to 1 hour behind UTC temporarily
2051 by setting TZ="XXX1:00" and try mktime again. */
2053 char tz2buf[sizeof "XXX" - 1 + TIME_ZONE_BUFSIZE];
2054 tz2buf[0] = tz2buf[1] = tz2buf[2] = 'X';
2055 time_zone_str (pc.time_zone, &tz2buf[3]);
2056 timezone_t tz2 = tzalloc (tz2buf);
2057 if (!tz2)
2059 if (pc.parse_datetime_debug)
2060 dbg_printf (_("error: tzalloc (\"%s\") failed\n"), tz2buf);
2061 goto fail;
2063 tm.tm_sec = tm0.tm_sec;
2064 tm.tm_min = tm0.tm_min;
2065 tm.tm_hour = tm0.tm_hour;
2066 tm.tm_mday = tm0.tm_mday;
2067 tm.tm_mon = tm0.tm_mon;
2068 tm.tm_year = tm0.tm_year;
2069 tm.tm_isdst = tm0.tm_isdst;
2070 tm.tm_wday = -1;
2071 Start = mktime_z (tz2, &tm);
2072 repaired = mktime_ok (&tm0, &tm);
2073 tzfree (tz2);
2076 if (! repaired)
2078 debug_mktime_not_ok (&tm0, &tm, &pc, time_zone_seen);
2079 goto fail;
2083 char dbg_ord[DBGBUFSIZE];
2085 if (pc.days_seen && ! pc.dates_seen)
2087 intmax_t dayincr;
2088 if (INT_MULTIPLY_WRAPV ((pc.day_ordinal
2089 - (0 < pc.day_ordinal
2090 && tm.tm_wday != pc.day_number)),
2091 7, &dayincr)
2092 || INT_ADD_WRAPV ((pc.day_number - tm.tm_wday + 7) % 7,
2093 dayincr, &dayincr)
2094 || INT_ADD_WRAPV (dayincr, tm.tm_mday, &tm.tm_mday))
2095 Start = -1;
2096 else
2098 tm.tm_isdst = -1;
2099 Start = mktime_z (tz, &tm);
2102 if (Start == (time_t) -1)
2104 if (pc.parse_datetime_debug)
2105 dbg_printf (_("error: day '%s' "
2106 "(day ordinal=%"PRIdMAX" number=%d) "
2107 "resulted in an invalid date: '%s'\n"),
2108 str_days (&pc, dbg_ord, sizeof dbg_ord),
2109 pc.day_ordinal, pc.day_number,
2110 debug_strfdatetime (&tm, &pc, dbg_tm,
2111 sizeof dbg_tm));
2112 goto fail;
2115 if (pc.parse_datetime_debug)
2116 dbg_printf (_("new start date: '%s' is '%s'\n"),
2117 str_days (&pc, dbg_ord, sizeof dbg_ord),
2118 debug_strfdatetime (&tm, &pc, dbg_tm, sizeof dbg_tm));
2122 if (pc.parse_datetime_debug)
2124 if (!pc.dates_seen && !pc.days_seen)
2125 dbg_printf (_("using current date as starting value: '%s'\n"),
2126 debug_strfdate (&tm, dbg_tm, sizeof dbg_tm));
2128 if (pc.days_seen && pc.dates_seen)
2129 dbg_printf (_("warning: day (%s) ignored when explicit dates "
2130 "are given\n"),
2131 str_days (&pc, dbg_ord, sizeof dbg_ord));
2133 dbg_printf (_("starting date/time: '%s'\n"),
2134 debug_strfdatetime (&tm, &pc, dbg_tm, sizeof dbg_tm));
2137 /* Add relative date. */
2138 if (pc.rel.year | pc.rel.month | pc.rel.day)
2140 if (pc.parse_datetime_debug)
2142 if ((pc.rel.year != 0 || pc.rel.month != 0) && tm.tm_mday != 15)
2143 dbg_printf (_("warning: when adding relative months/years, "
2144 "it is recommended to specify the 15th of the "
2145 "months\n"));
2147 if (pc.rel.day != 0 && tm.tm_hour != 12)
2148 dbg_printf (_("warning: when adding relative days, "
2149 "it is recommended to specify noon\n"));
2152 int year, month, day;
2153 if (INT_ADD_WRAPV (tm.tm_year, pc.rel.year, &year)
2154 || INT_ADD_WRAPV (tm.tm_mon, pc.rel.month, &month)
2155 || INT_ADD_WRAPV (tm.tm_mday, pc.rel.day, &day))
2157 if (pc.parse_datetime_debug)
2158 dbg_printf (_("error: %s:%d\n"), __FILE__, __LINE__);
2159 goto fail;
2161 tm.tm_year = year;
2162 tm.tm_mon = month;
2163 tm.tm_mday = day;
2164 tm.tm_hour = tm0.tm_hour;
2165 tm.tm_min = tm0.tm_min;
2166 tm.tm_sec = tm0.tm_sec;
2167 tm.tm_isdst = tm0.tm_isdst;
2168 Start = mktime_z (tz, &tm);
2169 if (Start == (time_t) -1)
2171 if (pc.parse_datetime_debug)
2172 dbg_printf (_("error: adding relative date resulted "
2173 "in an invalid date: '%s'\n"),
2174 debug_strfdatetime (&tm, &pc, dbg_tm,
2175 sizeof dbg_tm));
2176 goto fail;
2179 if (pc.parse_datetime_debug)
2181 dbg_printf (_("after date adjustment "
2182 "(%+"PRIdMAX" years, %+"PRIdMAX" months, "
2183 "%+"PRIdMAX" days),\n"),
2184 pc.rel.year, pc.rel.month, pc.rel.day);
2185 dbg_printf (_(" new date/time = '%s'\n"),
2186 debug_strfdatetime (&tm, &pc, dbg_tm,
2187 sizeof dbg_tm));
2189 /* Warn about crossing DST due to time adjustment.
2190 Example: https://bugs.gnu.org/8357
2191 env TZ=Europe/Helsinki \
2192 date --debug \
2193 -d 'Mon Mar 28 00:36:07 2011 EEST 1 day ago'
2195 This case is different than DST changes due to time adjustment,
2196 i.e., "1 day ago" vs "24 hours ago" are calculated in different
2197 places.
2199 'tm0.tm_isdst' contains the DST of the input date,
2200 'tm.tm_isdst' is the normalized result after calling
2201 mktime (&tm).
2203 if (tm0.tm_isdst != -1 && tm.tm_isdst != tm0.tm_isdst)
2204 dbg_printf (_("warning: daylight saving time changed after "
2205 "date adjustment\n"));
2207 /* Warn if the user did not ask to adjust days but mday changed,
2209 user did not ask to adjust months/days but the month changed.
2211 Example for first case:
2212 2016-05-31 + 1 month => 2016-06-31 => 2016-07-01.
2213 User asked to adjust month, but the day changed from 31 to 01.
2215 Example for second case:
2216 2016-02-29 + 1 year => 2017-02-29 => 2017-03-01.
2217 User asked to adjust year, but the month changed from 02 to 03.
2219 if (pc.rel.day == 0
2220 && (tm.tm_mday != day
2221 || (pc.rel.month == 0 && tm.tm_mon != month)))
2223 dbg_printf (_("warning: month/year adjustment resulted in "
2224 "shifted dates:\n"));
2225 char tm_year_buf[TM_YEAR_BUFSIZE];
2226 dbg_printf (_(" adjusted Y M D: %s %02d %02d\n"),
2227 tm_year_str (year, tm_year_buf), month + 1, day);
2228 dbg_printf (_(" normalized Y M D: %s %02d %02d\n"),
2229 tm_year_str (tm.tm_year, tm_year_buf),
2230 tm.tm_mon + 1, tm.tm_mday);
2236 /* The only "output" of this if-block is an updated Start value,
2237 so this block must follow others that clobber Start. */
2238 if (pc.zones_seen)
2240 intmax_t delta = pc.time_zone, t1;
2241 bool overflow = false;
2242 #ifdef HAVE_TM_GMTOFF
2243 long int utcoff = tm.tm_gmtoff;
2244 #else
2245 time_t t = Start;
2246 struct tm gmt;
2247 int utcoff = (gmtime_r (&t, &gmt)
2248 ? tm_diff (&tm, &gmt)
2249 : (overflow = true, 0));
2250 #endif
2251 overflow |= INT_SUBTRACT_WRAPV (delta, utcoff, &delta);
2252 overflow |= INT_SUBTRACT_WRAPV (Start, delta, &t1);
2253 if (overflow || time_overflow (t1))
2255 if (pc.parse_datetime_debug)
2256 dbg_printf (_("error: timezone %d caused time_t overflow\n"),
2257 pc.time_zone);
2258 goto fail;
2260 Start = t1;
2263 if (pc.parse_datetime_debug)
2265 intmax_t Starti = Start;
2266 dbg_printf (_("'%s' = %"PRIdMAX" epoch-seconds\n"),
2267 debug_strfdatetime (&tm, &pc, dbg_tm, sizeof dbg_tm),
2268 Starti);
2272 /* Add relative hours, minutes, and seconds. On hosts that support
2273 leap seconds, ignore the possibility of leap seconds; e.g.,
2274 "+ 10 minutes" adds 600 seconds, even if one of them is a
2275 leap second. Typically this is not what the user wants, but it's
2276 too hard to do it the other way, because the time zone indicator
2277 must be applied before relative times, and if mktime is applied
2278 again the time zone will be lost. */
2280 intmax_t orig_ns = pc.seconds.tv_nsec;
2281 intmax_t sum_ns = orig_ns + pc.rel.ns;
2282 int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
2283 int d4 = (sum_ns - normalized_ns) / BILLION;
2284 intmax_t d1, t1, d2, t2, t3, t4;
2285 if (INT_MULTIPLY_WRAPV (pc.rel.hour, 60 * 60, &d1)
2286 || INT_ADD_WRAPV (Start, d1, &t1)
2287 || INT_MULTIPLY_WRAPV (pc.rel.minutes, 60, &d2)
2288 || INT_ADD_WRAPV (t1, d2, &t2)
2289 || INT_ADD_WRAPV (t2, pc.rel.seconds, &t3)
2290 || INT_ADD_WRAPV (t3, d4, &t4)
2291 || time_overflow (t4))
2293 if (pc.parse_datetime_debug)
2294 dbg_printf (_("error: adding relative time caused an "
2295 "overflow\n"));
2296 goto fail;
2299 result->tv_sec = t4;
2300 result->tv_nsec = normalized_ns;
2302 if (pc.parse_datetime_debug
2303 && (pc.rel.hour | pc.rel.minutes | pc.rel.seconds | pc.rel.ns))
2305 dbg_printf (_("after time adjustment (%+"PRIdMAX" hours, "
2306 "%+"PRIdMAX" minutes, "
2307 "%+"PRIdMAX" seconds, %+d ns),\n"),
2308 pc.rel.hour, pc.rel.minutes, pc.rel.seconds,
2309 pc.rel.ns);
2310 dbg_printf (_(" new time = %"PRIdMAX" epoch-seconds\n"), t4);
2312 /* Warn about crossing DST due to time adjustment.
2313 Example: https://bugs.gnu.org/8357
2314 env TZ=Europe/Helsinki \
2315 date --debug \
2316 -d 'Mon Mar 28 00:36:07 2011 EEST 24 hours ago'
2318 This case is different than DST changes due to days adjustment,
2319 i.e., "1 day ago" vs "24 hours ago" are calculated in different
2320 places.
2322 'tm.tm_isdst' contains the date after date adjustment. */
2323 struct tm lmt;
2324 if (tm.tm_isdst != -1 && localtime_rz (tz, &result->tv_sec, &lmt)
2325 && tm.tm_isdst != lmt.tm_isdst)
2326 dbg_printf (_("warning: daylight saving time changed after "
2327 "time adjustment\n"));
2332 if (pc.parse_datetime_debug)
2334 /* Special case: using 'date -u' simply set TZ=UTC0 */
2335 if (! tzstring)
2336 dbg_printf (_("timezone: system default\n"));
2337 else if (STREQ (tzstring, "UTC0"))
2338 dbg_printf (_("timezone: Universal Time\n"));
2339 else
2340 dbg_printf (_("timezone: TZ=\"%s\" environment value\n"), tzstring);
2342 intmax_t sec = result->tv_sec;
2343 int nsec = result->tv_nsec;
2344 dbg_printf (_("final: %"PRIdMAX".%09d (epoch-seconds)\n"),
2345 sec, nsec);
2347 struct tm gmt, lmt;
2348 bool got_utc = !!gmtime_r (&result->tv_sec, &gmt);
2349 if (got_utc)
2350 dbg_printf (_("final: %s (UTC)\n"),
2351 debug_strfdatetime (&gmt, NULL,
2352 dbg_tm, sizeof dbg_tm));
2353 if (localtime_rz (tz, &result->tv_sec, &lmt))
2355 #ifdef HAVE_TM_GMTOFF
2356 bool got_utcoff = true;
2357 long int utcoff = lmt.tm_gmtoff;
2358 #else
2359 bool got_utcoff = got_utc;
2360 int utcoff;
2361 if (got_utcoff)
2362 utcoff = tm_diff (&lmt, &gmt);
2363 #endif
2364 if (got_utcoff)
2365 dbg_printf (_("final: %s (UTC%s)\n"),
2366 debug_strfdatetime (&lmt, NULL, dbg_tm, sizeof dbg_tm),
2367 time_zone_str (utcoff, time_zone_buf));
2368 else
2369 dbg_printf (_("final: %s (unknown time zone offset)\n"),
2370 debug_strfdatetime (&lmt, NULL, dbg_tm, sizeof dbg_tm));
2374 ok = true;
2376 fail:
2377 if (tz != tzdefault)
2378 tzfree (tz);
2379 free (tz1alloc);
2380 return ok;
2383 #if TEST
2386 main (int ac, char **av)
2388 char buff[BUFSIZ];
2390 printf ("Enter date, or blank line to exit.\n\t> ");
2391 fflush (stdout);
2393 buff[BUFSIZ - 1] = '\0';
2394 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
2396 struct timespec d;
2397 struct tm const *tm;
2398 if (! parse_datetime (&d, buff, NULL))
2399 printf ("Bad format - couldn't convert.\n");
2400 else if (! (tm = localtime (&d.tv_sec)))
2402 intmax_t sec = d.tv_sec;
2403 printf ("localtime (%"PRIdMAX") failed\n", sec);
2405 else
2407 int ns = d.tv_nsec;
2408 char tm_year_buf[TM_YEAR_BUFSIZE];
2409 printf ("%s-%02d-%02d %02d:%02d:%02d.%09d\n",
2410 tm_year_str (tm->tm_year, tm_year_buf),
2411 tm->tm_mon + 1, tm->tm_mday,
2412 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);
2414 printf ("\t> ");
2415 fflush (stdout);
2417 return 0;
2419 #endif /* TEST */