openat: don’t close (-1)
[gnulib.git] / lib / parse-datetime.y
blobc27ab60a845c712e0f4517f739fcf4983c02a244
1 %{
2 /* Parse a string into an internal timestamp.
4 Copyright (C) 1999-2000, 2002-2024 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"
42 /* There's no need to extend the stack, so there's no need to involve
43 alloca. */
44 #define YYSTACK_USE_ALLOCA 0
46 /* Tell Bison how much stack space is needed. 20 should be plenty for
47 this grammar, which is not right recursive. Beware setting it too
48 high, since that might cause problems on machines whose
49 implementations have lame stack-overflow checking. */
50 #define YYMAXDEPTH 20
51 #define YYINITDEPTH YYMAXDEPTH
53 #include <inttypes.h>
54 #include <c-ctype.h>
55 #include <stdarg.h>
56 #include <stdckdint.h>
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
61 #include "gettext.h"
63 #define _(str) gettext (str)
65 /* Bison's skeleton tests _STDLIB_H, while some stdlib.h headers
66 use _STDLIB_H_ as witness. Map the latter to the one bison uses. */
67 /* FIXME: this is temporary. Remove when we have a mechanism to ensure
68 that the version we're using is fixed, too. */
69 #ifdef _STDLIB_H_
70 # undef _STDLIB_H
71 # define _STDLIB_H 1
72 #endif
74 /* Shift A right by B bits portably, by dividing A by 2**B and
75 truncating towards minus infinity. A and B should be free of side
76 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
77 INT_BITS is the number of useful bits in an int. GNU code can
78 assume that INT_BITS is at least 32.
80 ISO C99 says that A >> B is implementation-defined if A < 0. Some
81 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
82 right in the usual way when A < 0, so SHR falls back on division if
83 ordinary A >> B doesn't seem to be the usual signed shift. */
84 #define SHR(a, b) \
85 (-1 >> 1 == -1 \
86 ? (a) >> (b) \
87 : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
89 #define HOUR(x) (60 * 60 * (x))
91 #define STREQ(a, b) (strcmp (a, b) == 0)
93 /* Verify that time_t is an integer as POSIX requires, and that every
94 time_t value fits in intmax_t. Please file a bug report if these
95 assumptions are false on your platform. */
96 static_assert (TYPE_IS_INTEGER (time_t));
97 static_assert (!TYPE_SIGNED (time_t) || INTMAX_MIN <= TYPE_MINIMUM (time_t));
98 static_assert (TYPE_MAXIMUM (time_t) <= INTMAX_MAX);
100 /* True if N is out of range for time_t. */
101 static bool
102 time_overflow (intmax_t n)
104 return ! ((TYPE_SIGNED (time_t) ? TYPE_MINIMUM (time_t) <= n : 0 <= n)
105 && n <= TYPE_MAXIMUM (time_t));
108 /* Convert a possibly-signed character to an unsigned character. This is
109 a bit safer than casting to unsigned char, since it catches some type
110 errors that the cast doesn't. */
111 static unsigned char to_uchar (char ch) { return ch; }
113 static void _GL_ATTRIBUTE_FORMAT ((__printf__, 1, 2))
114 dbg_printf (char const *msg, ...)
116 va_list args;
117 /* TODO: use gnulib's 'program_name' instead? */
118 fputs ("date: ", stderr);
120 va_start (args, msg);
121 vfprintf (stderr, msg, args);
122 va_end (args);
126 /* An integer value, and the number of digits in its textual
127 representation. */
128 typedef struct
130 bool negative;
131 intmax_t value;
132 idx_t digits;
133 } textint;
135 /* An entry in the lexical lookup table. */
136 typedef struct
138 char const *name;
139 int type;
140 int value;
141 } table;
143 /* Meridian: am, pm, or 24-hour style. */
144 enum { MERam, MERpm, MER24 };
146 /* Maximum length of a time zone abbreviation, plus 1. */
147 enum { TIME_ZONE_BUFSIZE = INT_STRLEN_BOUND (intmax_t) + sizeof ":MM:SS" };
149 /* A reasonable upper bound for the buffer used in debug output. */
150 enum { DBGBUFSIZE = 100 };
152 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
154 /* Relative times. */
155 typedef struct
157 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
158 intmax_t year;
159 intmax_t month;
160 intmax_t day;
161 intmax_t hour;
162 intmax_t minutes;
163 intmax_t seconds;
164 int ns;
165 } relative_time;
167 #if HAVE_COMPOUND_LITERALS
168 # define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
169 #else
170 static relative_time const RELATIVE_TIME_0;
171 #endif
173 /* Information passed to and from the parser. */
174 typedef struct
176 /* The input string remaining to be parsed. */
177 const char *input;
179 /* N, if this is the Nth Tuesday. */
180 intmax_t day_ordinal;
182 /* Day of week; Sunday is 0. */
183 int day_number;
185 /* tm_isdst flag for the local zone. */
186 int local_isdst;
188 /* Time zone, in seconds east of UT. */
189 int time_zone;
191 /* Style used for time. */
192 int meridian;
194 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
195 textint year;
196 intmax_t month;
197 intmax_t day;
198 intmax_t hour;
199 intmax_t minutes;
200 struct timespec seconds; /* includes nanoseconds */
202 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
203 relative_time rel;
205 /* Presence or counts of nonterminals of various flavors parsed so far. */
206 bool timespec_seen;
207 bool rels_seen;
208 idx_t dates_seen;
209 idx_t days_seen;
210 idx_t J_zones_seen;
211 idx_t local_zones_seen;
212 idx_t dsts_seen;
213 idx_t times_seen;
214 idx_t zones_seen;
215 bool year_seen;
217 #ifdef GNULIB_PARSE_DATETIME2
218 /* Print debugging output to stderr. */
219 bool parse_datetime_debug;
220 #endif
222 /* Which of the 'seen' parts have been printed when debugging. */
223 bool debug_dates_seen;
224 bool debug_days_seen;
225 bool debug_local_zones_seen;
226 bool debug_times_seen;
227 bool debug_zones_seen;
228 bool debug_year_seen;
230 /* The user specified explicit ordinal day value. */
231 bool debug_ordinal_day_seen;
233 /* Table of local time zone abbreviations, terminated by a null entry. */
234 table local_time_zone_table[3];
236 #if !HAVE_STRUCT_TM_TM_ZONE
237 /* The abbreviations in LOCAL_TIME_ZONE_TABLE. */
238 char tz_abbr[2][TIME_ZONE_BUFSIZE];
239 #endif
240 } parser_control;
242 static bool
243 debugging (parser_control const *pc)
245 #ifdef GNULIB_PARSE_DATETIME2
246 return pc->parse_datetime_debug;
247 #else
248 return false;
249 #endif
252 union YYSTYPE;
253 static int yylex (union YYSTYPE *, parser_control *);
254 static void yyerror (parser_control const *, char const *);
255 static bool time_zone_hhmm (parser_control *, textint, intmax_t);
257 /* Extract into *PC any date and time info from a string of digits
258 of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
259 YYYY, ...). */
260 static void
261 digits_to_date_time (parser_control *pc, textint text_int)
263 if (pc->dates_seen && ! pc->year.digits
264 && ! pc->rels_seen && (pc->times_seen || 2 < text_int.digits))
266 pc->year_seen = true;
267 pc->year = text_int;
269 else
271 if (4 < text_int.digits)
273 pc->dates_seen++;
274 pc->day = text_int.value % 100;
275 pc->month = (text_int.value / 100) % 100;
276 pc->year.value = text_int.value / 10000;
277 pc->year.digits = text_int.digits - 4;
279 else
281 pc->times_seen++;
282 if (text_int.digits <= 2)
284 pc->hour = text_int.value;
285 pc->minutes = 0;
287 else
289 pc->hour = text_int.value / 100;
290 pc->minutes = text_int.value % 100;
292 pc->seconds = (struct timespec) {0};
293 pc->meridian = MER24;
298 /* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1). Return true
299 if successful, false if an overflow occurred. */
300 static bool
301 apply_relative_time (parser_control *pc, relative_time rel, int factor)
303 if (factor < 0
304 ? (ckd_sub (&pc->rel.ns, pc->rel.ns, rel.ns)
305 | ckd_sub (&pc->rel.seconds, pc->rel.seconds, rel.seconds)
306 | ckd_sub (&pc->rel.minutes, pc->rel.minutes, rel.minutes)
307 | ckd_sub (&pc->rel.hour, pc->rel.hour, rel.hour)
308 | ckd_sub (&pc->rel.day, pc->rel.day, rel.day)
309 | ckd_sub (&pc->rel.month, pc->rel.month, rel.month)
310 | ckd_sub (&pc->rel.year, pc->rel.year, rel.year))
311 : (ckd_add (&pc->rel.ns, pc->rel.ns, rel.ns)
312 | ckd_add (&pc->rel.seconds, pc->rel.seconds, rel.seconds)
313 | ckd_add (&pc->rel.minutes, pc->rel.minutes, rel.minutes)
314 | ckd_add (&pc->rel.hour, pc->rel.hour, rel.hour)
315 | ckd_add (&pc->rel.day, pc->rel.day, rel.day)
316 | ckd_add (&pc->rel.month, pc->rel.month, rel.month)
317 | ckd_add (&pc->rel.year, pc->rel.year, rel.year)))
318 return false;
319 pc->rels_seen = true;
320 return true;
323 /* Set PC-> hour, minutes, seconds and nanoseconds members from arguments. */
324 static void
325 set_hhmmss (parser_control *pc, intmax_t hour, intmax_t minutes,
326 time_t sec, int nsec)
328 pc->hour = hour;
329 pc->minutes = minutes;
330 pc->seconds = (struct timespec) { .tv_sec = sec, .tv_nsec = nsec };
333 /* Return a textual representation of the day ordinal/number values
334 in the parser_control struct (e.g., "last wed", "this tues", "thu"). */
335 static const char *
336 str_days (parser_control *pc, char *buffer, int n)
338 /* TODO: use relative_time_table for reverse lookup. */
339 static char const ordinal_values[][11] = {
340 "last",
341 "this",
342 "next/first",
343 "(SECOND)", /* SECOND is commented out in relative_time_table. */
344 "third",
345 "fourth",
346 "fifth",
347 "sixth",
348 "seventh",
349 "eight",
350 "ninth",
351 "tenth",
352 "eleventh",
353 "twelfth"
356 static char const days_values[][4] = {
357 "Sun",
358 "Mon",
359 "Tue",
360 "Wed",
361 "Thu",
362 "Fri",
363 "Sat"
366 int len;
368 /* Don't add an ordinal prefix if the user didn't specify it
369 (e.g., "this wed" vs "wed"). */
370 if (pc->debug_ordinal_day_seen)
372 /* Use word description if possible (e.g., -1 = last, 3 = third). */
373 len = (-1 <= pc->day_ordinal && pc->day_ordinal <= 12
374 ? snprintf (buffer, n, "%s", ordinal_values[pc->day_ordinal + 1])
375 : snprintf (buffer, n, "%"PRIdMAX, pc->day_ordinal));
377 else
379 buffer[0] = '\0';
380 len = 0;
383 /* Add the day name */
384 if (0 <= pc->day_number && pc->day_number <= 6 && 0 <= len && len < n)
385 snprintf (buffer + len, n - len, &" %s"[len == 0],
386 days_values[pc->day_number]);
387 else
389 /* invalid day_number value - should never happen */
391 return buffer;
394 /* Convert a time zone to its string representation. */
396 static char const *
397 time_zone_str (int time_zone, char time_zone_buf[TIME_ZONE_BUFSIZE])
399 char *p = time_zone_buf;
400 char sign = time_zone < 0 ? '-' : '+';
401 int hour = abs (time_zone / (60 * 60));
402 p += sprintf (time_zone_buf, "%c%02d", sign, hour);
403 int offset_from_hour = abs (time_zone % (60 * 60));
404 if (offset_from_hour != 0)
406 int mm = offset_from_hour / 60;
407 int ss = offset_from_hour % 60;
408 *p++ = ':';
409 *p++ = '0' + mm / 10;
410 *p++ = '0' + mm % 10;
411 if (ss)
413 *p++ = ':';
414 *p++ = '0' + ss / 10;
415 *p++ = '0' + ss % 10;
417 *p = '\0';
419 return time_zone_buf;
422 /* debugging: print the current time in the parser_control structure.
423 The parser will increment "*_seen" members for those which were parsed.
424 This function will print only newly seen parts. */
425 static void
426 debug_print_current_time (char const *item, parser_control *pc)
428 bool space = false;
430 if (!debugging (pc))
431 return;
433 /* no newline, more items printed below */
434 dbg_printf (_("parsed %s part: "), item);
436 if (pc->dates_seen && !pc->debug_dates_seen)
438 /*TODO: use pc->year.negative? */
439 fprintf (stderr, "(Y-M-D) %04"PRIdMAX"-%02"PRIdMAX"-%02"PRIdMAX,
440 pc->year.value, pc->month, pc->day);
441 pc->debug_dates_seen = true;
442 space = true;
445 if (pc->year_seen != pc->debug_year_seen)
447 if (space)
448 fputc (' ', stderr);
449 fprintf (stderr, _("year: %04"PRIdMAX), pc->year.value);
451 pc->debug_year_seen = pc->year_seen;
452 space = true;
455 if (pc->times_seen && !pc->debug_times_seen)
457 intmax_t sec = pc->seconds.tv_sec;
458 fprintf (stderr, &" %02"PRIdMAX":%02"PRIdMAX":%02"PRIdMAX[!space],
459 pc->hour, pc->minutes, sec);
460 if (pc->seconds.tv_nsec != 0)
462 int nsec = pc->seconds.tv_nsec;
463 fprintf (stderr, ".%09d", nsec);
465 if (pc->meridian == MERpm)
466 fputs ("pm", stderr);
468 pc->debug_times_seen = true;
469 space = true;
472 if (pc->days_seen && !pc->debug_days_seen)
474 if (space)
475 fputc (' ', stderr);
476 char tmp[DBGBUFSIZE];
477 fprintf (stderr, _("%s (day ordinal=%"PRIdMAX" number=%d)"),
478 str_days (pc, tmp, sizeof tmp),
479 pc->day_ordinal, pc->day_number);
480 pc->debug_days_seen = true;
481 space = true;
484 /* local zone strings only change the DST settings,
485 not the timezone value. If seen, inform about the DST. */
486 if (pc->local_zones_seen && !pc->debug_local_zones_seen)
488 fprintf (stderr, &" isdst=%d%s"[!space],
489 pc->local_isdst, pc->dsts_seen ? " DST" : "");
490 pc->debug_local_zones_seen = true;
491 space = true;
494 if (pc->zones_seen && !pc->debug_zones_seen)
496 char time_zone_buf[TIME_ZONE_BUFSIZE];
497 fprintf (stderr, &" UTC%s"[!space],
498 time_zone_str (pc->time_zone, time_zone_buf));
499 pc->debug_zones_seen = true;
500 space = true;
503 if (pc->timespec_seen)
505 intmax_t sec = pc->seconds.tv_sec;
506 if (space)
507 fputc (' ', stderr);
508 fprintf (stderr, _("number of seconds: %"PRIdMAX), sec);
511 fputc ('\n', stderr);
514 /* Debugging: print the current relative values. */
516 static bool
517 print_rel_part (bool space, intmax_t val, char const *name)
519 if (val == 0)
520 return space;
521 fprintf (stderr, &" %+"PRIdMAX" %s"[!space], val, name);
522 return true;
525 static void
526 debug_print_relative_time (char const *item, parser_control const *pc)
528 bool space = false;
530 if (!debugging (pc))
531 return;
533 /* no newline, more items printed below */
534 dbg_printf (_("parsed %s part: "), item);
536 if (pc->rel.year == 0 && pc->rel.month == 0 && pc->rel.day == 0
537 && pc->rel.hour == 0 && pc->rel.minutes == 0 && pc->rel.seconds == 0
538 && pc->rel.ns == 0)
540 /* Special case: relative time of this/today/now */
541 fputs (_("today/this/now\n"), stderr);
542 return;
545 space = print_rel_part (space, pc->rel.year, "year(s)");
546 space = print_rel_part (space, pc->rel.month, "month(s)");
547 space = print_rel_part (space, pc->rel.day, "day(s)");
548 space = print_rel_part (space, pc->rel.hour, "hour(s)");
549 space = print_rel_part (space, pc->rel.minutes, "minutes");
550 space = print_rel_part (space, pc->rel.seconds, "seconds");
551 print_rel_part (space, pc->rel.ns, "nanoseconds");
553 fputc ('\n', stderr);
560 /* We want a reentrant parser, even if the TZ manipulation and the calls to
561 localtime and gmtime are not reentrant. */
562 %define api.pure
563 %parse-param { parser_control *pc }
564 %lex-param { parser_control *pc }
566 /* This grammar has 31 shift/reduce conflicts. */
567 %expect 31
569 %union
571 intmax_t intval;
572 textint textintval;
573 struct timespec timespec;
574 relative_time rel;
577 %token <intval> tAGO
578 %token tDST
580 %token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
581 %token <intval> tDAY_UNIT tDAY_SHIFT
583 %token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
584 %token <intval> tMONTH tORDINAL tZONE
586 %token <textintval> tSNUMBER tUNUMBER
587 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
589 %type <intval> o_colon_minutes
590 %type <timespec> seconds signed_seconds unsigned_seconds
592 %type <rel> relunit relunit_snumber dayshift
596 spec:
597 timespec
598 | items
601 timespec:
602 '@' seconds
604 pc->seconds = $2;
605 pc->timespec_seen = true;
606 debug_print_current_time (_("number of seconds"), pc);
610 items:
611 /* empty */
612 | items item
615 item:
616 datetime
618 pc->times_seen++; pc->dates_seen++;
619 debug_print_current_time (_("datetime"), pc);
621 | time
623 pc->times_seen++;
624 debug_print_current_time (_("time"), pc);
626 | local_zone
628 pc->local_zones_seen++;
629 debug_print_current_time (_("local_zone"), pc);
631 | 'J'
633 pc->J_zones_seen++;
634 debug_print_current_time ("J", pc);
636 | zone
638 pc->zones_seen++;
639 debug_print_current_time (_("zone"), pc);
641 | date
643 pc->dates_seen++;
644 debug_print_current_time (_("date"), pc);
646 | day
648 pc->days_seen++;
649 debug_print_current_time (_("day"), pc);
651 | rel
653 debug_print_relative_time (_("relative"), pc);
655 | number
657 debug_print_current_time (_("number"), pc);
659 | hybrid
661 debug_print_relative_time (_("hybrid"), pc);
665 datetime:
666 iso_8601_datetime
669 iso_8601_datetime:
670 iso_8601_date 'T' iso_8601_time
673 time:
674 tUNUMBER tMERIDIAN
676 set_hhmmss (pc, $1.value, 0, 0, 0);
677 pc->meridian = $2;
679 | tUNUMBER ':' tUNUMBER tMERIDIAN
681 set_hhmmss (pc, $1.value, $3.value, 0, 0);
682 pc->meridian = $4;
684 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tMERIDIAN
686 set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
687 pc->meridian = $6;
689 | iso_8601_time
692 iso_8601_time:
693 tUNUMBER zone_offset
695 set_hhmmss (pc, $1.value, 0, 0, 0);
696 pc->meridian = MER24;
698 | tUNUMBER ':' tUNUMBER o_zone_offset
700 set_hhmmss (pc, $1.value, $3.value, 0, 0);
701 pc->meridian = MER24;
703 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_zone_offset
705 set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
706 pc->meridian = MER24;
710 o_zone_offset:
711 /* empty */
712 | zone_offset
715 zone_offset:
716 tSNUMBER o_colon_minutes
718 pc->zones_seen++;
719 if (! time_zone_hhmm (pc, $1, $2)) YYABORT;
723 /* Local zone strings affect only the DST setting, and take effect
724 only if the current TZ setting is relevant.
726 Example 1:
727 'EEST' is parsed as tLOCAL_ZONE, as it relates to the effective TZ:
728 TZ='Europe/Helsinki' date -d '2016-06-30 EEST'
730 Example 2:
731 'EEST' is parsed as tDAYZONE:
732 TZ='Asia/Tokyo' date -d '2016-06-30 EEST'
734 This is implemented by probing the next three calendar quarters
735 of the effective timezone and looking for DST changes -
736 if found, the timezone name (EEST) is inserted into
737 the lexical lookup table with type tLOCAL_ZONE.
738 (Search for 'quarter' comment in 'parse_datetime2'.)
740 local_zone:
741 tLOCAL_ZONE
742 { pc->local_isdst = $1; }
743 | tLOCAL_ZONE tDST
745 pc->local_isdst = 1;
746 pc->dsts_seen++;
750 /* Note 'T' is a special case, as it is used as the separator in ISO
751 8601 date and time of day representation. */
752 zone:
753 tZONE
754 { pc->time_zone = $1; }
755 | 'T'
756 { pc->time_zone = -HOUR (7); }
757 | tZONE relunit_snumber
758 { pc->time_zone = $1;
759 if (! apply_relative_time (pc, $2, 1)) YYABORT;
760 debug_print_relative_time (_("relative"), pc);
762 | 'T' relunit_snumber
763 { pc->time_zone = -HOUR (7);
764 if (! apply_relative_time (pc, $2, 1)) YYABORT;
765 debug_print_relative_time (_("relative"), pc);
767 | tZONE tSNUMBER o_colon_minutes
768 { if (! time_zone_hhmm (pc, $2, $3)) YYABORT;
769 if (ckd_add (&pc->time_zone, pc->time_zone, $1)) YYABORT; }
770 | tDAYZONE
771 { pc->time_zone = $1 + 60 * 60; }
772 | tZONE tDST
773 { pc->time_zone = $1 + 60 * 60; }
776 day:
777 tDAY
779 pc->day_ordinal = 0;
780 pc->day_number = $1;
782 | tDAY ','
784 pc->day_ordinal = 0;
785 pc->day_number = $1;
787 | tORDINAL tDAY
789 pc->day_ordinal = $1;
790 pc->day_number = $2;
791 pc->debug_ordinal_day_seen = true;
793 | tUNUMBER tDAY
795 pc->day_ordinal = $1.value;
796 pc->day_number = $2;
797 pc->debug_ordinal_day_seen = true;
801 date:
802 tUNUMBER '/' tUNUMBER
804 pc->month = $1.value;
805 pc->day = $3.value;
807 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
809 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
810 otherwise as MM/DD/YY.
811 The goal in recognizing YYYY/MM/DD is solely to support legacy
812 machine-generated dates like those in an RCS log listing. If
813 you want portability, use the ISO 8601 format. */
814 if (4 <= $1.digits)
816 if (debugging (pc))
818 intmax_t digits = $1.digits;
819 dbg_printf (_("warning: value %"PRIdMAX" has %"PRIdMAX" digits. "
820 "Assuming YYYY/MM/DD\n"),
821 $1.value, digits);
824 pc->year = $1;
825 pc->month = $3.value;
826 pc->day = $5.value;
828 else
830 if (debugging (pc))
831 dbg_printf (_("warning: value %"PRIdMAX" has less than 4 digits. "
832 "Assuming MM/DD/YY[YY]\n"),
833 $1.value);
835 pc->month = $1.value;
836 pc->day = $3.value;
837 pc->year = $5;
840 | tUNUMBER tMONTH tSNUMBER
842 /* E.g., 17-JUN-1992. */
843 pc->day = $1.value;
844 pc->month = $2;
845 if (ckd_sub (&pc->year.value, 0, $3.value)) YYABORT;
846 pc->year.digits = $3.digits;
848 | tMONTH tSNUMBER tSNUMBER
850 /* E.g., JUN-17-1992. */
851 pc->month = $1;
852 if (ckd_sub (&pc->day, 0, $2.value)) YYABORT;
853 if (ckd_sub (&pc->year.value, 0, $3.value)) YYABORT;
854 pc->year.digits = $3.digits;
856 | tMONTH tUNUMBER
858 pc->month = $1;
859 pc->day = $2.value;
861 | tMONTH tUNUMBER ',' tUNUMBER
863 pc->month = $1;
864 pc->day = $2.value;
865 pc->year = $4;
867 | tUNUMBER tMONTH
869 pc->day = $1.value;
870 pc->month = $2;
872 | tUNUMBER tMONTH tUNUMBER
874 pc->day = $1.value;
875 pc->month = $2;
876 pc->year = $3;
878 | iso_8601_date
881 iso_8601_date:
882 tUNUMBER tSNUMBER tSNUMBER
884 /* ISO 8601 format. YYYY-MM-DD. */
885 pc->year = $1;
886 if (ckd_sub (&pc->month, 0, $2.value)) YYABORT;
887 if (ckd_sub (&pc->day, 0, $3.value)) YYABORT;
891 rel:
892 relunit tAGO
893 { if (! apply_relative_time (pc, $1, $2)) YYABORT; }
894 | relunit
895 { if (! apply_relative_time (pc, $1, 1)) YYABORT; }
896 | dayshift
897 { if (! apply_relative_time (pc, $1, 1)) YYABORT; }
900 relunit:
901 tORDINAL tYEAR_UNIT
902 { $$ = RELATIVE_TIME_0; $$.year = $1; }
903 | tUNUMBER tYEAR_UNIT
904 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
905 | tYEAR_UNIT
906 { $$ = RELATIVE_TIME_0; $$.year = 1; }
907 | tORDINAL tMONTH_UNIT
908 { $$ = RELATIVE_TIME_0; $$.month = $1; }
909 | tUNUMBER tMONTH_UNIT
910 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
911 | tMONTH_UNIT
912 { $$ = RELATIVE_TIME_0; $$.month = 1; }
913 | tORDINAL tDAY_UNIT
914 { $$ = RELATIVE_TIME_0;
915 if (ckd_mul (&$$.day, $1, $2)) YYABORT; }
916 | tUNUMBER tDAY_UNIT
917 { $$ = RELATIVE_TIME_0;
918 if (ckd_mul (&$$.day, $1.value, $2)) YYABORT; }
919 | tDAY_UNIT
920 { $$ = RELATIVE_TIME_0; $$.day = $1; }
921 | tORDINAL tHOUR_UNIT
922 { $$ = RELATIVE_TIME_0; $$.hour = $1; }
923 | tUNUMBER tHOUR_UNIT
924 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
925 | tHOUR_UNIT
926 { $$ = RELATIVE_TIME_0; $$.hour = 1; }
927 | tORDINAL tMINUTE_UNIT
928 { $$ = RELATIVE_TIME_0; $$.minutes = $1; }
929 | tUNUMBER tMINUTE_UNIT
930 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
931 | tMINUTE_UNIT
932 { $$ = RELATIVE_TIME_0; $$.minutes = 1; }
933 | tORDINAL tSEC_UNIT
934 { $$ = RELATIVE_TIME_0; $$.seconds = $1; }
935 | tUNUMBER tSEC_UNIT
936 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
937 | tSDECIMAL_NUMBER tSEC_UNIT
938 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
939 | tUDECIMAL_NUMBER tSEC_UNIT
940 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
941 | tSEC_UNIT
942 { $$ = RELATIVE_TIME_0; $$.seconds = 1; }
943 | relunit_snumber
946 relunit_snumber:
947 tSNUMBER tYEAR_UNIT
948 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
949 | tSNUMBER tMONTH_UNIT
950 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
951 | tSNUMBER tDAY_UNIT
952 { $$ = RELATIVE_TIME_0;
953 if (ckd_mul (&$$.day, $1.value, $2)) YYABORT; }
954 | tSNUMBER tHOUR_UNIT
955 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
956 | tSNUMBER tMINUTE_UNIT
957 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
958 | tSNUMBER tSEC_UNIT
959 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
962 dayshift:
963 tDAY_SHIFT
964 { $$ = RELATIVE_TIME_0; $$.day = $1; }
967 seconds: signed_seconds | unsigned_seconds;
969 signed_seconds:
970 tSDECIMAL_NUMBER
971 | tSNUMBER
972 { if (time_overflow ($1.value)) YYABORT;
973 $$ = (struct timespec) { .tv_sec = $1.value }; }
976 unsigned_seconds:
977 tUDECIMAL_NUMBER
978 | tUNUMBER
979 { if (time_overflow ($1.value)) YYABORT;
980 $$ = (struct timespec) { .tv_sec = $1.value }; }
983 number:
984 tUNUMBER
985 { digits_to_date_time (pc, $1); }
988 hybrid:
989 tUNUMBER relunit_snumber
991 /* Hybrid all-digit and relative offset, so that we accept e.g.,
992 "YYYYMMDD +N days" as well as "YYYYMMDD N days". */
993 digits_to_date_time (pc, $1);
994 if (! apply_relative_time (pc, $2, 1)) YYABORT;
998 o_colon_minutes:
999 /* empty */
1000 { $$ = -1; }
1001 | ':' tUNUMBER
1002 { $$ = $2.value; }
1007 static table const meridian_table[] =
1009 { "AM", tMERIDIAN, MERam },
1010 { "A.M.", tMERIDIAN, MERam },
1011 { "PM", tMERIDIAN, MERpm },
1012 { "P.M.", tMERIDIAN, MERpm },
1013 { NULL, 0, 0 }
1016 static table const dst_table[] =
1018 { "DST", tDST, 0 }
1021 static table const month_and_day_table[] =
1023 { "JANUARY", tMONTH, 1 },
1024 { "FEBRUARY", tMONTH, 2 },
1025 { "MARCH", tMONTH, 3 },
1026 { "APRIL", tMONTH, 4 },
1027 { "MAY", tMONTH, 5 },
1028 { "JUNE", tMONTH, 6 },
1029 { "JULY", tMONTH, 7 },
1030 { "AUGUST", tMONTH, 8 },
1031 { "SEPTEMBER",tMONTH, 9 },
1032 { "SEPT", tMONTH, 9 },
1033 { "OCTOBER", tMONTH, 10 },
1034 { "NOVEMBER", tMONTH, 11 },
1035 { "DECEMBER", tMONTH, 12 },
1036 { "SUNDAY", tDAY, 0 },
1037 { "MONDAY", tDAY, 1 },
1038 { "TUESDAY", tDAY, 2 },
1039 { "TUES", tDAY, 2 },
1040 { "WEDNESDAY",tDAY, 3 },
1041 { "WEDNES", tDAY, 3 },
1042 { "THURSDAY", tDAY, 4 },
1043 { "THUR", tDAY, 4 },
1044 { "THURS", tDAY, 4 },
1045 { "FRIDAY", tDAY, 5 },
1046 { "SATURDAY", tDAY, 6 },
1047 { NULL, 0, 0 }
1050 static table const time_units_table[] =
1052 { "YEAR", tYEAR_UNIT, 1 },
1053 { "MONTH", tMONTH_UNIT, 1 },
1054 { "FORTNIGHT",tDAY_UNIT, 14 },
1055 { "WEEK", tDAY_UNIT, 7 },
1056 { "DAY", tDAY_UNIT, 1 },
1057 { "HOUR", tHOUR_UNIT, 1 },
1058 { "MINUTE", tMINUTE_UNIT, 1 },
1059 { "MIN", tMINUTE_UNIT, 1 },
1060 { "SECOND", tSEC_UNIT, 1 },
1061 { "SEC", tSEC_UNIT, 1 },
1062 { NULL, 0, 0 }
1065 /* Assorted relative-time words. */
1066 static table const relative_time_table[] =
1068 { "TOMORROW", tDAY_SHIFT, 1 },
1069 { "YESTERDAY",tDAY_SHIFT, -1 },
1070 { "TODAY", tDAY_SHIFT, 0 },
1071 { "NOW", tDAY_SHIFT, 0 },
1072 { "LAST", tORDINAL, -1 },
1073 { "THIS", tORDINAL, 0 },
1074 { "NEXT", tORDINAL, 1 },
1075 { "FIRST", tORDINAL, 1 },
1076 /*{ "SECOND", tORDINAL, 2 }, */
1077 { "THIRD", tORDINAL, 3 },
1078 { "FOURTH", tORDINAL, 4 },
1079 { "FIFTH", tORDINAL, 5 },
1080 { "SIXTH", tORDINAL, 6 },
1081 { "SEVENTH", tORDINAL, 7 },
1082 { "EIGHTH", tORDINAL, 8 },
1083 { "NINTH", tORDINAL, 9 },
1084 { "TENTH", tORDINAL, 10 },
1085 { "ELEVENTH", tORDINAL, 11 },
1086 { "TWELFTH", tORDINAL, 12 },
1087 { "AGO", tAGO, -1 },
1088 { "HENCE", tAGO, 1 },
1089 { NULL, 0, 0 }
1092 /* The universal time zone table. These labels can be used even for
1093 timestamps that would not otherwise be valid, e.g., GMT timestamps
1094 oin London during summer. */
1095 static table const universal_time_zone_table[] =
1097 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
1098 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
1099 { "UTC", tZONE, HOUR ( 0) },
1100 { NULL, 0, 0 }
1103 /* The time zone table. This table is necessarily incomplete, as time
1104 zone abbreviations are ambiguous; e.g., Australians interpret "EST"
1105 as Eastern time in Australia, not as US Eastern Standard Time.
1106 You cannot rely on parse_datetime to handle arbitrary time zone
1107 abbreviations; use numeric abbreviations like "-0500" instead. */
1108 static table const time_zone_table[] =
1110 { "WET", tZONE, HOUR ( 0) }, /* Western European */
1111 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
1112 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
1113 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
1114 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
1115 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
1116 { "NST", tZONE, -(HOUR ( 3) + 30 * 60) }, /* Newfoundland Standard */
1117 { "NDT", tDAYZONE,-(HOUR ( 3) + 30 * 60) }, /* Newfoundland Daylight */
1118 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
1119 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
1120 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
1121 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
1122 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
1123 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
1124 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
1125 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
1126 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
1127 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
1128 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
1129 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
1130 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
1131 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
1132 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
1133 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
1134 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
1135 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
1136 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
1137 { "CET", tZONE, HOUR ( 1) }, /* Central European */
1138 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
1139 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
1140 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
1141 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
1142 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
1143 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
1144 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
1145 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
1146 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
1147 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
1148 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
1149 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
1150 { "IST", tZONE, (HOUR ( 5) + 30 * 60) }, /* India Standard */
1151 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
1152 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
1153 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
1154 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
1155 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
1156 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
1157 { NULL, 0, 0 }
1160 /* Military time zone table.
1162 RFC 822 got these backwards, but RFC 5322 makes the incorrect
1163 treatment optional, so do them the right way here.
1165 'J' is special, as it is local time.
1166 'T' is also special, as it is the separator in ISO
1167 8601 date and time of day representation. */
1168 static table const military_table[] =
1170 { "A", tZONE, HOUR ( 1) },
1171 { "B", tZONE, HOUR ( 2) },
1172 { "C", tZONE, HOUR ( 3) },
1173 { "D", tZONE, HOUR ( 4) },
1174 { "E", tZONE, HOUR ( 5) },
1175 { "F", tZONE, HOUR ( 6) },
1176 { "G", tZONE, HOUR ( 7) },
1177 { "H", tZONE, HOUR ( 8) },
1178 { "I", tZONE, HOUR ( 9) },
1179 { "J", 'J', 0 },
1180 { "K", tZONE, HOUR (10) },
1181 { "L", tZONE, HOUR (11) },
1182 { "M", tZONE, HOUR (12) },
1183 { "N", tZONE, -HOUR ( 1) },
1184 { "O", tZONE, -HOUR ( 2) },
1185 { "P", tZONE, -HOUR ( 3) },
1186 { "Q", tZONE, -HOUR ( 4) },
1187 { "R", tZONE, -HOUR ( 5) },
1188 { "S", tZONE, -HOUR ( 6) },
1189 { "T", 'T', 0 },
1190 { "U", tZONE, -HOUR ( 8) },
1191 { "V", tZONE, -HOUR ( 9) },
1192 { "W", tZONE, -HOUR (10) },
1193 { "X", tZONE, -HOUR (11) },
1194 { "Y", tZONE, -HOUR (12) },
1195 { "Z", tZONE, HOUR ( 0) },
1196 { NULL, 0, 0 }
1201 /* Convert a time zone expressed as HH:MM into an integer count of
1202 seconds. If MM is negative, then S is of the form HHMM and needs
1203 to be picked apart; otherwise, S is of the form HH. As specified in
1204 https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03, allow
1205 only valid TZ range, and consider first two digits as hours, if no
1206 minutes specified. Return true if successful. */
1208 static bool
1209 time_zone_hhmm (parser_control *pc, textint s, intmax_t mm)
1211 intmax_t n_minutes;
1212 bool overflow = false;
1214 /* If the length of S is 1 or 2 and no minutes are specified,
1215 interpret it as a number of hours. */
1216 if (s.digits <= 2 && mm < 0)
1217 s.value *= 100;
1219 if (mm < 0)
1220 n_minutes = (s.value / 100) * 60 + s.value % 100;
1221 else
1223 overflow |= ckd_mul (&n_minutes, s.value, 60);
1224 overflow |= (s.negative
1225 ? ckd_sub (&n_minutes, n_minutes, mm)
1226 : ckd_add (&n_minutes, n_minutes, mm));
1229 if (overflow || ! (-24 * 60 <= n_minutes && n_minutes <= 24 * 60))
1230 return false;
1231 pc->time_zone = n_minutes * 60;
1232 return true;
1235 static int
1236 to_hour (intmax_t hours, int meridian)
1238 switch (meridian)
1240 default: /* Pacify GCC. */
1241 case MER24:
1242 return 0 <= hours && hours < 24 ? hours : -1;
1243 case MERam:
1244 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
1245 case MERpm:
1246 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
1250 enum { TM_YEAR_BASE = 1900 };
1251 enum { TM_YEAR_BUFSIZE = INT_BUFSIZE_BOUND (int) + 1 };
1253 /* Convert TM_YEAR, a year minus 1900, to a string that is numerically
1254 correct even if subtracting 1900 would overflow. */
1256 static char const *
1257 tm_year_str (int tm_year, char buf[TM_YEAR_BUFSIZE])
1259 static_assert (TM_YEAR_BASE % 100 == 0);
1260 sprintf (buf, &"-%02d%02d"[-TM_YEAR_BASE <= tm_year],
1261 abs (tm_year / 100 + TM_YEAR_BASE / 100),
1262 abs (tm_year % 100));
1263 return buf;
1266 /* Convert a text year number to a year minus 1900, working correctly
1267 even if the input is in the range INT_MAX .. INT_MAX + 1900 - 1. */
1269 static bool
1270 to_tm_year (textint textyear, bool debug, int *tm_year)
1272 intmax_t year = textyear.value;
1274 /* XPG4 suggests that years 00-68 map to 2000-2068, and
1275 years 69-99 map to 1969-1999. */
1276 if (0 <= year && textyear.digits == 2)
1278 year += year < 69 ? 2000 : 1900;
1279 if (debug)
1280 dbg_printf (_("warning: adjusting year value %"PRIdMAX
1281 " to %"PRIdMAX"\n"),
1282 textyear.value, year);
1285 if (year < 0
1286 ? ckd_sub (tm_year, -TM_YEAR_BASE, year)
1287 : ckd_sub (tm_year, year, TM_YEAR_BASE))
1289 if (debug)
1290 dbg_printf (_("error: out-of-range year %"PRIdMAX"\n"), year);
1291 return false;
1294 return true;
1297 static table const * _GL_ATTRIBUTE_PURE
1298 lookup_zone (parser_control const *pc, char const *name)
1300 table const *tp;
1302 for (tp = universal_time_zone_table; tp->name; tp++)
1303 if (strcmp (name, tp->name) == 0)
1304 return tp;
1306 /* Try local zone abbreviations before those in time_zone_table, as
1307 the local ones are more likely to be right. */
1308 for (tp = pc->local_time_zone_table; tp->name; tp++)
1309 if (strcmp (name, tp->name) == 0)
1310 return tp;
1312 for (tp = time_zone_table; tp->name; tp++)
1313 if (strcmp (name, tp->name) == 0)
1314 return tp;
1316 return NULL;
1319 #if ! HAVE_STRUCT_TM_TM_GMTOFF
1320 /* Yield the difference between *A and *B,
1321 measured in seconds, ignoring leap seconds.
1322 The body of this function is taken directly from the GNU C Library;
1323 see strftime.c. */
1324 static int
1325 tm_diff (const struct tm *a, const struct tm *b)
1327 /* Compute intervening leap days correctly even if year is negative.
1328 Take care to avoid int overflow in leap day calculations,
1329 but it's OK to assume that A and B are close to each other. */
1330 int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
1331 int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
1332 int a100 = a4 / 25 - (a4 % 25 < 0);
1333 int b100 = b4 / 25 - (b4 % 25 < 0);
1334 int a400 = SHR (a100, 2);
1335 int b400 = SHR (b100, 2);
1336 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
1337 int years = a->tm_year - b->tm_year;
1338 int days = (365 * years + intervening_leap_days
1339 + (a->tm_yday - b->tm_yday));
1340 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
1341 + (a->tm_min - b->tm_min))
1342 + (a->tm_sec - b->tm_sec));
1344 #endif
1346 static table const *
1347 lookup_word (parser_control const *pc, char *word)
1349 char *p;
1350 char *q;
1351 idx_t wordlen;
1352 table const *tp;
1353 bool period_found;
1354 bool abbrev;
1356 /* Make it uppercase. */
1357 for (p = word; *p; p++)
1358 *p = c_toupper (to_uchar (*p));
1360 for (tp = meridian_table; tp->name; tp++)
1361 if (strcmp (word, tp->name) == 0)
1362 return tp;
1364 /* See if we have an abbreviation for a month. */
1365 wordlen = strlen (word);
1366 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
1368 for (tp = month_and_day_table; tp->name; tp++)
1369 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
1370 return tp;
1372 if ((tp = lookup_zone (pc, word)))
1373 return tp;
1375 if (strcmp (word, dst_table[0].name) == 0)
1376 return dst_table;
1378 for (tp = time_units_table; tp->name; tp++)
1379 if (strcmp (word, tp->name) == 0)
1380 return tp;
1382 /* Strip off any plural and try the units table again. */
1383 if (word[wordlen - 1] == 'S')
1385 word[wordlen - 1] = '\0';
1386 for (tp = time_units_table; tp->name; tp++)
1387 if (strcmp (word, tp->name) == 0)
1388 return tp;
1389 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
1392 for (tp = relative_time_table; tp->name; tp++)
1393 if (strcmp (word, tp->name) == 0)
1394 return tp;
1396 /* Military time zones. */
1397 if (wordlen == 1)
1398 for (tp = military_table; tp->name; tp++)
1399 if (word[0] == tp->name[0])
1400 return tp;
1402 /* Drop out any periods and try the time zone table again. */
1403 for (period_found = false, p = q = word; (*p = *q); q++)
1404 if (*q == '.')
1405 period_found = true;
1406 else
1407 p++;
1408 if (period_found && (tp = lookup_zone (pc, word)))
1409 return tp;
1411 return NULL;
1414 static int
1415 yylex (union YYSTYPE *lvalp, parser_control *pc)
1417 unsigned char c;
1419 for (;;)
1421 while (c = *pc->input, c_isspace (c))
1422 pc->input++;
1424 if (c_isdigit (c) || c == '-' || c == '+')
1426 char const *p = pc->input;
1427 int sign;
1428 if (c == '-' || c == '+')
1430 sign = c == '-' ? -1 : 1;
1431 while (c = *(pc->input = ++p), c_isspace (c))
1432 continue;
1433 if (! c_isdigit (c))
1434 /* skip the '-' sign */
1435 continue;
1437 else
1438 sign = 0;
1440 time_t value = 0;
1443 if (ckd_mul (&value, value, 10))
1444 return '?';
1445 if (ckd_add (&value, value, sign < 0 ? '0' - c : c - '0'))
1446 return '?';
1447 c = *++p;
1449 while (c_isdigit (c));
1451 if ((c == '.' || c == ',') && c_isdigit (p[1]))
1453 time_t s = value;
1454 int digits;
1456 /* Accumulate fraction, to ns precision. */
1457 p++;
1458 int ns = *p++ - '0';
1459 for (digits = 2; digits <= LOG10_BILLION; digits++)
1461 ns *= 10;
1462 if (c_isdigit (*p))
1463 ns += *p++ - '0';
1466 /* Skip excess digits, truncating toward -Infinity. */
1467 if (sign < 0)
1468 for (; c_isdigit (*p); p++)
1469 if (*p != '0')
1471 ns++;
1472 break;
1474 while (c_isdigit (*p))
1475 p++;
1477 /* Adjust to the timespec convention, which is that
1478 tv_nsec is always a positive offset even if tv_sec is
1479 negative. */
1480 if (sign < 0 && ns)
1482 if (ckd_sub (&s, s, 1))
1483 return '?';
1484 ns = BILLION - ns;
1487 lvalp->timespec = (struct timespec) { .tv_sec = s,
1488 .tv_nsec = ns };
1489 pc->input = p;
1490 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
1492 else
1494 lvalp->textintval.negative = sign < 0;
1495 lvalp->textintval.value = value;
1496 lvalp->textintval.digits = p - pc->input;
1497 pc->input = p;
1498 return sign ? tSNUMBER : tUNUMBER;
1502 if (c_isalpha (c))
1504 char buff[20];
1505 char *p = buff;
1506 table const *tp;
1510 if (p < buff + sizeof buff - 1)
1511 *p++ = c;
1512 c = *++pc->input;
1514 while (c_isalpha (c) || c == '.');
1516 *p = '\0';
1517 tp = lookup_word (pc, buff);
1518 if (! tp)
1520 if (debugging (pc))
1521 dbg_printf (_("error: unknown word '%s'\n"), buff);
1522 return '?';
1524 lvalp->intval = tp->value;
1525 return tp->type;
1528 if (c != '(')
1529 return to_uchar (*pc->input++);
1531 idx_t count = 0;
1534 c = *pc->input++;
1535 if (c == '\0')
1536 return c;
1537 if (c == '(')
1538 count++;
1539 else if (c == ')')
1540 count--;
1542 while (count != 0);
1546 /* Do nothing if the parser reports an error. */
1547 static void
1548 yyerror (_GL_UNUSED parser_control const *pc,
1549 _GL_UNUSED char const *s)
1553 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1554 passing it to mktime_z, return true if it's OK. It's not OK if
1555 mktime failed or if *TM0 has out-of-range mainline members.
1556 The caller should set TM1->tm_wday to -1 before calling mktime,
1557 as a negative tm_wday is how mktime failure is inferred. */
1559 static bool
1560 mktime_ok (struct tm const *tm0, struct tm const *tm1)
1562 if (tm1->tm_wday < 0)
1563 return false;
1565 return ! ((tm0->tm_sec ^ tm1->tm_sec)
1566 | (tm0->tm_min ^ tm1->tm_min)
1567 | (tm0->tm_hour ^ tm1->tm_hour)
1568 | (tm0->tm_mday ^ tm1->tm_mday)
1569 | (tm0->tm_mon ^ tm1->tm_mon)
1570 | (tm0->tm_year ^ tm1->tm_year));
1573 /* Populate PC's local time zone table with information from TM. */
1575 static void
1576 populate_local_time_zone_table (parser_control *pc, struct tm const *tm)
1578 bool first_entry_exists = !!pc->local_time_zone_table[0].name;
1580 /* The table entry to be filled in. There are only two, so this is
1581 the first entry if it is missing, the second entry otherwise. */
1582 table *e = &pc->local_time_zone_table[first_entry_exists];
1584 e->type = tLOCAL_ZONE;
1585 e->value = tm->tm_isdst;
1587 char const *zone = NULL;
1588 #if HAVE_STRUCT_TM_TM_ZONE
1589 if (tm->tm_zone[0])
1590 zone = tm->tm_zone;
1591 #else
1592 char *tz_abbr = pc->tz_abbr[first_entry_exists];
1593 if (strftime (tz_abbr, TIME_ZONE_BUFSIZE, "%Z", tm))
1594 zone = tz_abbr;
1595 #endif
1596 e->name = zone;
1597 e[1].name = NULL;
1600 /* Debugging: format a 'struct tm' into a buffer, taking the parser's
1601 timezone information into account (if pc != NULL). */
1602 static char const *
1603 debug_strfdatetime (struct tm const *tm, parser_control const *pc,
1604 char *buf, int n)
1606 /* TODO:
1607 1. find an optimal way to print date string in a clear and unambiguous
1608 format. Currently, always add '(Y-M-D)' prefix.
1609 Consider '2016y01m10d' or 'year(2016) month(01) day(10)'.
1611 If the user needs debug printing, it means he/she already having
1612 issues with the parsing - better to avoid formats that could
1613 be mis-interpreted (e.g., just YYYY-MM-DD).
1615 2. Print timezone information ?
1617 3. Print DST information ?
1619 4. Print nanosecond information ?
1621 NOTE:
1622 Printed date/time values might not be valid, e.g., '2016-02-31'
1623 or '2016-19-2016' . These are the values as parsed from the user
1624 string, before validation.
1626 int m = strftime (buf, n, "(Y-M-D) %Y-%m-%d %H:%M:%S", tm);
1628 /* If parser_control information was provided (for timezone),
1629 and there's enough space in the buffer, add timezone info. */
1630 if (pc && m < n && pc->zones_seen)
1632 int tz = pc->time_zone;
1634 /* Account for DST if tLOCAL_ZONE was seen. */
1635 if (pc->local_zones_seen && !pc->zones_seen && 0 < pc->local_isdst)
1636 tz += 60 * 60;
1638 char time_zone_buf[TIME_ZONE_BUFSIZE];
1639 snprintf (&buf[m], n - m, " TZ=%s", time_zone_str (tz, time_zone_buf));
1641 return buf;
1644 static char const *
1645 debug_strfdate (struct tm const *tm, char *buf, int n)
1647 char tm_year_buf[TM_YEAR_BUFSIZE];
1648 snprintf (buf, n, "(Y-M-D) %s-%02d-%02d",
1649 tm_year_str (tm->tm_year, tm_year_buf),
1650 tm->tm_mon + 1, tm->tm_mday);
1651 return buf;
1654 static char const *
1655 debug_strftime (struct tm const *tm, char *buf, int n)
1657 snprintf (buf, n, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
1658 return buf;
1661 /* If mktime_ok failed, display the failed time values,
1662 and provide possible hints. Example output:
1664 date: error: invalid date/time value:
1665 date: user provided time: '(Y-M-D) 2006-04-02 02:45:00'
1666 date: normalized time: '(Y-M-D) 2006-04-02 03:45:00'
1667 date: __
1668 date: possible reasons:
1669 date: nonexistent due to daylight-saving time;
1670 date: numeric values overflow;
1671 date: missing timezone;
1673 static void
1674 debug_mktime_not_ok (struct tm const *tm0, struct tm const *tm1,
1675 parser_control const *pc, bool time_zone_seen)
1677 /* TODO: handle t == -1 (as in 'mktime_ok'). */
1678 char tmp[DBGBUFSIZE];
1679 int i;
1680 const bool eq_sec = (tm0->tm_sec == tm1->tm_sec);
1681 const bool eq_min = (tm0->tm_min == tm1->tm_min);
1682 const bool eq_hour = (tm0->tm_hour == tm1->tm_hour);
1683 const bool eq_mday = (tm0->tm_mday == tm1->tm_mday);
1684 const bool eq_month = (tm0->tm_mon == tm1->tm_mon);
1685 const bool eq_year = (tm0->tm_year == tm1->tm_year);
1687 const bool dst_shift = eq_sec && eq_min && !eq_hour
1688 && eq_mday && eq_month && eq_year;
1690 if (!debugging (pc))
1691 return;
1693 dbg_printf (_("error: invalid date/time value:\n"));
1694 dbg_printf (_(" user provided time: '%s'\n"),
1695 debug_strfdatetime (tm0, pc, tmp, sizeof tmp));
1696 dbg_printf (_(" normalized time: '%s'\n"),
1697 debug_strfdatetime (tm1, pc, tmp, sizeof tmp));
1698 /* The format must be aligned with debug_strfdatetime and the two
1699 DEBUG statements above. This string is not translated. */
1700 i = snprintf (tmp, sizeof tmp,
1701 " %4s %2s %2s %2s %2s %2s",
1702 eq_year ? "" : "----",
1703 eq_month ? "" : "--",
1704 eq_mday ? "" : "--",
1705 eq_hour ? "" : "--",
1706 eq_min ? "" : "--",
1707 eq_sec ? "" : "--");
1708 /* Trim trailing whitespace. */
1709 if (0 <= i)
1711 if (sizeof tmp - 1 < i)
1712 i = sizeof tmp - 1;
1713 while (0 < i && tmp[i - 1] == ' ')
1714 --i;
1715 tmp[i] = '\0';
1717 dbg_printf ("%s\n", tmp);
1719 dbg_printf (_(" possible reasons:\n"));
1720 if (dst_shift)
1721 dbg_printf (_(" nonexistent due to daylight-saving time;\n"));
1722 if (!eq_mday && !eq_month)
1723 dbg_printf (_(" invalid day/month combination;\n"));
1724 dbg_printf (_(" numeric values overflow;\n"));
1725 dbg_printf (" %s\n", (time_zone_seen ? _("incorrect timezone")
1726 : _("missing timezone")));
1729 /* Parse a date/time string, storing the resulting time value into *RESULT.
1730 The string itself is pointed to by P. Return true if successful.
1731 P can be an incomplete or relative time specification; if so, use
1732 *NOW as the basis for the returned time. Default to timezone
1733 TZDEFAULT, which corresponds to tzalloc (TZSTRING). */
1734 static bool
1735 parse_datetime_body (struct timespec *result, char const *p,
1736 struct timespec const *now, unsigned int flags,
1737 timezone_t tzdefault, char const *tzstring)
1739 struct tm tm;
1740 struct tm tm0;
1741 char time_zone_buf[TIME_ZONE_BUFSIZE];
1742 char dbg_tm[DBGBUFSIZE];
1743 bool ok = false;
1744 char const *input_sentinel = p + strlen (p);
1745 char *tz1alloc = NULL;
1747 /* A reasonable upper bound for the size of ordinary TZ strings.
1748 Use heap allocation if TZ's length exceeds this. */
1749 enum { TZBUFSIZE = 100 };
1750 char tz1buf[TZBUFSIZE];
1752 struct timespec gettime_buffer;
1753 if (! now)
1755 gettime (&gettime_buffer);
1756 now = &gettime_buffer;
1759 time_t Start = now->tv_sec;
1760 int Start_ns = now->tv_nsec;
1762 unsigned char c;
1763 while (c = *p, c_isspace (c))
1764 p++;
1766 timezone_t tz = tzdefault;
1768 /* Store a local copy prior to first "goto". Without this, a prior use
1769 below of RELATIVE_TIME_0 on the RHS might translate to an assignment-
1770 to-temporary, which would trigger a -Wjump-misses-init warning. */
1771 const relative_time rel_time_0 = RELATIVE_TIME_0;
1773 if (strncmp (p, "TZ=\"", 4) == 0)
1775 char const *tzbase = p + 4;
1776 idx_t tzsize = 1;
1777 char const *s;
1779 for (s = tzbase; *s; s++, tzsize++)
1780 if (*s == '\\')
1782 s++;
1783 if (! (*s == '\\' || *s == '"'))
1784 break;
1786 else if (*s == '"')
1788 timezone_t tz1;
1789 char *tz1string = tz1buf;
1790 char *z;
1791 if (TZBUFSIZE < tzsize)
1793 tz1alloc = malloc (tzsize);
1794 if (!tz1alloc)
1795 goto fail;
1796 tz1string = tz1alloc;
1798 z = tz1string;
1799 for (s = tzbase; *s != '"'; s++)
1800 *z++ = *(s += *s == '\\');
1801 *z = '\0';
1802 tz1 = tzalloc (tz1string);
1803 if (!tz1)
1804 goto fail;
1805 tz = tz1;
1806 tzstring = tz1string;
1808 p = s + 1;
1809 while (c = *p, c_isspace (c))
1810 p++;
1812 break;
1816 struct tm tmp;
1817 if (! localtime_rz (tz, &now->tv_sec, &tmp))
1818 goto fail;
1820 /* As documented, be careful to treat the empty string just like
1821 a date string of "0". Without this, an empty string would be
1822 declared invalid when parsed during a DST transition. */
1823 if (*p == '\0')
1824 p = "0";
1826 parser_control pc;
1827 pc.input = p;
1828 #ifdef GNULIB_PARSE_DATETIME2
1829 pc.parse_datetime_debug = (flags & PARSE_DATETIME_DEBUG) != 0;
1830 #endif
1831 if (ckd_add (&pc.year.value, tmp.tm_year, TM_YEAR_BASE))
1833 if (debugging (&pc))
1834 dbg_printf (_("error: initial year out of range\n"));
1835 goto fail;
1837 pc.year.digits = 0;
1838 pc.month = tmp.tm_mon + 1;
1839 pc.day = tmp.tm_mday;
1840 pc.hour = tmp.tm_hour;
1841 pc.minutes = tmp.tm_min;
1842 pc.seconds = (struct timespec) { .tv_sec = tmp.tm_sec, .tv_nsec = Start_ns };
1843 tm.tm_isdst = tmp.tm_isdst;
1845 pc.meridian = MER24;
1846 pc.rel = rel_time_0;
1847 pc.timespec_seen = false;
1848 pc.rels_seen = false;
1849 pc.dates_seen = 0;
1850 pc.days_seen = 0;
1851 pc.times_seen = 0;
1852 pc.J_zones_seen = 0;
1853 pc.local_zones_seen = 0;
1854 pc.dsts_seen = 0;
1855 pc.zones_seen = 0;
1856 pc.year_seen = false;
1857 pc.debug_dates_seen = false;
1858 pc.debug_days_seen = false;
1859 pc.debug_times_seen = false;
1860 pc.debug_local_zones_seen = false;
1861 pc.debug_zones_seen = false;
1862 pc.debug_year_seen = false;
1863 pc.debug_ordinal_day_seen = false;
1865 pc.local_time_zone_table[0].name = NULL;
1866 populate_local_time_zone_table (&pc, &tmp);
1868 /* Probe the names used in the next three calendar quarters, looking
1869 for a tm_isdst different from the one we already have. */
1870 for (int quarter = 1; quarter <= 3; quarter++)
1872 time_t probe;
1873 if (ckd_add (&probe, Start, quarter * (90 * 24 * 60 * 60)))
1874 break;
1875 struct tm probe_tm;
1876 if (localtime_rz (tz, &probe, &probe_tm)
1877 && (! pc.local_time_zone_table[0].name
1878 || probe_tm.tm_isdst != pc.local_time_zone_table[0].value))
1880 populate_local_time_zone_table (&pc, &probe_tm);
1881 if (pc.local_time_zone_table[1].name)
1883 if (! strcmp (pc.local_time_zone_table[0].name,
1884 pc.local_time_zone_table[1].name))
1886 /* This locale uses the same abbreviation for standard and
1887 daylight times. So if we see that abbreviation, we don't
1888 know whether it's daylight time. */
1889 pc.local_time_zone_table[0].value = -1;
1890 pc.local_time_zone_table[1].name = NULL;
1893 break;
1898 if (yyparse (&pc) != 0)
1900 if (debugging (&pc))
1901 dbg_printf ((input_sentinel <= pc.input
1902 ? _("error: parsing failed\n")
1903 : _("error: parsing failed, stopped at '%s'\n")),
1904 pc.input);
1905 goto fail;
1909 /* Determine effective timezone source. */
1911 if (debugging (&pc))
1913 dbg_printf (_("input timezone: "));
1915 if (pc.timespec_seen)
1916 fprintf (stderr, _("'@timespec' - always UTC"));
1917 else if (pc.zones_seen)
1918 fprintf (stderr, _("parsed date/time string"));
1919 else if (tzstring)
1921 if (tz != tzdefault)
1922 fprintf (stderr, _("TZ=\"%s\" in date string"), tzstring);
1923 else if (STREQ (tzstring, "UTC0"))
1925 /* Special case: 'date -u' sets TZ="UTC0". */
1926 fprintf (stderr, _("TZ=\"UTC0\" environment value or -u"));
1928 else
1929 fprintf (stderr, _("TZ=\"%s\" environment value"), tzstring);
1931 else
1932 fprintf (stderr, _("system default"));
1934 /* Account for DST changes if tLOCAL_ZONE was seen.
1935 local timezone only changes DST and is relative to the
1936 default timezone.*/
1937 if (pc.local_zones_seen && !pc.zones_seen && 0 < pc.local_isdst)
1938 fprintf (stderr, ", dst");
1940 if (pc.zones_seen)
1941 fprintf (stderr, " (%s)", time_zone_str (pc.time_zone, time_zone_buf));
1943 fputc ('\n', stderr);
1946 if (pc.timespec_seen)
1947 *result = pc.seconds;
1948 else
1950 if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
1951 | (pc.J_zones_seen + pc.local_zones_seen + pc.zones_seen)))
1953 if (debugging (&pc))
1955 if (pc.times_seen > 1)
1956 dbg_printf ("error: seen multiple time parts\n");
1957 if (pc.dates_seen > 1)
1958 dbg_printf ("error: seen multiple date parts\n");
1959 if (pc.days_seen > 1)
1960 dbg_printf ("error: seen multiple days parts\n");
1961 if (pc.dsts_seen > 1)
1962 dbg_printf ("error: seen multiple daylight-saving parts\n");
1963 if ((pc.J_zones_seen + pc.local_zones_seen + pc.zones_seen) > 1)
1964 dbg_printf ("error: seen multiple time-zone parts\n");
1966 goto fail;
1969 if (! to_tm_year (pc.year, debugging (&pc), &tm.tm_year)
1970 || ckd_add (&tm.tm_mon, pc.month, -1)
1971 || ckd_add (&tm.tm_mday, pc.day, 0))
1973 if (debugging (&pc))
1974 dbg_printf (_("error: year, month, or day overflow\n"));
1975 goto fail;
1977 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1979 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1980 if (tm.tm_hour < 0)
1982 char const *mrd = (pc.meridian == MERam ? "am"
1983 : pc.meridian == MERpm ?"pm" : "");
1984 if (debugging (&pc))
1985 dbg_printf (_("error: invalid hour %"PRIdMAX"%s\n"),
1986 pc.hour, mrd);
1987 goto fail;
1989 tm.tm_min = pc.minutes;
1990 tm.tm_sec = pc.seconds.tv_sec;
1991 if (debugging (&pc))
1992 dbg_printf ((pc.times_seen
1993 ? _("using specified time as starting value: '%s'\n")
1994 : _("using current time as starting value: '%s'\n")),
1995 debug_strftime (&tm, dbg_tm, sizeof dbg_tm));
1997 else
1999 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
2000 pc.seconds.tv_nsec = 0;
2001 if (debugging (&pc))
2002 dbg_printf ("warning: using midnight as starting time: 00:00:00\n");
2005 /* Let mktime deduce tm_isdst if we have an absolute timestamp. */
2006 if (pc.dates_seen | pc.days_seen | pc.times_seen)
2007 tm.tm_isdst = -1;
2009 /* But if the input explicitly specifies local time with or without
2010 DST, give mktime that information. */
2011 if (pc.local_zones_seen)
2012 tm.tm_isdst = pc.local_isdst;
2014 tm0.tm_sec = tm.tm_sec;
2015 tm0.tm_min = tm.tm_min;
2016 tm0.tm_hour = tm.tm_hour;
2017 tm0.tm_mday = tm.tm_mday;
2018 tm0.tm_mon = tm.tm_mon;
2019 tm0.tm_year = tm.tm_year;
2020 tm0.tm_isdst = tm.tm_isdst;
2021 tm.tm_wday = -1;
2023 Start = mktime_z (tz, &tm);
2025 if (! mktime_ok (&tm0, &tm))
2027 bool repaired = false;
2028 bool time_zone_seen = pc.zones_seen != 0;
2029 if (time_zone_seen)
2031 /* Guard against falsely reporting errors near the time_t
2032 boundaries when parsing times in other time zones. For
2033 example, suppose the input string "1969-12-31 23:00:00 -0100",
2034 the current time zone is 8 hours ahead of UTC, and the min
2035 time_t value is 1970-01-01 00:00:00 UTC. Then the min
2036 localtime value is 1970-01-01 08:00:00, and mktime will
2037 therefore fail on 1969-12-31 23:00:00. To work around the
2038 problem, set the time zone to 1 hour behind UTC temporarily
2039 by setting TZ="XXX1:00" and try mktime again. */
2041 char tz2buf[sizeof "XXX" - 1 + TIME_ZONE_BUFSIZE];
2042 tz2buf[0] = tz2buf[1] = tz2buf[2] = 'X';
2043 time_zone_str (pc.time_zone, &tz2buf[3]);
2044 timezone_t tz2 = tzalloc (tz2buf);
2045 if (!tz2)
2047 if (debugging (&pc))
2048 dbg_printf (_("error: tzalloc (\"%s\") failed\n"), tz2buf);
2049 goto fail;
2051 tm.tm_sec = tm0.tm_sec;
2052 tm.tm_min = tm0.tm_min;
2053 tm.tm_hour = tm0.tm_hour;
2054 tm.tm_mday = tm0.tm_mday;
2055 tm.tm_mon = tm0.tm_mon;
2056 tm.tm_year = tm0.tm_year;
2057 tm.tm_isdst = tm0.tm_isdst;
2058 tm.tm_wday = -1;
2059 Start = mktime_z (tz2, &tm);
2060 repaired = mktime_ok (&tm0, &tm);
2061 tzfree (tz2);
2064 if (! repaired)
2066 debug_mktime_not_ok (&tm0, &tm, &pc, time_zone_seen);
2067 goto fail;
2071 char dbg_ord[DBGBUFSIZE];
2073 if (pc.days_seen && ! pc.dates_seen)
2075 intmax_t dayincr;
2076 tm.tm_yday = -1;
2077 intmax_t day_ordinal = (pc.day_ordinal
2078 - (0 < pc.day_ordinal
2079 && tm.tm_wday != pc.day_number));
2080 if (! (ckd_mul (&dayincr, day_ordinal, 7)
2081 || ckd_add (&dayincr, (pc.day_number - tm.tm_wday + 7) % 7,
2082 dayincr)
2083 || ckd_add (&tm.tm_mday, dayincr, tm.tm_mday)))
2085 tm.tm_isdst = -1;
2086 Start = mktime_z (tz, &tm);
2089 if (tm.tm_yday < 0)
2091 if (debugging (&pc))
2092 dbg_printf (_("error: day '%s' "
2093 "(day ordinal=%"PRIdMAX" number=%d) "
2094 "resulted in an invalid date: '%s'\n"),
2095 str_days (&pc, dbg_ord, sizeof dbg_ord),
2096 pc.day_ordinal, pc.day_number,
2097 debug_strfdatetime (&tm, &pc, dbg_tm,
2098 sizeof dbg_tm));
2099 goto fail;
2102 if (debugging (&pc))
2103 dbg_printf (_("new start date: '%s' is '%s'\n"),
2104 str_days (&pc, dbg_ord, sizeof dbg_ord),
2105 debug_strfdatetime (&tm, &pc, dbg_tm, sizeof dbg_tm));
2109 if (debugging (&pc))
2111 if (!pc.dates_seen && !pc.days_seen)
2112 dbg_printf (_("using current date as starting value: '%s'\n"),
2113 debug_strfdate (&tm, dbg_tm, sizeof dbg_tm));
2115 if (pc.days_seen && pc.dates_seen)
2116 dbg_printf (_("warning: day (%s) ignored when explicit dates "
2117 "are given\n"),
2118 str_days (&pc, dbg_ord, sizeof dbg_ord));
2120 dbg_printf (_("starting date/time: '%s'\n"),
2121 debug_strfdatetime (&tm, &pc, dbg_tm, sizeof dbg_tm));
2124 /* Add relative date. */
2125 if (pc.rel.year | pc.rel.month | pc.rel.day)
2127 if (debugging (&pc))
2129 if ((pc.rel.year != 0 || pc.rel.month != 0) && tm.tm_mday != 15)
2130 dbg_printf (_("warning: when adding relative months/years, "
2131 "it is recommended to specify the 15th of the "
2132 "months\n"));
2134 if (pc.rel.day != 0 && tm.tm_hour != 12)
2135 dbg_printf (_("warning: when adding relative days, "
2136 "it is recommended to specify noon\n"));
2139 int year, month, day;
2140 if (ckd_add (&year, tm.tm_year, pc.rel.year)
2141 || ckd_add (&month, tm.tm_mon, pc.rel.month)
2142 || ckd_add (&day, tm.tm_mday, pc.rel.day))
2144 if (debugging (&pc))
2145 dbg_printf (_("error: %s:%d\n"), __FILE__, __LINE__);
2146 goto fail;
2148 tm.tm_year = year;
2149 tm.tm_mon = month;
2150 tm.tm_mday = day;
2151 tm.tm_hour = tm0.tm_hour;
2152 tm.tm_min = tm0.tm_min;
2153 tm.tm_sec = tm0.tm_sec;
2154 tm.tm_isdst = tm0.tm_isdst;
2155 tm.tm_wday = -1;
2156 Start = mktime_z (tz, &tm);
2157 if (tm.tm_wday < 0)
2159 if (debugging (&pc))
2160 dbg_printf (_("error: adding relative date resulted "
2161 "in an invalid date: '%s'\n"),
2162 debug_strfdatetime (&tm, &pc, dbg_tm,
2163 sizeof dbg_tm));
2164 goto fail;
2167 if (debugging (&pc))
2169 dbg_printf (_("after date adjustment "
2170 "(%+"PRIdMAX" years, %+"PRIdMAX" months, "
2171 "%+"PRIdMAX" days),\n"),
2172 pc.rel.year, pc.rel.month, pc.rel.day);
2173 dbg_printf (_(" new date/time = '%s'\n"),
2174 debug_strfdatetime (&tm, &pc, dbg_tm,
2175 sizeof dbg_tm));
2177 /* Warn about crossing DST due to time adjustment.
2178 Example: https://bugs.gnu.org/8357
2179 env TZ=Europe/Helsinki \
2180 date --debug \
2181 -d 'Mon Mar 28 00:36:07 2011 EEST 1 day ago'
2183 This case is different than DST changes due to time adjustment,
2184 i.e., "1 day ago" vs "24 hours ago" are calculated in different
2185 places.
2187 'tm0.tm_isdst' contains the DST of the input date,
2188 'tm.tm_isdst' is the normalized result after calling
2189 mktime (&tm).
2191 if (tm0.tm_isdst != -1 && tm.tm_isdst != tm0.tm_isdst)
2192 dbg_printf (_("warning: daylight saving time changed after "
2193 "date adjustment\n"));
2195 /* Warn if the user did not ask to adjust days but mday changed,
2197 user did not ask to adjust months/days but the month changed.
2199 Example for first case:
2200 2016-05-31 + 1 month => 2016-06-31 => 2016-07-01.
2201 User asked to adjust month, but the day changed from 31 to 01.
2203 Example for second case:
2204 2016-02-29 + 1 year => 2017-02-29 => 2017-03-01.
2205 User asked to adjust year, but the month changed from 02 to 03.
2207 if (pc.rel.day == 0
2208 && (tm.tm_mday != day
2209 || (pc.rel.month == 0 && tm.tm_mon != month)))
2211 dbg_printf (_("warning: month/year adjustment resulted in "
2212 "shifted dates:\n"));
2213 char tm_year_buf[TM_YEAR_BUFSIZE];
2214 dbg_printf (_(" adjusted Y M D: %s %02d %02d\n"),
2215 tm_year_str (year, tm_year_buf), month + 1, day);
2216 dbg_printf (_(" normalized Y M D: %s %02d %02d\n"),
2217 tm_year_str (tm.tm_year, tm_year_buf),
2218 tm.tm_mon + 1, tm.tm_mday);
2224 /* The only "output" of this if-block is an updated Start value,
2225 so this block must follow others that clobber Start. */
2226 if (pc.zones_seen)
2228 bool overflow = false;
2229 #ifdef HAVE_STRUCT_TM_TM_GMTOFF
2230 long int utcoff = tm.tm_gmtoff;
2231 #else
2232 time_t t = Start;
2233 struct tm gmt;
2234 int utcoff = (gmtime_r (&t, &gmt)
2235 ? tm_diff (&tm, &gmt)
2236 : (overflow = true, 0));
2237 #endif
2238 intmax_t delta;
2239 overflow |= ckd_sub (&delta, pc.time_zone, utcoff);
2240 time_t t1;
2241 overflow |= ckd_sub (&t1, Start, delta);
2242 if (overflow)
2244 if (debugging (&pc))
2245 dbg_printf (_("error: timezone %d caused time_t overflow\n"),
2246 pc.time_zone);
2247 goto fail;
2249 Start = t1;
2252 if (debugging (&pc))
2254 intmax_t Starti = Start;
2255 dbg_printf (_("'%s' = %"PRIdMAX" epoch-seconds\n"),
2256 debug_strfdatetime (&tm, &pc, dbg_tm, sizeof dbg_tm),
2257 Starti);
2261 /* Add relative hours, minutes, and seconds. On hosts that support
2262 leap seconds, ignore the possibility of leap seconds; e.g.,
2263 "+ 10 minutes" adds 600 seconds, even if one of them is a
2264 leap second. Typically this is not what the user wants, but it's
2265 too hard to do it the other way, because the time zone indicator
2266 must be applied before relative times, and if mktime is applied
2267 again the time zone will be lost. */
2269 intmax_t orig_ns = pc.seconds.tv_nsec;
2270 intmax_t sum_ns = orig_ns + pc.rel.ns;
2271 int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
2272 int d4 = (sum_ns - normalized_ns) / BILLION;
2273 intmax_t d1, t1, d2, t2, t3;
2274 time_t t4;
2275 if (ckd_mul (&d1, pc.rel.hour, 60 * 60)
2276 || ckd_add (&t1, Start, d1)
2277 || ckd_mul (&d2, pc.rel.minutes, 60)
2278 || ckd_add (&t2, t1, d2)
2279 || ckd_add (&t3, t2, pc.rel.seconds)
2280 || ckd_add (&t4, t3, d4))
2282 if (debugging (&pc))
2283 dbg_printf (_("error: adding relative time caused an "
2284 "overflow\n"));
2285 goto fail;
2288 result->tv_sec = t4;
2289 result->tv_nsec = normalized_ns;
2291 if (debugging (&pc)
2292 && (pc.rel.hour | pc.rel.minutes | pc.rel.seconds | pc.rel.ns))
2294 dbg_printf (_("after time adjustment (%+"PRIdMAX" hours, "
2295 "%+"PRIdMAX" minutes, "
2296 "%+"PRIdMAX" seconds, %+d ns),\n"),
2297 pc.rel.hour, pc.rel.minutes, pc.rel.seconds,
2298 pc.rel.ns);
2299 intmax_t t4i = t4;
2300 dbg_printf (_(" new time = %"PRIdMAX" epoch-seconds\n"), t4i);
2302 /* Warn about crossing DST due to time adjustment.
2303 Example: https://bugs.gnu.org/8357
2304 env TZ=Europe/Helsinki \
2305 date --debug \
2306 -d 'Mon Mar 28 00:36:07 2011 EEST 24 hours ago'
2308 This case is different than DST changes due to days adjustment,
2309 i.e., "1 day ago" vs "24 hours ago" are calculated in different
2310 places.
2312 'tm.tm_isdst' contains the date after date adjustment. */
2313 struct tm lmt;
2314 if (tm.tm_isdst != -1 && localtime_rz (tz, &result->tv_sec, &lmt)
2315 && tm.tm_isdst != lmt.tm_isdst)
2316 dbg_printf (_("warning: daylight saving time changed after "
2317 "time adjustment\n"));
2322 if (debugging (&pc))
2324 /* Special case: using 'date -u' simply set TZ=UTC0 */
2325 if (! tzstring)
2326 dbg_printf (_("timezone: system default\n"));
2327 else if (STREQ (tzstring, "UTC0"))
2328 dbg_printf (_("timezone: Universal Time\n"));
2329 else
2330 dbg_printf (_("timezone: TZ=\"%s\" environment value\n"), tzstring);
2332 intmax_t sec = result->tv_sec;
2333 int nsec = result->tv_nsec;
2334 dbg_printf (_("final: %"PRIdMAX".%09d (epoch-seconds)\n"),
2335 sec, nsec);
2337 struct tm gmt, lmt;
2338 bool got_utc = !!gmtime_r (&result->tv_sec, &gmt);
2339 if (got_utc)
2340 dbg_printf (_("final: %s (UTC)\n"),
2341 debug_strfdatetime (&gmt, NULL,
2342 dbg_tm, sizeof dbg_tm));
2343 if (localtime_rz (tz, &result->tv_sec, &lmt))
2345 #ifdef HAVE_STRUCT_TM_TM_GMTOFF
2346 bool got_utcoff = true;
2347 long int utcoff = lmt.tm_gmtoff;
2348 #else
2349 bool got_utcoff = got_utc;
2350 int utcoff;
2351 if (got_utcoff)
2352 utcoff = tm_diff (&lmt, &gmt);
2353 #endif
2354 if (got_utcoff)
2355 dbg_printf (_("final: %s (UTC%s)\n"),
2356 debug_strfdatetime (&lmt, NULL, dbg_tm, sizeof dbg_tm),
2357 time_zone_str (utcoff, time_zone_buf));
2358 else
2359 dbg_printf (_("final: %s (unknown time zone offset)\n"),
2360 debug_strfdatetime (&lmt, NULL, dbg_tm, sizeof dbg_tm));
2364 ok = true;
2366 fail:
2367 if (tz != tzdefault)
2368 tzfree (tz);
2369 free (tz1alloc);
2370 return ok;
2373 #ifdef GNULIB_PARSE_DATETIME2
2374 /* Parse a date/time string, storing the resulting time value into *RESULT.
2375 The string itself is pointed to by P. Return true if successful.
2376 P can be an incomplete or relative time specification; if so, use
2377 *NOW as the basis for the returned time. Default to timezone
2378 TZDEFAULT, which corresponds to tzalloc (TZSTRING). */
2379 bool
2380 parse_datetime2 (struct timespec *result, char const *p,
2381 struct timespec const *now, unsigned int flags,
2382 timezone_t tzdefault, char const *tzstring)
2384 return parse_datetime_body (result, p, now, flags, tzdefault, tzstring);
2386 #endif
2389 /* The plain interface: run with debug=false and the default timezone. */
2390 bool
2391 parse_datetime (struct timespec *result, char const *p,
2392 struct timespec const *now)
2394 char const *tzstring = getenv ("TZ");
2395 timezone_t tz = tzalloc (tzstring);
2396 if (!tz)
2397 return false;
2398 bool ok = parse_datetime_body (result, p, now, 0, tz, tzstring);
2399 tzfree (tz);
2400 return ok;
2403 #if TEST
2406 main (int ac, char **av)
2408 char buff[BUFSIZ];
2410 printf ("Enter date, or blank line to exit.\n\t> ");
2411 fflush (stdout);
2413 buff[BUFSIZ - 1] = '\0';
2414 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
2416 struct timespec d;
2417 struct tm const *tm;
2418 if (! parse_datetime (&d, buff, NULL))
2419 printf ("Bad format - couldn't convert.\n");
2420 else if (! (tm = localtime (&d.tv_sec)))
2422 intmax_t sec = d.tv_sec;
2423 printf ("localtime (%"PRIdMAX") failed\n", sec);
2425 else
2427 int ns = d.tv_nsec;
2428 char tm_year_buf[TM_YEAR_BUFSIZE];
2429 printf ("%s-%02d-%02d %02d:%02d:%02d.%09d\n",
2430 tm_year_str (tm->tm_year, tm_year_buf),
2431 tm->tm_mon + 1, tm->tm_mday,
2432 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);
2434 printf ("\t> ");
2435 fflush (stdout);
2437 return 0;
2439 #endif /* TEST */