2 /* Parse a string into an internal timestamp.
4 Copyright (C) 1999-2000, 2002-2020 Free Software Foundation, Inc.
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <https://www.gnu.org/licenses/>. */
19 /* Originally written by Steven M. Bellovin <smb@research.att.com> while
20 at the University of North Carolina at Chapel Hill. Later tweaked by
21 a couple of people on Usenet. Completely overhauled by Rich $alz
22 <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
24 Modified by Assaf Gordon <assafgordon@gmail.com> in 2016 to add
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'. */
36 #include "parse-datetime.h"
43 /* There's no need to extend the stack, so there's no need to involve
45 #define YYSTACK_USE_ALLOCA 0
47 /* Tell Bison how much stack space is needed. 20 should be plenty for
48 this grammar, which is not right recursive. Beware setting it too
49 high, since that might cause problems on machines whose
50 implementations have lame stack-overflow checking. */
52 #define YYINITDEPTH YYMAXDEPTH
54 /* Since the code of parse-datetime.y is not included in the Emacs executable
55 itself, there is no need to #define static in this file. Even if
56 the code were included in the Emacs executable, it probably
57 wouldn't do any harm to #undef it here; this will only cause
58 problems if we try to write to a static variable, which I don't
59 think this code needs to do. */
74 #define _(str) gettext (str)
76 /* Bison's skeleton tests _STDLIB_H, while some stdlib.h headers
77 use _STDLIB_H_ as witness. Map the latter to the one bison uses. */
78 /* FIXME: this is temporary. Remove when we have a mechanism to ensure
79 that the version we're using is fixed, too. */
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))
94 # define _GL_ATTRIBUTE_FORMAT(spec) /* empty */
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.
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. */
110 : (a
) / (1 << (b
)) - ((a
) %
(1 << (b
)) < 0))
112 #define HOUR(x) (60 * 60 * (x))
114 #define STREQ(a, b) (strcmp (a, b) == 0)
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
);
123 /* True if N is out of range for time_t. */
125 time_overflow
(intmax_t n
)
127 return
! ((TYPE_SIGNED
(time_t) ? TYPE_MINIMUM
(time_t) <= n
: 0 <= n
)
128 && n
<= TYPE_MAXIMUM
(time_t));
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. */
134 static unsigned char to_uchar
(char ch
) { return ch
; }
136 static void _GL_ATTRIBUTE_FORMAT
((__printf__
, 1, 2))
137 dbg_printf
(char const *msg
, ...
)
140 /* TODO: use gnulib's 'program_name' instead? */
141 fputs
("date: ", stderr
);
143 va_start
(args
, msg
);
144 vfprintf
(stderr
, msg
, args
);
149 /* An integer value, and the number of digits in its textual
158 /* An entry in the lexical lookup table. */
166 /* Meridian: am, pm, or 24-hour style. */
167 enum { MERam
, MERpm
, MER24
};
169 /* A reasonable upper bound for the buffer used in debug output. */
170 enum { DBGBUFSIZE
= 100 };
172 enum { BILLION
= 1000000000, LOG10_BILLION
= 9 };
174 /* Relative times. */
177 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
187 #if HAVE_COMPOUND_LITERALS
188 # define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
190 static relative_time
const RELATIVE_TIME_0
;
193 /* Information passed to and from the parser. */
196 /* The input string remaining to be parsed. */
199 /* N, if this is the Nth Tuesday. */
200 intmax_t day_ordinal
;
202 /* Day of week; Sunday is 0. */
205 /* tm_isdst flag for the local zone. */
208 /* Time zone, in seconds east of UT. */
211 /* Style used for time. */
214 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
220 struct timespec seconds
; /* includes nanoseconds */
222 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
225 /* Presence or counts of nonterminals of various flavors parsed so far. */
228 ptrdiff_t dates_seen
;
230 ptrdiff_t local_zones_seen
;
232 ptrdiff_t times_seen
;
233 ptrdiff_t zones_seen
;
236 /* Print debugging output to stderr. */
237 bool parse_datetime_debug
;
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
;
247 /* The user specified explicit ordinal day value. */
248 bool debug_ordinal_day_seen
;
250 /* Table of local time zone abbreviations, terminated by a null entry. */
251 table local_time_zone_table
[3];
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);
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,
263 digits_to_date_time
(parser_control
*pc
, textint text_int
)
265 if
(pc
->dates_seen
&& ! pc
->year.digits
266 && ! pc
->rels_seen
&& (pc
->times_seen ||
2 < text_int.digits
))
268 pc
->year_seen
= true
;
273 if
(4 < text_int.digits
)
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;
284 if
(text_int.digits
<= 2)
286 pc
->hour
= text_int.value
;
291 pc
->hour
= text_int.value
/ 100;
292 pc
->minutes
= text_int.value %
100;
294 pc
->seconds.tv_sec
= 0;
295 pc
->seconds.tv_nsec
= 0;
296 pc
->meridian
= MER24
;
301 /* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1). Return true
302 if successful, false if an overflow occurred. */
304 apply_relative_time
(parser_control
*pc
, relative_time rel
, int factor
)
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
)))
322 pc
->rels_seen
= true
;
326 /* Set PC-> hour, minutes, seconds and nanoseconds members from arguments. */
328 set_hhmmss
(parser_control
*pc
, intmax_t hour
, intmax_t minutes
,
329 time_t sec
, int nsec
)
332 pc
->minutes
= minutes
;
333 pc
->seconds.tv_sec
= sec
;
334 pc
->seconds.tv_nsec
= nsec
;
337 /* Return a textual representation of the day ordinal/number values
338 in the parser_control struct (e.g., "last wed", "this tues", "thu"). */
340 str_days
(parser_control
*pc
, char *buffer
, int n
)
342 /* TODO: use relative_time_table for reverse lookup. */
343 static char const ordinal_values
[][11] = {
347 "(SECOND)", /* SECOND is commented out in relative_time_table. */
360 static char const days_values
[][4] = {
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
)
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
));
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
]);
393 /* invalid day_number value - should never happen */
398 /* Convert a time zone to its string representation. */
400 enum { TIME_ZONE_BUFSIZE
= INT_STRLEN_BOUND
(intmax_t) + sizeof
":MM:SS" } ;
403 time_zone_str
(int time_zone
, char time_zone_buf
[TIME_ZONE_BUFSIZE
])
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)
412 int mm
= offset_from_hour
/ 60;
413 int ss
= offset_from_hour %
60;
415 *p
++ = '0' + mm
/ 10;
416 *p
++ = '0' + mm %
10;
420 *p
++ = '0' + ss
/ 10;
421 *p
++ = '0' + ss %
10;
425 return time_zone_buf
;
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. */
432 debug_print_current_time
(char const *item
, parser_control
*pc
)
436 if
(!pc
->parse_datetime_debug
)
439 /* no newline, more items printed below */
440 dbg_printf
(_
("parsed %s part: "), item
);
442 if
(pc
->dates_seen
&& !pc
->debug_dates_seen
)
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
;
451 if
(pc
->year_seen
!= pc
->debug_year_seen
)
455 fprintf
(stderr
, _
("year: %04"PRIdMAX
), pc
->year.value
);
457 pc
->debug_year_seen
= pc
->year_seen
;
461 if
(pc
->times_seen
&& !pc
->debug_times_seen
)
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)
468 int nsec
= pc
->seconds.tv_nsec
;
469 fprintf
(stderr
, ".%09d", nsec
);
471 if
(pc
->meridian
== MERpm
)
472 fputs
("pm", stderr
);
474 pc
->debug_times_seen
= true
;
478 if
(pc
->days_seen
&& !pc
->debug_days_seen
)
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
;
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
)
494 fprintf
(stderr
, &" isdst=%d%s"[!space
],
495 pc
->local_isdst
, pc
->dsts_seen ?
" DST" : "");
496 pc
->debug_local_zones_seen
= true
;
500 if
(pc
->zones_seen
&& !pc
->debug_zones_seen
)
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
;
509 if
(pc
->timespec_seen
)
511 intmax_t sec
= pc
->seconds.tv_sec
;
514 fprintf
(stderr
, _
("number of seconds: %"PRIdMAX
), sec
);
517 fputc
('\n', stderr
);
520 /* Debugging: print the current relative values. */
523 print_rel_part
(bool space
, intmax_t val
, char const *name
)
527 fprintf
(stderr
, &" %+"PRIdMAX
" %s"[!space
], val
, name
);
532 debug_print_relative_time
(char const *item
, parser_control
const *pc
)
536 if
(!pc
->parse_datetime_debug
)
539 /* no newline, more items printed below */
540 dbg_printf
(_
("parsed %s part: "), item
);
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
546 /* Special case: relative time of this/today/now */
547 fputs
(_
("today/this/now\n"), stderr
);
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");
559 fputc
('\n', stderr
);
566 /* We want a reentrant parser, even if the TZ manipulation and the calls to
567 localtime and gmtime are not reentrant. */
569 %parse
-param
{ parser_control
*pc
}
570 %lex
-param
{ parser_control
*pc
}
572 /* This grammar has 31 shift/reduce conflicts. */
579 struct timespec timespec
;
586 %token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
587 %token
<intval
> tDAY_UNIT tDAY_SHIFT
589 %token
<intval
> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
590 %token
<intval
> tMONTH tORDINAL tZONE
592 %token
<textintval
> tSNUMBER tUNUMBER
593 %token
<timespec
> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
595 %type
<intval
> o_colon_minutes
596 %type
<timespec
> seconds signed_seconds unsigned_seconds
598 %type
<rel
> relunit relunit_snumber dayshift
611 pc
->timespec_seen
= true
;
612 debug_print_current_time
(_
("number of seconds"), pc
);
624 pc
->times_seen
++; pc
->dates_seen
++;
625 debug_print_current_time
(_
("datetime"), pc
);
630 debug_print_current_time
(_
("time"), pc
);
634 pc
->local_zones_seen
++;
635 debug_print_current_time
(_
("local_zone"), pc
);
640 debug_print_current_time
(_
("zone"), pc
);
645 debug_print_current_time
(_
("date"), pc
);
650 debug_print_current_time
(_
("day"), pc
);
654 debug_print_relative_time
(_
("relative"), pc
);
658 debug_print_current_time
(_
("number"), pc
);
662 debug_print_relative_time
(_
("hybrid"), pc
);
671 iso_8601_date
'T' iso_8601_time
677 set_hhmmss
(pc
, $1.value
, 0, 0, 0);
680 | tUNUMBER
':' tUNUMBER tMERIDIAN
682 set_hhmmss
(pc
, $1.value
, $3.value
, 0, 0);
685 | tUNUMBER
':' tUNUMBER
':' unsigned_seconds tMERIDIAN
687 set_hhmmss
(pc
, $1.value
, $3.value
, $5.tv_sec
, $5.tv_nsec
);
696 set_hhmmss
(pc
, $1.value
, 0, 0, 0);
697 pc
->meridian
= MER24
;
699 | tUNUMBER
':' tUNUMBER o_zone_offset
701 set_hhmmss
(pc
, $1.value
, $3.value
, 0, 0);
702 pc
->meridian
= MER24
;
704 | tUNUMBER
':' tUNUMBER
':' unsigned_seconds o_zone_offset
706 set_hhmmss
(pc
, $1.value
, $3.value
, $5.tv_sec
, $5.tv_nsec
);
707 pc
->meridian
= MER24
;
717 tSNUMBER o_colon_minutes
720 if
(! time_zone_hhmm
(pc
, $1, $2)) YYABORT;
724 /* Local zone strings affect only the DST setting, and take effect
725 only if the current TZ setting is relevant.
728 'EEST' is parsed as tLOCAL_ZONE, as it relates to the effective TZ:
729 TZ='Europe/Helsinki' date -d '2016-06-30 EEST'
732 'EEST' is parsed as tDAYZONE:
733 TZ='Asia/Tokyo' date -d '2016-06-30 EEST'
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'.)
743 { pc
->local_isdst
= $1; }
751 /* Note 'T' is a special case, as it is used as the separator in ISO
752 8601 date and time of day representation. */
755 { pc
->time_zone
= $1; }
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
);
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
);
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; }
772 { pc
->time_zone
= $1 + 60 * 60; }
774 { pc
->time_zone
= $1 + 60 * 60; }
790 pc
->day_ordinal
= $1;
792 pc
->debug_ordinal_day_seen
= true
;
796 pc
->day_ordinal
= $1.value
;
798 pc
->debug_ordinal_day_seen
= true
;
803 tUNUMBER
'/' tUNUMBER
805 pc
->month
= $1.value
;
808 | tUNUMBER
'/' tUNUMBER
'/' tUNUMBER
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. */
817 if
(pc
->parse_datetime_debug
)
819 intmax_t digits
= $1.digits
;
820 dbg_printf
(_
("warning: value %"PRIdMAX
" has %"PRIdMAX
" digits. "
821 "Assuming YYYY/MM/DD\n"),
826 pc
->month
= $3.value
;
831 if
(pc
->parse_datetime_debug
)
832 dbg_printf
(_
("warning: value %"PRIdMAX
" has less than 4 digits. "
833 "Assuming MM/DD/YY[YY]\n"),
836 pc
->month
= $1.value
;
841 | tUNUMBER tMONTH tSNUMBER
843 /* E.g., 17-JUN-1992. */
846 if
(INT_SUBTRACT_WRAPV
(0, $3.value
, &pc
->year.value
)) YYABORT;
847 pc
->year.digits
= $3.digits
;
849 | tMONTH tSNUMBER tSNUMBER
851 /* E.g., JUN-17-1992. */
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
;
862 | tMONTH tUNUMBER
',' tUNUMBER
873 | tUNUMBER tMONTH tUNUMBER
883 tUNUMBER tSNUMBER tSNUMBER
885 /* ISO 8601 format. YYYY-MM-DD. */
887 if
(INT_SUBTRACT_WRAPV
(0, $2.value
, &pc
->month
)) YYABORT;
888 if
(INT_SUBTRACT_WRAPV
(0, $3.value
, &pc
->day
)) YYABORT;
894 { if
(! apply_relative_time
(pc
, $1, $2)) YYABORT; }
896 { if
(! apply_relative_time
(pc
, $1, 1)) YYABORT; }
898 { if
(! apply_relative_time
(pc
, $1, 1)) YYABORT; }
903 { $$
= RELATIVE_TIME_0
; $$.year
= $1; }
904 | tUNUMBER tYEAR_UNIT
905 { $$
= RELATIVE_TIME_0
; $$.year
= $1.value
; }
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
; }
913 { $$
= RELATIVE_TIME_0
; $$.month
= 1; }
915 { $$
= RELATIVE_TIME_0
;
916 if
(INT_MULTIPLY_WRAPV
($1, $2, &$$.day
)) YYABORT; }
918 { $$
= RELATIVE_TIME_0
;
919 if
(INT_MULTIPLY_WRAPV
($1.value
, $2, &$$.day
)) YYABORT; }
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
; }
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
; }
933 { $$
= RELATIVE_TIME_0
; $$.minutes
= 1; }
935 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1; }
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
; }
943 { $$
= RELATIVE_TIME_0
; $$.seconds
= 1; }
949 { $$
= RELATIVE_TIME_0
; $$.year
= $1.value
; }
950 | tSNUMBER tMONTH_UNIT
951 { $$
= RELATIVE_TIME_0
; $$.month
= $1.value
; }
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
; }
960 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1.value
; }
965 { $$
= RELATIVE_TIME_0
; $$.day
= $1; }
968 seconds: signed_seconds | unsigned_seconds
;
973 { if
(time_overflow
($1.value
)) YYABORT;
974 $$.tv_sec
= $1.value
; $$.tv_nsec
= 0; }
980 { if
(time_overflow
($1.value
)) YYABORT;
981 $$.tv_sec
= $1.value
; $$.tv_nsec
= 0; }
986 { digits_to_date_time
(pc
, $1); }
990 tUNUMBER relunit_snumber
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;
1008 static table
const meridian_table
[] =
1010 { "AM", tMERIDIAN
, MERam
},
1011 { "A.M.", tMERIDIAN
, MERam
},
1012 { "PM", tMERIDIAN
, MERpm
},
1013 { "P.M.", tMERIDIAN
, MERpm
},
1017 static table
const dst_table
[] =
1022 static table
const month_and_day_table
[] =
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 },
1051 static table
const time_units_table
[] =
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 },
1066 /* Assorted relative-time words. */
1067 static table
const relative_time_table
[] =
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 },
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
[] =
1098 { "GMT", tZONE
, HOUR
( 0) }, /* Greenwich Mean */
1099 { "UT", tZONE
, HOUR
( 0) }, /* Universal (Coordinated) */
1100 { "UTC", tZONE
, HOUR
( 0) },
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
[] =
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 */
1161 /* Military time zone table.
1163 RFC 822 got these backwards, but RFC 5322 makes the incorrect
1164 treatment optional, so do them the right way here.
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
[] =
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) },
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) },
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. */
1208 time_zone_hhmm
(parser_control
*pc
, textint s
, intmax_t mm
)
1211 bool overflow
= false
;
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)
1219 n_minutes
= (s.value
/ 100) * 60 + s.value %
100;
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
));
1228 if
(overflow ||
! (-24 * 60 <= n_minutes
&& n_minutes
<= 24 * 60))
1230 pc
->time_zone
= n_minutes
* 60;
1235 to_hour
(intmax_t hours
, int meridian
)
1239 default
: /* Pacify GCC. */
1241 return
0 <= hours
&& hours
< 24 ? hours
: -1;
1243 return
0 < hours
&& hours
< 12 ? hours
: hours
== 12 ?
0 : -1;
1245 return
0 < hours
&& hours
< 12 ? hours
+ 12 : hours
== 12 ?
12 : -1;
1249 enum { TM_YEAR_BASE
= 1900 };
1250 enum { TM_YEAR_BUFSIZE
= INT_BUFSIZE_BOUND
(int) + 1 };
1252 /* Convert TM_YEAR, a year minus 1900, to a string that is numerically
1253 correct even if subtracting 1900 would overflow. */
1256 tm_year_str
(int tm_year
, char buf
[TM_YEAR_BUFSIZE
])
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));
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. */
1269 to_tm_year
(textint textyear
, bool debug
, int *tm_year
)
1271 intmax_t year
= textyear.value
;
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)
1277 year
+= year
< 69 ?
2000 : 1900;
1279 dbg_printf
(_
("warning: adjusting year value %"PRIdMAX
1280 " to %"PRIdMAX
"\n"),
1281 textyear.value
, year
);
1285 ? INT_SUBTRACT_WRAPV
(-TM_YEAR_BASE
, year
, tm_year
)
1286 : INT_SUBTRACT_WRAPV
(year
, TM_YEAR_BASE
, tm_year
))
1289 dbg_printf
(_
("error: out-of-range year %"PRIdMAX
"\n"), year
);
1296 static table
const * _GL_ATTRIBUTE_PURE
1297 lookup_zone
(parser_control
const *pc
, char const *name
)
1301 for
(tp
= universal_time_zone_table
; tp
->name
; tp
++)
1302 if
(strcmp
(name
, tp
->name
) == 0)
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)
1311 for
(tp
= time_zone_table
; tp
->name
; tp
++)
1312 if
(strcmp
(name
, tp
->name
) == 0)
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;
1324 tm_diff
(const struct tm
*a
, const struct tm
*b
)
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
));
1343 #endif /* ! HAVE_TM_GMTOFF */
1345 static table
const *
1346 lookup_word
(parser_control
const *pc
, char *word
)
1355 /* Make it uppercase. */
1356 for
(p
= word
; *p
; p
++)
1357 *p
= c_toupper
(to_uchar
(*p
));
1359 for
(tp
= meridian_table
; tp
->name
; tp
++)
1360 if
(strcmp
(word
, tp
->name
) == 0)
1363 /* See if we have an abbreviation for a month. */
1364 wordlen
= strlen
(word
);
1365 abbrev
= wordlen
== 3 ||
(wordlen
== 4 && word
[3] == '.');
1367 for
(tp
= month_and_day_table
; tp
->name
; tp
++)
1368 if
((abbrev ? strncmp
(word
, tp
->name
, 3) : strcmp
(word
, tp
->name
)) == 0)
1371 if
((tp
= lookup_zone
(pc
, word
)))
1374 if
(strcmp
(word
, dst_table
[0].name
) == 0)
1377 for
(tp
= time_units_table
; tp
->name
; tp
++)
1378 if
(strcmp
(word
, tp
->name
) == 0)
1381 /* Strip off any plural and try the units table again. */
1382 if
(word
[wordlen
- 1] == 'S')
1384 word
[wordlen
- 1] = '\0';
1385 for
(tp
= time_units_table
; tp
->name
; tp
++)
1386 if
(strcmp
(word
, tp
->name
) == 0)
1388 word
[wordlen
- 1] = 'S'; /* For "this" in relative_time_table. */
1391 for
(tp
= relative_time_table
; tp
->name
; tp
++)
1392 if
(strcmp
(word
, tp
->name
) == 0)
1395 /* Military time zones. */
1397 for
(tp
= military_table
; tp
->name
; tp
++)
1398 if
(word
[0] == tp
->name
[0])
1401 /* Drop out any periods and try the time zone table again. */
1402 for
(period_found
= false
, p
= q
= word
; (*p
= *q
); q
++)
1404 period_found
= true
;
1407 if
(period_found
&& (tp
= lookup_zone
(pc
, word
)))
1414 yylex (union YYSTYPE *lvalp
, parser_control
*pc
)
1420 while
(c
= *pc
->input
, c_isspace
(c
))
1423 if
(c_isdigit
(c
) || c
== '-' || c
== '+')
1428 if
(c
== '-' || c
== '+')
1430 sign
= c
== '-' ?
-1 : 1;
1431 while
(c
= *++pc
->input
, c_isspace
(c
))
1433 if
(! c_isdigit
(c
))
1434 /* skip the '-' sign */
1443 if
(INT_MULTIPLY_WRAPV
(value
, 10, &value
))
1445 if
(INT_ADD_WRAPV
(value
, sign
< 0 ?
'0' - c
: c
- '0', &value
))
1449 while
(c_isdigit
(c
));
1451 if
((c
== '.' || c
== ',') && c_isdigit
(p
[1]))
1457 if
(time_overflow
(value
))
1461 /* Accumulate fraction, to ns precision. */
1464 for
(digits
= 2; digits
<= LOG10_BILLION
; digits
++)
1471 /* Skip excess digits, truncating toward -Infinity. */
1473 for
(; c_isdigit
(*p
); p
++)
1479 while
(c_isdigit
(*p
))
1482 /* Adjust to the timespec convention, which is that
1483 tv_nsec is always a positive offset even if tv_sec is
1487 if
(s
== TYPE_MINIMUM
(time_t))
1493 lvalp
->timespec.tv_sec
= s
;
1494 lvalp
->timespec.tv_nsec
= ns
;
1496 return sign ? tSDECIMAL_NUMBER
: tUDECIMAL_NUMBER
;
1500 lvalp
->textintval.negative
= sign
< 0;
1501 lvalp
->textintval.value
= value
;
1502 lvalp
->textintval.digits
= p
- pc
->input
;
1504 return sign ? tSNUMBER
: tUNUMBER
;
1516 if
(p
< buff
+ sizeof buff
- 1)
1520 while
(c_isalpha
(c
) || c
== '.');
1523 tp
= lookup_word
(pc
, buff
);
1526 if
(pc
->parse_datetime_debug
)
1527 dbg_printf
(_
("error: unknown word '%s'\n"), buff
);
1530 lvalp
->intval
= tp
->value
;
1535 return to_uchar
(*pc
->input
++);
1537 ptrdiff_t count
= 0;
1552 /* Do nothing if the parser reports an error. */
1554 yyerror (parser_control
const *pc _GL_UNUSED
,
1555 char const *s _GL_UNUSED
)
1560 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1561 passing it to mktime_z, return true if it's OK. It's not OK if
1562 mktime failed or if *TM0 has out-of-range mainline members.
1563 The caller should set TM1->tm_wday to -1 before calling mktime,
1564 as a negative tm_wday is how mktime failure is inferred. */
1567 mktime_ok
(struct tm
const *tm0
, struct tm
const *tm1
)
1569 if
(tm1
->tm_wday
< 0)
1572 return
! ((tm0
->tm_sec ^ tm1
->tm_sec
)
1573 |
(tm0
->tm_min ^ tm1
->tm_min
)
1574 |
(tm0
->tm_hour ^ tm1
->tm_hour
)
1575 |
(tm0
->tm_mday ^ tm1
->tm_mday
)
1576 |
(tm0
->tm_mon ^ tm1
->tm_mon
)
1577 |
(tm0
->tm_year ^ tm1
->tm_year
));
1580 /* Debugging: format a 'struct tm' into a buffer, taking the parser's
1581 timezone information into account (if pc != NULL). */
1583 debug_strfdatetime
(struct tm
const *tm
, parser_control
const *pc
,
1587 1. find an optimal way to print date string in a clear and unambiguous
1588 format. Currently, always add '(Y-M-D)' prefix.
1589 Consider '2016y01m10d' or 'year(2016) month(01) day(10)'.
1591 If the user needs debug printing, it means he/she already having
1592 issues with the parsing - better to avoid formats that could
1593 be mis-interpreted (e.g., just YYYY-MM-DD).
1595 2. Can strftime be used instead?
1596 depends if it is portable and can print invalid dates on all systems.
1598 3. Print timezone information ?
1600 4. Print DST information ?
1602 5. Print nanosecond information ?
1605 Printed date/time values might not be valid, e.g., '2016-02-31'
1606 or '2016-19-2016' . These are the values as parsed from the user
1607 string, before validation.
1609 int m
= nstrftime
(buf
, n
, "(Y-M-D) %Y-%m-%d %H:%M:%S", tm
, 0, 0);
1611 /* If parser_control information was provided (for timezone),
1612 and there's enough space in the buffer, add timezone info. */
1613 if
(pc
&& m
< n
&& pc
->zones_seen
)
1615 int tz
= pc
->time_zone
;
1617 /* Account for DST if tLOCAL_ZONE was seen. */
1618 if
(pc
->local_zones_seen
&& !pc
->zones_seen
&& 0 < pc
->local_isdst
)
1621 char time_zone_buf
[TIME_ZONE_BUFSIZE
];
1622 snprintf
(&buf
[m
], n
- m
, " TZ=%s", time_zone_str
(tz
, time_zone_buf
));
1628 debug_strfdate
(struct tm
const *tm
, char *buf
, int n
)
1630 char tm_year_buf
[TM_YEAR_BUFSIZE
];
1631 snprintf
(buf
, n
, "(Y-M-D) %s-%02d-%02d",
1632 tm_year_str
(tm
->tm_year
, tm_year_buf
),
1633 tm
->tm_mon
+ 1, tm
->tm_mday
);
1638 debug_strftime
(struct tm
const *tm
, char *buf
, int n
)
1640 snprintf
(buf
, n
, "%02d:%02d:%02d", tm
->tm_hour
, tm
->tm_min
, tm
->tm_sec
);
1644 /* If mktime_ok failed, display the failed time values,
1645 and provide possible hints. Example output:
1647 date: error: invalid date/time value:
1648 date: user provided time: '(Y-M-D) 2006-04-02 02:45:00'
1649 date: normalized time: '(Y-M-D) 2006-04-02 03:45:00'
1651 date: possible reasons:
1652 date: non-existing due to daylight-saving time;
1653 date: numeric values overflow;
1654 date: missing timezone;
1657 debug_mktime_not_ok
(struct tm
const *tm0
, struct tm
const *tm1
,
1658 parser_control
const *pc
, bool time_zone_seen
)
1660 /* TODO: handle t == -1 (as in 'mktime_ok'). */
1661 char tmp
[DBGBUFSIZE
];
1663 const bool eq_sec
= (tm0
->tm_sec
== tm1
->tm_sec
);
1664 const bool eq_min
= (tm0
->tm_min
== tm1
->tm_min
);
1665 const bool eq_hour
= (tm0
->tm_hour
== tm1
->tm_hour
);
1666 const bool eq_mday
= (tm0
->tm_mday
== tm1
->tm_mday
);
1667 const bool eq_month
= (tm0
->tm_mon
== tm1
->tm_mon
);
1668 const bool eq_year
= (tm0
->tm_year
== tm1
->tm_year
);
1670 const bool dst_shift
= eq_sec
&& eq_min
&& !eq_hour
1671 && eq_mday
&& eq_month
&& eq_year
;
1673 if
(!pc
->parse_datetime_debug
)
1676 dbg_printf
(_
("error: invalid date/time value:\n"));
1677 dbg_printf
(_
(" user provided time: '%s'\n"),
1678 debug_strfdatetime
(tm0
, pc
, tmp
, sizeof tmp
));
1679 dbg_printf
(_
(" normalized time: '%s'\n"),
1680 debug_strfdatetime
(tm1
, pc
, tmp
, sizeof tmp
));
1681 /* The format must be aligned with debug_strfdatetime and the two
1682 DEBUG statements above. This string is not translated. */
1683 i
= snprintf
(tmp
, sizeof tmp
,
1684 " %4s %2s %2s %2s %2s %2s",
1685 eq_year ?
"" : "----",
1686 eq_month ?
"" : "--",
1687 eq_mday ?
"" : "--",
1688 eq_hour ?
"" : "--",
1690 eq_sec ?
"" : "--");
1691 /* Trim trailing whitespace. */
1694 if
(sizeof tmp
- 1 < i
)
1696 while
(0 < i
&& tmp
[i
- 1] == ' ')
1700 dbg_printf
("%s\n", tmp
);
1702 dbg_printf
(_
(" possible reasons:\n"));
1704 dbg_printf
(_
(" non-existing due to daylight-saving time;\n"));
1705 if
(!eq_mday
&& !eq_month
)
1706 dbg_printf
(_
(" invalid day/month combination;\n"));
1707 dbg_printf
(_
(" numeric values overflow;\n"));
1708 dbg_printf
(" %s\n", (time_zone_seen ? _
("incorrect timezone")
1709 : _
("missing timezone")));
1712 /* The original interface: run with debug=false and the default timezone. */
1714 parse_datetime
(struct timespec
*result
, char const *p
,
1715 struct timespec
const *now
)
1717 char const *tzstring
= getenv
("TZ");
1718 timezone_t tz
= tzalloc
(tzstring
);
1721 bool ok
= parse_datetime2
(result
, p
, now
, 0, tz
, tzstring
);
1726 /* Parse a date/time string, storing the resulting time value into *RESULT.
1727 The string itself is pointed to by P. Return true if successful.
1728 P can be an incomplete or relative time specification; if so, use
1729 *NOW as the basis for the returned time. Default to timezone
1730 TZDEFAULT, which corresponds to tzalloc (TZSTRING). */
1732 parse_datetime2
(struct timespec
*result
, char const *p
,
1733 struct timespec
const *now
, unsigned int flags
,
1734 timezone_t tzdefault
, char const *tzstring
)
1738 char time_zone_buf
[TIME_ZONE_BUFSIZE
];
1739 char dbg_tm
[DBGBUFSIZE
];
1741 char const *input_sentinel
= p
+ strlen
(p
);
1742 char *tz1alloc
= NULL
;
1744 /* A reasonable upper bound for the size of ordinary TZ strings.
1745 Use heap allocation if TZ's length exceeds this. */
1746 enum { TZBUFSIZE
= 100 };
1747 char tz1buf
[TZBUFSIZE
];
1749 struct timespec gettime_buffer
;
1752 gettime
(&gettime_buffer
);
1753 now
= &gettime_buffer
;
1756 time_t Start
= now
->tv_sec
;
1757 int Start_ns
= now
->tv_nsec
;
1760 while
(c
= *p
, c_isspace
(c
))
1763 timezone_t tz
= tzdefault
;
1765 /* Store a local copy prior to first "goto". Without this, a prior use
1766 below of RELATIVE_TIME_0 on the RHS might translate to an assignment-
1767 to-temporary, which would trigger a -Wjump-misses-init warning. */
1768 const relative_time rel_time_0
= RELATIVE_TIME_0
;
1770 if
(strncmp
(p
, "TZ=\"", 4) == 0)
1772 char const *tzbase
= p
+ 4;
1773 ptrdiff_t tzsize
= 1;
1776 for
(s
= tzbase
; *s
; s
++, tzsize
++)
1780 if
(! (*s
== '\\' ||
*s
== '"'))
1786 char *tz1string
= tz1buf
;
1788 if
(TZBUFSIZE
< tzsize
)
1790 tz1alloc
= malloc
(tzsize
);
1793 tz1string
= tz1alloc
;
1796 for
(s
= tzbase
; *s
!= '"'; s
++)
1797 *z
++ = *(s
+= *s
== '\\');
1799 tz1
= tzalloc
(tz1string
);
1803 tzstring
= tz1string
;
1806 while
(c
= *p
, c_isspace
(c
))
1814 if
(! localtime_rz
(tz
, &now
->tv_sec
, &tmp
))
1817 /* As documented, be careful to treat the empty string just like
1818 a date string of "0". Without this, an empty string would be
1819 declared invalid when parsed during a DST transition. */
1825 pc.parse_datetime_debug
= (flags
& PARSE_DATETIME_DEBUG
) != 0;
1826 if
(INT_ADD_WRAPV
(tmp.tm_year
, TM_YEAR_BASE
, &pc.year.value
))
1828 if
(pc.parse_datetime_debug
)
1829 dbg_printf
(_
("error: initial year out of range\n"));
1833 pc.month
= tmp.tm_mon
+ 1;
1834 pc.day
= tmp.tm_mday
;
1835 pc.hour
= tmp.tm_hour
;
1836 pc.minutes
= tmp.tm_min
;
1837 pc.seconds.tv_sec
= tmp.tm_sec
;
1838 pc.seconds.tv_nsec
= Start_ns
;
1839 tm.tm_isdst
= tmp.tm_isdst
;
1841 pc.meridian
= MER24
;
1842 pc.rel
= rel_time_0
;
1843 pc.timespec_seen
= false
;
1844 pc.rels_seen
= false
;
1848 pc.local_zones_seen
= 0;
1851 pc.year_seen
= false
;
1852 pc.debug_dates_seen
= false
;
1853 pc.debug_days_seen
= false
;
1854 pc.debug_times_seen
= false
;
1855 pc.debug_local_zones_seen
= false
;
1856 pc.debug_zones_seen
= false
;
1857 pc.debug_year_seen
= false
;
1858 pc.debug_ordinal_day_seen
= false
;
1860 #if HAVE_STRUCT_TM_TM_ZONE
1861 pc.local_time_zone_table
[0].name
= tmp.tm_zone
;
1862 pc.local_time_zone_table
[0].type
= tLOCAL_ZONE
;
1863 pc.local_time_zone_table
[0].value
= tmp.tm_isdst
;
1864 pc.local_time_zone_table
[1].name
= NULL
;
1866 /* Probe the names used in the next three calendar quarters, looking
1867 for a tm_isdst different from the one we already have. */
1870 for
(quarter
= 1; quarter
<= 3; quarter
++)
1873 if
(INT_ADD_WRAPV
(Start
, quarter
* (90 * 24 * 60 * 60), &iprobe
)
1874 || time_overflow
(iprobe
))
1876 time_t probe
= iprobe
;
1878 if
(localtime_rz
(tz
, &probe
, &probe_tm
) && probe_tm.tm_zone
1879 && probe_tm.tm_isdst
!= pc.local_time_zone_table
[0].value
)
1882 pc.local_time_zone_table
[1].name
= probe_tm.tm_zone
;
1883 pc.local_time_zone_table
[1].type
= tLOCAL_ZONE
;
1884 pc.local_time_zone_table
[1].value
= probe_tm.tm_isdst
;
1885 pc.local_time_zone_table
[2].name
= NULL
;
1894 # if !HAVE_DECL_TZNAME
1895 extern
char *tzname
[];
1898 for
(i
= 0; i
< 2; i
++)
1900 pc.local_time_zone_table
[i
].name
= tzname
[i
];
1901 pc.local_time_zone_table
[i
].type
= tLOCAL_ZONE
;
1902 pc.local_time_zone_table
[i
].value
= i
;
1904 pc.local_time_zone_table
[i
].name
= NULL
;
1907 pc.local_time_zone_table
[0].name
= NULL
;
1911 if
(pc.local_time_zone_table
[0].name
&& pc.local_time_zone_table
[1].name
1912 && ! strcmp
(pc.local_time_zone_table
[0].name
,
1913 pc.local_time_zone_table
[1].name
))
1915 /* This locale uses the same abbreviation for standard and
1916 daylight times. So if we see that abbreviation, we don't
1917 know whether it's daylight time. */
1918 pc.local_time_zone_table
[0].value
= -1;
1919 pc.local_time_zone_table
[1].name
= NULL
;
1922 if
(yyparse (&pc
) != 0)
1924 if
(pc.parse_datetime_debug
)
1925 dbg_printf
((input_sentinel
<= pc.input
1926 ? _
("error: parsing failed\n")
1927 : _
("error: parsing failed, stopped at '%s'\n")),
1933 /* Determine effective timezone source. */
1935 if
(pc.parse_datetime_debug
)
1937 dbg_printf
(_
("input timezone: "));
1939 if
(pc.timespec_seen
)
1940 fprintf
(stderr
, _
("'@timespec' - always UTC"));
1941 else if
(pc.zones_seen
)
1942 fprintf
(stderr
, _
("parsed date/time string"));
1945 if
(tz
!= tzdefault
)
1946 fprintf
(stderr
, _
("TZ=\"%s\" in date string"), tzstring
);
1947 else if
(STREQ
(tzstring
, "UTC0"))
1949 /* Special case: 'date -u' sets TZ="UTC0". */
1950 fprintf
(stderr
, _
("TZ=\"UTC0\" environment value or -u"));
1953 fprintf
(stderr
, _
("TZ=\"%s\" environment value"), tzstring
);
1956 fprintf
(stderr
, _
("system default"));
1958 /* Account for DST changes if tLOCAL_ZONE was seen.
1959 local timezone only changes DST and is relative to the
1961 if
(pc.local_zones_seen
&& !pc.zones_seen
&& 0 < pc.local_isdst
)
1962 fprintf
(stderr
, ", dst");
1965 fprintf
(stderr
, " (%s)", time_zone_str
(pc.time_zone
, time_zone_buf
));
1967 fputc
('\n', stderr
);
1970 if
(pc.timespec_seen
)
1971 *result
= pc.seconds
;
1974 if
(1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
1975 |
(pc.local_zones_seen
+ pc.zones_seen
)))
1977 if
(pc.parse_datetime_debug
)
1979 if
(pc.times_seen
> 1)
1980 dbg_printf
("error: seen multiple time parts\n");
1981 if
(pc.dates_seen
> 1)
1982 dbg_printf
("error: seen multiple date parts\n");
1983 if
(pc.days_seen
> 1)
1984 dbg_printf
("error: seen multiple days parts\n");
1985 if
(pc.dsts_seen
> 1)
1986 dbg_printf
("error: seen multiple daylight-saving parts\n");
1987 if
((pc.local_zones_seen
+ pc.zones_seen
) > 1)
1988 dbg_printf
("error: seen multiple time-zone parts\n");
1993 if
(! to_tm_year
(pc.year
, pc.parse_datetime_debug
, &tm.tm_year
)
1994 || INT_ADD_WRAPV
(pc.month
, -1, &tm.tm_mon
)
1995 || INT_ADD_WRAPV
(pc.day
, 0, &tm.tm_mday
))
1997 if
(pc.parse_datetime_debug
)
1998 dbg_printf
(_
("error: year, month, or day overflow\n"));
2001 if
(pc.times_seen ||
(pc.rels_seen
&& ! pc.dates_seen
&& ! pc.days_seen
))
2003 tm.tm_hour
= to_hour
(pc.hour
, pc.meridian
);
2006 char const *mrd
= (pc.meridian
== MERam ?
"am"
2007 : pc.meridian
== MERpm ?
"pm" : "");
2008 if
(pc.parse_datetime_debug
)
2009 dbg_printf
(_
("error: invalid hour %"PRIdMAX
"%s\n"),
2013 tm.tm_min
= pc.minutes
;
2014 tm.tm_sec
= pc.seconds.tv_sec
;
2015 if
(pc.parse_datetime_debug
)
2016 dbg_printf
((pc.times_seen
2017 ? _
("using specified time as starting value: '%s'\n")
2018 : _
("using current time as starting value: '%s'\n")),
2019 debug_strftime
(&tm
, dbg_tm
, sizeof dbg_tm
));
2023 tm.tm_hour
= tm.tm_min
= tm.tm_sec
= 0;
2024 pc.seconds.tv_nsec
= 0;
2025 if
(pc.parse_datetime_debug
)
2026 dbg_printf
("warning: using midnight as starting time: 00:00:00\n");
2029 /* Let mktime deduce tm_isdst if we have an absolute timestamp. */
2030 if
(pc.dates_seen | pc.days_seen | pc.times_seen
)
2033 /* But if the input explicitly specifies local time with or without
2034 DST, give mktime that information. */
2035 if
(pc.local_zones_seen
)
2036 tm.tm_isdst
= pc.local_isdst
;
2038 tm0.tm_sec
= tm.tm_sec
;
2039 tm0.tm_min
= tm.tm_min
;
2040 tm0.tm_hour
= tm.tm_hour
;
2041 tm0.tm_mday
= tm.tm_mday
;
2042 tm0.tm_mon
= tm.tm_mon
;
2043 tm0.tm_year
= tm.tm_year
;
2044 tm0.tm_isdst
= tm.tm_isdst
;
2047 Start
= mktime_z
(tz
, &tm
);
2049 if
(! mktime_ok
(&tm0
, &tm
))
2051 bool repaired
= false
;
2052 bool time_zone_seen
= pc.zones_seen
!= 0;
2055 /* Guard against falsely reporting errors near the time_t
2056 boundaries when parsing times in other time zones. For
2057 example, suppose the input string "1969-12-31 23:00:00 -0100",
2058 the current time zone is 8 hours ahead of UTC, and the min
2059 time_t value is 1970-01-01 00:00:00 UTC. Then the min
2060 localtime value is 1970-01-01 08:00:00, and mktime will
2061 therefore fail on 1969-12-31 23:00:00. To work around the
2062 problem, set the time zone to 1 hour behind UTC temporarily
2063 by setting TZ="XXX1:00" and try mktime again. */
2065 char tz2buf
[sizeof
"XXX" - 1 + TIME_ZONE_BUFSIZE
];
2066 tz2buf
[0] = tz2buf
[1] = tz2buf
[2] = 'X';
2067 time_zone_str
(pc.time_zone
, &tz2buf
[3]);
2068 timezone_t tz2
= tzalloc
(tz2buf
);
2071 if
(pc.parse_datetime_debug
)
2072 dbg_printf
(_
("error: tzalloc (\"%s\") failed\n"), tz2buf
);
2075 tm.tm_sec
= tm0.tm_sec
;
2076 tm.tm_min
= tm0.tm_min
;
2077 tm.tm_hour
= tm0.tm_hour
;
2078 tm.tm_mday
= tm0.tm_mday
;
2079 tm.tm_mon
= tm0.tm_mon
;
2080 tm.tm_year
= tm0.tm_year
;
2081 tm.tm_isdst
= tm0.tm_isdst
;
2083 Start
= mktime_z
(tz2
, &tm
);
2084 repaired
= mktime_ok
(&tm0
, &tm
);
2090 debug_mktime_not_ok
(&tm0
, &tm
, &pc
, time_zone_seen
);
2095 char dbg_ord
[DBGBUFSIZE
];
2097 if
(pc.days_seen
&& ! pc.dates_seen
)
2100 if
(INT_MULTIPLY_WRAPV
((pc.day_ordinal
2101 - (0 < pc.day_ordinal
2102 && tm.tm_wday
!= pc.day_number
)),
2104 || INT_ADD_WRAPV
((pc.day_number
- tm.tm_wday
+ 7) %
7,
2106 || INT_ADD_WRAPV
(dayincr
, tm.tm_mday
, &tm.tm_mday
))
2111 Start
= mktime_z
(tz
, &tm
);
2114 if
(Start
== (time_t) -1)
2116 if
(pc.parse_datetime_debug
)
2117 dbg_printf
(_
("error: day '%s' "
2118 "(day ordinal=%"PRIdMAX
" number=%d) "
2119 "resulted in an invalid date: '%s'\n"),
2120 str_days
(&pc
, dbg_ord
, sizeof dbg_ord
),
2121 pc.day_ordinal
, pc.day_number
,
2122 debug_strfdatetime
(&tm
, &pc
, dbg_tm
,
2127 if
(pc.parse_datetime_debug
)
2128 dbg_printf
(_
("new start date: '%s' is '%s'\n"),
2129 str_days
(&pc
, dbg_ord
, sizeof dbg_ord
),
2130 debug_strfdatetime
(&tm
, &pc
, dbg_tm
, sizeof dbg_tm
));
2134 if
(pc.parse_datetime_debug
)
2136 if
(!pc.dates_seen
&& !pc.days_seen
)
2137 dbg_printf
(_
("using current date as starting value: '%s'\n"),
2138 debug_strfdate
(&tm
, dbg_tm
, sizeof dbg_tm
));
2140 if
(pc.days_seen
&& pc.dates_seen
)
2141 dbg_printf
(_
("warning: day (%s) ignored when explicit dates "
2143 str_days
(&pc
, dbg_ord
, sizeof dbg_ord
));
2145 dbg_printf
(_
("starting date/time: '%s'\n"),
2146 debug_strfdatetime
(&tm
, &pc
, dbg_tm
, sizeof dbg_tm
));
2149 /* Add relative date. */
2150 if
(pc.rel.year | pc.rel.month | pc.rel.day
)
2152 if
(pc.parse_datetime_debug
)
2154 if
((pc.rel.year
!= 0 || pc.rel.month
!= 0) && tm.tm_mday
!= 15)
2155 dbg_printf
(_
("warning: when adding relative months/years, "
2156 "it is recommended to specify the 15th of the "
2159 if
(pc.rel.day
!= 0 && tm.tm_hour
!= 12)
2160 dbg_printf
(_
("warning: when adding relative days, "
2161 "it is recommended to specify noon\n"));
2164 int year
, month
, day
;
2165 if
(INT_ADD_WRAPV
(tm.tm_year
, pc.rel.year
, &year
)
2166 || INT_ADD_WRAPV
(tm.tm_mon
, pc.rel.month
, &month
)
2167 || INT_ADD_WRAPV
(tm.tm_mday
, pc.rel.day
, &day
))
2169 if
(pc.parse_datetime_debug
)
2170 dbg_printf
(_
("error: %s:%d\n"), __FILE__
, __LINE__
);
2176 tm.tm_hour
= tm0.tm_hour
;
2177 tm.tm_min
= tm0.tm_min
;
2178 tm.tm_sec
= tm0.tm_sec
;
2179 tm.tm_isdst
= tm0.tm_isdst
;
2180 Start
= mktime_z
(tz
, &tm
);
2181 if
(Start
== (time_t) -1)
2183 if
(pc.parse_datetime_debug
)
2184 dbg_printf
(_
("error: adding relative date resulted "
2185 "in an invalid date: '%s'\n"),
2186 debug_strfdatetime
(&tm
, &pc
, dbg_tm
,
2191 if
(pc.parse_datetime_debug
)
2193 dbg_printf
(_
("after date adjustment "
2194 "(%+"PRIdMAX
" years, %+"PRIdMAX
" months, "
2195 "%+"PRIdMAX
" days),\n"),
2196 pc.rel.year
, pc.rel.month
, pc.rel.day
);
2197 dbg_printf
(_
(" new date/time = '%s'\n"),
2198 debug_strfdatetime
(&tm
, &pc
, dbg_tm
,
2201 /* Warn about crossing DST due to time adjustment.
2202 Example: https://bugs.gnu.org/8357
2203 env TZ=Europe/Helsinki \
2205 -d 'Mon Mar 28 00:36:07 2011 EEST 1 day ago'
2207 This case is different than DST changes due to time adjustment,
2208 i.e., "1 day ago" vs "24 hours ago" are calculated in different
2211 'tm0.tm_isdst' contains the DST of the input date,
2212 'tm.tm_isdst' is the normalized result after calling
2215 if
(tm0.tm_isdst
!= -1 && tm.tm_isdst
!= tm0.tm_isdst
)
2216 dbg_printf
(_
("warning: daylight saving time changed after "
2217 "date adjustment\n"));
2219 /* Warn if the user did not ask to adjust days but mday changed,
2221 user did not ask to adjust months/days but the month changed.
2223 Example for first case:
2224 2016-05-31 + 1 month => 2016-06-31 => 2016-07-01.
2225 User asked to adjust month, but the day changed from 31 to 01.
2227 Example for second case:
2228 2016-02-29 + 1 year => 2017-02-29 => 2017-03-01.
2229 User asked to adjust year, but the month changed from 02 to 03.
2232 && (tm.tm_mday
!= day
2233 ||
(pc.rel.month
== 0 && tm.tm_mon
!= month
)))
2235 dbg_printf
(_
("warning: month/year adjustment resulted in "
2236 "shifted dates:\n"));
2237 char tm_year_buf
[TM_YEAR_BUFSIZE
];
2238 dbg_printf
(_
(" adjusted Y M D: %s %02d %02d\n"),
2239 tm_year_str
(year
, tm_year_buf
), month
+ 1, day
);
2240 dbg_printf
(_
(" normalized Y M D: %s %02d %02d\n"),
2241 tm_year_str
(tm.tm_year
, tm_year_buf
),
2242 tm.tm_mon
+ 1, tm.tm_mday
);
2248 /* The only "output" of this if-block is an updated Start value,
2249 so this block must follow others that clobber Start. */
2252 intmax_t delta
= pc.time_zone
, t1
;
2253 bool overflow
= false
;
2254 #ifdef HAVE_TM_GMTOFF
2255 long int utcoff
= tm.tm_gmtoff
;
2259 int utcoff
= (gmtime_r
(&t
, &gmt
)
2260 ? tm_diff
(&tm
, &gmt
)
2261 : (overflow
= true
, 0));
2263 overflow |
= INT_SUBTRACT_WRAPV
(delta
, utcoff
, &delta
);
2264 overflow |
= INT_SUBTRACT_WRAPV
(Start
, delta
, &t1
);
2265 if
(overflow || time_overflow
(t1
))
2267 if
(pc.parse_datetime_debug
)
2268 dbg_printf
(_
("error: timezone %d caused time_t overflow\n"),
2275 if
(pc.parse_datetime_debug
)
2277 intmax_t Starti
= Start
;
2278 dbg_printf
(_
("'%s' = %"PRIdMAX
" epoch-seconds\n"),
2279 debug_strfdatetime
(&tm
, &pc
, dbg_tm
, sizeof dbg_tm
),
2284 /* Add relative hours, minutes, and seconds. On hosts that support
2285 leap seconds, ignore the possibility of leap seconds; e.g.,
2286 "+ 10 minutes" adds 600 seconds, even if one of them is a
2287 leap second. Typically this is not what the user wants, but it's
2288 too hard to do it the other way, because the time zone indicator
2289 must be applied before relative times, and if mktime is applied
2290 again the time zone will be lost. */
2292 intmax_t orig_ns
= pc.seconds.tv_nsec
;
2293 intmax_t sum_ns
= orig_ns
+ pc.rel.ns
;
2294 int normalized_ns
= (sum_ns % BILLION
+ BILLION
) % BILLION
;
2295 int d4
= (sum_ns
- normalized_ns
) / BILLION
;
2296 intmax_t d1
, t1
, d2
, t2
, t3
, t4
;
2297 if
(INT_MULTIPLY_WRAPV
(pc.rel.hour
, 60 * 60, &d1
)
2298 || INT_ADD_WRAPV
(Start
, d1
, &t1
)
2299 || INT_MULTIPLY_WRAPV
(pc.rel.minutes
, 60, &d2
)
2300 || INT_ADD_WRAPV
(t1
, d2
, &t2
)
2301 || INT_ADD_WRAPV
(t2
, pc.rel.seconds
, &t3
)
2302 || INT_ADD_WRAPV
(t3
, d4
, &t4
)
2303 || time_overflow
(t4
))
2305 if
(pc.parse_datetime_debug
)
2306 dbg_printf
(_
("error: adding relative time caused an "
2311 result
->tv_sec
= t4
;
2312 result
->tv_nsec
= normalized_ns
;
2314 if
(pc.parse_datetime_debug
2315 && (pc.rel.hour | pc.rel.minutes | pc.rel.seconds | pc.rel.ns
))
2317 dbg_printf
(_
("after time adjustment (%+"PRIdMAX
" hours, "
2318 "%+"PRIdMAX
" minutes, "
2319 "%+"PRIdMAX
" seconds, %+d ns),\n"),
2320 pc.rel.hour
, pc.rel.minutes
, pc.rel.seconds
,
2322 dbg_printf
(_
(" new time = %"PRIdMAX
" epoch-seconds\n"), t4
);
2324 /* Warn about crossing DST due to time adjustment.
2325 Example: https://bugs.gnu.org/8357
2326 env TZ=Europe/Helsinki \
2328 -d 'Mon Mar 28 00:36:07 2011 EEST 24 hours ago'
2330 This case is different than DST changes due to days adjustment,
2331 i.e., "1 day ago" vs "24 hours ago" are calculated in different
2334 'tm.tm_isdst' contains the date after date adjustment. */
2336 if
(tm.tm_isdst
!= -1 && localtime_rz
(tz
, &result
->tv_sec
, &lmt
)
2337 && tm.tm_isdst
!= lmt.tm_isdst
)
2338 dbg_printf
(_
("warning: daylight saving time changed after "
2339 "time adjustment\n"));
2344 if
(pc.parse_datetime_debug
)
2346 /* Special case: using 'date -u' simply set TZ=UTC0 */
2348 dbg_printf
(_
("timezone: system default\n"));
2349 else if
(STREQ
(tzstring
, "UTC0"))
2350 dbg_printf
(_
("timezone: Universal Time\n"));
2352 dbg_printf
(_
("timezone: TZ=\"%s\" environment value\n"), tzstring
);
2354 intmax_t sec
= result
->tv_sec
;
2355 int nsec
= result
->tv_nsec
;
2356 dbg_printf
(_
("final: %"PRIdMAX
".%09d (epoch-seconds)\n"),
2360 bool got_utc
= !!gmtime_r
(&result
->tv_sec
, &gmt
);
2362 dbg_printf
(_
("final: %s (UTC)\n"),
2363 debug_strfdatetime
(&gmt
, NULL
,
2364 dbg_tm
, sizeof dbg_tm
));
2365 if
(localtime_rz
(tz
, &result
->tv_sec
, &lmt
))
2367 #ifdef HAVE_TM_GMTOFF
2368 bool got_utcoff
= true
;
2369 long int utcoff
= lmt.tm_gmtoff
;
2371 bool got_utcoff
= got_utc
;
2374 utcoff
= tm_diff
(&lmt
, &gmt
);
2377 dbg_printf
(_
("final: %s (UTC%s)\n"),
2378 debug_strfdatetime
(&lmt
, NULL
, dbg_tm
, sizeof dbg_tm
),
2379 time_zone_str
(utcoff
, time_zone_buf
));
2381 dbg_printf
(_
("final: %s (unknown time zone offset)\n"),
2382 debug_strfdatetime
(&lmt
, NULL
, dbg_tm
, sizeof dbg_tm
));
2389 if
(tz
!= tzdefault
)
2398 main
(int ac
, char **av
)
2402 printf
("Enter date, or blank line to exit.\n\t> ");
2405 buff
[BUFSIZ
- 1] = '\0';
2406 while
(fgets
(buff
, BUFSIZ
- 1, stdin
) && buff
[0])
2409 struct tm
const *tm
;
2410 if
(! parse_datetime
(&d
, buff
, NULL
))
2411 printf
("Bad format - couldn't convert.\n");
2412 else if
(! (tm
= localtime
(&d.tv_sec
)))
2414 intmax_t sec
= d.tv_sec
;
2415 printf
("localtime (%"PRIdMAX
") failed\n", sec
);
2420 char tm_year_buf
[TM_YEAR_BUFSIZE
];
2421 printf
("%s-%02d-%02d %02d:%02d:%02d.%09d\n",
2422 tm_year_str
(tm
->tm_year
, tm_year_buf
),
2423 tm
->tm_mon
+ 1, tm
->tm_mday
,
2424 tm
->tm_hour
, tm
->tm_min
, tm
->tm_sec
, ns
);