1 %{
2 /* Parse a string into an internal timestamp.
3
4 Copyright (C) 1999-2000, 2002-2021 Free Software Foundation, Inc.
5
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.
10
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.
15
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/>. */
18
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.
23
24 Modified by Assaf Gordon <assafgordon@gmail.com> in 2016 to add
25 debug output.
26
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'. */
33
34 #include <config.h>
35
36 #include "parse-datetime.h"
37
38 #include "idx.h"
39 #include "intprops.h"
40 #include "timespec.h"
41 #include "verify.h"
42 #include "strftime.h"
43
44 /* There's no need to extend the stack, so there's no need to involve
45 alloca. */
46 #define YYSTACK_USE_ALLOCA 0
47
48 /* Tell Bison how much stack space is needed. 20 should be plenty for
49 this grammar, which is not right recursive. Beware setting it too
50 high, since that might cause problems on machines whose
51 implementations have lame stack-overflow checking. */
52 #define YYMAXDEPTH 20
53 #define YYINITDEPTH YYMAXDEPTH
54
55 /* Since the code of parse-datetime.y is not included in the Emacs executable
56 itself, there is no need to #define static in this file. Even if
57 the code were included in the Emacs executable, it probably
58 wouldn't do any harm to #undef it here; this will only cause
59 problems if we try to write to a static variable, which I don't
60 think this code needs to do. */
61 #ifdef emacs
62 # undef static
63 #endif
64
65 #include <inttypes.h>
66 #include <c-ctype.h>
67 #include <stdarg.h>
68 #include <stdio.h>
69 #include <stdlib.h>
70 #include <string.h>
71
72 #include "gettext.h"
73
74 #define _(str) gettext (str)
75
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
84
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.
90
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))
99
100 #define HOUR(x) (60 * 60 * (x))
101
102 #define STREQ(a, b) (strcmp (a, b) == 0)
103
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);
110
111 /* True if N is out of range for time_t. */
112 static bool
time_overflow(intmax_t n)113 time_overflow (intmax_t n)
114 {
115 return ! ((TYPE_SIGNED (time_t) ? TYPE_MINIMUM (time_t) <= n : 0 <= n)
116 && n <= TYPE_MAXIMUM (time_t));
117 }
118
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. */
to_uchar(char ch)122 static unsigned char to_uchar (char ch) { return ch; }
123
124 static void _GL_ATTRIBUTE_FORMAT ((__printf__, 1, 2))
dbg_printf(char const * msg,...)125 dbg_printf (char const *msg, ...)
126 {
127 va_list args;
128 /* TODO: use gnulib's 'program_name' instead? */
129 fputs ("date: ", stderr);
130
131 va_start (args, msg);
132 vfprintf (stderr, msg, args);
133 va_end (args);
134 }
135
136
137 /* An integer value, and the number of digits in its textual
138 representation. */
139 typedef struct
140 {
141 bool negative;
142 intmax_t value;
143 idx_t digits;
144 } textint;
145
146 /* An entry in the lexical lookup table. */
147 typedef struct
148 {
149 char const *name;
150 int type;
151 int value;
152 } table;
153
154 /* Meridian: am, pm, or 24-hour style. */
155 enum { MERam, MERpm, MER24 };
156
157 /* A reasonable upper bound for the buffer used in debug output. */
158 enum { DBGBUFSIZE = 100 };
159
160 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
161
162 /* Relative times. */
163 typedef struct
164 {
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;
174
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
180
181 /* Information passed to and from the parser. */
182 typedef struct
183 {
184 /* The input string remaining to be parsed. */
185 const char *input;
186
187 /* N, if this is the Nth Tuesday. */
188 intmax_t day_ordinal;
189
190 /* Day of week; Sunday is 0. */
191 int day_number;
192
193 /* tm_isdst flag for the local zone. */
194 int local_isdst;
195
196 /* Time zone, in seconds east of UT. */
197 int time_zone;
198
199 /* Style used for time. */
200 int meridian;
201
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 */
209
210 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
211 relative_time rel;
212
213 /* Presence or counts of nonterminals of various flavors parsed so far. */
214 bool timespec_seen;
215 bool rels_seen;
216 idx_t dates_seen;
217 idx_t days_seen;
218 idx_t local_zones_seen;
219 idx_t dsts_seen;
220 idx_t times_seen;
221 idx_t zones_seen;
222 bool year_seen;
223
224 #ifdef GNULIB_PARSE_DATETIME2
225 /* Print debugging output to stderr. */
226 bool parse_datetime_debug;
227 #endif
228
229 /* Which of the 'seen' parts have been printed when debugging. */
230 bool debug_dates_seen;
231 bool debug_days_seen;
232 bool debug_local_zones_seen;
233 bool debug_times_seen;
234 bool debug_zones_seen;
235 bool debug_year_seen;
236
237 /* The user specified explicit ordinal day value. */
238 bool debug_ordinal_day_seen;
239
240 /* Table of local time zone abbreviations, terminated by a null entry. */
241 table local_time_zone_table[3];
242 } parser_control;
243
244 static bool
debugging(parser_control const * pc)245 debugging (parser_control const *pc)
246 {
247 #ifdef GNULIB_PARSE_DATETIME2
248 return pc->parse_datetime_debug;
249 #else
250 return false;
251 #endif
252 }
253
254 union YYSTYPE;
255 static int yylex (union YYSTYPE *, parser_control *);
256 static int yyerror (parser_control const *, char const *);
257 static bool time_zone_hhmm (parser_control *, textint, intmax_t);
258
259 /* Extract into *PC any date and time info from a string of digits
260 of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
261 YYYY, ...). */
262 static void
digits_to_date_time(parser_control * pc,textint text_int)263 digits_to_date_time (parser_control *pc, textint text_int)
264 {
265 if (pc->dates_seen && ! pc->year.digits
266 && ! pc->rels_seen && (pc->times_seen || 2 < text_int.digits))
267 {
268 pc->year_seen = true;
269 pc->year = text_int;
270 }
271 else
272 {
273 if (4 < text_int.digits)
274 {
275 pc->dates_seen++;
276 pc->day = text_int.value % 100;
277 pc->month = (text_int.value / 100) % 100;
278 pc->year.value = text_int.value / 10000;
279 pc->year.digits = text_int.digits - 4;
280 }
281 else
282 {
283 pc->times_seen++;
284 if (text_int.digits <= 2)
285 {
286 pc->hour = text_int.value;
287 pc->minutes = 0;
288 }
289 else
290 {
291 pc->hour = text_int.value / 100;
292 pc->minutes = text_int.value % 100;
293 }
294 pc->seconds.tv_sec = 0;
295 pc->seconds.tv_nsec = 0;
296 pc->meridian = MER24;
297 }
298 }
299 }
300
301 /* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1). Return true
302 if successful, false if an overflow occurred. */
303 static bool
apply_relative_time(parser_control * pc,relative_time rel,int factor)304 apply_relative_time (parser_control *pc, relative_time rel, int factor)
305 {
306 if (factor < 0
307 ? (INT_SUBTRACT_WRAPV (pc->rel.ns, rel.ns, &pc->rel.ns)
308 | INT_SUBTRACT_WRAPV (pc->rel.seconds, rel.seconds, &pc->rel.seconds)
309 | INT_SUBTRACT_WRAPV (pc->rel.minutes, rel.minutes, &pc->rel.minutes)
310 | INT_SUBTRACT_WRAPV (pc->rel.hour, rel.hour, &pc->rel.hour)
311 | INT_SUBTRACT_WRAPV (pc->rel.day, rel.day, &pc->rel.day)
312 | INT_SUBTRACT_WRAPV (pc->rel.month, rel.month, &pc->rel.month)
313 | INT_SUBTRACT_WRAPV (pc->rel.year, rel.year, &pc->rel.year))
314 : (INT_ADD_WRAPV (pc->rel.ns, rel.ns, &pc->rel.ns)
315 | INT_ADD_WRAPV (pc->rel.seconds, rel.seconds, &pc->rel.seconds)
316 | INT_ADD_WRAPV (pc->rel.minutes, rel.minutes, &pc->rel.minutes)
317 | INT_ADD_WRAPV (pc->rel.hour, rel.hour, &pc->rel.hour)
318 | INT_ADD_WRAPV (pc->rel.day, rel.day, &pc->rel.day)
319 | INT_ADD_WRAPV (pc->rel.month, rel.month, &pc->rel.month)
320 | INT_ADD_WRAPV (pc->rel.year, rel.year, &pc->rel.year)))
321 return false;
322 pc->rels_seen = true;
323 return true;
324 }
325
326 /* Set PC-> hour, minutes, seconds and nanoseconds members from arguments. */
327 static void
set_hhmmss(parser_control * pc,intmax_t hour,intmax_t minutes,time_t sec,int nsec)328 set_hhmmss (parser_control *pc, intmax_t hour, intmax_t minutes,
329 time_t sec, int nsec)
330 {
331 pc->hour = hour;
332 pc->minutes = minutes;
333 pc->seconds.tv_sec = sec;
334 pc->seconds.tv_nsec = nsec;
335 }
336
337 /* Return a textual representation of the day ordinal/number values
338 in the parser_control struct (e.g., "last wed", "this tues", "thu"). */
339 static const char *
str_days(parser_control * pc,char * buffer,int n)340 str_days (parser_control *pc, char *buffer, int n)
341 {
342 /* TODO: use relative_time_table for reverse lookup. */
343 static char const ordinal_values[][11] = {
344 "last",
345 "this",
346 "next/first",
347 "(SECOND)", /* SECOND is commented out in relative_time_table. */
348 "third",
349 "fourth",
350 "fifth",
351 "sixth",
352 "seventh",
353 "eight",
354 "ninth",
355 "tenth",
356 "eleventh",
357 "twelfth"
358 };
359
360 static char const days_values[][4] = {
361 "Sun",
362 "Mon",
363 "Tue",
364 "Wed",
365 "Thu",
366 "Fri",
367 "Sat"
368 };
369
370 int len;
371
372 /* Don't add an ordinal prefix if the user didn't specify it
373 (e.g., "this wed" vs "wed"). */
374 if (pc->debug_ordinal_day_seen)
375 {
376 /* Use word description if possible (e.g., -1 = last, 3 = third). */
377 len = (-1 <= pc->day_ordinal && pc->day_ordinal <= 12
378 ? snprintf (buffer, n, "%s", ordinal_values[pc->day_ordinal + 1])
379 : snprintf (buffer, n, "%"PRIdMAX, pc->day_ordinal));
380 }
381 else
382 {
383 buffer[0] = '\0';
384 len = 0;
385 }
386
387 /* Add the day name */
388 if (0 <= pc->day_number && pc->day_number <= 6 && 0 <= len && len < n)
389 snprintf (buffer + len, n - len, &" %s"[len == 0],
390 days_values[pc->day_number]);
391 else
392 {
393 /* invalid day_number value - should never happen */
394 }
395 return buffer;
396 }
397
398 /* Convert a time zone to its string representation. */
399
400 enum { TIME_ZONE_BUFSIZE = INT_STRLEN_BOUND (intmax_t) + sizeof ":MM:SS" } ;
401
402 static char const *
time_zone_str(int time_zone,char time_zone_buf[TIME_ZONE_BUFSIZE])403 time_zone_str (int time_zone, char time_zone_buf[TIME_ZONE_BUFSIZE])
404 {
405 char *p = time_zone_buf;
406 char sign = time_zone < 0 ? '-' : '+';
407 int hour = abs (time_zone / (60 * 60));
408 p += sprintf (time_zone_buf, "%c%02d", sign, hour);
409 int offset_from_hour = abs (time_zone % (60 * 60));
410 if (offset_from_hour != 0)
411 {
412 int mm = offset_from_hour / 60;
413 int ss = offset_from_hour % 60;
414 *p++ = ':';
415 *p++ = '0' + mm / 10;
416 *p++ = '0' + mm % 10;
417 if (ss)
418 {
419 *p++ = ':';
420 *p++ = '0' + ss / 10;
421 *p++ = '0' + ss % 10;
422 }
423 *p = '\0';
424 }
425 return time_zone_buf;
426 }
427
428 /* debugging: print the current time in the parser_control structure.
429 The parser will increment "*_seen" members for those which were parsed.
430 This function will print only newly seen parts. */
431 static void
debug_print_current_time(char const * item,parser_control * pc)432 debug_print_current_time (char const *item, parser_control *pc)
433 {
434 bool space = false;
435
436 if (!debugging (pc))
437 return;
438
439 /* no newline, more items printed below */
440 dbg_printf (_("parsed %s part: "), item);
441
442 if (pc->dates_seen && !pc->debug_dates_seen)
443 {
444 /*TODO: use pc->year.negative? */
445 fprintf (stderr, "(Y-M-D) %04"PRIdMAX"-%02"PRIdMAX"-%02"PRIdMAX,
446 pc->year.value, pc->month, pc->day);
447 pc->debug_dates_seen = true;
448 space = true;
449 }
450
451 if (pc->year_seen != pc->debug_year_seen)
452 {
453 if (space)
454 fputc (' ', stderr);
455 fprintf (stderr, _("year: %04"PRIdMAX), pc->year.value);
456
457 pc->debug_year_seen = pc->year_seen;
458 space = true;
459 }
460
461 if (pc->times_seen && !pc->debug_times_seen)
462 {
463 intmax_t sec = pc->seconds.tv_sec;
464 fprintf (stderr, &" %02"PRIdMAX":%02"PRIdMAX":%02"PRIdMAX[!space],
465 pc->hour, pc->minutes, sec);
466 if (pc->seconds.tv_nsec != 0)
467 {
468 int nsec = pc->seconds.tv_nsec;
469 fprintf (stderr, ".%09d", nsec);
470 }
471 if (pc->meridian == MERpm)
472 fputs ("pm", stderr);
473
474 pc->debug_times_seen = true;
475 space = true;
476 }
477
478 if (pc->days_seen && !pc->debug_days_seen)
479 {
480 if (space)
481 fputc (' ', stderr);
482 char tmp[DBGBUFSIZE];
483 fprintf (stderr, _("%s (day ordinal=%"PRIdMAX" number=%d)"),
484 str_days (pc, tmp, sizeof tmp),
485 pc->day_ordinal, pc->day_number);
486 pc->debug_days_seen = true;
487 space = true;
488 }
489
490 /* local zone strings only change the DST settings,
491 not the timezone value. If seen, inform about the DST. */
492 if (pc->local_zones_seen && !pc->debug_local_zones_seen)
493 {
494 fprintf (stderr, &" isdst=%d%s"[!space],
495 pc->local_isdst, pc->dsts_seen ? " DST" : "");
496 pc->debug_local_zones_seen = true;
497 space = true;
498 }
499
500 if (pc->zones_seen && !pc->debug_zones_seen)
501 {
502 char time_zone_buf[TIME_ZONE_BUFSIZE];
503 fprintf (stderr, &" UTC%s"[!space],
504 time_zone_str (pc->time_zone, time_zone_buf));
505 pc->debug_zones_seen = true;
506 space = true;
507 }
508
509 if (pc->timespec_seen)
510 {
511 intmax_t sec = pc->seconds.tv_sec;
512 if (space)
513 fputc (' ', stderr);
514 fprintf (stderr, _("number of seconds: %"PRIdMAX), sec);
515 }
516
517 fputc ('\n', stderr);
518 }
519
520 /* Debugging: print the current relative values. */
521
522 static bool
print_rel_part(bool space,intmax_t val,char const * name)523 print_rel_part (bool space, intmax_t val, char const *name)
524 {
525 if (val == 0)
526 return space;
527 fprintf (stderr, &" %+"PRIdMAX" %s"[!space], val, name);
528 return true;
529 }
530
531 static void
debug_print_relative_time(char const * item,parser_control const * pc)532 debug_print_relative_time (char const *item, parser_control const *pc)
533 {
534 bool space = false;
535
536 if (!debugging (pc))
537 return;
538
539 /* no newline, more items printed below */
540 dbg_printf (_("parsed %s part: "), item);
541
542 if (pc->rel.year == 0 && pc->rel.month == 0 && pc->rel.day == 0
543 && pc->rel.hour == 0 && pc->rel.minutes == 0 && pc->rel.seconds == 0
544 && pc->rel.ns == 0)
545 {
546 /* Special case: relative time of this/today/now */
547 fputs (_("today/this/now\n"), stderr);
548 return;
549 }
550
551 space = print_rel_part (space, pc->rel.year, "year(s)");
552 space = print_rel_part (space, pc->rel.month, "month(s)");
553 space = print_rel_part (space, pc->rel.day, "day(s)");
554 space = print_rel_part (space, pc->rel.hour, "hour(s)");
555 space = print_rel_part (space, pc->rel.minutes, "minutes");
556 space = print_rel_part (space, pc->rel.seconds, "seconds");
557 print_rel_part (space, pc->rel.ns, "nanoseconds");
558
559 fputc ('\n', stderr);
560 }
561
562
563
564 %}
565
566 /* We want a reentrant parser, even if the TZ manipulation and the calls to
567 localtime and gmtime are not reentrant. */
568 %define api.pure
569 %parse-param { parser_control *pc }
570 %lex-param { parser_control *pc }
571
572 /* This grammar has 31 shift/reduce conflicts. */
573 %expect 31
574
575 %union
576 {
577 intmax_t intval;
578 textint textintval;
579 struct timespec timespec;
580 relative_time rel;
581 }
582
583 %token <intval> tAGO
584 %token tDST
585
586 %token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
587 %token <intval> tDAY_UNIT tDAY_SHIFT
588
589 %token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
590 %token <intval> tMONTH tORDINAL tZONE
591
592 %token <textintval> tSNUMBER tUNUMBER
593 %token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
594
595 %type <intval> o_colon_minutes
596 %type <timespec> seconds signed_seconds unsigned_seconds
597
598 %type <rel> relunit relunit_snumber dayshift
599
600 %%
601
602 spec:
603 timespec
604 | items
605 ;
606
607 timespec:
608 '@' seconds
609 {
610 pc->seconds = $2;
611 pc->timespec_seen = true;
612 debug_print_current_time (_("number of seconds"), pc);
613 }
614 ;
615
616 items:
617 /* empty */
618 | items item
619 ;
620
621 item:
622 datetime
623 {
624 pc->times_seen++; pc->dates_seen++;
625 debug_print_current_time (_("datetime"), pc);
626 }
627 | time
628 {
629 pc->times_seen++;
630 debug_print_current_time (_("time"), pc);
631 }
632 | local_zone
633 {
634 pc->local_zones_seen++;
635 debug_print_current_time (_("local_zone"), pc);
636 }
637 | zone
638 {
639 pc->zones_seen++;
640 debug_print_current_time (_("zone"), pc);
641 }
642 | date
643 {
644 pc->dates_seen++;
645 debug_print_current_time (_("date"), pc);
646 }
647 | day
648 {
649 pc->days_seen++;
650 debug_print_current_time (_("day"), pc);
651 }
652 | rel
653 {
654 debug_print_relative_time (_("relative"), pc);
655 }
656 | number
657 {
658 debug_print_current_time (_("number"), pc);
659 }
660 | hybrid
661 {
662 debug_print_relative_time (_("hybrid"), pc);
663 }
664 ;
665
666 datetime:
667 iso_8601_datetime
668 ;
669
670 iso_8601_datetime:
671 iso_8601_date 'T' iso_8601_time
672 ;
673
674 time:
675 tUNUMBER tMERIDIAN
676 {
677 set_hhmmss (pc, $1.value, 0, 0, 0);
678 pc->meridian = $2;
679 }
680 | tUNUMBER ':' tUNUMBER tMERIDIAN
681 {
682 set_hhmmss (pc, $1.value, $3.value, 0, 0);
683 pc->meridian = $4;
684 }
685 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tMERIDIAN
686 {
687 set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
688 pc->meridian = $6;
689 }
690 | iso_8601_time
691 ;
692
693 iso_8601_time:
694 tUNUMBER zone_offset
695 {
696 set_hhmmss (pc, $1.value, 0, 0, 0);
697 pc->meridian = MER24;
698 }
699 | tUNUMBER ':' tUNUMBER o_zone_offset
700 {
701 set_hhmmss (pc, $1.value, $3.value, 0, 0);
702 pc->meridian = MER24;
703 }
704 | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_zone_offset
705 {
706 set_hhmmss (pc, $1.value, $3.value, $5.tv_sec, $5.tv_nsec);
707 pc->meridian = MER24;
708 }
709 ;
710
711 o_zone_offset:
712 /* empty */
713 | zone_offset
714 ;
715
716 zone_offset:
717 tSNUMBER o_colon_minutes
718 {
719 pc->zones_seen++;
720 if (! time_zone_hhmm (pc, $1, $2)) YYABORT;
721 }
722 ;
723
724 /* Local zone strings affect only the DST setting, and take effect
725 only if the current TZ setting is relevant.
726
727 Example 1:
728 'EEST' is parsed as tLOCAL_ZONE, as it relates to the effective TZ:
729 TZ='Europe/Helsinki' date -d '2016-06-30 EEST'
730
731 Example 2:
732 'EEST' is parsed as tDAYZONE:
733 TZ='Asia/Tokyo' date -d '2016-06-30 EEST'
734
735 This is implemented by probing the next three calendar quarters
736 of the effective timezone and looking for DST changes -
737 if found, the timezone name (EEST) is inserted into
738 the lexical lookup table with type tLOCAL_ZONE.
739 (Search for 'quarter' comment in 'parse_datetime2'.)
740 */
741 local_zone:
742 tLOCAL_ZONE
743 { pc->local_isdst = $1; }
744 | tLOCAL_ZONE tDST
745 {
746 pc->local_isdst = 1;
747 pc->dsts_seen++;
748 }
749 ;
750
751 /* Note 'T' is a special case, as it is used as the separator in ISO
752 8601 date and time of day representation. */
753 zone:
754 tZONE
755 { pc->time_zone = $1; }
756 | 'T'
757 { pc->time_zone = -HOUR (7); }
758 | tZONE relunit_snumber
759 { pc->time_zone = $1;
760 if (! apply_relative_time (pc, $2, 1)) YYABORT;
761 debug_print_relative_time (_("relative"), pc);
762 }
763 | 'T' relunit_snumber
764 { pc->time_zone = -HOUR (7);
765 if (! apply_relative_time (pc, $2, 1)) YYABORT;
766 debug_print_relative_time (_("relative"), pc);
767 }
768 | tZONE tSNUMBER o_colon_minutes
769 { if (! time_zone_hhmm (pc, $2, $3)) YYABORT;
770 if (INT_ADD_WRAPV (pc->time_zone, $1, &pc->time_zone)) YYABORT; }
771 | tDAYZONE
772 { pc->time_zone = $1 + 60 * 60; }
773 | tZONE tDST
774 { pc->time_zone = $1 + 60 * 60; }
775 ;
776
777 day:
778 tDAY
779 {
780 pc->day_ordinal = 0;
781 pc->day_number = $1;
782 }
783 | tDAY ','
784 {
785 pc->day_ordinal = 0;
786 pc->day_number = $1;
787 }
788 | tORDINAL tDAY
789 {
790 pc->day_ordinal = $1;
791 pc->day_number = $2;
792 pc->debug_ordinal_day_seen = true;
793 }
794 | tUNUMBER tDAY
795 {
796 pc->day_ordinal = $1.value;
797 pc->day_number = $2;
798 pc->debug_ordinal_day_seen = true;
799 }
800 ;
801
802 date:
803 tUNUMBER '/' tUNUMBER
804 {
805 pc->month = $1.value;
806 pc->day = $3.value;
807 }
808 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
809 {
810 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
811 otherwise as MM/DD/YY.
812 The goal in recognizing YYYY/MM/DD is solely to support legacy
813 machine-generated dates like those in an RCS log listing. If
814 you want portability, use the ISO 8601 format. */
815 if (4 <= $1.digits)
816 {
817 if (debugging (pc))
818 {
819 intmax_t digits = $1.digits;
820 dbg_printf (_("warning: value %"PRIdMAX" has %"PRIdMAX" digits. "
821 "Assuming YYYY/MM/DD\n"),
822 $1.value, digits);
823 }
824
825 pc->year = $1;
826 pc->month = $3.value;
827 pc->day = $5.value;
828 }
829 else
830 {
831 if (debugging (pc))
832 dbg_printf (_("warning: value %"PRIdMAX" has less than 4 digits. "
833 "Assuming MM/DD/YY[YY]\n"),
834 $1.value);
835
836 pc->month = $1.value;
837 pc->day = $3.value;
838 pc->year = $5;
839 }
840 }
841 | tUNUMBER tMONTH tSNUMBER
842 {
843 /* E.g., 17-JUN-1992. */
844 pc->day = $1.value;
845 pc->month = $2;
846 if (INT_SUBTRACT_WRAPV (0, $3.value, &pc->year.value)) YYABORT;
847 pc->year.digits = $3.digits;
848 }
849 | tMONTH tSNUMBER tSNUMBER
850 {
851 /* E.g., JUN-17-1992. */
852 pc->month = $1;
853 if (INT_SUBTRACT_WRAPV (0, $2.value, &pc->day)) YYABORT;
854 if (INT_SUBTRACT_WRAPV (0, $3.value, &pc->year.value)) YYABORT;
855 pc->year.digits = $3.digits;
856 }
857 | tMONTH tUNUMBER
858 {
859 pc->month = $1;
860 pc->day = $2.value;
861 }
862 | tMONTH tUNUMBER ',' tUNUMBER
863 {
864 pc->month = $1;
865 pc->day = $2.value;
866 pc->year = $4;
867 }
868 | tUNUMBER tMONTH
869 {
870 pc->day = $1.value;
871 pc->month = $2;
872 }
873 | tUNUMBER tMONTH tUNUMBER
874 {
875 pc->day = $1.value;
876 pc->month = $2;
877 pc->year = $3;
878 }
879 | iso_8601_date
880 ;
881
882 iso_8601_date:
883 tUNUMBER tSNUMBER tSNUMBER
884 {
885 /* ISO 8601 format. YYYY-MM-DD. */
886 pc->year = $1;
887 if (INT_SUBTRACT_WRAPV (0, $2.value, &pc->month)) YYABORT;
888 if (INT_SUBTRACT_WRAPV (0, $3.value, &pc->day)) YYABORT;
889 }
890 ;
891
892 rel:
893 relunit tAGO
894 { if (! apply_relative_time (pc, $1, $2)) YYABORT; }
895 | relunit
896 { if (! apply_relative_time (pc, $1, 1)) YYABORT; }
897 | dayshift
898 { if (! apply_relative_time (pc, $1, 1)) YYABORT; }
899 ;
900
901 relunit:
902 tORDINAL tYEAR_UNIT
903 { $$ = RELATIVE_TIME_0; $$.year = $1; }
904 | tUNUMBER tYEAR_UNIT
905 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
906 | tYEAR_UNIT
907 { $$ = RELATIVE_TIME_0; $$.year = 1; }
908 | tORDINAL tMONTH_UNIT
909 { $$ = RELATIVE_TIME_0; $$.month = $1; }
910 | tUNUMBER tMONTH_UNIT
911 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
912 | tMONTH_UNIT
913 { $$ = RELATIVE_TIME_0; $$.month = 1; }
914 | tORDINAL tDAY_UNIT
915 { $$ = RELATIVE_TIME_0;
916 if (INT_MULTIPLY_WRAPV ($1, $2, &$$.day)) YYABORT; }
917 | tUNUMBER tDAY_UNIT
918 { $$ = RELATIVE_TIME_0;
919 if (INT_MULTIPLY_WRAPV ($1.value, $2, &$$.day)) YYABORT; }
920 | tDAY_UNIT
921 { $$ = RELATIVE_TIME_0; $$.day = $1; }
922 | tORDINAL tHOUR_UNIT
923 { $$ = RELATIVE_TIME_0; $$.hour = $1; }
924 | tUNUMBER tHOUR_UNIT
925 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
926 | tHOUR_UNIT
927 { $$ = RELATIVE_TIME_0; $$.hour = 1; }
928 | tORDINAL tMINUTE_UNIT
929 { $$ = RELATIVE_TIME_0; $$.minutes = $1; }
930 | tUNUMBER tMINUTE_UNIT
931 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
932 | tMINUTE_UNIT
933 { $$ = RELATIVE_TIME_0; $$.minutes = 1; }
934 | tORDINAL tSEC_UNIT
935 { $$ = RELATIVE_TIME_0; $$.seconds = $1; }
936 | tUNUMBER tSEC_UNIT
937 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
938 | tSDECIMAL_NUMBER tSEC_UNIT
939 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
940 | tUDECIMAL_NUMBER tSEC_UNIT
941 { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
942 | tSEC_UNIT
943 { $$ = RELATIVE_TIME_0; $$.seconds = 1; }
944 | relunit_snumber
945 ;
946
947 relunit_snumber:
948 tSNUMBER tYEAR_UNIT
949 { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
950 | tSNUMBER tMONTH_UNIT
951 { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
952 | tSNUMBER tDAY_UNIT
953 { $$ = RELATIVE_TIME_0;
954 if (INT_MULTIPLY_WRAPV ($1.value, $2, &$$.day)) YYABORT; }
955 | tSNUMBER tHOUR_UNIT
956 { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
957 | tSNUMBER tMINUTE_UNIT
958 { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
959 | tSNUMBER tSEC_UNIT
960 { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
961 ;
962
963 dayshift:
964 tDAY_SHIFT
965 { $$ = RELATIVE_TIME_0; $$.day = $1; }
966 ;
967
968 seconds: signed_seconds | unsigned_seconds;
969
970 signed_seconds:
971 tSDECIMAL_NUMBER
972 | tSNUMBER
973 { if (time_overflow ($1.value)) YYABORT;
974 $$.tv_sec = $1.value; $$.tv_nsec = 0; }
975 ;
976
977 unsigned_seconds:
978 tUDECIMAL_NUMBER
979 | tUNUMBER
980 { if (time_overflow ($1.value)) YYABORT;
981 $$.tv_sec = $1.value; $$.tv_nsec = 0; }
982 ;
983
984 number:
985 tUNUMBER
986 { digits_to_date_time (pc, $1); }
987 ;
988
989 hybrid:
990 tUNUMBER relunit_snumber
991 {
992 /* Hybrid all-digit and relative offset, so that we accept e.g.,
993 "YYYYMMDD +N days" as well as "YYYYMMDD N days". */
994 digits_to_date_time (pc, $1);
995 if (! apply_relative_time (pc, $2, 1)) YYABORT;
996 }
997 ;
998
999 o_colon_minutes:
1000 /* empty */
1001 { $$ = -1; }
1002 | ':' tUNUMBER
1003 { $$ = $2.value; }
1004 ;
1005
1006 %%
1007
1008 static table const meridian_table[] =
1009 {
1010 { "AM", tMERIDIAN, MERam },
1011 { "A.M.", tMERIDIAN, MERam },
1012 { "PM", tMERIDIAN, MERpm },
1013 { "P.M.", tMERIDIAN, MERpm },
1014 { NULL, 0, 0 }
1015 };
1016
1017 static table const dst_table[] =
1018 {
1019 { "DST", tDST, 0 }
1020 };
1021
1022 static table const month_and_day_table[] =
1023 {
1024 { "JANUARY", tMONTH, 1 },
1025 { "FEBRUARY", tMONTH, 2 },
1026 { "MARCH", tMONTH, 3 },
1027 { "APRIL", tMONTH, 4 },
1028 { "MAY", tMONTH, 5 },
1029 { "JUNE", tMONTH, 6 },
1030 { "JULY", tMONTH, 7 },
1031 { "AUGUST", tMONTH, 8 },
1032 { "SEPTEMBER",tMONTH, 9 },
1033 { "SEPT", tMONTH, 9 },
1034 { "OCTOBER", tMONTH, 10 },
1035 { "NOVEMBER", tMONTH, 11 },
1036 { "DECEMBER", tMONTH, 12 },
1037 { "SUNDAY", tDAY, 0 },
1038 { "MONDAY", tDAY, 1 },
1039 { "TUESDAY", tDAY, 2 },
1040 { "TUES", tDAY, 2 },
1041 { "WEDNESDAY",tDAY, 3 },
1042 { "WEDNES", tDAY, 3 },
1043 { "THURSDAY", tDAY, 4 },
1044 { "THUR", tDAY, 4 },
1045 { "THURS", tDAY, 4 },
1046 { "FRIDAY", tDAY, 5 },
1047 { "SATURDAY", tDAY, 6 },
1048 { NULL, 0, 0 }
1049 };
1050
1051 static table const time_units_table[] =
1052 {
1053 { "YEAR", tYEAR_UNIT, 1 },
1054 { "MONTH", tMONTH_UNIT, 1 },
1055 { "FORTNIGHT",tDAY_UNIT, 14 },
1056 { "WEEK", tDAY_UNIT, 7 },
1057 { "DAY", tDAY_UNIT, 1 },
1058 { "HOUR", tHOUR_UNIT, 1 },
1059 { "MINUTE", tMINUTE_UNIT, 1 },
1060 { "MIN", tMINUTE_UNIT, 1 },
1061 { "SECOND", tSEC_UNIT, 1 },
1062 { "SEC", tSEC_UNIT, 1 },
1063 { NULL, 0, 0 }
1064 };
1065
1066 /* Assorted relative-time words. */
1067 static table const relative_time_table[] =
1068 {
1069 { "TOMORROW", tDAY_SHIFT, 1 },
1070 { "YESTERDAY",tDAY_SHIFT, -1 },
1071 { "TODAY", tDAY_SHIFT, 0 },
1072 { "NOW", tDAY_SHIFT, 0 },
1073 { "LAST", tORDINAL, -1 },
1074 { "THIS", tORDINAL, 0 },
1075 { "NEXT", tORDINAL, 1 },
1076 { "FIRST", tORDINAL, 1 },
1077 /*{ "SECOND", tORDINAL, 2 }, */
1078 { "THIRD", tORDINAL, 3 },
1079 { "FOURTH", tORDINAL, 4 },
1080 { "FIFTH", tORDINAL, 5 },
1081 { "SIXTH", tORDINAL, 6 },
1082 { "SEVENTH", tORDINAL, 7 },
1083 { "EIGHTH", tORDINAL, 8 },
1084 { "NINTH", tORDINAL, 9 },
1085 { "TENTH", tORDINAL, 10 },
1086 { "ELEVENTH", tORDINAL, 11 },
1087 { "TWELFTH", tORDINAL, 12 },
1088 { "AGO", tAGO, -1 },
1089 { "HENCE", tAGO, 1 },
1090 { NULL, 0, 0 }
1091 };
1092
1093 /* The universal time zone table. These labels can be used even for
1094 timestamps that would not otherwise be valid, e.g., GMT timestamps
1095 oin London during summer. */
1096 static table const universal_time_zone_table[] =
1097 {
1098 { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
1099 { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
1100 { "UTC", tZONE, HOUR ( 0) },
1101 { NULL, 0, 0 }
1102 };
1103
1104 /* The time zone table. This table is necessarily incomplete, as time
1105 zone abbreviations are ambiguous; e.g., Australians interpret "EST"
1106 as Eastern time in Australia, not as US Eastern Standard Time.
1107 You cannot rely on parse_datetime to handle arbitrary time zone
1108 abbreviations; use numeric abbreviations like "-0500" instead. */
1109 static table const time_zone_table[] =
1110 {
1111 { "WET", tZONE, HOUR ( 0) }, /* Western European */
1112 { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
1113 { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
1114 { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
1115 { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
1116 { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
1117 { "NST", tZONE, -(HOUR ( 3) + 30 * 60) }, /* Newfoundland Standard */
1118 { "NDT", tDAYZONE,-(HOUR ( 3) + 30 * 60) }, /* Newfoundland Daylight */
1119 { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
1120 { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
1121 { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
1122 { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
1123 { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
1124 { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
1125 { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
1126 { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
1127 { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
1128 { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
1129 { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
1130 { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
1131 { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
1132 { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
1133 { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
1134 { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
1135 { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
1136 { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
1137 { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
1138 { "CET", tZONE, HOUR ( 1) }, /* Central European */
1139 { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
1140 { "MET", tZONE, HOUR ( 1) }, /* Middle European */
1141 { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
1142 { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
1143 { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
1144 { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
1145 { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
1146 { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
1147 { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
1148 { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
1149 { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
1150 { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
1151 { "IST", tZONE, (HOUR ( 5) + 30 * 60) }, /* India Standard */
1152 { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
1153 { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
1154 { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
1155 { "GST", tZONE, HOUR (10) }, /* Guam Standard */
1156 { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
1157 { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
1158 { NULL, 0, 0 }
1159 };
1160
1161 /* Military time zone table.
1162
1163 RFC 822 got these backwards, but RFC 5322 makes the incorrect
1164 treatment optional, so do them the right way here.
1165
1166 Note 'T' is a special case, as it is used as the separator in ISO
1167 8601 date and time of day representation. */
1168 static table const military_table[] =
1169 {
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 { "K", tZONE, HOUR (10) },
1180 { "L", tZONE, HOUR (11) },
1181 { "M", tZONE, HOUR (12) },
1182 { "N", tZONE, -HOUR ( 1) },
1183 { "O", tZONE, -HOUR ( 2) },
1184 { "P", tZONE, -HOUR ( 3) },
1185 { "Q", tZONE, -HOUR ( 4) },
1186 { "R", tZONE, -HOUR ( 5) },
1187 { "S", tZONE, -HOUR ( 6) },
1188 { "T", 'T', 0 },
1189 { "U", tZONE, -HOUR ( 8) },
1190 { "V", tZONE, -HOUR ( 9) },
1191 { "W", tZONE, -HOUR (10) },
1192 { "X", tZONE, -HOUR (11) },
1193 { "Y", tZONE, -HOUR (12) },
1194 { "Z", tZONE, HOUR ( 0) },
1195 { NULL, 0, 0 }
1196 };
1197
1198
1199
1200 /* Convert a time zone expressed as HH:MM into an integer count of
1201 seconds. If MM is negative, then S is of the form HHMM and needs
1202 to be picked apart; otherwise, S is of the form HH. As specified in
1203 https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03, allow
1204 only valid TZ range, and consider first two digits as hours, if no
1205 minutes specified. Return true if successful. */
1206
1207 static bool
time_zone_hhmm(parser_control * pc,textint s,intmax_t mm)1208 time_zone_hhmm (parser_control *pc, textint s, intmax_t mm)
1209 {
1210 intmax_t n_minutes;
1211 bool overflow = false;
1212
1213 /* If the length of S is 1 or 2 and no minutes are specified,
1214 interpret it as a number of hours. */
1215 if (s.digits <= 2 && mm < 0)
1216 s.value *= 100;
1217
1218 if (mm < 0)
1219 n_minutes = (s.value / 100) * 60 + s.value % 100;
1220 else
1221 {
1222 overflow |= INT_MULTIPLY_WRAPV (s.value, 60, &n_minutes);
1223 overflow |= (s.negative
1224 ? INT_SUBTRACT_WRAPV (n_minutes, mm, &n_minutes)
1225 : INT_ADD_WRAPV (n_minutes, mm, &n_minutes));
1226 }
1227
1228 if (overflow || ! (-24 * 60 <= n_minutes && n_minutes <= 24 * 60))
1229 return false;
1230 pc->time_zone = n_minutes * 60;
1231 return true;
1232 }
1233
1234 static int
to_hour(intmax_t hours,int meridian)1235 to_hour (intmax_t hours, int meridian)
1236 {
1237 switch (meridian)
1238 {
1239 default: /* Pacify GCC. */
1240 case MER24:
1241 return 0 <= hours && hours < 24 ? hours : -1;
1242 case MERam:
1243 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
1244 case MERpm:
1245 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
1246 }
1247 }
1248
1249 enum { TM_YEAR_BASE = 1900 };
1250 enum { TM_YEAR_BUFSIZE = INT_BUFSIZE_BOUND (int) + 1 };
1251
1252 /* Convert TM_YEAR, a year minus 1900, to a string that is numerically
1253 correct even if subtracting 1900 would overflow. */
1254
1255 static char const *
tm_year_str(int tm_year,char buf[TM_YEAR_BUFSIZE])1256 tm_year_str (int tm_year, char buf[TM_YEAR_BUFSIZE])
1257 {
1258 verify (TM_YEAR_BASE % 100 == 0);
1259 sprintf (buf, &"-%02d%02d"[-TM_YEAR_BASE <= tm_year],
1260 abs (tm_year / 100 + TM_YEAR_BASE / 100),
1261 abs (tm_year % 100));
1262 return buf;
1263 }
1264
1265 /* Convert a text year number to a year minus 1900, working correctly
1266 even if the input is in the range INT_MAX .. INT_MAX + 1900 - 1. */
1267
1268 static bool
to_tm_year(textint textyear,bool debug,int * tm_year)1269 to_tm_year (textint textyear, bool debug, int *tm_year)
1270 {
1271 intmax_t year = textyear.value;
1272
1273 /* XPG4 suggests that years 00-68 map to 2000-2068, and
1274 years 69-99 map to 1969-1999. */
1275 if (0 <= year && textyear.digits == 2)
1276 {
1277 year += year < 69 ? 2000 : 1900;
1278 if (debug)
1279 dbg_printf (_("warning: adjusting year value %"PRIdMAX
1280 " to %"PRIdMAX"\n"),
1281 textyear.value, year);
1282 }
1283
1284 if (year < 0
1285 ? INT_SUBTRACT_WRAPV (-TM_YEAR_BASE, year, tm_year)
1286 : INT_SUBTRACT_WRAPV (year, TM_YEAR_BASE, tm_year))
1287 {
1288 if (debug)
1289 dbg_printf (_("error: out-of-range year %"PRIdMAX"\n"), year);
1290 return false;
1291 }
1292
1293 return true;
1294 }
1295
1296 static table const * _GL_ATTRIBUTE_PURE
lookup_zone(parser_control const * pc,char const * name)1297 lookup_zone (parser_control const *pc, char const *name)
1298 {
1299 table const *tp;
1300
1301 for (tp = universal_time_zone_table; tp->name; tp++)
1302 if (strcmp (name, tp->name) == 0)
1303 return tp;
1304
1305 /* Try local zone abbreviations before those in time_zone_table, as
1306 the local ones are more likely to be right. */
1307 for (tp = pc->local_time_zone_table; tp->name; tp++)
1308 if (strcmp (name, tp->name) == 0)
1309 return tp;
1310
1311 for (tp = time_zone_table; tp->name; tp++)
1312 if (strcmp (name, tp->name) == 0)
1313 return tp;
1314
1315 return NULL;
1316 }
1317
1318 #if ! HAVE_TM_GMTOFF
1319 /* Yield the difference between *A and *B,
1320 measured in seconds, ignoring leap seconds.
1321 The body of this function is taken directly from the GNU C Library;
1322 see strftime.c. */
1323 static int
tm_diff(const struct tm * a,const struct tm * b)1324 tm_diff (const struct tm *a, const struct tm *b)
1325 {
1326 /* Compute intervening leap days correctly even if year is negative.
1327 Take care to avoid int overflow in leap day calculations,
1328 but it's OK to assume that A and B are close to each other. */
1329 int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
1330 int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
1331 int a100 = a4 / 25 - (a4 % 25 < 0);
1332 int b100 = b4 / 25 - (b4 % 25 < 0);
1333 int a400 = SHR (a100, 2);
1334 int b400 = SHR (b100, 2);
1335 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
1336 int years = a->tm_year - b->tm_year;
1337 int days = (365 * years + intervening_leap_days
1338 + (a->tm_yday - b->tm_yday));
1339 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
1340 + (a->tm_min - b->tm_min))
1341 + (a->tm_sec - b->tm_sec));
1342 }
1343 #endif /* ! HAVE_TM_GMTOFF */
1344
1345 static table const *
lookup_word(parser_control const * pc,char * word)1346 lookup_word (parser_control const *pc, char *word)
1347 {
1348 char *p;
1349 char *q;
1350 idx_t wordlen;
1351 table const *tp;
1352 bool period_found;
1353 bool abbrev;
1354
1355 /* Make it uppercase. */
1356 for (p = word; *p; p++)
1357 *p = c_toupper (to_uchar (*p));
1358
1359 for (tp = meridian_table; tp->name; tp++)
1360 if (strcmp (word, tp->name) == 0)
1361 return tp;
1362
1363 /* See if we have an abbreviation for a month. */
1364 wordlen = strlen (word);
1365 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
1366
1367 for (tp = month_and_day_table; tp->name; tp++)
1368 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
1369 return tp;
1370
1371 if ((tp = lookup_zone (pc, word)))
1372 return tp;
1373
1374 if (strcmp (word, dst_table[0].name) == 0)
1375 return dst_table;
1376
1377 for (tp = time_units_table; tp->name; tp++)
1378 if (strcmp (word, tp->name) == 0)
1379 return tp;
1380
1381 /* Strip off any plural and try the units table again. */
1382 if (word[wordlen - 1] == 'S')
1383 {
1384 word[wordlen - 1] = '\0';
1385 for (tp = time_units_table; tp->name; tp++)
1386 if (strcmp (word, tp->name) == 0)
1387 return tp;
1388 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
1389 }
1390
1391 for (tp = relative_time_table; tp->name; tp++)
1392 if (strcmp (word, tp->name) == 0)
1393 return tp;
1394
1395 /* Military time zones. */
1396 if (wordlen == 1)
1397 for (tp = military_table; tp->name; tp++)
1398 if (word[0] == tp->name[0])
1399 return tp;
1400
1401 /* Drop out any periods and try the time zone table again. */
1402 for (period_found = false, p = q = word; (*p = *q); q++)
1403 if (*q == '.')
1404 period_found = true;
1405 else
1406 p++;
1407 if (period_found && (tp = lookup_zone (pc, word)))
1408 return tp;
1409
1410 return NULL;
1411 }
1412
1413 static int
yylex(union YYSTYPE * lvalp,parser_control * pc)1414 yylex (union YYSTYPE *lvalp, parser_control *pc)
1415 {
1416 unsigned char c;
1417
1418 for (;;)
1419 {
1420 while (c = *pc->input, c_isspace (c))
1421 pc->input++;
1422
1423 if (c_isdigit (c) || c == '-' || c == '+')
1424 {
1425 char const *p = pc->input;
1426 int sign;
1427 if (c == '-' || c == '+')
1428 {
1429 sign = c == '-' ? -1 : 1;
1430 while (c = *(pc->input = ++p), c_isspace (c))
1431 continue;
1432 if (! c_isdigit (c))
1433 /* skip the '-' sign */
1434 continue;
1435 }
1436 else
1437 sign = 0;
1438
1439 time_t value = 0;
1440 do
1441 {
1442 if (INT_MULTIPLY_WRAPV (value, 10, &value))
1443 return '?';
1444 if (INT_ADD_WRAPV (value, sign < 0 ? '0' - c : c - '0', &value))
1445 return '?';
1446 c = *++p;
1447 }
1448 while (c_isdigit (c));
1449
1450 if ((c == '.' || c == ',') && c_isdigit (p[1]))
1451 {
1452 time_t s = value;
1453 int digits;
1454
1455 /* Accumulate fraction, to ns precision. */
1456 p++;
1457 int ns = *p++ - '0';
1458 for (digits = 2; digits <= LOG10_BILLION; digits++)
1459 {
1460 ns *= 10;
1461 if (c_isdigit (*p))
1462 ns += *p++ - '0';
1463 }
1464
1465 /* Skip excess digits, truncating toward -Infinity. */
1466 if (sign < 0)
1467 for (; c_isdigit (*p); p++)
1468 if (*p != '0')
1469 {
1470 ns++;
1471 break;
1472 }
1473 while (c_isdigit (*p))
1474 p++;
1475
1476 /* Adjust to the timespec convention, which is that
1477 tv_nsec is always a positive offset even if tv_sec is
1478 negative. */
1479 if (sign < 0 && ns)
1480 {
1481 if (INT_SUBTRACT_WRAPV (s, 1, &s))
1482 return '?';
1483 ns = BILLION - ns;
1484 }
1485
1486 lvalp->timespec.tv_sec = s;
1487 lvalp->timespec.tv_nsec = ns;
1488 pc->input = p;
1489 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
1490 }
1491 else
1492 {
1493 lvalp->textintval.negative = sign < 0;
1494 lvalp->textintval.value = value;
1495 lvalp->textintval.digits = p - pc->input;
1496 pc->input = p;
1497 return sign ? tSNUMBER : tUNUMBER;
1498 }
1499 }
1500
1501 if (c_isalpha (c))
1502 {
1503 char buff[20];
1504 char *p = buff;
1505 table const *tp;
1506
1507 do
1508 {
1509 if (p < buff + sizeof buff - 1)
1510 *p++ = c;
1511 c = *++pc->input;
1512 }
1513 while (c_isalpha (c) || c == '.');
1514
1515 *p = '\0';
1516 tp = lookup_word (pc, buff);
1517 if (! tp)
1518 {
1519 if (debugging (pc))
1520 dbg_printf (_("error: unknown word '%s'\n"), buff);
1521 return '?';
1522 }
1523 lvalp->intval = tp->value;
1524 return tp->type;
1525 }
1526
1527 if (c != '(')
1528 return to_uchar (*pc->input++);
1529
1530 idx_t count = 0;
1531 do
1532 {
1533 c = *pc->input++;
1534 if (c == '\0')
1535 return c;
1536 if (c == '(')
1537 count++;
1538 else if (c == ')')
1539 count--;
1540 }
1541 while (count != 0);
1542 }
1543 }
1544
1545 /* Do nothing if the parser reports an error. */
1546 static int
yyerror(_GL_UNUSED parser_control const * pc,_GL_UNUSED char const * s)1547 yyerror (_GL_UNUSED parser_control const *pc,
1548 _GL_UNUSED char const *s)
1549 {
1550 return 0;
1551 }
1552
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. */
1558
1559 static bool
mktime_ok(struct tm const * tm0,struct tm const * tm1)1560 mktime_ok (struct tm const *tm0, struct tm const *tm1)
1561 {
1562 if (tm1->tm_wday < 0)
1563 return false;
1564
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));
1571 }
1572
1573 /* Debugging: format a 'struct tm' into a buffer, taking the parser's
1574 timezone information into account (if pc != NULL). */
1575 static char const *
debug_strfdatetime(struct tm const * tm,parser_control const * pc,char * buf,int n)1576 debug_strfdatetime (struct tm const *tm, parser_control const *pc,
1577 char *buf, int n)
1578 {
1579 /* TODO:
1580 1. find an optimal way to print date string in a clear and unambiguous
1581 format. Currently, always add '(Y-M-D)' prefix.
1582 Consider '2016y01m10d' or 'year(2016) month(01) day(10)'.
1583
1584 If the user needs debug printing, it means he/she already having
1585 issues with the parsing - better to avoid formats that could
1586 be mis-interpreted (e.g., just YYYY-MM-DD).
1587
1588 2. Can strftime be used instead?
1589 depends if it is portable and can print invalid dates on all systems.
1590
1591 3. Print timezone information ?
1592
1593 4. Print DST information ?
1594
1595 5. Print nanosecond information ?
1596
1597 NOTE:
1598 Printed date/time values might not be valid, e.g., '2016-02-31'
1599 or '2016-19-2016' . These are the values as parsed from the user
1600 string, before validation.
1601 */
1602 int m = nstrftime (buf, n, "(Y-M-D) %Y-%m-%d %H:%M:%S", tm, 0, 0);
1603
1604 /* If parser_control information was provided (for timezone),
1605 and there's enough space in the buffer, add timezone info. */
1606 if (pc && m < n && pc->zones_seen)
1607 {
1608 int tz = pc->time_zone;
1609
1610 /* Account for DST if tLOCAL_ZONE was seen. */
1611 if (pc->local_zones_seen && !pc->zones_seen && 0 < pc->local_isdst)
1612 tz += 60 * 60;
1613
1614 char time_zone_buf[TIME_ZONE_BUFSIZE];
1615 snprintf (&buf[m], n - m, " TZ=%s", time_zone_str (tz, time_zone_buf));
1616 }
1617 return buf;
1618 }
1619
1620 static char const *
debug_strfdate(struct tm const * tm,char * buf,int n)1621 debug_strfdate (struct tm const *tm, char *buf, int n)
1622 {
1623 char tm_year_buf[TM_YEAR_BUFSIZE];
1624 snprintf (buf, n, "(Y-M-D) %s-%02d-%02d",
1625 tm_year_str (tm->tm_year, tm_year_buf),
1626 tm->tm_mon + 1, tm->tm_mday);
1627 return buf;
1628 }
1629
1630 static char const *
debug_strftime(struct tm const * tm,char * buf,int n)1631 debug_strftime (struct tm const *tm, char *buf, int n)
1632 {
1633 snprintf (buf, n, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
1634 return buf;
1635 }
1636
1637 /* If mktime_ok failed, display the failed time values,
1638 and provide possible hints. Example output:
1639
1640 date: error: invalid date/time value:
1641 date: user provided time: '(Y-M-D) 2006-04-02 02:45:00'
1642 date: normalized time: '(Y-M-D) 2006-04-02 03:45:00'
1643 date: __
1644 date: possible reasons:
1645 date: non-existing due to daylight-saving time;
1646 date: numeric values overflow;
1647 date: missing timezone;
1648 */
1649 static void
debug_mktime_not_ok(struct tm const * tm0,struct tm const * tm1,parser_control const * pc,bool time_zone_seen)1650 debug_mktime_not_ok (struct tm const *tm0, struct tm const *tm1,
1651 parser_control const *pc, bool time_zone_seen)
1652 {
1653 /* TODO: handle t == -1 (as in 'mktime_ok'). */
1654 char tmp[DBGBUFSIZE];
1655 int i;
1656 const bool eq_sec = (tm0->tm_sec == tm1->tm_sec);
1657 const bool eq_min = (tm0->tm_min == tm1->tm_min);
1658 const bool eq_hour = (tm0->tm_hour == tm1->tm_hour);
1659 const bool eq_mday = (tm0->tm_mday == tm1->tm_mday);
1660 const bool eq_month = (tm0->tm_mon == tm1->tm_mon);
1661 const bool eq_year = (tm0->tm_year == tm1->tm_year);
1662
1663 const bool dst_shift = eq_sec && eq_min && !eq_hour
1664 && eq_mday && eq_month && eq_year;
1665
1666 if (!debugging (pc))
1667 return;
1668
1669 dbg_printf (_("error: invalid date/time value:\n"));
1670 dbg_printf (_(" user provided time: '%s'\n"),
1671 debug_strfdatetime (tm0, pc, tmp, sizeof tmp));
1672 dbg_printf (_(" normalized time: '%s'\n"),
1673 debug_strfdatetime (tm1, pc, tmp, sizeof tmp));
1674 /* The format must be aligned with debug_strfdatetime and the two
1675 DEBUG statements above. This string is not translated. */
1676 i = snprintf (tmp, sizeof tmp,
1677 " %4s %2s %2s %2s %2s %2s",
1678 eq_year ? "" : "----",
1679 eq_month ? "" : "--",
1680 eq_mday ? "" : "--",
1681 eq_hour ? "" : "--",
1682 eq_min ? "" : "--",
1683 eq_sec ? "" : "--");
1684 /* Trim trailing whitespace. */
1685 if (0 <= i)
1686 {
1687 if (sizeof tmp - 1 < i)
1688 i = sizeof tmp - 1;
1689 while (0 < i && tmp[i - 1] == ' ')
1690 --i;
1691 tmp[i] = '\0';
1692 }
1693 dbg_printf ("%s\n", tmp);
1694
1695 dbg_printf (_(" possible reasons:\n"));
1696 if (dst_shift)
1697 dbg_printf (_(" non-existing due to daylight-saving time;\n"));
1698 if (!eq_mday && !eq_month)
1699 dbg_printf (_(" invalid day/month combination;\n"));
1700 dbg_printf (_(" numeric values overflow;\n"));
1701 dbg_printf (" %s\n", (time_zone_seen ? _("incorrect timezone")
1702 : _("missing timezone")));
1703 }
1704
1705 /* Parse a date/time string, storing the resulting time value into *RESULT.
1706 The string itself is pointed to by P. Return true if successful.
1707 P can be an incomplete or relative time specification; if so, use
1708 *NOW as the basis for the returned time. Default to timezone
1709 TZDEFAULT, which corresponds to tzalloc (TZSTRING). */
1710 static bool
parse_datetime_body(struct timespec * result,char const * p,struct timespec const * now,unsigned int flags,timezone_t tzdefault,char const * tzstring)1711 parse_datetime_body (struct timespec *result, char const *p,
1712 struct timespec const *now, unsigned int flags,
1713 timezone_t tzdefault, char const *tzstring)
1714 {
1715 struct tm tm;
1716 struct tm tm0;
1717 char time_zone_buf[TIME_ZONE_BUFSIZE];
1718 char dbg_tm[DBGBUFSIZE];
1719 bool ok = false;
1720 char const *input_sentinel = p + strlen (p);
1721 char *tz1alloc = NULL;
1722
1723 /* A reasonable upper bound for the size of ordinary TZ strings.
1724 Use heap allocation if TZ's length exceeds this. */
1725 enum { TZBUFSIZE = 100 };
1726 char tz1buf[TZBUFSIZE];
1727
1728 struct timespec gettime_buffer;
1729 if (! now)
1730 {
1731 gettime (&gettime_buffer);
1732 now = &gettime_buffer;
1733 }
1734
1735 time_t Start = now->tv_sec;
1736 int Start_ns = now->tv_nsec;
1737
1738 unsigned char c;
1739 while (c = *p, c_isspace (c))
1740 p++;
1741
1742 timezone_t tz = tzdefault;
1743
1744 /* Store a local copy prior to first "goto". Without this, a prior use
1745 below of RELATIVE_TIME_0 on the RHS might translate to an assignment-
1746 to-temporary, which would trigger a -Wjump-misses-init warning. */
1747 const relative_time rel_time_0 = RELATIVE_TIME_0;
1748
1749 if (strncmp (p, "TZ=\"", 4) == 0)
1750 {
1751 char const *tzbase = p + 4;
1752 idx_t tzsize = 1;
1753 char const *s;
1754
1755 for (s = tzbase; *s; s++, tzsize++)
1756 if (*s == '\\')
1757 {
1758 s++;
1759 if (! (*s == '\\' || *s == '"'))
1760 break;
1761 }
1762 else if (*s == '"')
1763 {
1764 timezone_t tz1;
1765 char *tz1string = tz1buf;
1766 char *z;
1767 if (TZBUFSIZE < tzsize)
1768 {
1769 tz1alloc = malloc (tzsize);
1770 if (!tz1alloc)
1771 goto fail;
1772 tz1string = tz1alloc;
1773 }
1774 z = tz1string;
1775 for (s = tzbase; *s != '"'; s++)
1776 *z++ = *(s += *s == '\\');
1777 *z = '\0';
1778 tz1 = tzalloc (tz1string);
1779 if (!tz1)
1780 goto fail;
1781 tz = tz1;
1782 tzstring = tz1string;
1783
1784 p = s + 1;
1785 while (c = *p, c_isspace (c))
1786 p++;
1787
1788 break;
1789 }
1790 }
1791
1792 struct tm tmp;
1793 if (! localtime_rz (tz, &now->tv_sec, &tmp))
1794 goto fail;
1795
1796 /* As documented, be careful to treat the empty string just like
1797 a date string of "0". Without this, an empty string would be
1798 declared invalid when parsed during a DST transition. */
1799 if (*p == '\0')
1800 p = "0";
1801
1802 parser_control pc;
1803 pc.input = p;
1804 #ifdef GNULIB_PARSE_DATETIME2
1805 pc.parse_datetime_debug = (flags & PARSE_DATETIME_DEBUG) != 0;
1806 #endif
1807 if (INT_ADD_WRAPV (tmp.tm_year, TM_YEAR_BASE, &pc.year.value))
1808 {
1809 if (debugging (&pc))
1810 dbg_printf (_("error: initial year out of range\n"));
1811 goto fail;
1812 }
1813 pc.year.digits = 0;
1814 pc.month = tmp.tm_mon + 1;
1815 pc.day = tmp.tm_mday;
1816 pc.hour = tmp.tm_hour;
1817 pc.minutes = tmp.tm_min;
1818 pc.seconds.tv_sec = tmp.tm_sec;
1819 pc.seconds.tv_nsec = Start_ns;
1820 tm.tm_isdst = tmp.tm_isdst;
1821
1822 pc.meridian = MER24;
1823 pc.rel = rel_time_0;
1824 pc.timespec_seen = false;
1825 pc.rels_seen = false;
1826 pc.dates_seen = 0;
1827 pc.days_seen = 0;
1828 pc.times_seen = 0;
1829 pc.local_zones_seen = 0;
1830 pc.dsts_seen = 0;
1831 pc.zones_seen = 0;
1832 pc.year_seen = false;
1833 pc.debug_dates_seen = false;
1834 pc.debug_days_seen = false;
1835 pc.debug_times_seen = false;
1836 pc.debug_local_zones_seen = false;
1837 pc.debug_zones_seen = false;
1838 pc.debug_year_seen = false;
1839 pc.debug_ordinal_day_seen = false;
1840
1841 #if HAVE_STRUCT_TM_TM_ZONE
1842 pc.local_time_zone_table[0].name = tmp.tm_zone;
1843 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1844 pc.local_time_zone_table[0].value = tmp.tm_isdst;
1845 pc.local_time_zone_table[1].name = NULL;
1846
1847 /* Probe the names used in the next three calendar quarters, looking
1848 for a tm_isdst different from the one we already have. */
1849 {
1850 int quarter;
1851 for (quarter = 1; quarter <= 3; quarter++)
1852 {
1853 time_t probe;
1854 if (INT_ADD_WRAPV (Start, quarter * (90 * 24 * 60 * 60), &probe))
1855 break;
1856 struct tm probe_tm;
1857 if (localtime_rz (tz, &probe, &probe_tm) && probe_tm.tm_zone
1858 && probe_tm.tm_isdst != pc.local_time_zone_table[0].value)
1859 {
1860 {
1861 pc.local_time_zone_table[1].name = probe_tm.tm_zone;
1862 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1863 pc.local_time_zone_table[1].value = probe_tm.tm_isdst;
1864 pc.local_time_zone_table[2].name = NULL;
1865 }
1866 break;
1867 }
1868 }
1869 }
1870 #else
1871 #if HAVE_TZNAME
1872 {
1873 # if !HAVE_DECL_TZNAME
1874 extern char *tzname[];
1875 # endif
1876 int i;
1877 for (i = 0; i < 2; i++)
1878 {
1879 pc.local_time_zone_table[i].name = tzname[i];
1880 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1881 pc.local_time_zone_table[i].value = i;
1882 }
1883 pc.local_time_zone_table[i].name = NULL;
1884 }
1885 #else
1886 pc.local_time_zone_table[0].name = NULL;
1887 #endif
1888 #endif
1889
1890 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1891 && ! strcmp (pc.local_time_zone_table[0].name,
1892 pc.local_time_zone_table[1].name))
1893 {
1894 /* This locale uses the same abbreviation for standard and
1895 daylight times. So if we see that abbreviation, we don't
1896 know whether it's daylight time. */
1897 pc.local_time_zone_table[0].value = -1;
1898 pc.local_time_zone_table[1].name = NULL;
1899 }
1900
1901 if (yyparse (&pc) != 0)
1902 {
1903 if (debugging (&pc))
1904 dbg_printf ((input_sentinel <= pc.input
1905 ? _("error: parsing failed\n")
1906 : _("error: parsing failed, stopped at '%s'\n")),
1907 pc.input);
1908 goto fail;
1909 }
1910
1911
1912 /* Determine effective timezone source. */
1913
1914 if (debugging (&pc))
1915 {
1916 dbg_printf (_("input timezone: "));
1917
1918 if (pc.timespec_seen)
1919 fprintf (stderr, _("'@timespec' - always UTC"));
1920 else if (pc.zones_seen)
1921 fprintf (stderr, _("parsed date/time string"));
1922 else if (tzstring)
1923 {
1924 if (tz != tzdefault)
1925 fprintf (stderr, _("TZ=\"%s\" in date string"), tzstring);
1926 else if (STREQ (tzstring, "UTC0"))
1927 {
1928 /* Special case: 'date -u' sets TZ="UTC0". */
1929 fprintf (stderr, _("TZ=\"UTC0\" environment value or -u"));
1930 }
1931 else
1932 fprintf (stderr, _("TZ=\"%s\" environment value"), tzstring);
1933 }
1934 else
1935 fprintf (stderr, _("system default"));
1936
1937 /* Account for DST changes if tLOCAL_ZONE was seen.
1938 local timezone only changes DST and is relative to the
1939 default timezone.*/
1940 if (pc.local_zones_seen && !pc.zones_seen && 0 < pc.local_isdst)
1941 fprintf (stderr, ", dst");
1942
1943 if (pc.zones_seen)
1944 fprintf (stderr, " (%s)", time_zone_str (pc.time_zone, time_zone_buf));
1945
1946 fputc ('\n', stderr);
1947 }
1948
1949 if (pc.timespec_seen)
1950 *result = pc.seconds;
1951 else
1952 {
1953 if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
1954 | (pc.local_zones_seen + pc.zones_seen)))
1955 {
1956 if (debugging (&pc))
1957 {
1958 if (pc.times_seen > 1)
1959 dbg_printf ("error: seen multiple time parts\n");
1960 if (pc.dates_seen > 1)
1961 dbg_printf ("error: seen multiple date parts\n");
1962 if (pc.days_seen > 1)
1963 dbg_printf ("error: seen multiple days parts\n");
1964 if (pc.dsts_seen > 1)
1965 dbg_printf ("error: seen multiple daylight-saving parts\n");
1966 if ((pc.local_zones_seen + pc.zones_seen) > 1)
1967 dbg_printf ("error: seen multiple time-zone parts\n");
1968 }
1969 goto fail;
1970 }
1971
1972 if (! to_tm_year (pc.year, debugging (&pc), &tm.tm_year)
1973 || INT_ADD_WRAPV (pc.month, -1, &tm.tm_mon)
1974 || INT_ADD_WRAPV (pc.day, 0, &tm.tm_mday))
1975 {
1976 if (debugging (&pc))
1977 dbg_printf (_("error: year, month, or day overflow\n"));
1978 goto fail;
1979 }
1980 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
1981 {
1982 tm.tm_hour = to_hour (pc.hour, pc.meridian);
1983 if (tm.tm_hour < 0)
1984 {
1985 char const *mrd = (pc.meridian == MERam ? "am"
1986 : pc.meridian == MERpm ?"pm" : "");
1987 if (debugging (&pc))
1988 dbg_printf (_("error: invalid hour %"PRIdMAX"%s\n"),
1989 pc.hour, mrd);
1990 goto fail;
1991 }
1992 tm.tm_min = pc.minutes;
1993 tm.tm_sec = pc.seconds.tv_sec;
1994 if (debugging (&pc))
1995 dbg_printf ((pc.times_seen
1996 ? _("using specified time as starting value: '%s'\n")
1997 : _("using current time as starting value: '%s'\n")),
1998 debug_strftime (&tm, dbg_tm, sizeof dbg_tm));
1999 }
2000 else
2001 {
2002 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
2003 pc.seconds.tv_nsec = 0;
2004 if (debugging (&pc))
2005 dbg_printf ("warning: using midnight as starting time: 00:00:00\n");
2006 }
2007
2008 /* Let mktime deduce tm_isdst if we have an absolute timestamp. */
2009 if (pc.dates_seen | pc.days_seen | pc.times_seen)
2010 tm.tm_isdst = -1;
2011
2012 /* But if the input explicitly specifies local time with or without
2013 DST, give mktime that information. */
2014 if (pc.local_zones_seen)
2015 tm.tm_isdst = pc.local_isdst;
2016
2017 tm0.tm_sec = tm.tm_sec;
2018 tm0.tm_min = tm.tm_min;
2019 tm0.tm_hour = tm.tm_hour;
2020 tm0.tm_mday = tm.tm_mday;
2021 tm0.tm_mon = tm.tm_mon;
2022 tm0.tm_year = tm.tm_year;
2023 tm0.tm_isdst = tm.tm_isdst;
2024 tm.tm_wday = -1;
2025
2026 Start = mktime_z (tz, &tm);
2027
2028 if (! mktime_ok (&tm0, &tm))
2029 {
2030 bool repaired = false;
2031 bool time_zone_seen = pc.zones_seen != 0;
2032 if (time_zone_seen)
2033 {
2034 /* Guard against falsely reporting errors near the time_t
2035 boundaries when parsing times in other time zones. For
2036 example, suppose the input string "1969-12-31 23:00:00 -0100",
2037 the current time zone is 8 hours ahead of UTC, and the min
2038 time_t value is 1970-01-01 00:00:00 UTC. Then the min
2039 localtime value is 1970-01-01 08:00:00, and mktime will
2040 therefore fail on 1969-12-31 23:00:00. To work around the
2041 problem, set the time zone to 1 hour behind UTC temporarily
2042 by setting TZ="XXX1:00" and try mktime again. */
2043
2044 char tz2buf[sizeof "XXX" - 1 + TIME_ZONE_BUFSIZE];
2045 tz2buf[0] = tz2buf[1] = tz2buf[2] = 'X';
2046 time_zone_str (pc.time_zone, &tz2buf[3]);
2047 timezone_t tz2 = tzalloc (tz2buf);
2048 if (!tz2)
2049 {
2050 if (debugging (&pc))
2051 dbg_printf (_("error: tzalloc (\"%s\") failed\n"), tz2buf);
2052 goto fail;
2053 }
2054 tm.tm_sec = tm0.tm_sec;
2055 tm.tm_min = tm0.tm_min;
2056 tm.tm_hour = tm0.tm_hour;
2057 tm.tm_mday = tm0.tm_mday;
2058 tm.tm_mon = tm0.tm_mon;
2059 tm.tm_year = tm0.tm_year;
2060 tm.tm_isdst = tm0.tm_isdst;
2061 tm.tm_wday = -1;
2062 Start = mktime_z (tz2, &tm);
2063 repaired = mktime_ok (&tm0, &tm);
2064 tzfree (tz2);
2065 }
2066
2067 if (! repaired)
2068 {
2069 debug_mktime_not_ok (&tm0, &tm, &pc, time_zone_seen);
2070 goto fail;
2071 }
2072 }
2073
2074 char dbg_ord[DBGBUFSIZE];
2075
2076 if (pc.days_seen && ! pc.dates_seen)
2077 {
2078 intmax_t dayincr;
2079 if (INT_MULTIPLY_WRAPV ((pc.day_ordinal
2080 - (0 < pc.day_ordinal
2081 && tm.tm_wday != pc.day_number)),
2082 7, &dayincr)
2083 || INT_ADD_WRAPV ((pc.day_number - tm.tm_wday + 7) % 7,
2084 dayincr, &dayincr)
2085 || INT_ADD_WRAPV (dayincr, tm.tm_mday, &tm.tm_mday))
2086 Start = -1;
2087 else
2088 {
2089 tm.tm_isdst = -1;
2090 Start = mktime_z (tz, &tm);
2091 }
2092
2093 if (Start == (time_t) -1)
2094 {
2095 if (debugging (&pc))
2096 dbg_printf (_("error: day '%s' "
2097 "(day ordinal=%"PRIdMAX" number=%d) "
2098 "resulted in an invalid date: '%s'\n"),
2099 str_days (&pc, dbg_ord, sizeof dbg_ord),
2100 pc.day_ordinal, pc.day_number,
2101 debug_strfdatetime (&tm, &pc, dbg_tm,
2102 sizeof dbg_tm));
2103 goto fail;
2104 }
2105
2106 if (debugging (&pc))
2107 dbg_printf (_("new start date: '%s' is '%s'\n"),
2108 str_days (&pc, dbg_ord, sizeof dbg_ord),
2109 debug_strfdatetime (&tm, &pc, dbg_tm, sizeof dbg_tm));
2110
2111 }
2112
2113 if (debugging (&pc))
2114 {
2115 if (!pc.dates_seen && !pc.days_seen)
2116 dbg_printf (_("using current date as starting value: '%s'\n"),
2117 debug_strfdate (&tm, dbg_tm, sizeof dbg_tm));
2118
2119 if (pc.days_seen && pc.dates_seen)
2120 dbg_printf (_("warning: day (%s) ignored when explicit dates "
2121 "are given\n"),
2122 str_days (&pc, dbg_ord, sizeof dbg_ord));
2123
2124 dbg_printf (_("starting date/time: '%s'\n"),
2125 debug_strfdatetime (&tm, &pc, dbg_tm, sizeof dbg_tm));
2126 }
2127
2128 /* Add relative date. */
2129 if (pc.rel.year | pc.rel.month | pc.rel.day)
2130 {
2131 if (debugging (&pc))
2132 {
2133 if ((pc.rel.year != 0 || pc.rel.month != 0) && tm.tm_mday != 15)
2134 dbg_printf (_("warning: when adding relative months/years, "
2135 "it is recommended to specify the 15th of the "
2136 "months\n"));
2137
2138 if (pc.rel.day != 0 && tm.tm_hour != 12)
2139 dbg_printf (_("warning: when adding relative days, "
2140 "it is recommended to specify noon\n"));
2141 }
2142
2143 int year, month, day;
2144 if (INT_ADD_WRAPV (tm.tm_year, pc.rel.year, &year)
2145 || INT_ADD_WRAPV (tm.tm_mon, pc.rel.month, &month)
2146 || INT_ADD_WRAPV (tm.tm_mday, pc.rel.day, &day))
2147 {
2148 if (debugging (&pc))
2149 dbg_printf (_("error: %s:%d\n"), __FILE__, __LINE__);
2150 goto fail;
2151 }
2152 tm.tm_year = year;
2153 tm.tm_mon = month;
2154 tm.tm_mday = day;
2155 tm.tm_hour = tm0.tm_hour;
2156 tm.tm_min = tm0.tm_min;
2157 tm.tm_sec = tm0.tm_sec;
2158 tm.tm_isdst = tm0.tm_isdst;
2159 Start = mktime_z (tz, &tm);
2160 if (Start == (time_t) -1)
2161 {
2162 if (debugging (&pc))
2163 dbg_printf (_("error: adding relative date resulted "
2164 "in an invalid date: '%s'\n"),
2165 debug_strfdatetime (&tm, &pc, dbg_tm,
2166 sizeof dbg_tm));
2167 goto fail;
2168 }
2169
2170 if (debugging (&pc))
2171 {
2172 dbg_printf (_("after date adjustment "
2173 "(%+"PRIdMAX" years, %+"PRIdMAX" months, "
2174 "%+"PRIdMAX" days),\n"),
2175 pc.rel.year, pc.rel.month, pc.rel.day);
2176 dbg_printf (_(" new date/time = '%s'\n"),
2177 debug_strfdatetime (&tm, &pc, dbg_tm,
2178 sizeof dbg_tm));
2179
2180 /* Warn about crossing DST due to time adjustment.
2181 Example: https://bugs.gnu.org/8357
2182 env TZ=Europe/Helsinki \
2183 date --debug \
2184 -d 'Mon Mar 28 00:36:07 2011 EEST 1 day ago'
2185
2186 This case is different than DST changes due to time adjustment,
2187 i.e., "1 day ago" vs "24 hours ago" are calculated in different
2188 places.
2189
2190 'tm0.tm_isdst' contains the DST of the input date,
2191 'tm.tm_isdst' is the normalized result after calling
2192 mktime (&tm).
2193 */
2194 if (tm0.tm_isdst != -1 && tm.tm_isdst != tm0.tm_isdst)
2195 dbg_printf (_("warning: daylight saving time changed after "
2196 "date adjustment\n"));
2197
2198 /* Warn if the user did not ask to adjust days but mday changed,
2199 or
2200 user did not ask to adjust months/days but the month changed.
2201
2202 Example for first case:
2203 2016-05-31 + 1 month => 2016-06-31 => 2016-07-01.
2204 User asked to adjust month, but the day changed from 31 to 01.
2205
2206 Example for second case:
2207 2016-02-29 + 1 year => 2017-02-29 => 2017-03-01.
2208 User asked to adjust year, but the month changed from 02 to 03.
2209 */
2210 if (pc.rel.day == 0
2211 && (tm.tm_mday != day
2212 || (pc.rel.month == 0 && tm.tm_mon != month)))
2213 {
2214 dbg_printf (_("warning: month/year adjustment resulted in "
2215 "shifted dates:\n"));
2216 char tm_year_buf[TM_YEAR_BUFSIZE];
2217 dbg_printf (_(" adjusted Y M D: %s %02d %02d\n"),
2218 tm_year_str (year, tm_year_buf), month + 1, day);
2219 dbg_printf (_(" normalized Y M D: %s %02d %02d\n"),
2220 tm_year_str (tm.tm_year, tm_year_buf),
2221 tm.tm_mon + 1, tm.tm_mday);
2222 }
2223 }
2224
2225 }
2226
2227 /* The only "output" of this if-block is an updated Start value,
2228 so this block must follow others that clobber Start. */
2229 if (pc.zones_seen)
2230 {
2231 bool overflow = false;
2232 #ifdef HAVE_TM_GMTOFF
2233 long int utcoff = tm.tm_gmtoff;
2234 #else
2235 time_t t = Start;
2236 struct tm gmt;
2237 int utcoff = (gmtime_r (&t, &gmt)
2238 ? tm_diff (&tm, &gmt)
2239 : (overflow = true, 0));
2240 #endif
2241 intmax_t delta;
2242 overflow |= INT_SUBTRACT_WRAPV (pc.time_zone, utcoff, &delta);
2243 time_t t1;
2244 overflow |= INT_SUBTRACT_WRAPV (Start, delta, &t1);
2245 if (overflow)
2246 {
2247 if (debugging (&pc))
2248 dbg_printf (_("error: timezone %d caused time_t overflow\n"),
2249 pc.time_zone);
2250 goto fail;
2251 }
2252 Start = t1;
2253 }
2254
2255 if (debugging (&pc))
2256 {
2257 intmax_t Starti = Start;
2258 dbg_printf (_("'%s' = %"PRIdMAX" epoch-seconds\n"),
2259 debug_strfdatetime (&tm, &pc, dbg_tm, sizeof dbg_tm),
2260 Starti);
2261 }
2262
2263
2264 /* Add relative hours, minutes, and seconds. On hosts that support
2265 leap seconds, ignore the possibility of leap seconds; e.g.,
2266 "+ 10 minutes" adds 600 seconds, even if one of them is a
2267 leap second. Typically this is not what the user wants, but it's
2268 too hard to do it the other way, because the time zone indicator
2269 must be applied before relative times, and if mktime is applied
2270 again the time zone will be lost. */
2271 {
2272 intmax_t orig_ns = pc.seconds.tv_nsec;
2273 intmax_t sum_ns = orig_ns + pc.rel.ns;
2274 int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
2275 int d4 = (sum_ns - normalized_ns) / BILLION;
2276 intmax_t d1, t1, d2, t2, t3;
2277 time_t t4;
2278 if (INT_MULTIPLY_WRAPV (pc.rel.hour, 60 * 60, &d1)
2279 || INT_ADD_WRAPV (Start, d1, &t1)
2280 || INT_MULTIPLY_WRAPV (pc.rel.minutes, 60, &d2)
2281 || INT_ADD_WRAPV (t1, d2, &t2)
2282 || INT_ADD_WRAPV (t2, pc.rel.seconds, &t3)
2283 || INT_ADD_WRAPV (t3, d4, &t4))
2284 {
2285 if (debugging (&pc))
2286 dbg_printf (_("error: adding relative time caused an "
2287 "overflow\n"));
2288 goto fail;
2289 }
2290
2291 result->tv_sec = t4;
2292 result->tv_nsec = normalized_ns;
2293
2294 if (debugging (&pc)
2295 && (pc.rel.hour | pc.rel.minutes | pc.rel.seconds | pc.rel.ns))
2296 {
2297 dbg_printf (_("after time adjustment (%+"PRIdMAX" hours, "
2298 "%+"PRIdMAX" minutes, "
2299 "%+"PRIdMAX" seconds, %+d ns),\n"),
2300 pc.rel.hour, pc.rel.minutes, pc.rel.seconds,
2301 pc.rel.ns);
2302 intmax_t t4i = t4;
2303 dbg_printf (_(" new time = %"PRIdMAX" epoch-seconds\n"), t4i);
2304
2305 /* Warn about crossing DST due to time adjustment.
2306 Example: https://bugs.gnu.org/8357
2307 env TZ=Europe/Helsinki \
2308 date --debug \
2309 -d 'Mon Mar 28 00:36:07 2011 EEST 24 hours ago'
2310
2311 This case is different than DST changes due to days adjustment,
2312 i.e., "1 day ago" vs "24 hours ago" are calculated in different
2313 places.
2314
2315 'tm.tm_isdst' contains the date after date adjustment. */
2316 struct tm lmt;
2317 if (tm.tm_isdst != -1 && localtime_rz (tz, &result->tv_sec, &lmt)
2318 && tm.tm_isdst != lmt.tm_isdst)
2319 dbg_printf (_("warning: daylight saving time changed after "
2320 "time adjustment\n"));
2321 }
2322 }
2323 }
2324
2325 if (debugging (&pc))
2326 {
2327 /* Special case: using 'date -u' simply set TZ=UTC0 */
2328 if (! tzstring)
2329 dbg_printf (_("timezone: system default\n"));
2330 else if (STREQ (tzstring, "UTC0"))
2331 dbg_printf (_("timezone: Universal Time\n"));
2332 else
2333 dbg_printf (_("timezone: TZ=\"%s\" environment value\n"), tzstring);
2334
2335 intmax_t sec = result->tv_sec;
2336 int nsec = result->tv_nsec;
2337 dbg_printf (_("final: %"PRIdMAX".%09d (epoch-seconds)\n"),
2338 sec, nsec);
2339
2340 struct tm gmt, lmt;
2341 bool got_utc = !!gmtime_r (&result->tv_sec, &gmt);
2342 if (got_utc)
2343 dbg_printf (_("final: %s (UTC)\n"),
2344 debug_strfdatetime (&gmt, NULL,
2345 dbg_tm, sizeof dbg_tm));
2346 if (localtime_rz (tz, &result->tv_sec, &lmt))
2347 {
2348 #ifdef HAVE_TM_GMTOFF
2349 bool got_utcoff = true;
2350 long int utcoff = lmt.tm_gmtoff;
2351 #else
2352 bool got_utcoff = got_utc;
2353 int utcoff;
2354 if (got_utcoff)
2355 utcoff = tm_diff (&lmt, &gmt);
2356 #endif
2357 if (got_utcoff)
2358 dbg_printf (_("final: %s (UTC%s)\n"),
2359 debug_strfdatetime (&lmt, NULL, dbg_tm, sizeof dbg_tm),
2360 time_zone_str (utcoff, time_zone_buf));
2361 else
2362 dbg_printf (_("final: %s (unknown time zone offset)\n"),
2363 debug_strfdatetime (&lmt, NULL, dbg_tm, sizeof dbg_tm));
2364 }
2365 }
2366
2367 ok = true;
2368
2369 fail:
2370 if (tz != tzdefault)
2371 tzfree (tz);
2372 free (tz1alloc);
2373 return ok;
2374 }
2375
2376 #ifdef GNULIB_PARSE_DATETIME2
2377 /* Parse a date/time string, storing the resulting time value into *RESULT.
2378 The string itself is pointed to by P. Return true if successful.
2379 P can be an incomplete or relative time specification; if so, use
2380 *NOW as the basis for the returned time. Default to timezone
2381 TZDEFAULT, which corresponds to tzalloc (TZSTRING). */
2382 bool
parse_datetime2(struct timespec * result,char const * p,struct timespec const * now,unsigned int flags,timezone_t tzdefault,char const * tzstring)2383 parse_datetime2 (struct timespec *result, char const *p,
2384 struct timespec const *now, unsigned int flags,
2385 timezone_t tzdefault, char const *tzstring)
2386 {
2387 return parse_datetime_body (result, p, now, flags, tzdefault, tzstring);
2388 }
2389 #endif
2390
2391
2392 /* The plain interface: run with debug=false and the default timezone. */
2393 bool
parse_datetime(struct timespec * result,char const * p,struct timespec const * now)2394 parse_datetime (struct timespec *result, char const *p,
2395 struct timespec const *now)
2396 {
2397 char const *tzstring = getenv ("TZ");
2398 timezone_t tz = tzalloc (tzstring);
2399 if (!tz)
2400 return false;
2401 bool ok = parse_datetime_body (result, p, now, 0, tz, tzstring);
2402 tzfree (tz);
2403 return ok;
2404 }
2405
2406 #if TEST
2407
2408 int
main(int ac,char ** av)2409 main (int ac, char **av)
2410 {
2411 char buff[BUFSIZ];
2412
2413 printf ("Enter date, or blank line to exit.\n\t> ");
2414 fflush (stdout);
2415
2416 buff[BUFSIZ - 1] = '\0';
2417 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
2418 {
2419 struct timespec d;
2420 struct tm const *tm;
2421 if (! parse_datetime (&d, buff, NULL))
2422 printf ("Bad format - couldn't convert.\n");
2423 else if (! (tm = localtime (&d.tv_sec)))
2424 {
2425 intmax_t sec = d.tv_sec;
2426 printf ("localtime (%"PRIdMAX") failed\n", sec);
2427 }
2428 else
2429 {
2430 int ns = d.tv_nsec;
2431 char tm_year_buf[TM_YEAR_BUFSIZE];
2432 printf ("%s-%02d-%02d %02d:%02d:%02d.%09d\n",
2433 tm_year_str (tm->tm_year, tm_year_buf),
2434 tm->tm_mon + 1, tm->tm_mday,
2435 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);
2436 }
2437 printf ("\t> ");
2438 fflush (stdout);
2439 }
2440 return 0;
2441 }
2442 #endif /* TEST */
2443