1 %{
2 /* Parse a string into an internal timestamp.
3
4 Copyright (C) 1999-2000, 2002-2018 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 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 "intprops.h"
39 #include "timespec.h"
40 #include "verify.h"
41 #include "strftime.h"
42
43 /* There's no need to extend the stack, so there's no need to involve
44 alloca. */
45 #define YYSTACK_USE_ALLOCA 0
46
47 /* Tell Bison how much stack space is needed. 20 should be plenty for
48 this grammar, which is not right recursive. Beware setting it too
49 high, since that might cause problems on machines whose
50 implementations have lame stack-overflow checking. */
51 #define YYMAXDEPTH 20
52 #define YYINITDEPTH YYMAXDEPTH
53
54 /* Since the code of parse-datetime.y is not included in the Emacs executable
55 itself, there is no need to #define static in this file. Even if
56 the code were included in the Emacs executable, it probably
57 wouldn't do any harm to #undef it here; this will only cause
58 problems if we try to write to a static variable, which I don't
59 think this code needs to do. */
60 #ifdef emacs
61 # undef static
62 #endif
63
64 #include <inttypes.h>
65 #include <c-ctype.h>
66 #include <limits.h>
67 #include <stdarg.h>
68 #include <stdio.h>
69 #include <stdlib.h>
70 #include <string.h>
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 /* The __attribute__ feature is available in gcc versions 2.5 and later.
86 The __-protected variants of the attributes 'format' and 'printf' are
87 accepted by gcc versions 2.6.4 (effectively 2.7) and later.
88 Enable _GL_ATTRIBUTE_FORMAT only if these are supported too, because
89 gnulib and libintl do '#define printf __printf__' when they override
90 the 'printf' function. */
91 #if 2 < __GNUC__ + (7 <= __GNUC_MINOR__)
92 # define _GL_ATTRIBUTE_FORMAT(spec) __attribute__ ((__format__ spec))
93 #else
94 # define _GL_ATTRIBUTE_FORMAT(spec) /* empty */
95 #endif
96
97 /* Shift A right by B bits portably, by dividing A by 2**B and
98 truncating towards minus infinity. A and B should be free of side
99 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
100 INT_BITS is the number of useful bits in an int. GNU code can
101 assume that INT_BITS is at least 32.
102
103 ISO C99 says that A >> B is implementation-defined if A < 0. Some
104 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
105 right in the usual way when A < 0, so SHR falls back on division if
106 ordinary A >> B doesn't seem to be the usual signed shift. */
107 #define SHR(a, b) \
108 (-1 >> 1 == -1 \
109 ? (a) >> (b) \
110 : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
111
112 #define HOUR(x) (60 * 60 * (x))
113
114 #define STREQ(a, b) (strcmp (a, b) == 0)
115
116 /* Verify that time_t is an integer as POSIX requires, and that every
117 time_t value fits in intmax_t. Please file a bug report if these
118 assumptions are false on your platform. */
119 verify (TYPE_IS_INTEGER (time_t));
120 verify (!TYPE_SIGNED (time_t) || INTMAX_MIN <= TYPE_MINIMUM (time_t));
121 verify (TYPE_MAXIMUM (time_t) <= INTMAX_MAX);
122
123 /* True if N is out of range for time_t. */
124 static bool
time_overflow(intmax_t n)125 time_overflow (intmax_t n)
126 {
127 return ! ((TYPE_SIGNED (time_t) ? TYPE_MINIMUM (time_t) <= n : 0 <= n)
128 && n <= TYPE_MAXIMUM (time_t));
129 }
130
131 /* Convert a possibly-signed character to an unsigned character. This is
132 a bit safer than casting to unsigned char, since it catches some type
133 errors that the cast doesn't. */
to_uchar(char ch)134 static unsigned char to_uchar (char ch) { return ch; }
135
136 static void _GL_ATTRIBUTE_FORMAT ((__printf__, 1, 2))
dbg_printf(char const * msg,...)137 dbg_printf (char const *msg, ...)
138 {
139 va_list args;
140 /* TODO: use gnulib's 'program_name' instead? */
141 fputs ("date: ", stderr);
142
143 va_start (args, msg);
144 vfprintf (stderr, msg, args);
145 va_end (args);
146 }
147
148
149 /* An integer value, and the number of digits in its textual
150 representation. */
151 typedef struct
152 {
153 bool negative;
154 intmax_t value;
155 ptrdiff_t digits;
156 } textint;
157
158 /* An entry in the lexical lookup table. */
159 typedef struct
160 {
161 char const *name;
162 int type;
163 int value;
164 } table;
165
166 /* Meridian: am, pm, or 24-hour style. */
167 enum { MERam, MERpm, MER24 };
168
169 /* A reasonable upper bound for the buffer used in debug output. */
170 enum { DBGBUFSIZE = 100 };
171
172 enum { BILLION = 1000000000, LOG10_BILLION = 9 };
173
174 /* Relative times. */
175 typedef struct
176 {
177 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
178 intmax_t year;
179 intmax_t month;
180 intmax_t day;
181 intmax_t hour;
182 intmax_t minutes;
183 intmax_t seconds;
184 int ns;
185 } relative_time;
186
187 #if HAVE_COMPOUND_LITERALS
188 # define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
189 #else
190 static relative_time const RELATIVE_TIME_0;
191 #endif
192
193 /* Information passed to and from the parser. */
194 typedef struct
195 {
196 /* The input string remaining to be parsed. */
197 const char *input;
198
199 /* N, if this is the Nth Tuesday. */
200 intmax_t day_ordinal;
201
202 /* Day of week; Sunday is 0. */
203 int day_number;
204
205 /* tm_isdst flag for the local zone. */
206 int local_isdst;
207
208 /* Time zone, in seconds east of UT. */
209 int time_zone;
210
211 /* Style used for time. */
212 int meridian;
213
214 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
215 textint year;
216 intmax_t month;
217 intmax_t day;
218 intmax_t hour;
219 intmax_t minutes;
220 struct timespec seconds; /* includes nanoseconds */
221
222 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
223 relative_time rel;
224
225 /* Presence or counts of nonterminals of various flavors parsed so far. */
226 bool timespec_seen;
227 bool rels_seen;
228 ptrdiff_t dates_seen;
229 ptrdiff_t days_seen;
230 ptrdiff_t local_zones_seen;
231 ptrdiff_t dsts_seen;
232 ptrdiff_t times_seen;
233 ptrdiff_t zones_seen;
234 bool year_seen;
235
236 /* Print debugging output to stderr. */
237 bool parse_datetime_debug;
238
239 /* Which of the 'seen' parts have been printed when debugging. */
240 bool debug_dates_seen;
241 bool debug_days_seen;
242 bool debug_local_zones_seen;
243 bool debug_times_seen;
244 bool debug_zones_seen;
245 bool debug_year_seen;
246
247 /* The user specified explicit ordinal day value. */
248 bool debug_ordinal_day_seen;
249
250 /* Table of local time zone abbreviations, terminated by a null entry. */
251 table local_time_zone_table[3];
252 } parser_control;
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 (!pc->parse_datetime_debug)
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 (!pc->parse_datetime_debug)
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 %pure-parser
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 (pc->parse_datetime_debug)
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 (pc->parse_datetime_debug)
832 dbg_printf (_("warning: value %"PRIdMAX" has less than 4 digits. "
833 "Assuming MM/DD/YY[YY]\n"),
834 $1.value);
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 Note 'T' is a special case, as it is used as the separator in ISO
1164 8601 date and time of day representation. */
1165 static table const military_table[] =
1166 {
1167 { "A", tZONE, -HOUR ( 1) },
1168 { "B", tZONE, -HOUR ( 2) },
1169 { "C", tZONE, -HOUR ( 3) },
1170 { "D", tZONE, -HOUR ( 4) },
1171 { "E", tZONE, -HOUR ( 5) },
1172 { "F", tZONE, -HOUR ( 6) },
1173 { "G", tZONE, -HOUR ( 7) },
1174 { "H", tZONE, -HOUR ( 8) },
1175 { "I", tZONE, -HOUR ( 9) },
1176 { "K", tZONE, -HOUR (10) },
1177 { "L", tZONE, -HOUR (11) },
1178 { "M", tZONE, -HOUR (12) },
1179 { "N", tZONE, HOUR ( 1) },
1180 { "O", tZONE, HOUR ( 2) },
1181 { "P", tZONE, HOUR ( 3) },
1182 { "Q", tZONE, HOUR ( 4) },
1183 { "R", tZONE, HOUR ( 5) },
1184 { "S", tZONE, HOUR ( 6) },
1185 { "T", 'T', 0 },
1186 { "U", tZONE, HOUR ( 8) },
1187 { "V", tZONE, HOUR ( 9) },
1188 { "W", tZONE, HOUR (10) },
1189 { "X", tZONE, HOUR (11) },
1190 { "Y", tZONE, HOUR (12) },
1191 { "Z", tZONE, HOUR ( 0) },
1192 { NULL, 0, 0 }
1193 };
1194
1195
1196
1197 /* Convert a time zone expressed as HH:MM into an integer count of
1198 seconds. If MM is negative, then S is of the form HHMM and needs
1199 to be picked apart; otherwise, S is of the form HH. As specified in
1200 http://www.opengroup.org/susv3xbd/xbd_chap08.html#tag_08_03, allow
1201 only valid TZ range, and consider first two digits as hours, if no
1202 minutes specified. Return true if successful. */
1203
1204 static bool
time_zone_hhmm(parser_control * pc,textint s,intmax_t mm)1205 time_zone_hhmm (parser_control *pc, textint s, intmax_t mm)
1206 {
1207 intmax_t n_minutes;
1208 bool overflow = false;
1209
1210 /* If the length of S is 1 or 2 and no minutes are specified,
1211 interpret it as a number of hours. */
1212 if (s.digits <= 2 && mm < 0)
1213 s.value *= 100;
1214
1215 if (mm < 0)
1216 n_minutes = (s.value / 100) * 60 + s.value % 100;
1217 else
1218 {
1219 overflow |= INT_MULTIPLY_WRAPV (s.value, 60, &n_minutes);
1220 overflow |= (s.negative
1221 ? INT_SUBTRACT_WRAPV (n_minutes, mm, &n_minutes)
1222 : INT_ADD_WRAPV (n_minutes, mm, &n_minutes));
1223 }
1224
1225 if (overflow || ! (-24 * 60 <= n_minutes && n_minutes <= 24 * 60))
1226 return false;
1227 pc->time_zone = n_minutes * 60;
1228 return true;
1229 }
1230
1231 static int
to_hour(intmax_t hours,int meridian)1232 to_hour (intmax_t hours, int meridian)
1233 {
1234 switch (meridian)
1235 {
1236 default: /* Pacify GCC. */
1237 case MER24:
1238 return 0 <= hours && hours < 24 ? hours : -1;
1239 case MERam:
1240 return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
1241 case MERpm:
1242 return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
1243 }
1244 }
1245
1246 enum { TM_YEAR_BASE = 1900 };
1247 enum { TM_YEAR_BUFSIZE = INT_BUFSIZE_BOUND (int) + 1 };
1248
1249 /* Convert TM_YEAR, a year minus 1900, to a string that is numerically
1250 correct even if subtracting 1900 would overflow. */
1251
1252 static char const *
tm_year_str(int tm_year,char buf[TM_YEAR_BUFSIZE])1253 tm_year_str (int tm_year, char buf[TM_YEAR_BUFSIZE])
1254 {
1255 verify (TM_YEAR_BASE % 100 == 0);
1256 sprintf (buf, &"-%02d%02d"[-TM_YEAR_BASE <= tm_year],
1257 abs (tm_year / 100 + TM_YEAR_BASE / 100),
1258 abs (tm_year % 100));
1259 return buf;
1260 }
1261
1262 /* Convert a text year number to a year minus 1900, working correctly
1263 even if the input is in the range INT_MAX .. INT_MAX + 1900 - 1. */
1264
1265 static bool
to_tm_year(textint textyear,bool debug,int * tm_year)1266 to_tm_year (textint textyear, bool debug, int *tm_year)
1267 {
1268 intmax_t year = textyear.value;
1269
1270 /* XPG4 suggests that years 00-68 map to 2000-2068, and
1271 years 69-99 map to 1969-1999. */
1272 if (0 <= year && textyear.digits == 2)
1273 {
1274 year += year < 69 ? 2000 : 1900;
1275 if (debug)
1276 dbg_printf (_("warning: adjusting year value %"PRIdMAX
1277 " to %"PRIdMAX"\n"),
1278 textyear.value, year);
1279 }
1280
1281 if (year < 0
1282 ? INT_SUBTRACT_WRAPV (-TM_YEAR_BASE, year, tm_year)
1283 : INT_SUBTRACT_WRAPV (year, TM_YEAR_BASE, tm_year))
1284 {
1285 if (debug)
1286 dbg_printf (_("error: out-of-range year %"PRIdMAX"\n"), year);
1287 return false;
1288 }
1289
1290 return true;
1291 }
1292
1293 static table const * _GL_ATTRIBUTE_PURE
lookup_zone(parser_control const * pc,char const * name)1294 lookup_zone (parser_control const *pc, char const *name)
1295 {
1296 table const *tp;
1297
1298 for (tp = universal_time_zone_table; tp->name; tp++)
1299 if (strcmp (name, tp->name) == 0)
1300 return tp;
1301
1302 /* Try local zone abbreviations before those in time_zone_table, as
1303 the local ones are more likely to be right. */
1304 for (tp = pc->local_time_zone_table; tp->name; tp++)
1305 if (strcmp (name, tp->name) == 0)
1306 return tp;
1307
1308 for (tp = time_zone_table; tp->name; tp++)
1309 if (strcmp (name, tp->name) == 0)
1310 return tp;
1311
1312 return NULL;
1313 }
1314
1315 #if ! HAVE_TM_GMTOFF
1316 /* Yield the difference between *A and *B,
1317 measured in seconds, ignoring leap seconds.
1318 The body of this function is taken directly from the GNU C Library;
1319 see strftime.c. */
1320 static int
tm_diff(const struct tm * a,const struct tm * b)1321 tm_diff (const struct tm *a, const struct tm *b)
1322 {
1323 /* Compute intervening leap days correctly even if year is negative.
1324 Take care to avoid int overflow in leap day calculations,
1325 but it's OK to assume that A and B are close to each other. */
1326 int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
1327 int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
1328 int a100 = a4 / 25 - (a4 % 25 < 0);
1329 int b100 = b4 / 25 - (b4 % 25 < 0);
1330 int a400 = SHR (a100, 2);
1331 int b400 = SHR (b100, 2);
1332 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
1333 int years = a->tm_year - b->tm_year;
1334 int days = (365 * years + intervening_leap_days
1335 + (a->tm_yday - b->tm_yday));
1336 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
1337 + (a->tm_min - b->tm_min))
1338 + (a->tm_sec - b->tm_sec));
1339 }
1340 #endif /* ! HAVE_TM_GMTOFF */
1341
1342 static table const *
lookup_word(parser_control const * pc,char * word)1343 lookup_word (parser_control const *pc, char *word)
1344 {
1345 char *p;
1346 char *q;
1347 ptrdiff_t wordlen;
1348 table const *tp;
1349 bool period_found;
1350 bool abbrev;
1351
1352 /* Make it uppercase. */
1353 for (p = word; *p; p++)
1354 *p = c_toupper (to_uchar (*p));
1355
1356 for (tp = meridian_table; tp->name; tp++)
1357 if (strcmp (word, tp->name) == 0)
1358 return tp;
1359
1360 /* See if we have an abbreviation for a month. */
1361 wordlen = strlen (word);
1362 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
1363
1364 for (tp = month_and_day_table; tp->name; tp++)
1365 if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
1366 return tp;
1367
1368 if ((tp = lookup_zone (pc, word)))
1369 return tp;
1370
1371 if (strcmp (word, dst_table[0].name) == 0)
1372 return dst_table;
1373
1374 for (tp = time_units_table; tp->name; tp++)
1375 if (strcmp (word, tp->name) == 0)
1376 return tp;
1377
1378 /* Strip off any plural and try the units table again. */
1379 if (word[wordlen - 1] == 'S')
1380 {
1381 word[wordlen - 1] = '\0';
1382 for (tp = time_units_table; tp->name; tp++)
1383 if (strcmp (word, tp->name) == 0)
1384 return tp;
1385 word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
1386 }
1387
1388 for (tp = relative_time_table; tp->name; tp++)
1389 if (strcmp (word, tp->name) == 0)
1390 return tp;
1391
1392 /* Military time zones. */
1393 if (wordlen == 1)
1394 for (tp = military_table; tp->name; tp++)
1395 if (word[0] == tp->name[0])
1396 return tp;
1397
1398 /* Drop out any periods and try the time zone table again. */
1399 for (period_found = false, p = q = word; (*p = *q); q++)
1400 if (*q == '.')
1401 period_found = true;
1402 else
1403 p++;
1404 if (period_found && (tp = lookup_zone (pc, word)))
1405 return tp;
1406
1407 return NULL;
1408 }
1409
1410 static int
yylex(union YYSTYPE * lvalp,parser_control * pc)1411 yylex (union YYSTYPE *lvalp, parser_control *pc)
1412 {
1413 unsigned char c;
1414
1415 for (;;)
1416 {
1417 while (c = *pc->input, c_isspace (c))
1418 pc->input++;
1419
1420 if (c_isdigit (c) || c == '-' || c == '+')
1421 {
1422 char const *p;
1423 int sign;
1424 intmax_t value = 0;
1425 if (c == '-' || c == '+')
1426 {
1427 sign = c == '-' ? -1 : 1;
1428 while (c = *++pc->input, c_isspace (c))
1429 continue;
1430 if (! c_isdigit (c))
1431 /* skip the '-' sign */
1432 continue;
1433 }
1434 else
1435 sign = 0;
1436 p = pc->input;
1437
1438 do
1439 {
1440 if (INT_MULTIPLY_WRAPV (value, 10, &value))
1441 return '?';
1442 if (INT_ADD_WRAPV (value, sign < 0 ? '0' - c : c - '0', &value))
1443 return '?';
1444 c = *++p;
1445 }
1446 while (c_isdigit (c));
1447
1448 if ((c == '.' || c == ',') && c_isdigit (p[1]))
1449 {
1450 time_t s;
1451 int ns;
1452 int digits;
1453
1454 if (time_overflow (value))
1455 return '?';
1456 s = value;
1457
1458 /* Accumulate fraction, to ns precision. */
1459 p++;
1460 ns = *p++ - '0';
1461 for (digits = 2; digits <= LOG10_BILLION; digits++)
1462 {
1463 ns *= 10;
1464 if (c_isdigit (*p))
1465 ns += *p++ - '0';
1466 }
1467
1468 /* Skip excess digits, truncating toward -Infinity. */
1469 if (sign < 0)
1470 for (; c_isdigit (*p); p++)
1471 if (*p != '0')
1472 {
1473 ns++;
1474 break;
1475 }
1476 while (c_isdigit (*p))
1477 p++;
1478
1479 /* Adjust to the timespec convention, which is that
1480 tv_nsec is always a positive offset even if tv_sec is
1481 negative. */
1482 if (sign < 0 && ns)
1483 {
1484 if (s == TYPE_MINIMUM (time_t))
1485 return '?';
1486 s--;
1487 ns = BILLION - ns;
1488 }
1489
1490 lvalp->timespec.tv_sec = s;
1491 lvalp->timespec.tv_nsec = ns;
1492 pc->input = p;
1493 return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
1494 }
1495 else
1496 {
1497 lvalp->textintval.negative = sign < 0;
1498 lvalp->textintval.value = value;
1499 lvalp->textintval.digits = p - pc->input;
1500 pc->input = p;
1501 return sign ? tSNUMBER : tUNUMBER;
1502 }
1503 }
1504
1505 if (c_isalpha (c))
1506 {
1507 char buff[20];
1508 char *p = buff;
1509 table const *tp;
1510
1511 do
1512 {
1513 if (p < buff + sizeof buff - 1)
1514 *p++ = c;
1515 c = *++pc->input;
1516 }
1517 while (c_isalpha (c) || c == '.');
1518
1519 *p = '\0';
1520 tp = lookup_word (pc, buff);
1521 if (! tp)
1522 {
1523 if (pc->parse_datetime_debug)
1524 dbg_printf (_("error: unknown word '%s'\n"), buff);
1525 return '?';
1526 }
1527 lvalp->intval = tp->value;
1528 return tp->type;
1529 }
1530
1531 if (c != '(')
1532 return to_uchar (*pc->input++);
1533
1534 ptrdiff_t count = 0;
1535 do
1536 {
1537 c = *pc->input++;
1538 if (c == '\0')
1539 return c;
1540 if (c == '(')
1541 count++;
1542 else if (c == ')')
1543 count--;
1544 }
1545 while (count != 0);
1546 }
1547 }
1548
1549 /* Do nothing if the parser reports an error. */
1550 static int
yyerror(parser_control const * pc _GL_UNUSED,char const * s _GL_UNUSED)1551 yyerror (parser_control const *pc _GL_UNUSED,
1552 char const *s _GL_UNUSED)
1553 {
1554 return 0;
1555 }
1556
1557 /* In timezone TZ, if *TM0 is the old and *TM1 is the new value of a
1558 struct tm after passing it to mktime_z, return true if it's OK that
1559 mktime_z returned T. It's not OK if *TM0 has out-of-range
1560 members. */
1561
1562 static bool
mktime_ok(timezone_t tz,struct tm const * tm0,struct tm const * tm1,time_t t)1563 mktime_ok (timezone_t tz, struct tm const *tm0, struct tm const *tm1, time_t t)
1564 {
1565 struct tm ltm;
1566 if (t == (time_t) -1)
1567 {
1568 /* Guard against falsely reporting an error when parsing a
1569 timestamp that happens to equal (time_t) -1, on a host that
1570 supports such a timestamp. */
1571 tm1 = localtime_rz (tz, &t, <m);
1572 if (!tm1)
1573 return false;
1574 }
1575
1576 return ! ((tm0->tm_sec ^ tm1->tm_sec)
1577 | (tm0->tm_min ^ tm1->tm_min)
1578 | (tm0->tm_hour ^ tm1->tm_hour)
1579 | (tm0->tm_mday ^ tm1->tm_mday)
1580 | (tm0->tm_mon ^ tm1->tm_mon)
1581 | (tm0->tm_year ^ tm1->tm_year));
1582 }
1583
1584 /* Debugging: format a 'struct tm' into a buffer, taking the parser's
1585 timezone information into account (if pc != NULL). */
1586 static char const *
debug_strfdatetime(struct tm const * tm,parser_control const * pc,char * buf,int n)1587 debug_strfdatetime (struct tm const *tm, parser_control const *pc,
1588 char *buf, int n)
1589 {
1590 /* TODO:
1591 1. find an optimal way to print date string in a clear and unambiguous
1592 format. Currently, always add '(Y-M-D)' prefix.
1593 Consider '2016y01m10d' or 'year(2016) month(01) day(10)'.
1594
1595 If the user needs debug printing, it means he/she already having
1596 issues with the parsing - better to avoid formats that could
1597 be mis-interpreted (e.g., just YYYY-MM-DD).
1598
1599 2. Can strftime be used instead?
1600 depends if it is portable and can print invalid dates on all systems.
1601
1602 3. Print timezone information ?
1603
1604 4. Print DST information ?
1605
1606 5. Print nanosecond information ?
1607
1608 NOTE:
1609 Printed date/time values might not be valid, e.g., '2016-02-31'
1610 or '2016-19-2016' . These are the values as parsed from the user
1611 string, before validation.
1612 */
1613 int m = nstrftime (buf, n, "(Y-M-D) %Y-%m-%d %H:%M:%S", tm, 0, 0);
1614
1615 /* If parser_control information was provided (for timezone),
1616 and there's enough space in the buffer, add timezone info. */
1617 if (pc && m < n && pc->zones_seen)
1618 {
1619 int tz = pc->time_zone;
1620
1621 /* Account for DST if tLOCAL_ZONE was seen. */
1622 if (pc->local_zones_seen && !pc->zones_seen && 0 < pc->local_isdst)
1623 tz += 60 * 60;
1624
1625 char time_zone_buf[TIME_ZONE_BUFSIZE];
1626 snprintf (&buf[m], n - m, " TZ=%s", time_zone_str (tz, time_zone_buf));
1627 }
1628 return buf;
1629 }
1630
1631 static char const *
debug_strfdate(struct tm const * tm,char * buf,int n)1632 debug_strfdate (struct tm const *tm, char *buf, int n)
1633 {
1634 char tm_year_buf[TM_YEAR_BUFSIZE];
1635 snprintf (buf, n, "(Y-M-D) %s-%02d-%02d",
1636 tm_year_str (tm->tm_year, tm_year_buf),
1637 tm->tm_mon + 1, tm->tm_mday);
1638 return buf;
1639 }
1640
1641 static char const *
debug_strftime(struct tm const * tm,char * buf,int n)1642 debug_strftime (struct tm const *tm, char *buf, int n)
1643 {
1644 snprintf (buf, n, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
1645 return buf;
1646 }
1647
1648 /* If mktime_ok failed, display the failed time values,
1649 and provide possible hints. Example output:
1650
1651 date: error: invalid date/time value:
1652 date: user provided time: '(Y-M-D) 2006-04-02 02:45:00'
1653 date: normalized time: '(Y-M-D) 2006-04-02 03:45:00'
1654 date: __
1655 date: possible reasons:
1656 date: non-existing due to daylight-saving time;
1657 date: numeric values overflow;
1658 date: missing timezone;
1659 */
1660 static void
debug_mktime_not_ok(struct tm const * tm0,struct tm const * tm1,parser_control const * pc,bool time_zone_seen)1661 debug_mktime_not_ok (struct tm const *tm0, struct tm const *tm1,
1662 parser_control const *pc, bool time_zone_seen)
1663 {
1664 /* TODO: handle t == -1 (as in 'mktime_ok'). */
1665 char tmp[DBGBUFSIZE];
1666 int i;
1667 const bool eq_sec = (tm0->tm_sec == tm1->tm_sec);
1668 const bool eq_min = (tm0->tm_min == tm1->tm_min);
1669 const bool eq_hour = (tm0->tm_hour == tm1->tm_hour);
1670 const bool eq_mday = (tm0->tm_mday == tm1->tm_mday);
1671 const bool eq_month = (tm0->tm_mon == tm1->tm_mon);
1672 const bool eq_year = (tm0->tm_year == tm1->tm_year);
1673
1674 const bool dst_shift = eq_sec && eq_min && !eq_hour
1675 && eq_mday && eq_month && eq_year;
1676
1677 if (!pc->parse_datetime_debug)
1678 return;
1679
1680 dbg_printf (_("error: invalid date/time value:\n"));
1681 dbg_printf (_(" user provided time: '%s'\n"),
1682 debug_strfdatetime (tm0, pc, tmp, sizeof tmp));
1683 dbg_printf (_(" normalized time: '%s'\n"),
1684 debug_strfdatetime (tm1, pc, tmp, sizeof tmp));
1685 /* The format must be aligned with debug_strfdatetime and the two
1686 DEBUG statements above. This string is not translated. */
1687 i = snprintf (tmp, sizeof tmp,
1688 " %4s %2s %2s %2s %2s %2s",
1689 eq_year ? "" : "----",
1690 eq_month ? "" : "--",
1691 eq_mday ? "" : "--",
1692 eq_hour ? "" : "--",
1693 eq_min ? "" : "--",
1694 eq_sec ? "" : "--");
1695 /* Trim trailing whitespace. */
1696 if (0 <= i)
1697 {
1698 if (sizeof tmp - 1 < i)
1699 i = sizeof tmp - 1;
1700 while (0 < i && tmp[i - 1] == ' ')
1701 --i;
1702 tmp[i] = '\0';
1703 }
1704 dbg_printf ("%s\n", tmp);
1705
1706 dbg_printf (_(" possible reasons:\n"));
1707 if (dst_shift)
1708 dbg_printf (_(" non-existing due to daylight-saving time;\n"));
1709 if (!eq_mday && !eq_month)
1710 dbg_printf (_(" invalid day/month combination;\n"));
1711 dbg_printf (_(" numeric values overflow;\n"));
1712 dbg_printf (" %s\n", (time_zone_seen ? _("incorrect timezone")
1713 : _("missing timezone")));
1714 }
1715
1716 /* The original interface: run with debug=false and the default timezone. */
1717 bool
parse_datetime(struct timespec * result,char const * p,struct timespec const * now)1718 parse_datetime (struct timespec *result, char const *p,
1719 struct timespec const *now)
1720 {
1721 char const *tzstring = getenv ("TZ");
1722 timezone_t tz = tzalloc (tzstring);
1723 if (!tz)
1724 return false;
1725 bool ok = parse_datetime2 (result, p, now, 0, tz, tzstring);
1726 tzfree (tz);
1727 return ok;
1728 }
1729
1730 /* Parse a date/time string, storing the resulting time value into *RESULT.
1731 The string itself is pointed to by P. Return true if successful.
1732 P can be an incomplete or relative time specification; if so, use
1733 *NOW as the basis for the returned time. Default to timezone
1734 TZDEFAULT, which corresponds to tzalloc (TZSTRING). */
1735 bool
parse_datetime2(struct timespec * result,char const * p,struct timespec const * now,unsigned int flags,timezone_t tzdefault,char const * tzstring)1736 parse_datetime2 (struct timespec *result, char const *p,
1737 struct timespec const *now, unsigned int flags,
1738 timezone_t tzdefault, char const *tzstring)
1739 {
1740 struct tm tm;
1741 struct tm tm0;
1742 char time_zone_buf[TIME_ZONE_BUFSIZE];
1743 char dbg_tm[DBGBUFSIZE];
1744 bool ok = false;
1745 char const *input_sentinel = p + strlen (p);
1746 char *tz1alloc = NULL;
1747
1748 /* A reasonable upper bound for the size of ordinary TZ strings.
1749 Use heap allocation if TZ's length exceeds this. */
1750 enum { TZBUFSIZE = 100 };
1751 char tz1buf[TZBUFSIZE];
1752
1753 struct timespec gettime_buffer;
1754 if (! now)
1755 {
1756 gettime (&gettime_buffer);
1757 now = &gettime_buffer;
1758 }
1759
1760 time_t Start = now->tv_sec;
1761 int Start_ns = now->tv_nsec;
1762
1763 unsigned char c;
1764 while (c = *p, c_isspace (c))
1765 p++;
1766
1767 timezone_t tz = tzdefault;
1768
1769 /* Store a local copy prior to first "goto". Without this, a prior use
1770 below of RELATIVE_TIME_0 on the RHS might translate to an assignment-
1771 to-temporary, which would trigger a -Wjump-misses-init warning. */
1772 const relative_time rel_time_0 = RELATIVE_TIME_0;
1773
1774 if (strncmp (p, "TZ=\"", 4) == 0)
1775 {
1776 char const *tzbase = p + 4;
1777 ptrdiff_t tzsize = 1;
1778 char const *s;
1779
1780 for (s = tzbase; *s; s++, tzsize++)
1781 if (*s == '\\')
1782 {
1783 s++;
1784 if (! (*s == '\\' || *s == '"'))
1785 break;
1786 }
1787 else if (*s == '"')
1788 {
1789 timezone_t tz1;
1790 char *tz1string = tz1buf;
1791 char *z;
1792 if (TZBUFSIZE < tzsize)
1793 {
1794 tz1alloc = malloc (tzsize);
1795 if (!tz1alloc)
1796 goto fail;
1797 tz1string = tz1alloc;
1798 }
1799 z = tz1string;
1800 for (s = tzbase; *s != '"'; s++)
1801 *z++ = *(s += *s == '\\');
1802 *z = '\0';
1803 tz1 = tzalloc (tz1string);
1804 if (!tz1)
1805 goto fail;
1806 tz = tz1;
1807 tzstring = tz1string;
1808
1809 p = s + 1;
1810 while (c = *p, c_isspace (c))
1811 p++;
1812
1813 break;
1814 }
1815 }
1816
1817 struct tm tmp;
1818 if (! localtime_rz (tz, &now->tv_sec, &tmp))
1819 goto fail;
1820
1821 /* As documented, be careful to treat the empty string just like
1822 a date string of "0". Without this, an empty string would be
1823 declared invalid when parsed during a DST transition. */
1824 if (*p == '\0')
1825 p = "0";
1826
1827 parser_control pc;
1828 pc.input = p;
1829 pc.parse_datetime_debug = (flags & PARSE_DATETIME_DEBUG) != 0;
1830 if (INT_ADD_WRAPV (tmp.tm_year, TM_YEAR_BASE, &pc.year.value))
1831 {
1832 if (pc.parse_datetime_debug)
1833 dbg_printf (_("error: initial year out of range\n"));
1834 goto fail;
1835 }
1836 pc.year.digits = 0;
1837 pc.month = tmp.tm_mon + 1;
1838 pc.day = tmp.tm_mday;
1839 pc.hour = tmp.tm_hour;
1840 pc.minutes = tmp.tm_min;
1841 pc.seconds.tv_sec = tmp.tm_sec;
1842 pc.seconds.tv_nsec = Start_ns;
1843 tm.tm_isdst = tmp.tm_isdst;
1844
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.local_zones_seen = 0;
1853 pc.dsts_seen = 0;
1854 pc.zones_seen = 0;
1855 pc.year_seen = false;
1856 pc.debug_dates_seen = false;
1857 pc.debug_days_seen = false;
1858 pc.debug_times_seen = false;
1859 pc.debug_local_zones_seen = false;
1860 pc.debug_zones_seen = false;
1861 pc.debug_year_seen = false;
1862 pc.debug_ordinal_day_seen = false;
1863
1864 #if HAVE_STRUCT_TM_TM_ZONE
1865 pc.local_time_zone_table[0].name = tmp.tm_zone;
1866 pc.local_time_zone_table[0].type = tLOCAL_ZONE;
1867 pc.local_time_zone_table[0].value = tmp.tm_isdst;
1868 pc.local_time_zone_table[1].name = NULL;
1869
1870 /* Probe the names used in the next three calendar quarters, looking
1871 for a tm_isdst different from the one we already have. */
1872 {
1873 int quarter;
1874 for (quarter = 1; quarter <= 3; quarter++)
1875 {
1876 intmax_t iprobe;
1877 if (INT_ADD_WRAPV (Start, quarter * (90 * 24 * 60 * 60), &iprobe)
1878 || time_overflow (iprobe))
1879 break;
1880 time_t probe = iprobe;
1881 struct tm probe_tm;
1882 if (localtime_rz (tz, &probe, &probe_tm) && probe_tm.tm_zone
1883 && probe_tm.tm_isdst != pc.local_time_zone_table[0].value)
1884 {
1885 {
1886 pc.local_time_zone_table[1].name = probe_tm.tm_zone;
1887 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
1888 pc.local_time_zone_table[1].value = probe_tm.tm_isdst;
1889 pc.local_time_zone_table[2].name = NULL;
1890 }
1891 break;
1892 }
1893 }
1894 }
1895 #else
1896 #if HAVE_TZNAME
1897 {
1898 # if !HAVE_DECL_TZNAME
1899 extern char *tzname[];
1900 # endif
1901 int i;
1902 for (i = 0; i < 2; i++)
1903 {
1904 pc.local_time_zone_table[i].name = tzname[i];
1905 pc.local_time_zone_table[i].type = tLOCAL_ZONE;
1906 pc.local_time_zone_table[i].value = i;
1907 }
1908 pc.local_time_zone_table[i].name = NULL;
1909 }
1910 #else
1911 pc.local_time_zone_table[0].name = NULL;
1912 #endif
1913 #endif
1914
1915 if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
1916 && ! strcmp (pc.local_time_zone_table[0].name,
1917 pc.local_time_zone_table[1].name))
1918 {
1919 /* This locale uses the same abbreviation for standard and
1920 daylight times. So if we see that abbreviation, we don't
1921 know whether it's daylight time. */
1922 pc.local_time_zone_table[0].value = -1;
1923 pc.local_time_zone_table[1].name = NULL;
1924 }
1925
1926 if (yyparse (&pc) != 0)
1927 {
1928 if (pc.parse_datetime_debug)
1929 dbg_printf ((input_sentinel <= pc.input
1930 ? _("error: parsing failed\n")
1931 : _("error: parsing failed, stopped at '%s'\n")),
1932 pc.input);
1933 goto fail;
1934 }
1935
1936
1937 /* Determine effective timezone source. */
1938
1939 if (pc.parse_datetime_debug)
1940 {
1941 dbg_printf (_("input timezone: "));
1942
1943 if (pc.timespec_seen)
1944 fprintf (stderr, _("'@timespec' - always UTC"));
1945 else if (pc.zones_seen)
1946 fprintf (stderr, _("parsed date/time string"));
1947 else if (tzstring)
1948 {
1949 if (tz != tzdefault)
1950 fprintf (stderr, _("TZ=\"%s\" in date string"), tzstring);
1951 else if (STREQ (tzstring, "UTC0"))
1952 {
1953 /* Special case: 'date -u' sets TZ="UTC0". */
1954 fprintf (stderr, _("TZ=\"UTC0\" environment value or -u"));
1955 }
1956 else
1957 fprintf (stderr, _("TZ=\"%s\" environment value"), tzstring);
1958 }
1959 else
1960 fprintf (stderr, _("system default"));
1961
1962 /* Account for DST changes if tLOCAL_ZONE was seen.
1963 local timezone only changes DST and is relative to the
1964 default timezone.*/
1965 if (pc.local_zones_seen && !pc.zones_seen && 0 < pc.local_isdst)
1966 fprintf (stderr, ", dst");
1967
1968 if (pc.zones_seen)
1969 fprintf (stderr, " (%s)", time_zone_str (pc.time_zone, time_zone_buf));
1970
1971 fputc ('\n', stderr);
1972 }
1973
1974 if (pc.timespec_seen)
1975 *result = pc.seconds;
1976 else
1977 {
1978 if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
1979 | (pc.local_zones_seen + pc.zones_seen)))
1980 {
1981 if (pc.parse_datetime_debug)
1982 {
1983 if (pc.times_seen > 1)
1984 dbg_printf ("error: seen multiple time parts\n");
1985 if (pc.dates_seen > 1)
1986 dbg_printf ("error: seen multiple date parts\n");
1987 if (pc.days_seen > 1)
1988 dbg_printf ("error: seen multiple days parts\n");
1989 if (pc.dsts_seen > 1)
1990 dbg_printf ("error: seen multiple daylight-saving parts\n");
1991 if ((pc.local_zones_seen + pc.zones_seen) > 1)
1992 dbg_printf ("error: seen multiple time-zone parts\n");
1993 }
1994 goto fail;
1995 }
1996
1997 if (! to_tm_year (pc.year, pc.parse_datetime_debug, &tm.tm_year)
1998 || INT_ADD_WRAPV (pc.month, -1, &tm.tm_mon)
1999 || INT_ADD_WRAPV (pc.day, 0, &tm.tm_mday))
2000 {
2001 if (pc.parse_datetime_debug)
2002 dbg_printf (_("error: year, month, or day overflow\n"));
2003 goto fail;
2004 }
2005 if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
2006 {
2007 tm.tm_hour = to_hour (pc.hour, pc.meridian);
2008 if (tm.tm_hour < 0)
2009 {
2010 char const *mrd = (pc.meridian == MERam ? "am"
2011 : pc.meridian == MERpm ?"pm" : "");
2012 if (pc.parse_datetime_debug)
2013 dbg_printf (_("error: invalid hour %"PRIdMAX"%s\n"),
2014 pc.hour, mrd);
2015 goto fail;
2016 }
2017 tm.tm_min = pc.minutes;
2018 tm.tm_sec = pc.seconds.tv_sec;
2019 if (pc.parse_datetime_debug)
2020 dbg_printf ((pc.times_seen
2021 ? _("using specified time as starting value: '%s'\n")
2022 : _("using current time as starting value: '%s'\n")),
2023 debug_strftime (&tm, dbg_tm, sizeof dbg_tm));
2024 }
2025 else
2026 {
2027 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
2028 pc.seconds.tv_nsec = 0;
2029 if (pc.parse_datetime_debug)
2030 dbg_printf ("warning: using midnight as starting time: 00:00:00\n");
2031 }
2032
2033 /* Let mktime deduce tm_isdst if we have an absolute timestamp. */
2034 if (pc.dates_seen | pc.days_seen | pc.times_seen)
2035 tm.tm_isdst = -1;
2036
2037 /* But if the input explicitly specifies local time with or without
2038 DST, give mktime that information. */
2039 if (pc.local_zones_seen)
2040 tm.tm_isdst = pc.local_isdst;
2041
2042 tm0.tm_sec = tm.tm_sec;
2043 tm0.tm_min = tm.tm_min;
2044 tm0.tm_hour = tm.tm_hour;
2045 tm0.tm_mday = tm.tm_mday;
2046 tm0.tm_mon = tm.tm_mon;
2047 tm0.tm_year = tm.tm_year;
2048 tm0.tm_isdst = tm.tm_isdst;
2049
2050 Start = mktime_z (tz, &tm);
2051
2052 if (! mktime_ok (tz, &tm0, &tm, Start))
2053 {
2054 bool repaired = false;
2055 bool time_zone_seen = pc.zones_seen != 0;
2056 if (time_zone_seen)
2057 {
2058 /* Guard against falsely reporting errors near the time_t
2059 boundaries when parsing times in other time zones. For
2060 example, suppose the input string "1969-12-31 23:00:00 -0100",
2061 the current time zone is 8 hours ahead of UTC, and the min
2062 time_t value is 1970-01-01 00:00:00 UTC. Then the min
2063 localtime value is 1970-01-01 08:00:00, and mktime will
2064 therefore fail on 1969-12-31 23:00:00. To work around the
2065 problem, set the time zone to 1 hour behind UTC temporarily
2066 by setting TZ="XXX1:00" and try mktime again. */
2067
2068 char tz2buf[sizeof "XXX" - 1 + TIME_ZONE_BUFSIZE];
2069 tz2buf[0] = tz2buf[1] = tz2buf[2] = 'X';
2070 time_zone_str (pc.time_zone, &tz2buf[3]);
2071 timezone_t tz2 = tzalloc (tz2buf);
2072 if (!tz2)
2073 {
2074 if (pc.parse_datetime_debug)
2075 dbg_printf (_("error: tzalloc (\"%s\") failed\n"), tz2buf);
2076 goto fail;
2077 }
2078 tm.tm_sec = tm0.tm_sec;
2079 tm.tm_min = tm0.tm_min;
2080 tm.tm_hour = tm0.tm_hour;
2081 tm.tm_mday = tm0.tm_mday;
2082 tm.tm_mon = tm0.tm_mon;
2083 tm.tm_year = tm0.tm_year;
2084 tm.tm_isdst = tm0.tm_isdst;
2085 Start = mktime_z (tz2, &tm);
2086 repaired = mktime_ok (tz2, &tm0, &tm, Start);
2087 tzfree (tz2);
2088 }
2089
2090 if (! repaired)
2091 {
2092 debug_mktime_not_ok (&tm0, &tm, &pc, time_zone_seen);
2093 goto fail;
2094 }
2095 }
2096
2097 char dbg_ord[DBGBUFSIZE];
2098
2099 if (pc.days_seen && ! pc.dates_seen)
2100 {
2101 intmax_t dayincr;
2102 if (INT_MULTIPLY_WRAPV ((pc.day_ordinal
2103 - (0 < pc.day_ordinal
2104 && tm.tm_wday != pc.day_number)),
2105 7, &dayincr)
2106 || INT_ADD_WRAPV ((pc.day_number - tm.tm_wday + 7) % 7,
2107 dayincr, &dayincr)
2108 || INT_ADD_WRAPV (dayincr, tm.tm_mday, &tm.tm_mday))
2109 Start = -1;
2110 else
2111 {
2112 tm.tm_isdst = -1;
2113 Start = mktime_z (tz, &tm);
2114 }
2115
2116 if (Start == (time_t) -1)
2117 {
2118 if (pc.parse_datetime_debug)
2119 dbg_printf (_("error: day '%s' "
2120 "(day ordinal=%"PRIdMAX" number=%d) "
2121 "resulted in an invalid date: '%s'\n"),
2122 str_days (&pc, dbg_ord, sizeof dbg_ord),
2123 pc.day_ordinal, pc.day_number,
2124 debug_strfdatetime (&tm, &pc, dbg_tm,
2125 sizeof dbg_tm));
2126 goto fail;
2127 }
2128
2129 if (pc.parse_datetime_debug)
2130 dbg_printf (_("new start date: '%s' is '%s'\n"),
2131 str_days (&pc, dbg_ord, sizeof dbg_ord),
2132 debug_strfdatetime (&tm, &pc, dbg_tm, sizeof dbg_tm));
2133
2134 }
2135
2136 if (pc.parse_datetime_debug)
2137 {
2138 if (!pc.dates_seen && !pc.days_seen)
2139 dbg_printf (_("using current date as starting value: '%s'\n"),
2140 debug_strfdate (&tm, dbg_tm, sizeof dbg_tm));
2141
2142 if (pc.days_seen && pc.dates_seen)
2143 dbg_printf (_("warning: day (%s) ignored when explicit dates "
2144 "are given\n"),
2145 str_days (&pc, dbg_ord, sizeof dbg_ord));
2146
2147 dbg_printf (_("starting date/time: '%s'\n"),
2148 debug_strfdatetime (&tm, &pc, dbg_tm, sizeof dbg_tm));
2149 }
2150
2151 /* Add relative date. */
2152 if (pc.rel.year | pc.rel.month | pc.rel.day)
2153 {
2154 if (pc.parse_datetime_debug)
2155 {
2156 if ((pc.rel.year != 0 || pc.rel.month != 0) && tm.tm_mday != 15)
2157 dbg_printf (_("warning: when adding relative months/years, "
2158 "it is recommended to specify the 15th of the "
2159 "months\n"));
2160
2161 if (pc.rel.day != 0 && tm.tm_hour != 12)
2162 dbg_printf (_("warning: when adding relative days, "
2163 "it is recommended to specify noon\n"));
2164 }
2165
2166 int year, month, day;
2167 if (INT_ADD_WRAPV (tm.tm_year, pc.rel.year, &year)
2168 || INT_ADD_WRAPV (tm.tm_mon, pc.rel.month, &month)
2169 || INT_ADD_WRAPV (tm.tm_mday, pc.rel.day, &day))
2170 {
2171 if (pc.parse_datetime_debug)
2172 dbg_printf (_("error: %s:%d\n"), __FILE__, __LINE__);
2173 goto fail;
2174 }
2175 tm.tm_year = year;
2176 tm.tm_mon = month;
2177 tm.tm_mday = day;
2178 tm.tm_hour = tm0.tm_hour;
2179 tm.tm_min = tm0.tm_min;
2180 tm.tm_sec = tm0.tm_sec;
2181 tm.tm_isdst = tm0.tm_isdst;
2182 Start = mktime_z (tz, &tm);
2183 if (Start == (time_t) -1)
2184 {
2185 if (pc.parse_datetime_debug)
2186 dbg_printf (_("error: adding relative date resulted "
2187 "in an invalid date: '%s'\n"),
2188 debug_strfdatetime (&tm, &pc, dbg_tm,
2189 sizeof dbg_tm));
2190 goto fail;
2191 }
2192
2193 if (pc.parse_datetime_debug)
2194 {
2195 dbg_printf (_("after date adjustment "
2196 "(%+"PRIdMAX" years, %+"PRIdMAX" months, "
2197 "%+"PRIdMAX" days),\n"),
2198 pc.rel.year, pc.rel.month, pc.rel.day);
2199 dbg_printf (_(" new date/time = '%s'\n"),
2200 debug_strfdatetime (&tm, &pc, dbg_tm,
2201 sizeof dbg_tm));
2202
2203 /* Warn about crossing DST due to time adjustment.
2204 Example: https://bugs.gnu.org/8357
2205 env TZ=Europe/Helsinki \
2206 date --debug \
2207 -d 'Mon Mar 28 00:36:07 2011 EEST 1 day ago'
2208
2209 This case is different than DST changes due to time adjustment,
2210 i.e., "1 day ago" vs "24 hours ago" are calculated in different
2211 places.
2212
2213 'tm0.tm_isdst' contains the DST of the input date,
2214 'tm.tm_isdst' is the normalized result after calling
2215 mktime (&tm).
2216 */
2217 if (tm0.tm_isdst != -1 && tm.tm_isdst != tm0.tm_isdst)
2218 dbg_printf (_("warning: daylight saving time changed after "
2219 "date adjustment\n"));
2220
2221 /* Warn if the user did not ask to adjust days but mday changed,
2222 or
2223 user did not ask to adjust months/days but the month changed.
2224
2225 Example for first case:
2226 2016-05-31 + 1 month => 2016-06-31 => 2016-07-01.
2227 User asked to adjust month, but the day changed from 31 to 01.
2228
2229 Example for second case:
2230 2016-02-29 + 1 year => 2017-02-29 => 2017-03-01.
2231 User asked to adjust year, but the month changed from 02 to 03.
2232 */
2233 if (pc.rel.day == 0
2234 && (tm.tm_mday != day
2235 || (pc.rel.month == 0 && tm.tm_mon != month)))
2236 {
2237 dbg_printf (_("warning: month/year adjustment resulted in "
2238 "shifted dates:\n"));
2239 char tm_year_buf[TM_YEAR_BUFSIZE];
2240 dbg_printf (_(" adjusted Y M D: %s %02d %02d\n"),
2241 tm_year_str (year, tm_year_buf), month + 1, day);
2242 dbg_printf (_(" normalized Y M D: %s %02d %02d\n"),
2243 tm_year_str (tm.tm_year, tm_year_buf),
2244 tm.tm_mon + 1, tm.tm_mday);
2245 }
2246 }
2247
2248 }
2249
2250 /* The only "output" of this if-block is an updated Start value,
2251 so this block must follow others that clobber Start. */
2252 if (pc.zones_seen)
2253 {
2254 intmax_t delta = pc.time_zone, t1;
2255 bool overflow = false;
2256 #ifdef HAVE_TM_GMTOFF
2257 long int utcoff = tm.tm_gmtoff;
2258 #else
2259 time_t t = Start;
2260 struct tm gmt;
2261 int utcoff = (gmtime_r (&t, &gmt)
2262 ? tm_diff (&tm, &gmt)
2263 : (overflow = true, 0));
2264 #endif
2265 overflow |= INT_SUBTRACT_WRAPV (delta, utcoff, &delta);
2266 overflow |= INT_SUBTRACT_WRAPV (Start, delta, &t1);
2267 if (overflow || time_overflow (t1))
2268 {
2269 if (pc.parse_datetime_debug)
2270 dbg_printf (_("error: timezone %d caused time_t overflow\n"),
2271 pc.time_zone);
2272 goto fail;
2273 }
2274 Start = t1;
2275 }
2276
2277 if (pc.parse_datetime_debug)
2278 {
2279 intmax_t Starti = Start;
2280 dbg_printf (_("'%s' = %"PRIdMAX" epoch-seconds\n"),
2281 debug_strfdatetime (&tm, &pc, dbg_tm, sizeof dbg_tm),
2282 Starti);
2283 }
2284
2285
2286 /* Add relative hours, minutes, and seconds. On hosts that support
2287 leap seconds, ignore the possibility of leap seconds; e.g.,
2288 "+ 10 minutes" adds 600 seconds, even if one of them is a
2289 leap second. Typically this is not what the user wants, but it's
2290 too hard to do it the other way, because the time zone indicator
2291 must be applied before relative times, and if mktime is applied
2292 again the time zone will be lost. */
2293 {
2294 intmax_t orig_ns = pc.seconds.tv_nsec;
2295 intmax_t sum_ns = orig_ns + pc.rel.ns;
2296 int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
2297 int d4 = (sum_ns - normalized_ns) / BILLION;
2298 intmax_t d1, t1, d2, t2, t3, t4;
2299 if (INT_MULTIPLY_WRAPV (pc.rel.hour, 60 * 60, &d1)
2300 || INT_ADD_WRAPV (Start, d1, &t1)
2301 || INT_MULTIPLY_WRAPV (pc.rel.minutes, 60, &d2)
2302 || INT_ADD_WRAPV (t1, d2, &t2)
2303 || INT_ADD_WRAPV (t2, pc.rel.seconds, &t3)
2304 || INT_ADD_WRAPV (t3, d4, &t4)
2305 || time_overflow (t4))
2306 {
2307 if (pc.parse_datetime_debug)
2308 dbg_printf (_("error: adding relative time caused an "
2309 "overflow\n"));
2310 goto fail;
2311 }
2312
2313 result->tv_sec = t4;
2314 result->tv_nsec = normalized_ns;
2315
2316 if (pc.parse_datetime_debug
2317 && (pc.rel.hour | pc.rel.minutes | pc.rel.seconds | pc.rel.ns))
2318 {
2319 dbg_printf (_("after time adjustment (%+"PRIdMAX" hours, "
2320 "%+"PRIdMAX" minutes, "
2321 "%+"PRIdMAX" seconds, %+d ns),\n"),
2322 pc.rel.hour, pc.rel.minutes, pc.rel.seconds,
2323 pc.rel.ns);
2324 dbg_printf (_(" new time = %"PRIdMAX" epoch-seconds\n"), t4);
2325
2326 /* Warn about crossing DST due to time adjustment.
2327 Example: https://bugs.gnu.org/8357
2328 env TZ=Europe/Helsinki \
2329 date --debug \
2330 -d 'Mon Mar 28 00:36:07 2011 EEST 24 hours ago'
2331
2332 This case is different than DST changes due to days adjustment,
2333 i.e., "1 day ago" vs "24 hours ago" are calculated in different
2334 places.
2335
2336 'tm.tm_isdst' contains the date after date adjustment. */
2337 struct tm lmt;
2338 if (tm.tm_isdst != -1 && localtime_rz (tz, &result->tv_sec, &lmt)
2339 && tm.tm_isdst != lmt.tm_isdst)
2340 dbg_printf (_("warning: daylight saving time changed after "
2341 "time adjustment\n"));
2342 }
2343 }
2344 }
2345
2346 if (pc.parse_datetime_debug)
2347 {
2348 /* Special case: using 'date -u' simply set TZ=UTC0 */
2349 if (! tzstring)
2350 dbg_printf (_("timezone: system default\n"));
2351 else if (STREQ (tzstring, "UTC0"))
2352 dbg_printf (_("timezone: Universal Time\n"));
2353 else
2354 dbg_printf (_("timezone: TZ=\"%s\" environment value\n"), tzstring);
2355
2356 intmax_t sec = result->tv_sec;
2357 int nsec = result->tv_nsec;
2358 dbg_printf (_("final: %"PRIdMAX".%09d (epoch-seconds)\n"),
2359 sec, nsec);
2360
2361 struct tm gmt, lmt;
2362 bool got_utc = !!gmtime_r (&result->tv_sec, &gmt);
2363 if (got_utc)
2364 dbg_printf (_("final: %s (UTC)\n"),
2365 debug_strfdatetime (&gmt, NULL,
2366 dbg_tm, sizeof dbg_tm));
2367 if (localtime_rz (tz, &result->tv_sec, &lmt))
2368 {
2369 #ifdef HAVE_TM_GMTOFF
2370 bool got_utcoff = true;
2371 long int utcoff = lmt.tm_gmtoff;
2372 #else
2373 bool got_utcoff = got_utc;
2374 int utcoff;
2375 if (got_utcoff)
2376 utcoff = tm_diff (&lmt, &gmt);
2377 #endif
2378 if (got_utcoff)
2379 dbg_printf (_("final: %s (UTC%s)\n"),
2380 debug_strfdatetime (&lmt, NULL, dbg_tm, sizeof dbg_tm),
2381 time_zone_str (utcoff, time_zone_buf));
2382 else
2383 dbg_printf (_("final: %s (unknown time zone offset)\n"),
2384 debug_strfdatetime (&lmt, NULL, dbg_tm, sizeof dbg_tm));
2385 }
2386 }
2387
2388 ok = true;
2389
2390 fail:
2391 if (tz != tzdefault)
2392 tzfree (tz);
2393 free (tz1alloc);
2394 return ok;
2395 }
2396
2397 #if TEST
2398
2399 int
main(int ac,char ** av)2400 main (int ac, char **av)
2401 {
2402 char buff[BUFSIZ];
2403
2404 printf ("Enter date, or blank line to exit.\n\t> ");
2405 fflush (stdout);
2406
2407 buff[BUFSIZ - 1] = '\0';
2408 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
2409 {
2410 struct timespec d;
2411 struct tm const *tm;
2412 if (! parse_datetime (&d, buff, NULL))
2413 printf ("Bad format - couldn't convert.\n");
2414 else if (! (tm = localtime (&d.tv_sec)))
2415 {
2416 intmax_t sec = d.tv_sec;
2417 printf ("localtime (%"PRIdMAX") failed\n", sec);
2418 }
2419 else
2420 {
2421 int ns = d.tv_nsec;
2422 char tm_year_buf[TM_YEAR_BUFSIZE];
2423 printf ("%s-%02d-%02d %02d:%02d:%02d.%09d\n",
2424 tm_year_str (tm->tm_year, tm_year_buf),
2425 tm->tm_mon + 1, tm->tm_mday,
2426 tm->tm_hour, tm->tm_min, tm->tm_sec, ns);
2427 }
2428 printf ("\t> ");
2429 fflush (stdout);
2430 }
2431 return 0;
2432 }
2433 #endif /* TEST */
2434