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 /* Shift A right by B bits portably, by dividing A by 2**B and
86 truncating towards minus infinity. A and B should be free of side
87 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
88 INT_BITS is the number of useful bits in an int. GNU code can
89 assume that INT_BITS is at least 32.
91 ISO C99 says that A >> B is implementation-defined if A < 0. Some
92 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
93 right in the usual way when A < 0, so SHR falls back on division if
94 ordinary A >> B doesn't seem to be the usual signed shift. */
98 : (a
) / (1 << (b
)) - ((a
) %
(1 << (b
)) < 0))
100 #define HOUR(x) (60 * 60 * (x))
102 #define STREQ(a, b) (strcmp (a, b) == 0)
104 /* Verify that time_t is an integer as POSIX requires, and that every
105 time_t value fits in intmax_t. Please file a bug report if these
106 assumptions are false on your platform. */
107 verify
(TYPE_IS_INTEGER
(time_t));
108 verify
(!TYPE_SIGNED
(time_t) || INTMAX_MIN
<= TYPE_MINIMUM
(time_t));
109 verify
(TYPE_MAXIMUM
(time_t) <= INTMAX_MAX
);
111 /* True if N is out of range for time_t. */
113 time_overflow
(intmax_t n
)
115 return
! ((TYPE_SIGNED
(time_t) ? TYPE_MINIMUM
(time_t) <= n
: 0 <= n
)
116 && n
<= TYPE_MAXIMUM
(time_t));
119 /* Convert a possibly-signed character to an unsigned character. This is
120 a bit safer than casting to unsigned char, since it catches some type
121 errors that the cast doesn't. */
122 static unsigned char to_uchar
(char ch
) { return ch
; }
124 static void _GL_ATTRIBUTE_FORMAT
((__printf__
, 1, 2))
125 dbg_printf
(char const *msg
, ...
)
128 /* TODO: use gnulib's 'program_name' instead? */
129 fputs
("date: ", stderr
);
131 va_start
(args
, msg
);
132 vfprintf
(stderr
, msg
, args
);
137 /* An integer value, and the number of digits in its textual
146 /* An entry in the lexical lookup table. */
154 /* Meridian: am, pm, or 24-hour style. */
155 enum { MERam
, MERpm
, MER24
};
157 /* A reasonable upper bound for the buffer used in debug output. */
158 enum { DBGBUFSIZE
= 100 };
160 enum { BILLION
= 1000000000, LOG10_BILLION
= 9 };
162 /* Relative times. */
165 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
175 #if HAVE_COMPOUND_LITERALS
176 # define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
178 static relative_time
const RELATIVE_TIME_0
;
181 /* Information passed to and from the parser. */
184 /* The input string remaining to be parsed. */
187 /* N, if this is the Nth Tuesday. */
188 intmax_t day_ordinal
;
190 /* Day of week; Sunday is 0. */
193 /* tm_isdst flag for the local zone. */
196 /* Time zone, in seconds east of UT. */
199 /* Style used for time. */
202 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
208 struct timespec seconds
; /* includes nanoseconds */
210 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
213 /* Presence or counts of nonterminals of various flavors parsed so far. */
216 ptrdiff_t dates_seen
;
218 ptrdiff_t local_zones_seen
;
220 ptrdiff_t times_seen
;
221 ptrdiff_t zones_seen
;
224 /* Print debugging output to stderr. */
225 bool parse_datetime_debug
;
227 /* Which of the 'seen' parts have been printed when debugging. */
228 bool debug_dates_seen
;
229 bool debug_days_seen
;
230 bool debug_local_zones_seen
;
231 bool debug_times_seen
;
232 bool debug_zones_seen
;
233 bool debug_year_seen
;
235 /* The user specified explicit ordinal day value. */
236 bool debug_ordinal_day_seen
;
238 /* Table of local time zone abbreviations, terminated by a null entry. */
239 table local_time_zone_table
[3];
243 static int yylex (union YYSTYPE *, parser_control
*);
244 static int yyerror (parser_control
const *, char const *);
245 static bool time_zone_hhmm
(parser_control
*, textint
, intmax_t);
247 /* Extract into *PC any date and time info from a string of digits
248 of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
251 digits_to_date_time
(parser_control
*pc
, textint text_int
)
253 if
(pc
->dates_seen
&& ! pc
->year.digits
254 && ! pc
->rels_seen
&& (pc
->times_seen ||
2 < text_int.digits
))
256 pc
->year_seen
= true
;
261 if
(4 < text_int.digits
)
264 pc
->day
= text_int.value %
100;
265 pc
->month
= (text_int.value
/ 100) %
100;
266 pc
->year.value
= text_int.value
/ 10000;
267 pc
->year.digits
= text_int.digits
- 4;
272 if
(text_int.digits
<= 2)
274 pc
->hour
= text_int.value
;
279 pc
->hour
= text_int.value
/ 100;
280 pc
->minutes
= text_int.value %
100;
282 pc
->seconds.tv_sec
= 0;
283 pc
->seconds.tv_nsec
= 0;
284 pc
->meridian
= MER24
;
289 /* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1). Return true
290 if successful, false if an overflow occurred. */
292 apply_relative_time
(parser_control
*pc
, relative_time rel
, int factor
)
295 ?
(INT_SUBTRACT_WRAPV
(pc
->rel.ns
, rel.ns
, &pc
->rel.ns
)
296 | INT_SUBTRACT_WRAPV
(pc
->rel.seconds
, rel.seconds
, &pc
->rel.seconds
)
297 | INT_SUBTRACT_WRAPV
(pc
->rel.minutes
, rel.minutes
, &pc
->rel.minutes
)
298 | INT_SUBTRACT_WRAPV
(pc
->rel.hour
, rel.hour
, &pc
->rel.hour
)
299 | INT_SUBTRACT_WRAPV
(pc
->rel.day
, rel.day
, &pc
->rel.day
)
300 | INT_SUBTRACT_WRAPV
(pc
->rel.month
, rel.month
, &pc
->rel.month
)
301 | INT_SUBTRACT_WRAPV
(pc
->rel.year
, rel.year
, &pc
->rel.year
))
302 : (INT_ADD_WRAPV
(pc
->rel.ns
, rel.ns
, &pc
->rel.ns
)
303 | INT_ADD_WRAPV
(pc
->rel.seconds
, rel.seconds
, &pc
->rel.seconds
)
304 | INT_ADD_WRAPV
(pc
->rel.minutes
, rel.minutes
, &pc
->rel.minutes
)
305 | INT_ADD_WRAPV
(pc
->rel.hour
, rel.hour
, &pc
->rel.hour
)
306 | INT_ADD_WRAPV
(pc
->rel.day
, rel.day
, &pc
->rel.day
)
307 | INT_ADD_WRAPV
(pc
->rel.month
, rel.month
, &pc
->rel.month
)
308 | INT_ADD_WRAPV
(pc
->rel.year
, rel.year
, &pc
->rel.year
)))
310 pc
->rels_seen
= true
;
314 /* Set PC-> hour, minutes, seconds and nanoseconds members from arguments. */
316 set_hhmmss
(parser_control
*pc
, intmax_t hour
, intmax_t minutes
,
317 time_t sec
, int nsec
)
320 pc
->minutes
= minutes
;
321 pc
->seconds.tv_sec
= sec
;
322 pc
->seconds.tv_nsec
= nsec
;
325 /* Return a textual representation of the day ordinal/number values
326 in the parser_control struct (e.g., "last wed", "this tues", "thu"). */
328 str_days
(parser_control
*pc
, char *buffer
, int n
)
330 /* TODO: use relative_time_table for reverse lookup. */
331 static char const ordinal_values
[][11] = {
335 "(SECOND)", /* SECOND is commented out in relative_time_table. */
348 static char const days_values
[][4] = {
360 /* Don't add an ordinal prefix if the user didn't specify it
361 (e.g., "this wed" vs "wed"). */
362 if
(pc
->debug_ordinal_day_seen
)
364 /* Use word description if possible (e.g., -1 = last, 3 = third). */
365 len
= (-1 <= pc
->day_ordinal
&& pc
->day_ordinal
<= 12
366 ? snprintf
(buffer
, n
, "%s", ordinal_values
[pc
->day_ordinal
+ 1])
367 : snprintf
(buffer
, n
, "%"PRIdMAX
, pc
->day_ordinal
));
375 /* Add the day name */
376 if
(0 <= pc
->day_number
&& pc
->day_number
<= 6 && 0 <= len
&& len
< n
)
377 snprintf
(buffer
+ len
, n
- len
, &" %s"[len
== 0],
378 days_values
[pc
->day_number
]);
381 /* invalid day_number value - should never happen */
386 /* Convert a time zone to its string representation. */
388 enum { TIME_ZONE_BUFSIZE
= INT_STRLEN_BOUND
(intmax_t) + sizeof
":MM:SS" } ;
391 time_zone_str
(int time_zone
, char time_zone_buf
[TIME_ZONE_BUFSIZE
])
393 char *p
= time_zone_buf
;
394 char sign
= time_zone
< 0 ?
'-' : '+';
395 int hour
= abs
(time_zone
/ (60 * 60));
396 p
+= sprintf
(time_zone_buf
, "%c%02d", sign
, hour
);
397 int offset_from_hour
= abs
(time_zone %
(60 * 60));
398 if
(offset_from_hour
!= 0)
400 int mm
= offset_from_hour
/ 60;
401 int ss
= offset_from_hour %
60;
403 *p
++ = '0' + mm
/ 10;
404 *p
++ = '0' + mm %
10;
408 *p
++ = '0' + ss
/ 10;
409 *p
++ = '0' + ss %
10;
413 return time_zone_buf
;
416 /* debugging: print the current time in the parser_control structure.
417 The parser will increment "*_seen" members for those which were parsed.
418 This function will print only newly seen parts. */
420 debug_print_current_time
(char const *item
, parser_control
*pc
)
424 if
(!pc
->parse_datetime_debug
)
427 /* no newline, more items printed below */
428 dbg_printf
(_
("parsed %s part: "), item
);
430 if
(pc
->dates_seen
&& !pc
->debug_dates_seen
)
432 /*TODO: use pc->year.negative? */
433 fprintf
(stderr
, "(Y-M-D) %04"PRIdMAX
"-%02"PRIdMAX
"-%02"PRIdMAX
,
434 pc
->year.value
, pc
->month
, pc
->day
);
435 pc
->debug_dates_seen
= true
;
439 if
(pc
->year_seen
!= pc
->debug_year_seen
)
443 fprintf
(stderr
, _
("year: %04"PRIdMAX
), pc
->year.value
);
445 pc
->debug_year_seen
= pc
->year_seen
;
449 if
(pc
->times_seen
&& !pc
->debug_times_seen
)
451 intmax_t sec
= pc
->seconds.tv_sec
;
452 fprintf
(stderr
, &" %02"PRIdMAX
":%02"PRIdMAX
":%02"PRIdMAX
[!space
],
453 pc
->hour
, pc
->minutes
, sec
);
454 if
(pc
->seconds.tv_nsec
!= 0)
456 int nsec
= pc
->seconds.tv_nsec
;
457 fprintf
(stderr
, ".%09d", nsec
);
459 if
(pc
->meridian
== MERpm
)
460 fputs
("pm", stderr
);
462 pc
->debug_times_seen
= true
;
466 if
(pc
->days_seen
&& !pc
->debug_days_seen
)
470 char tmp
[DBGBUFSIZE
];
471 fprintf
(stderr
, _
("%s (day ordinal=%"PRIdMAX
" number=%d)"),
472 str_days
(pc
, tmp
, sizeof tmp
),
473 pc
->day_ordinal
, pc
->day_number
);
474 pc
->debug_days_seen
= true
;
478 /* local zone strings only change the DST settings,
479 not the timezone value. If seen, inform about the DST. */
480 if
(pc
->local_zones_seen
&& !pc
->debug_local_zones_seen
)
482 fprintf
(stderr
, &" isdst=%d%s"[!space
],
483 pc
->local_isdst
, pc
->dsts_seen ?
" DST" : "");
484 pc
->debug_local_zones_seen
= true
;
488 if
(pc
->zones_seen
&& !pc
->debug_zones_seen
)
490 char time_zone_buf
[TIME_ZONE_BUFSIZE
];
491 fprintf
(stderr
, &" UTC%s"[!space
],
492 time_zone_str
(pc
->time_zone
, time_zone_buf
));
493 pc
->debug_zones_seen
= true
;
497 if
(pc
->timespec_seen
)
499 intmax_t sec
= pc
->seconds.tv_sec
;
502 fprintf
(stderr
, _
("number of seconds: %"PRIdMAX
), sec
);
505 fputc
('\n', stderr
);
508 /* Debugging: print the current relative values. */
511 print_rel_part
(bool space
, intmax_t val
, char const *name
)
515 fprintf
(stderr
, &" %+"PRIdMAX
" %s"[!space
], val
, name
);
520 debug_print_relative_time
(char const *item
, parser_control
const *pc
)
524 if
(!pc
->parse_datetime_debug
)
527 /* no newline, more items printed below */
528 dbg_printf
(_
("parsed %s part: "), item
);
530 if
(pc
->rel.year
== 0 && pc
->rel.month
== 0 && pc
->rel.day
== 0
531 && pc
->rel.hour
== 0 && pc
->rel.minutes
== 0 && pc
->rel.seconds
== 0
534 /* Special case: relative time of this/today/now */
535 fputs
(_
("today/this/now\n"), stderr
);
539 space
= print_rel_part
(space
, pc
->rel.year
, "year(s)");
540 space
= print_rel_part
(space
, pc
->rel.month
, "month(s)");
541 space
= print_rel_part
(space
, pc
->rel.day
, "day(s)");
542 space
= print_rel_part
(space
, pc
->rel.hour
, "hour(s)");
543 space
= print_rel_part
(space
, pc
->rel.minutes
, "minutes");
544 space
= print_rel_part
(space
, pc
->rel.seconds
, "seconds");
545 print_rel_part
(space
, pc
->rel.ns
, "nanoseconds");
547 fputc
('\n', stderr
);
554 /* We want a reentrant parser, even if the TZ manipulation and the calls to
555 localtime and gmtime are not reentrant. */
557 %parse
-param
{ parser_control
*pc
}
558 %lex
-param
{ parser_control
*pc
}
560 /* This grammar has 31 shift/reduce conflicts. */
567 struct timespec timespec
;
574 %token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
575 %token
<intval
> tDAY_UNIT tDAY_SHIFT
577 %token
<intval
> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
578 %token
<intval
> tMONTH tORDINAL tZONE
580 %token
<textintval
> tSNUMBER tUNUMBER
581 %token
<timespec
> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
583 %type
<intval
> o_colon_minutes
584 %type
<timespec
> seconds signed_seconds unsigned_seconds
586 %type
<rel
> relunit relunit_snumber dayshift
599 pc
->timespec_seen
= true
;
600 debug_print_current_time
(_
("number of seconds"), pc
);
612 pc
->times_seen
++; pc
->dates_seen
++;
613 debug_print_current_time
(_
("datetime"), pc
);
618 debug_print_current_time
(_
("time"), pc
);
622 pc
->local_zones_seen
++;
623 debug_print_current_time
(_
("local_zone"), pc
);
628 debug_print_current_time
(_
("zone"), pc
);
633 debug_print_current_time
(_
("date"), pc
);
638 debug_print_current_time
(_
("day"), pc
);
642 debug_print_relative_time
(_
("relative"), pc
);
646 debug_print_current_time
(_
("number"), pc
);
650 debug_print_relative_time
(_
("hybrid"), pc
);
659 iso_8601_date
'T' iso_8601_time
665 set_hhmmss
(pc
, $1.value
, 0, 0, 0);
668 | tUNUMBER
':' tUNUMBER tMERIDIAN
670 set_hhmmss
(pc
, $1.value
, $3.value
, 0, 0);
673 | tUNUMBER
':' tUNUMBER
':' unsigned_seconds tMERIDIAN
675 set_hhmmss
(pc
, $1.value
, $3.value
, $5.tv_sec
, $5.tv_nsec
);
684 set_hhmmss
(pc
, $1.value
, 0, 0, 0);
685 pc
->meridian
= MER24
;
687 | tUNUMBER
':' tUNUMBER o_zone_offset
689 set_hhmmss
(pc
, $1.value
, $3.value
, 0, 0);
690 pc
->meridian
= MER24
;
692 | tUNUMBER
':' tUNUMBER
':' unsigned_seconds o_zone_offset
694 set_hhmmss
(pc
, $1.value
, $3.value
, $5.tv_sec
, $5.tv_nsec
);
695 pc
->meridian
= MER24
;
705 tSNUMBER o_colon_minutes
708 if
(! time_zone_hhmm
(pc
, $1, $2)) YYABORT;
712 /* Local zone strings affect only the DST setting, and take effect
713 only if the current TZ setting is relevant.
716 'EEST' is parsed as tLOCAL_ZONE, as it relates to the effective TZ:
717 TZ='Europe/Helsinki' date -d '2016-06-30 EEST'
720 'EEST' is parsed as tDAYZONE:
721 TZ='Asia/Tokyo' date -d '2016-06-30 EEST'
723 This is implemented by probing the next three calendar quarters
724 of the effective timezone and looking for DST changes -
725 if found, the timezone name (EEST) is inserted into
726 the lexical lookup table with type tLOCAL_ZONE.
727 (Search for 'quarter' comment in 'parse_datetime2'.)
731 { pc
->local_isdst
= $1; }
739 /* Note 'T' is a special case, as it is used as the separator in ISO
740 8601 date and time of day representation. */
743 { pc
->time_zone
= $1; }
745 { pc
->time_zone
= -HOUR
(7); }
746 | tZONE relunit_snumber
747 { pc
->time_zone
= $1;
748 if
(! apply_relative_time
(pc
, $2, 1)) YYABORT;
749 debug_print_relative_time
(_
("relative"), pc
);
751 |
'T' relunit_snumber
752 { pc
->time_zone
= -HOUR
(7);
753 if
(! apply_relative_time
(pc
, $2, 1)) YYABORT;
754 debug_print_relative_time
(_
("relative"), pc
);
756 | tZONE tSNUMBER o_colon_minutes
757 { if
(! time_zone_hhmm
(pc
, $2, $3)) YYABORT;
758 if
(INT_ADD_WRAPV
(pc
->time_zone
, $1, &pc
->time_zone
)) YYABORT; }
760 { pc
->time_zone
= $1 + 60 * 60; }
762 { pc
->time_zone
= $1 + 60 * 60; }
778 pc
->day_ordinal
= $1;
780 pc
->debug_ordinal_day_seen
= true
;
784 pc
->day_ordinal
= $1.value
;
786 pc
->debug_ordinal_day_seen
= true
;
791 tUNUMBER
'/' tUNUMBER
793 pc
->month
= $1.value
;
796 | tUNUMBER
'/' tUNUMBER
'/' tUNUMBER
798 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
799 otherwise as MM/DD/YY.
800 The goal in recognizing YYYY/MM/DD is solely to support legacy
801 machine-generated dates like those in an RCS log listing. If
802 you want portability, use the ISO 8601 format. */
805 if
(pc
->parse_datetime_debug
)
807 intmax_t digits
= $1.digits
;
808 dbg_printf
(_
("warning: value %"PRIdMAX
" has %"PRIdMAX
" digits. "
809 "Assuming YYYY/MM/DD\n"),
814 pc
->month
= $3.value
;
819 if
(pc
->parse_datetime_debug
)
820 dbg_printf
(_
("warning: value %"PRIdMAX
" has less than 4 digits. "
821 "Assuming MM/DD/YY[YY]\n"),
824 pc
->month
= $1.value
;
829 | tUNUMBER tMONTH tSNUMBER
831 /* E.g., 17-JUN-1992. */
834 if
(INT_SUBTRACT_WRAPV
(0, $3.value
, &pc
->year.value
)) YYABORT;
835 pc
->year.digits
= $3.digits
;
837 | tMONTH tSNUMBER tSNUMBER
839 /* E.g., JUN-17-1992. */
841 if
(INT_SUBTRACT_WRAPV
(0, $2.value
, &pc
->day
)) YYABORT;
842 if
(INT_SUBTRACT_WRAPV
(0, $3.value
, &pc
->year.value
)) YYABORT;
843 pc
->year.digits
= $3.digits
;
850 | tMONTH tUNUMBER
',' tUNUMBER
861 | tUNUMBER tMONTH tUNUMBER
871 tUNUMBER tSNUMBER tSNUMBER
873 /* ISO 8601 format. YYYY-MM-DD. */
875 if
(INT_SUBTRACT_WRAPV
(0, $2.value
, &pc
->month
)) YYABORT;
876 if
(INT_SUBTRACT_WRAPV
(0, $3.value
, &pc
->day
)) YYABORT;
882 { if
(! apply_relative_time
(pc
, $1, $2)) YYABORT; }
884 { if
(! apply_relative_time
(pc
, $1, 1)) YYABORT; }
886 { if
(! apply_relative_time
(pc
, $1, 1)) YYABORT; }
891 { $$
= RELATIVE_TIME_0
; $$.year
= $1; }
892 | tUNUMBER tYEAR_UNIT
893 { $$
= RELATIVE_TIME_0
; $$.year
= $1.value
; }
895 { $$
= RELATIVE_TIME_0
; $$.year
= 1; }
896 | tORDINAL tMONTH_UNIT
897 { $$
= RELATIVE_TIME_0
; $$.month
= $1; }
898 | tUNUMBER tMONTH_UNIT
899 { $$
= RELATIVE_TIME_0
; $$.month
= $1.value
; }
901 { $$
= RELATIVE_TIME_0
; $$.month
= 1; }
903 { $$
= RELATIVE_TIME_0
;
904 if
(INT_MULTIPLY_WRAPV
($1, $2, &$$.day
)) YYABORT; }
906 { $$
= RELATIVE_TIME_0
;
907 if
(INT_MULTIPLY_WRAPV
($1.value
, $2, &$$.day
)) YYABORT; }
909 { $$
= RELATIVE_TIME_0
; $$.day
= $1; }
910 | tORDINAL tHOUR_UNIT
911 { $$
= RELATIVE_TIME_0
; $$.hour
= $1; }
912 | tUNUMBER tHOUR_UNIT
913 { $$
= RELATIVE_TIME_0
; $$.hour
= $1.value
; }
915 { $$
= RELATIVE_TIME_0
; $$.hour
= 1; }
916 | tORDINAL tMINUTE_UNIT
917 { $$
= RELATIVE_TIME_0
; $$.minutes
= $1; }
918 | tUNUMBER tMINUTE_UNIT
919 { $$
= RELATIVE_TIME_0
; $$.minutes
= $1.value
; }
921 { $$
= RELATIVE_TIME_0
; $$.minutes
= 1; }
923 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1; }
925 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1.value
; }
926 | tSDECIMAL_NUMBER tSEC_UNIT
927 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1.tv_sec
; $$.ns
= $1.tv_nsec
; }
928 | tUDECIMAL_NUMBER tSEC_UNIT
929 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1.tv_sec
; $$.ns
= $1.tv_nsec
; }
931 { $$
= RELATIVE_TIME_0
; $$.seconds
= 1; }
937 { $$
= RELATIVE_TIME_0
; $$.year
= $1.value
; }
938 | tSNUMBER tMONTH_UNIT
939 { $$
= RELATIVE_TIME_0
; $$.month
= $1.value
; }
941 { $$
= RELATIVE_TIME_0
;
942 if
(INT_MULTIPLY_WRAPV
($1.value
, $2, &$$.day
)) YYABORT; }
943 | tSNUMBER tHOUR_UNIT
944 { $$
= RELATIVE_TIME_0
; $$.hour
= $1.value
; }
945 | tSNUMBER tMINUTE_UNIT
946 { $$
= RELATIVE_TIME_0
; $$.minutes
= $1.value
; }
948 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1.value
; }
953 { $$
= RELATIVE_TIME_0
; $$.day
= $1; }
956 seconds: signed_seconds | unsigned_seconds
;
961 { if
(time_overflow
($1.value
)) YYABORT;
962 $$.tv_sec
= $1.value
; $$.tv_nsec
= 0; }
968 { if
(time_overflow
($1.value
)) YYABORT;
969 $$.tv_sec
= $1.value
; $$.tv_nsec
= 0; }
974 { digits_to_date_time
(pc
, $1); }
978 tUNUMBER relunit_snumber
980 /* Hybrid all-digit and relative offset, so that we accept e.g.,
981 "YYYYMMDD +N days" as well as "YYYYMMDD N days". */
982 digits_to_date_time
(pc
, $1);
983 if
(! apply_relative_time
(pc
, $2, 1)) YYABORT;
996 static table
const meridian_table
[] =
998 { "AM", tMERIDIAN
, MERam
},
999 { "A.M.", tMERIDIAN
, MERam
},
1000 { "PM", tMERIDIAN
, MERpm
},
1001 { "P.M.", tMERIDIAN
, MERpm
},
1005 static table
const dst_table
[] =
1010 static table
const month_and_day_table
[] =
1012 { "JANUARY", tMONTH
, 1 },
1013 { "FEBRUARY", tMONTH
, 2 },
1014 { "MARCH", tMONTH
, 3 },
1015 { "APRIL", tMONTH
, 4 },
1016 { "MAY", tMONTH
, 5 },
1017 { "JUNE", tMONTH
, 6 },
1018 { "JULY", tMONTH
, 7 },
1019 { "AUGUST", tMONTH
, 8 },
1020 { "SEPTEMBER",tMONTH
, 9 },
1021 { "SEPT", tMONTH
, 9 },
1022 { "OCTOBER", tMONTH
, 10 },
1023 { "NOVEMBER", tMONTH
, 11 },
1024 { "DECEMBER", tMONTH
, 12 },
1025 { "SUNDAY", tDAY
, 0 },
1026 { "MONDAY", tDAY
, 1 },
1027 { "TUESDAY", tDAY
, 2 },
1028 { "TUES", tDAY
, 2 },
1029 { "WEDNESDAY",tDAY
, 3 },
1030 { "WEDNES", tDAY
, 3 },
1031 { "THURSDAY", tDAY
, 4 },
1032 { "THUR", tDAY
, 4 },
1033 { "THURS", tDAY
, 4 },
1034 { "FRIDAY", tDAY
, 5 },
1035 { "SATURDAY", tDAY
, 6 },
1039 static table
const time_units_table
[] =
1041 { "YEAR", tYEAR_UNIT
, 1 },
1042 { "MONTH", tMONTH_UNIT
, 1 },
1043 { "FORTNIGHT",tDAY_UNIT
, 14 },
1044 { "WEEK", tDAY_UNIT
, 7 },
1045 { "DAY", tDAY_UNIT
, 1 },
1046 { "HOUR", tHOUR_UNIT
, 1 },
1047 { "MINUTE", tMINUTE_UNIT
, 1 },
1048 { "MIN", tMINUTE_UNIT
, 1 },
1049 { "SECOND", tSEC_UNIT
, 1 },
1050 { "SEC", tSEC_UNIT
, 1 },
1054 /* Assorted relative-time words. */
1055 static table
const relative_time_table
[] =
1057 { "TOMORROW", tDAY_SHIFT
, 1 },
1058 { "YESTERDAY",tDAY_SHIFT
, -1 },
1059 { "TODAY", tDAY_SHIFT
, 0 },
1060 { "NOW", tDAY_SHIFT
, 0 },
1061 { "LAST", tORDINAL
, -1 },
1062 { "THIS", tORDINAL
, 0 },
1063 { "NEXT", tORDINAL
, 1 },
1064 { "FIRST", tORDINAL
, 1 },
1065 /*{ "SECOND", tORDINAL, 2 }, */
1066 { "THIRD", tORDINAL
, 3 },
1067 { "FOURTH", tORDINAL
, 4 },
1068 { "FIFTH", tORDINAL
, 5 },
1069 { "SIXTH", tORDINAL
, 6 },
1070 { "SEVENTH", tORDINAL
, 7 },
1071 { "EIGHTH", tORDINAL
, 8 },
1072 { "NINTH", tORDINAL
, 9 },
1073 { "TENTH", tORDINAL
, 10 },
1074 { "ELEVENTH", tORDINAL
, 11 },
1075 { "TWELFTH", tORDINAL
, 12 },
1076 { "AGO", tAGO
, -1 },
1077 { "HENCE", tAGO
, 1 },
1081 /* The universal time zone table. These labels can be used even for
1082 timestamps that would not otherwise be valid, e.g., GMT timestamps
1083 oin London during summer. */
1084 static table
const universal_time_zone_table
[] =
1086 { "GMT", tZONE
, HOUR
( 0) }, /* Greenwich Mean */
1087 { "UT", tZONE
, HOUR
( 0) }, /* Universal (Coordinated) */
1088 { "UTC", tZONE
, HOUR
( 0) },
1092 /* The time zone table. This table is necessarily incomplete, as time
1093 zone abbreviations are ambiguous; e.g., Australians interpret "EST"
1094 as Eastern time in Australia, not as US Eastern Standard Time.
1095 You cannot rely on parse_datetime to handle arbitrary time zone
1096 abbreviations; use numeric abbreviations like "-0500" instead. */
1097 static table
const time_zone_table
[] =
1099 { "WET", tZONE
, HOUR
( 0) }, /* Western European */
1100 { "WEST", tDAYZONE
, HOUR
( 0) }, /* Western European Summer */
1101 { "BST", tDAYZONE
, HOUR
( 0) }, /* British Summer */
1102 { "ART", tZONE
, -HOUR
( 3) }, /* Argentina */
1103 { "BRT", tZONE
, -HOUR
( 3) }, /* Brazil */
1104 { "BRST", tDAYZONE
, -HOUR
( 3) }, /* Brazil Summer */
1105 { "NST", tZONE
, -(HOUR
( 3) + 30 * 60) }, /* Newfoundland Standard */
1106 { "NDT", tDAYZONE
,-(HOUR
( 3) + 30 * 60) }, /* Newfoundland Daylight */
1107 { "AST", tZONE
, -HOUR
( 4) }, /* Atlantic Standard */
1108 { "ADT", tDAYZONE
, -HOUR
( 4) }, /* Atlantic Daylight */
1109 { "CLT", tZONE
, -HOUR
( 4) }, /* Chile */
1110 { "CLST", tDAYZONE
, -HOUR
( 4) }, /* Chile Summer */
1111 { "EST", tZONE
, -HOUR
( 5) }, /* Eastern Standard */
1112 { "EDT", tDAYZONE
, -HOUR
( 5) }, /* Eastern Daylight */
1113 { "CST", tZONE
, -HOUR
( 6) }, /* Central Standard */
1114 { "CDT", tDAYZONE
, -HOUR
( 6) }, /* Central Daylight */
1115 { "MST", tZONE
, -HOUR
( 7) }, /* Mountain Standard */
1116 { "MDT", tDAYZONE
, -HOUR
( 7) }, /* Mountain Daylight */
1117 { "PST", tZONE
, -HOUR
( 8) }, /* Pacific Standard */
1118 { "PDT", tDAYZONE
, -HOUR
( 8) }, /* Pacific Daylight */
1119 { "AKST", tZONE
, -HOUR
( 9) }, /* Alaska Standard */
1120 { "AKDT", tDAYZONE
, -HOUR
( 9) }, /* Alaska Daylight */
1121 { "HST", tZONE
, -HOUR
(10) }, /* Hawaii Standard */
1122 { "HAST", tZONE
, -HOUR
(10) }, /* Hawaii-Aleutian Standard */
1123 { "HADT", tDAYZONE
, -HOUR
(10) }, /* Hawaii-Aleutian Daylight */
1124 { "SST", tZONE
, -HOUR
(12) }, /* Samoa Standard */
1125 { "WAT", tZONE
, HOUR
( 1) }, /* West Africa */
1126 { "CET", tZONE
, HOUR
( 1) }, /* Central European */
1127 { "CEST", tDAYZONE
, HOUR
( 1) }, /* Central European Summer */
1128 { "MET", tZONE
, HOUR
( 1) }, /* Middle European */
1129 { "MEZ", tZONE
, HOUR
( 1) }, /* Middle European */
1130 { "MEST", tDAYZONE
, HOUR
( 1) }, /* Middle European Summer */
1131 { "MESZ", tDAYZONE
, HOUR
( 1) }, /* Middle European Summer */
1132 { "EET", tZONE
, HOUR
( 2) }, /* Eastern European */
1133 { "EEST", tDAYZONE
, HOUR
( 2) }, /* Eastern European Summer */
1134 { "CAT", tZONE
, HOUR
( 2) }, /* Central Africa */
1135 { "SAST", tZONE
, HOUR
( 2) }, /* South Africa Standard */
1136 { "EAT", tZONE
, HOUR
( 3) }, /* East Africa */
1137 { "MSK", tZONE
, HOUR
( 3) }, /* Moscow */
1138 { "MSD", tDAYZONE
, HOUR
( 3) }, /* Moscow Daylight */
1139 { "IST", tZONE
, (HOUR
( 5) + 30 * 60) }, /* India Standard */
1140 { "SGT", tZONE
, HOUR
( 8) }, /* Singapore */
1141 { "KST", tZONE
, HOUR
( 9) }, /* Korea Standard */
1142 { "JST", tZONE
, HOUR
( 9) }, /* Japan Standard */
1143 { "GST", tZONE
, HOUR
(10) }, /* Guam Standard */
1144 { "NZST", tZONE
, HOUR
(12) }, /* New Zealand Standard */
1145 { "NZDT", tDAYZONE
, HOUR
(12) }, /* New Zealand Daylight */
1149 /* Military time zone table.
1151 RFC 822 got these backwards, but RFC 5322 makes the incorrect
1152 treatment optional, so do them the right way here.
1154 Note 'T' is a special case, as it is used as the separator in ISO
1155 8601 date and time of day representation. */
1156 static table
const military_table
[] =
1158 { "A", tZONE
, HOUR
( 1) },
1159 { "B", tZONE
, HOUR
( 2) },
1160 { "C", tZONE
, HOUR
( 3) },
1161 { "D", tZONE
, HOUR
( 4) },
1162 { "E", tZONE
, HOUR
( 5) },
1163 { "F", tZONE
, HOUR
( 6) },
1164 { "G", tZONE
, HOUR
( 7) },
1165 { "H", tZONE
, HOUR
( 8) },
1166 { "I", tZONE
, HOUR
( 9) },
1167 { "K", tZONE
, HOUR
(10) },
1168 { "L", tZONE
, HOUR
(11) },
1169 { "M", tZONE
, HOUR
(12) },
1170 { "N", tZONE
, -HOUR
( 1) },
1171 { "O", tZONE
, -HOUR
( 2) },
1172 { "P", tZONE
, -HOUR
( 3) },
1173 { "Q", tZONE
, -HOUR
( 4) },
1174 { "R", tZONE
, -HOUR
( 5) },
1175 { "S", tZONE
, -HOUR
( 6) },
1177 { "U", tZONE
, -HOUR
( 8) },
1178 { "V", tZONE
, -HOUR
( 9) },
1179 { "W", tZONE
, -HOUR
(10) },
1180 { "X", tZONE
, -HOUR
(11) },
1181 { "Y", tZONE
, -HOUR
(12) },
1182 { "Z", tZONE
, HOUR
( 0) },
1188 /* Convert a time zone expressed as HH:MM into an integer count of
1189 seconds. If MM is negative, then S is of the form HHMM and needs
1190 to be picked apart; otherwise, S is of the form HH. As specified in
1191 https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03, allow
1192 only valid TZ range, and consider first two digits as hours, if no
1193 minutes specified. Return true if successful. */
1196 time_zone_hhmm
(parser_control
*pc
, textint s
, intmax_t mm
)
1199 bool overflow
= false
;
1201 /* If the length of S is 1 or 2 and no minutes are specified,
1202 interpret it as a number of hours. */
1203 if
(s.digits
<= 2 && mm
< 0)
1207 n_minutes
= (s.value
/ 100) * 60 + s.value %
100;
1210 overflow |
= INT_MULTIPLY_WRAPV
(s.value
, 60, &n_minutes
);
1211 overflow |
= (s.negative
1212 ? INT_SUBTRACT_WRAPV
(n_minutes
, mm
, &n_minutes
)
1213 : INT_ADD_WRAPV
(n_minutes
, mm
, &n_minutes
));
1216 if
(overflow ||
! (-24 * 60 <= n_minutes
&& n_minutes
<= 24 * 60))
1218 pc
->time_zone
= n_minutes
* 60;
1223 to_hour
(intmax_t hours
, int meridian
)
1227 default
: /* Pacify GCC. */
1229 return
0 <= hours
&& hours
< 24 ? hours
: -1;
1231 return
0 < hours
&& hours
< 12 ? hours
: hours
== 12 ?
0 : -1;
1233 return
0 < hours
&& hours
< 12 ? hours
+ 12 : hours
== 12 ?
12 : -1;
1237 enum { TM_YEAR_BASE
= 1900 };
1238 enum { TM_YEAR_BUFSIZE
= INT_BUFSIZE_BOUND
(int) + 1 };
1240 /* Convert TM_YEAR, a year minus 1900, to a string that is numerically
1241 correct even if subtracting 1900 would overflow. */
1244 tm_year_str
(int tm_year
, char buf
[TM_YEAR_BUFSIZE
])
1246 verify
(TM_YEAR_BASE %
100 == 0);
1247 sprintf
(buf
, &"-%02d%02d"[-TM_YEAR_BASE
<= tm_year
],
1248 abs
(tm_year
/ 100 + TM_YEAR_BASE
/ 100),
1249 abs
(tm_year %
100));
1253 /* Convert a text year number to a year minus 1900, working correctly
1254 even if the input is in the range INT_MAX .. INT_MAX + 1900 - 1. */
1257 to_tm_year
(textint textyear
, bool debug
, int *tm_year
)
1259 intmax_t year
= textyear.value
;
1261 /* XPG4 suggests that years 00-68 map to 2000-2068, and
1262 years 69-99 map to 1969-1999. */
1263 if
(0 <= year
&& textyear.digits
== 2)
1265 year
+= year
< 69 ?
2000 : 1900;
1267 dbg_printf
(_
("warning: adjusting year value %"PRIdMAX
1268 " to %"PRIdMAX
"\n"),
1269 textyear.value
, year
);
1273 ? INT_SUBTRACT_WRAPV
(-TM_YEAR_BASE
, year
, tm_year
)
1274 : INT_SUBTRACT_WRAPV
(year
, TM_YEAR_BASE
, tm_year
))
1277 dbg_printf
(_
("error: out-of-range year %"PRIdMAX
"\n"), year
);
1284 static table
const * _GL_ATTRIBUTE_PURE
1285 lookup_zone
(parser_control
const *pc
, char const *name
)
1289 for
(tp
= universal_time_zone_table
; tp
->name
; tp
++)
1290 if
(strcmp
(name
, tp
->name
) == 0)
1293 /* Try local zone abbreviations before those in time_zone_table, as
1294 the local ones are more likely to be right. */
1295 for
(tp
= pc
->local_time_zone_table
; tp
->name
; tp
++)
1296 if
(strcmp
(name
, tp
->name
) == 0)
1299 for
(tp
= time_zone_table
; tp
->name
; tp
++)
1300 if
(strcmp
(name
, tp
->name
) == 0)
1306 #if ! HAVE_TM_GMTOFF
1307 /* Yield the difference between *A and *B,
1308 measured in seconds, ignoring leap seconds.
1309 The body of this function is taken directly from the GNU C Library;
1312 tm_diff
(const struct tm
*a
, const struct tm
*b
)
1314 /* Compute intervening leap days correctly even if year is negative.
1315 Take care to avoid int overflow in leap day calculations,
1316 but it's OK to assume that A and B are close to each other. */
1317 int a4
= SHR
(a
->tm_year
, 2) + SHR
(TM_YEAR_BASE
, 2) - ! (a
->tm_year
& 3);
1318 int b4
= SHR
(b
->tm_year
, 2) + SHR
(TM_YEAR_BASE
, 2) - ! (b
->tm_year
& 3);
1319 int a100
= a4
/ 25 - (a4 %
25 < 0);
1320 int b100
= b4
/ 25 - (b4 %
25 < 0);
1321 int a400
= SHR
(a100
, 2);
1322 int b400
= SHR
(b100
, 2);
1323 int intervening_leap_days
= (a4
- b4
) - (a100
- b100
) + (a400
- b400
);
1324 int years
= a
->tm_year
- b
->tm_year
;
1325 int days
= (365 * years
+ intervening_leap_days
1326 + (a
->tm_yday
- b
->tm_yday
));
1327 return
(60 * (60 * (24 * days
+ (a
->tm_hour
- b
->tm_hour
))
1328 + (a
->tm_min
- b
->tm_min
))
1329 + (a
->tm_sec
- b
->tm_sec
));
1331 #endif /* ! HAVE_TM_GMTOFF */
1333 static table
const *
1334 lookup_word
(parser_control
const *pc
, char *word
)
1343 /* Make it uppercase. */
1344 for
(p
= word
; *p
; p
++)
1345 *p
= c_toupper
(to_uchar
(*p
));
1347 for
(tp
= meridian_table
; tp
->name
; tp
++)
1348 if
(strcmp
(word
, tp
->name
) == 0)
1351 /* See if we have an abbreviation for a month. */
1352 wordlen
= strlen
(word
);
1353 abbrev
= wordlen
== 3 ||
(wordlen
== 4 && word
[3] == '.');
1355 for
(tp
= month_and_day_table
; tp
->name
; tp
++)
1356 if
((abbrev ? strncmp
(word
, tp
->name
, 3) : strcmp
(word
, tp
->name
)) == 0)
1359 if
((tp
= lookup_zone
(pc
, word
)))
1362 if
(strcmp
(word
, dst_table
[0].name
) == 0)
1365 for
(tp
= time_units_table
; tp
->name
; tp
++)
1366 if
(strcmp
(word
, tp
->name
) == 0)
1369 /* Strip off any plural and try the units table again. */
1370 if
(word
[wordlen
- 1] == 'S')
1372 word
[wordlen
- 1] = '\0';
1373 for
(tp
= time_units_table
; tp
->name
; tp
++)
1374 if
(strcmp
(word
, tp
->name
) == 0)
1376 word
[wordlen
- 1] = 'S'; /* For "this" in relative_time_table. */
1379 for
(tp
= relative_time_table
; tp
->name
; tp
++)
1380 if
(strcmp
(word
, tp
->name
) == 0)
1383 /* Military time zones. */
1385 for
(tp
= military_table
; tp
->name
; tp
++)
1386 if
(word
[0] == tp
->name
[0])
1389 /* Drop out any periods and try the time zone table again. */
1390 for
(period_found
= false
, p
= q
= word
; (*p
= *q
); q
++)
1392 period_found
= true
;
1395 if
(period_found
&& (tp
= lookup_zone
(pc
, word
)))
1402 yylex (union YYSTYPE *lvalp
, parser_control
*pc
)
1408 while
(c
= *pc
->input
, c_isspace
(c
))
1411 if
(c_isdigit
(c
) || c
== '-' || c
== '+')
1416 if
(c
== '-' || c
== '+')
1418 sign
= c
== '-' ?
-1 : 1;
1419 while
(c
= *++pc
->input
, c_isspace
(c
))
1421 if
(! c_isdigit
(c
))
1422 /* skip the '-' sign */
1431 if
(INT_MULTIPLY_WRAPV
(value
, 10, &value
))
1433 if
(INT_ADD_WRAPV
(value
, sign
< 0 ?
'0' - c
: c
- '0', &value
))
1437 while
(c_isdigit
(c
));
1439 if
((c
== '.' || c
== ',') && c_isdigit
(p
[1]))
1445 if
(time_overflow
(value
))
1449 /* Accumulate fraction, to ns precision. */
1452 for
(digits
= 2; digits
<= LOG10_BILLION
; digits
++)
1459 /* Skip excess digits, truncating toward -Infinity. */
1461 for
(; c_isdigit
(*p
); p
++)
1467 while
(c_isdigit
(*p
))
1470 /* Adjust to the timespec convention, which is that
1471 tv_nsec is always a positive offset even if tv_sec is
1475 if
(s
== TYPE_MINIMUM
(time_t))
1481 lvalp
->timespec.tv_sec
= s
;
1482 lvalp
->timespec.tv_nsec
= ns
;
1484 return sign ? tSDECIMAL_NUMBER
: tUDECIMAL_NUMBER
;
1488 lvalp
->textintval.negative
= sign
< 0;
1489 lvalp
->textintval.value
= value
;
1490 lvalp
->textintval.digits
= p
- pc
->input
;
1492 return sign ? tSNUMBER
: tUNUMBER
;
1504 if
(p
< buff
+ sizeof buff
- 1)
1508 while
(c_isalpha
(c
) || c
== '.');
1511 tp
= lookup_word
(pc
, buff
);
1514 if
(pc
->parse_datetime_debug
)
1515 dbg_printf
(_
("error: unknown word '%s'\n"), buff
);
1518 lvalp
->intval
= tp
->value
;
1523 return to_uchar
(*pc
->input
++);
1525 ptrdiff_t count
= 0;
1540 /* Do nothing if the parser reports an error. */
1542 yyerror (parser_control
const *pc _GL_UNUSED
,
1543 char const *s _GL_UNUSED
)
1548 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1549 passing it to mktime_z, return true if it's OK. It's not OK if
1550 mktime failed or if *TM0 has out-of-range mainline members.
1551 The caller should set TM1->tm_wday to -1 before calling mktime,
1552 as a negative tm_wday is how mktime failure is inferred. */
1555 mktime_ok
(struct tm
const *tm0
, struct tm
const *tm1
)
1557 if
(tm1
->tm_wday
< 0)
1560 return
! ((tm0
->tm_sec ^ tm1
->tm_sec
)
1561 |
(tm0
->tm_min ^ tm1
->tm_min
)
1562 |
(tm0
->tm_hour ^ tm1
->tm_hour
)
1563 |
(tm0
->tm_mday ^ tm1
->tm_mday
)
1564 |
(tm0
->tm_mon ^ tm1
->tm_mon
)
1565 |
(tm0
->tm_year ^ tm1
->tm_year
));
1568 /* Debugging: format a 'struct tm' into a buffer, taking the parser's
1569 timezone information into account (if pc != NULL). */
1571 debug_strfdatetime
(struct tm
const *tm
, parser_control
const *pc
,
1575 1. find an optimal way to print date string in a clear and unambiguous
1576 format. Currently, always add '(Y-M-D)' prefix.
1577 Consider '2016y01m10d' or 'year(2016) month(01) day(10)'.
1579 If the user needs debug printing, it means he/she already having
1580 issues with the parsing - better to avoid formats that could
1581 be mis-interpreted (e.g., just YYYY-MM-DD).
1583 2. Can strftime be used instead?
1584 depends if it is portable and can print invalid dates on all systems.
1586 3. Print timezone information ?
1588 4. Print DST information ?
1590 5. Print nanosecond information ?
1593 Printed date/time values might not be valid, e.g., '2016-02-31'
1594 or '2016-19-2016' . These are the values as parsed from the user
1595 string, before validation.
1597 int m
= nstrftime
(buf
, n
, "(Y-M-D) %Y-%m-%d %H:%M:%S", tm
, 0, 0);
1599 /* If parser_control information was provided (for timezone),
1600 and there's enough space in the buffer, add timezone info. */
1601 if
(pc
&& m
< n
&& pc
->zones_seen
)
1603 int tz
= pc
->time_zone
;
1605 /* Account for DST if tLOCAL_ZONE was seen. */
1606 if
(pc
->local_zones_seen
&& !pc
->zones_seen
&& 0 < pc
->local_isdst
)
1609 char time_zone_buf
[TIME_ZONE_BUFSIZE
];
1610 snprintf
(&buf
[m
], n
- m
, " TZ=%s", time_zone_str
(tz
, time_zone_buf
));
1616 debug_strfdate
(struct tm
const *tm
, char *buf
, int n
)
1618 char tm_year_buf
[TM_YEAR_BUFSIZE
];
1619 snprintf
(buf
, n
, "(Y-M-D) %s-%02d-%02d",
1620 tm_year_str
(tm
->tm_year
, tm_year_buf
),
1621 tm
->tm_mon
+ 1, tm
->tm_mday
);
1626 debug_strftime
(struct tm
const *tm
, char *buf
, int n
)
1628 snprintf
(buf
, n
, "%02d:%02d:%02d", tm
->tm_hour
, tm
->tm_min
, tm
->tm_sec
);
1632 /* If mktime_ok failed, display the failed time values,
1633 and provide possible hints. Example output:
1635 date: error: invalid date/time value:
1636 date: user provided time: '(Y-M-D) 2006-04-02 02:45:00'
1637 date: normalized time: '(Y-M-D) 2006-04-02 03:45:00'
1639 date: possible reasons:
1640 date: non-existing due to daylight-saving time;
1641 date: numeric values overflow;
1642 date: missing timezone;
1645 debug_mktime_not_ok
(struct tm
const *tm0
, struct tm
const *tm1
,
1646 parser_control
const *pc
, bool time_zone_seen
)
1648 /* TODO: handle t == -1 (as in 'mktime_ok'). */
1649 char tmp
[DBGBUFSIZE
];
1651 const bool eq_sec
= (tm0
->tm_sec
== tm1
->tm_sec
);
1652 const bool eq_min
= (tm0
->tm_min
== tm1
->tm_min
);
1653 const bool eq_hour
= (tm0
->tm_hour
== tm1
->tm_hour
);
1654 const bool eq_mday
= (tm0
->tm_mday
== tm1
->tm_mday
);
1655 const bool eq_month
= (tm0
->tm_mon
== tm1
->tm_mon
);
1656 const bool eq_year
= (tm0
->tm_year
== tm1
->tm_year
);
1658 const bool dst_shift
= eq_sec
&& eq_min
&& !eq_hour
1659 && eq_mday
&& eq_month
&& eq_year
;
1661 if
(!pc
->parse_datetime_debug
)
1664 dbg_printf
(_
("error: invalid date/time value:\n"));
1665 dbg_printf
(_
(" user provided time: '%s'\n"),
1666 debug_strfdatetime
(tm0
, pc
, tmp
, sizeof tmp
));
1667 dbg_printf
(_
(" normalized time: '%s'\n"),
1668 debug_strfdatetime
(tm1
, pc
, tmp
, sizeof tmp
));
1669 /* The format must be aligned with debug_strfdatetime and the two
1670 DEBUG statements above. This string is not translated. */
1671 i
= snprintf
(tmp
, sizeof tmp
,
1672 " %4s %2s %2s %2s %2s %2s",
1673 eq_year ?
"" : "----",
1674 eq_month ?
"" : "--",
1675 eq_mday ?
"" : "--",
1676 eq_hour ?
"" : "--",
1678 eq_sec ?
"" : "--");
1679 /* Trim trailing whitespace. */
1682 if
(sizeof tmp
- 1 < i
)
1684 while
(0 < i
&& tmp
[i
- 1] == ' ')
1688 dbg_printf
("%s\n", tmp
);
1690 dbg_printf
(_
(" possible reasons:\n"));
1692 dbg_printf
(_
(" non-existing due to daylight-saving time;\n"));
1693 if
(!eq_mday
&& !eq_month
)
1694 dbg_printf
(_
(" invalid day/month combination;\n"));
1695 dbg_printf
(_
(" numeric values overflow;\n"));
1696 dbg_printf
(" %s\n", (time_zone_seen ? _
("incorrect timezone")
1697 : _
("missing timezone")));
1700 /* The original interface: run with debug=false and the default timezone. */
1702 parse_datetime
(struct timespec
*result
, char const *p
,
1703 struct timespec
const *now
)
1705 char const *tzstring
= getenv
("TZ");
1706 timezone_t tz
= tzalloc
(tzstring
);
1709 bool ok
= parse_datetime2
(result
, p
, now
, 0, tz
, tzstring
);
1714 /* Parse a date/time string, storing the resulting time value into *RESULT.
1715 The string itself is pointed to by P. Return true if successful.
1716 P can be an incomplete or relative time specification; if so, use
1717 *NOW as the basis for the returned time. Default to timezone
1718 TZDEFAULT, which corresponds to tzalloc (TZSTRING). */
1720 parse_datetime2
(struct timespec
*result
, char const *p
,
1721 struct timespec
const *now
, unsigned int flags
,
1722 timezone_t tzdefault
, char const *tzstring
)
1726 char time_zone_buf
[TIME_ZONE_BUFSIZE
];
1727 char dbg_tm
[DBGBUFSIZE
];
1729 char const *input_sentinel
= p
+ strlen
(p
);
1730 char *tz1alloc
= NULL
;
1732 /* A reasonable upper bound for the size of ordinary TZ strings.
1733 Use heap allocation if TZ's length exceeds this. */
1734 enum { TZBUFSIZE
= 100 };
1735 char tz1buf
[TZBUFSIZE
];
1737 struct timespec gettime_buffer
;
1740 gettime
(&gettime_buffer
);
1741 now
= &gettime_buffer
;
1744 time_t Start
= now
->tv_sec
;
1745 int Start_ns
= now
->tv_nsec
;
1748 while
(c
= *p
, c_isspace
(c
))
1751 timezone_t tz
= tzdefault
;
1753 /* Store a local copy prior to first "goto". Without this, a prior use
1754 below of RELATIVE_TIME_0 on the RHS might translate to an assignment-
1755 to-temporary, which would trigger a -Wjump-misses-init warning. */
1756 const relative_time rel_time_0
= RELATIVE_TIME_0
;
1758 if
(strncmp
(p
, "TZ=\"", 4) == 0)
1760 char const *tzbase
= p
+ 4;
1761 ptrdiff_t tzsize
= 1;
1764 for
(s
= tzbase
; *s
; s
++, tzsize
++)
1768 if
(! (*s
== '\\' ||
*s
== '"'))
1774 char *tz1string
= tz1buf
;
1776 if
(TZBUFSIZE
< tzsize
)
1778 tz1alloc
= malloc
(tzsize
);
1781 tz1string
= tz1alloc
;
1784 for
(s
= tzbase
; *s
!= '"'; s
++)
1785 *z
++ = *(s
+= *s
== '\\');
1787 tz1
= tzalloc
(tz1string
);
1791 tzstring
= tz1string
;
1794 while
(c
= *p
, c_isspace
(c
))
1802 if
(! localtime_rz
(tz
, &now
->tv_sec
, &tmp
))
1805 /* As documented, be careful to treat the empty string just like
1806 a date string of "0". Without this, an empty string would be
1807 declared invalid when parsed during a DST transition. */
1813 pc.parse_datetime_debug
= (flags
& PARSE_DATETIME_DEBUG
) != 0;
1814 if
(INT_ADD_WRAPV
(tmp.tm_year
, TM_YEAR_BASE
, &pc.year.value
))
1816 if
(pc.parse_datetime_debug
)
1817 dbg_printf
(_
("error: initial year out of range\n"));
1821 pc.month
= tmp.tm_mon
+ 1;
1822 pc.day
= tmp.tm_mday
;
1823 pc.hour
= tmp.tm_hour
;
1824 pc.minutes
= tmp.tm_min
;
1825 pc.seconds.tv_sec
= tmp.tm_sec
;
1826 pc.seconds.tv_nsec
= Start_ns
;
1827 tm.tm_isdst
= tmp.tm_isdst
;
1829 pc.meridian
= MER24
;
1830 pc.rel
= rel_time_0
;
1831 pc.timespec_seen
= false
;
1832 pc.rels_seen
= false
;
1836 pc.local_zones_seen
= 0;
1839 pc.year_seen
= false
;
1840 pc.debug_dates_seen
= false
;
1841 pc.debug_days_seen
= false
;
1842 pc.debug_times_seen
= false
;
1843 pc.debug_local_zones_seen
= false
;
1844 pc.debug_zones_seen
= false
;
1845 pc.debug_year_seen
= false
;
1846 pc.debug_ordinal_day_seen
= false
;
1848 #if HAVE_STRUCT_TM_TM_ZONE
1849 pc.local_time_zone_table
[0].name
= tmp.tm_zone
;
1850 pc.local_time_zone_table
[0].type
= tLOCAL_ZONE
;
1851 pc.local_time_zone_table
[0].value
= tmp.tm_isdst
;
1852 pc.local_time_zone_table
[1].name
= NULL
;
1854 /* Probe the names used in the next three calendar quarters, looking
1855 for a tm_isdst different from the one we already have. */
1858 for
(quarter
= 1; quarter
<= 3; quarter
++)
1861 if
(INT_ADD_WRAPV
(Start
, quarter
* (90 * 24 * 60 * 60), &iprobe
)
1862 || time_overflow
(iprobe
))
1864 time_t probe
= iprobe
;
1866 if
(localtime_rz
(tz
, &probe
, &probe_tm
) && probe_tm.tm_zone
1867 && probe_tm.tm_isdst
!= pc.local_time_zone_table
[0].value
)
1870 pc.local_time_zone_table
[1].name
= probe_tm.tm_zone
;
1871 pc.local_time_zone_table
[1].type
= tLOCAL_ZONE
;
1872 pc.local_time_zone_table
[1].value
= probe_tm.tm_isdst
;
1873 pc.local_time_zone_table
[2].name
= NULL
;
1882 # if !HAVE_DECL_TZNAME
1883 extern
char *tzname
[];
1886 for
(i
= 0; i
< 2; i
++)
1888 pc.local_time_zone_table
[i
].name
= tzname
[i
];
1889 pc.local_time_zone_table
[i
].type
= tLOCAL_ZONE
;
1890 pc.local_time_zone_table
[i
].value
= i
;
1892 pc.local_time_zone_table
[i
].name
= NULL
;
1895 pc.local_time_zone_table
[0].name
= NULL
;
1899 if
(pc.local_time_zone_table
[0].name
&& pc.local_time_zone_table
[1].name
1900 && ! strcmp
(pc.local_time_zone_table
[0].name
,
1901 pc.local_time_zone_table
[1].name
))
1903 /* This locale uses the same abbreviation for standard and
1904 daylight times. So if we see that abbreviation, we don't
1905 know whether it's daylight time. */
1906 pc.local_time_zone_table
[0].value
= -1;
1907 pc.local_time_zone_table
[1].name
= NULL
;
1910 if
(yyparse (&pc
) != 0)
1912 if
(pc.parse_datetime_debug
)
1913 dbg_printf
((input_sentinel
<= pc.input
1914 ? _
("error: parsing failed\n")
1915 : _
("error: parsing failed, stopped at '%s'\n")),
1921 /* Determine effective timezone source. */
1923 if
(pc.parse_datetime_debug
)
1925 dbg_printf
(_
("input timezone: "));
1927 if
(pc.timespec_seen
)
1928 fprintf
(stderr
, _
("'@timespec' - always UTC"));
1929 else if
(pc.zones_seen
)
1930 fprintf
(stderr
, _
("parsed date/time string"));
1933 if
(tz
!= tzdefault
)
1934 fprintf
(stderr
, _
("TZ=\"%s\" in date string"), tzstring
);
1935 else if
(STREQ
(tzstring
, "UTC0"))
1937 /* Special case: 'date -u' sets TZ="UTC0". */
1938 fprintf
(stderr
, _
("TZ=\"UTC0\" environment value or -u"));
1941 fprintf
(stderr
, _
("TZ=\"%s\" environment value"), tzstring
);
1944 fprintf
(stderr
, _
("system default"));
1946 /* Account for DST changes if tLOCAL_ZONE was seen.
1947 local timezone only changes DST and is relative to the
1949 if
(pc.local_zones_seen
&& !pc.zones_seen
&& 0 < pc.local_isdst
)
1950 fprintf
(stderr
, ", dst");
1953 fprintf
(stderr
, " (%s)", time_zone_str
(pc.time_zone
, time_zone_buf
));
1955 fputc
('\n', stderr
);
1958 if
(pc.timespec_seen
)
1959 *result
= pc.seconds
;
1962 if
(1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
1963 |
(pc.local_zones_seen
+ pc.zones_seen
)))
1965 if
(pc.parse_datetime_debug
)
1967 if
(pc.times_seen
> 1)
1968 dbg_printf
("error: seen multiple time parts\n");
1969 if
(pc.dates_seen
> 1)
1970 dbg_printf
("error: seen multiple date parts\n");
1971 if
(pc.days_seen
> 1)
1972 dbg_printf
("error: seen multiple days parts\n");
1973 if
(pc.dsts_seen
> 1)
1974 dbg_printf
("error: seen multiple daylight-saving parts\n");
1975 if
((pc.local_zones_seen
+ pc.zones_seen
) > 1)
1976 dbg_printf
("error: seen multiple time-zone parts\n");
1981 if
(! to_tm_year
(pc.year
, pc.parse_datetime_debug
, &tm.tm_year
)
1982 || INT_ADD_WRAPV
(pc.month
, -1, &tm.tm_mon
)
1983 || INT_ADD_WRAPV
(pc.day
, 0, &tm.tm_mday
))
1985 if
(pc.parse_datetime_debug
)
1986 dbg_printf
(_
("error: year, month, or day overflow\n"));
1989 if
(pc.times_seen ||
(pc.rels_seen
&& ! pc.dates_seen
&& ! pc.days_seen
))
1991 tm.tm_hour
= to_hour
(pc.hour
, pc.meridian
);
1994 char const *mrd
= (pc.meridian
== MERam ?
"am"
1995 : pc.meridian
== MERpm ?
"pm" : "");
1996 if
(pc.parse_datetime_debug
)
1997 dbg_printf
(_
("error: invalid hour %"PRIdMAX
"%s\n"),
2001 tm.tm_min
= pc.minutes
;
2002 tm.tm_sec
= pc.seconds.tv_sec
;
2003 if
(pc.parse_datetime_debug
)
2004 dbg_printf
((pc.times_seen
2005 ? _
("using specified time as starting value: '%s'\n")
2006 : _
("using current time as starting value: '%s'\n")),
2007 debug_strftime
(&tm
, dbg_tm
, sizeof dbg_tm
));
2011 tm.tm_hour
= tm.tm_min
= tm.tm_sec
= 0;
2012 pc.seconds.tv_nsec
= 0;
2013 if
(pc.parse_datetime_debug
)
2014 dbg_printf
("warning: using midnight as starting time: 00:00:00\n");
2017 /* Let mktime deduce tm_isdst if we have an absolute timestamp. */
2018 if
(pc.dates_seen | pc.days_seen | pc.times_seen
)
2021 /* But if the input explicitly specifies local time with or without
2022 DST, give mktime that information. */
2023 if
(pc.local_zones_seen
)
2024 tm.tm_isdst
= pc.local_isdst
;
2026 tm0.tm_sec
= tm.tm_sec
;
2027 tm0.tm_min
= tm.tm_min
;
2028 tm0.tm_hour
= tm.tm_hour
;
2029 tm0.tm_mday
= tm.tm_mday
;
2030 tm0.tm_mon
= tm.tm_mon
;
2031 tm0.tm_year
= tm.tm_year
;
2032 tm0.tm_isdst
= tm.tm_isdst
;
2035 Start
= mktime_z
(tz
, &tm
);
2037 if
(! mktime_ok
(&tm0
, &tm
))
2039 bool repaired
= false
;
2040 bool time_zone_seen
= pc.zones_seen
!= 0;
2043 /* Guard against falsely reporting errors near the time_t
2044 boundaries when parsing times in other time zones. For
2045 example, suppose the input string "1969-12-31 23:00:00 -0100",
2046 the current time zone is 8 hours ahead of UTC, and the min
2047 time_t value is 1970-01-01 00:00:00 UTC. Then the min
2048 localtime value is 1970-01-01 08:00:00, and mktime will
2049 therefore fail on 1969-12-31 23:00:00. To work around the
2050 problem, set the time zone to 1 hour behind UTC temporarily
2051 by setting TZ="XXX1:00" and try mktime again. */
2053 char tz2buf
[sizeof
"XXX" - 1 + TIME_ZONE_BUFSIZE
];
2054 tz2buf
[0] = tz2buf
[1] = tz2buf
[2] = 'X';
2055 time_zone_str
(pc.time_zone
, &tz2buf
[3]);
2056 timezone_t tz2
= tzalloc
(tz2buf
);
2059 if
(pc.parse_datetime_debug
)
2060 dbg_printf
(_
("error: tzalloc (\"%s\") failed\n"), tz2buf
);
2063 tm.tm_sec
= tm0.tm_sec
;
2064 tm.tm_min
= tm0.tm_min
;
2065 tm.tm_hour
= tm0.tm_hour
;
2066 tm.tm_mday
= tm0.tm_mday
;
2067 tm.tm_mon
= tm0.tm_mon
;
2068 tm.tm_year
= tm0.tm_year
;
2069 tm.tm_isdst
= tm0.tm_isdst
;
2071 Start
= mktime_z
(tz2
, &tm
);
2072 repaired
= mktime_ok
(&tm0
, &tm
);
2078 debug_mktime_not_ok
(&tm0
, &tm
, &pc
, time_zone_seen
);
2083 char dbg_ord
[DBGBUFSIZE
];
2085 if
(pc.days_seen
&& ! pc.dates_seen
)
2088 if
(INT_MULTIPLY_WRAPV
((pc.day_ordinal
2089 - (0 < pc.day_ordinal
2090 && tm.tm_wday
!= pc.day_number
)),
2092 || INT_ADD_WRAPV
((pc.day_number
- tm.tm_wday
+ 7) %
7,
2094 || INT_ADD_WRAPV
(dayincr
, tm.tm_mday
, &tm.tm_mday
))
2099 Start
= mktime_z
(tz
, &tm
);
2102 if
(Start
== (time_t) -1)
2104 if
(pc.parse_datetime_debug
)
2105 dbg_printf
(_
("error: day '%s' "
2106 "(day ordinal=%"PRIdMAX
" number=%d) "
2107 "resulted in an invalid date: '%s'\n"),
2108 str_days
(&pc
, dbg_ord
, sizeof dbg_ord
),
2109 pc.day_ordinal
, pc.day_number
,
2110 debug_strfdatetime
(&tm
, &pc
, dbg_tm
,
2115 if
(pc.parse_datetime_debug
)
2116 dbg_printf
(_
("new start date: '%s' is '%s'\n"),
2117 str_days
(&pc
, dbg_ord
, sizeof dbg_ord
),
2118 debug_strfdatetime
(&tm
, &pc
, dbg_tm
, sizeof dbg_tm
));
2122 if
(pc.parse_datetime_debug
)
2124 if
(!pc.dates_seen
&& !pc.days_seen
)
2125 dbg_printf
(_
("using current date as starting value: '%s'\n"),
2126 debug_strfdate
(&tm
, dbg_tm
, sizeof dbg_tm
));
2128 if
(pc.days_seen
&& pc.dates_seen
)
2129 dbg_printf
(_
("warning: day (%s) ignored when explicit dates "
2131 str_days
(&pc
, dbg_ord
, sizeof dbg_ord
));
2133 dbg_printf
(_
("starting date/time: '%s'\n"),
2134 debug_strfdatetime
(&tm
, &pc
, dbg_tm
, sizeof dbg_tm
));
2137 /* Add relative date. */
2138 if
(pc.rel.year | pc.rel.month | pc.rel.day
)
2140 if
(pc.parse_datetime_debug
)
2142 if
((pc.rel.year
!= 0 || pc.rel.month
!= 0) && tm.tm_mday
!= 15)
2143 dbg_printf
(_
("warning: when adding relative months/years, "
2144 "it is recommended to specify the 15th of the "
2147 if
(pc.rel.day
!= 0 && tm.tm_hour
!= 12)
2148 dbg_printf
(_
("warning: when adding relative days, "
2149 "it is recommended to specify noon\n"));
2152 int year
, month
, day
;
2153 if
(INT_ADD_WRAPV
(tm.tm_year
, pc.rel.year
, &year
)
2154 || INT_ADD_WRAPV
(tm.tm_mon
, pc.rel.month
, &month
)
2155 || INT_ADD_WRAPV
(tm.tm_mday
, pc.rel.day
, &day
))
2157 if
(pc.parse_datetime_debug
)
2158 dbg_printf
(_
("error: %s:%d\n"), __FILE__
, __LINE__
);
2164 tm.tm_hour
= tm0.tm_hour
;
2165 tm.tm_min
= tm0.tm_min
;
2166 tm.tm_sec
= tm0.tm_sec
;
2167 tm.tm_isdst
= tm0.tm_isdst
;
2168 Start
= mktime_z
(tz
, &tm
);
2169 if
(Start
== (time_t) -1)
2171 if
(pc.parse_datetime_debug
)
2172 dbg_printf
(_
("error: adding relative date resulted "
2173 "in an invalid date: '%s'\n"),
2174 debug_strfdatetime
(&tm
, &pc
, dbg_tm
,
2179 if
(pc.parse_datetime_debug
)
2181 dbg_printf
(_
("after date adjustment "
2182 "(%+"PRIdMAX
" years, %+"PRIdMAX
" months, "
2183 "%+"PRIdMAX
" days),\n"),
2184 pc.rel.year
, pc.rel.month
, pc.rel.day
);
2185 dbg_printf
(_
(" new date/time = '%s'\n"),
2186 debug_strfdatetime
(&tm
, &pc
, dbg_tm
,
2189 /* Warn about crossing DST due to time adjustment.
2190 Example: https://bugs.gnu.org/8357
2191 env TZ=Europe/Helsinki \
2193 -d 'Mon Mar 28 00:36:07 2011 EEST 1 day ago'
2195 This case is different than DST changes due to time adjustment,
2196 i.e., "1 day ago" vs "24 hours ago" are calculated in different
2199 'tm0.tm_isdst' contains the DST of the input date,
2200 'tm.tm_isdst' is the normalized result after calling
2203 if
(tm0.tm_isdst
!= -1 && tm.tm_isdst
!= tm0.tm_isdst
)
2204 dbg_printf
(_
("warning: daylight saving time changed after "
2205 "date adjustment\n"));
2207 /* Warn if the user did not ask to adjust days but mday changed,
2209 user did not ask to adjust months/days but the month changed.
2211 Example for first case:
2212 2016-05-31 + 1 month => 2016-06-31 => 2016-07-01.
2213 User asked to adjust month, but the day changed from 31 to 01.
2215 Example for second case:
2216 2016-02-29 + 1 year => 2017-02-29 => 2017-03-01.
2217 User asked to adjust year, but the month changed from 02 to 03.
2220 && (tm.tm_mday
!= day
2221 ||
(pc.rel.month
== 0 && tm.tm_mon
!= month
)))
2223 dbg_printf
(_
("warning: month/year adjustment resulted in "
2224 "shifted dates:\n"));
2225 char tm_year_buf
[TM_YEAR_BUFSIZE
];
2226 dbg_printf
(_
(" adjusted Y M D: %s %02d %02d\n"),
2227 tm_year_str
(year
, tm_year_buf
), month
+ 1, day
);
2228 dbg_printf
(_
(" normalized Y M D: %s %02d %02d\n"),
2229 tm_year_str
(tm.tm_year
, tm_year_buf
),
2230 tm.tm_mon
+ 1, tm.tm_mday
);
2236 /* The only "output" of this if-block is an updated Start value,
2237 so this block must follow others that clobber Start. */
2240 intmax_t delta
= pc.time_zone
, t1
;
2241 bool overflow
= false
;
2242 #ifdef HAVE_TM_GMTOFF
2243 long int utcoff
= tm.tm_gmtoff
;
2247 int utcoff
= (gmtime_r
(&t
, &gmt
)
2248 ? tm_diff
(&tm
, &gmt
)
2249 : (overflow
= true
, 0));
2251 overflow |
= INT_SUBTRACT_WRAPV
(delta
, utcoff
, &delta
);
2252 overflow |
= INT_SUBTRACT_WRAPV
(Start
, delta
, &t1
);
2253 if
(overflow || time_overflow
(t1
))
2255 if
(pc.parse_datetime_debug
)
2256 dbg_printf
(_
("error: timezone %d caused time_t overflow\n"),
2263 if
(pc.parse_datetime_debug
)
2265 intmax_t Starti
= Start
;
2266 dbg_printf
(_
("'%s' = %"PRIdMAX
" epoch-seconds\n"),
2267 debug_strfdatetime
(&tm
, &pc
, dbg_tm
, sizeof dbg_tm
),
2272 /* Add relative hours, minutes, and seconds. On hosts that support
2273 leap seconds, ignore the possibility of leap seconds; e.g.,
2274 "+ 10 minutes" adds 600 seconds, even if one of them is a
2275 leap second. Typically this is not what the user wants, but it's
2276 too hard to do it the other way, because the time zone indicator
2277 must be applied before relative times, and if mktime is applied
2278 again the time zone will be lost. */
2280 intmax_t orig_ns
= pc.seconds.tv_nsec
;
2281 intmax_t sum_ns
= orig_ns
+ pc.rel.ns
;
2282 int normalized_ns
= (sum_ns % BILLION
+ BILLION
) % BILLION
;
2283 int d4
= (sum_ns
- normalized_ns
) / BILLION
;
2284 intmax_t d1
, t1
, d2
, t2
, t3
, t4
;
2285 if
(INT_MULTIPLY_WRAPV
(pc.rel.hour
, 60 * 60, &d1
)
2286 || INT_ADD_WRAPV
(Start
, d1
, &t1
)
2287 || INT_MULTIPLY_WRAPV
(pc.rel.minutes
, 60, &d2
)
2288 || INT_ADD_WRAPV
(t1
, d2
, &t2
)
2289 || INT_ADD_WRAPV
(t2
, pc.rel.seconds
, &t3
)
2290 || INT_ADD_WRAPV
(t3
, d4
, &t4
)
2291 || time_overflow
(t4
))
2293 if
(pc.parse_datetime_debug
)
2294 dbg_printf
(_
("error: adding relative time caused an "
2299 result
->tv_sec
= t4
;
2300 result
->tv_nsec
= normalized_ns
;
2302 if
(pc.parse_datetime_debug
2303 && (pc.rel.hour | pc.rel.minutes | pc.rel.seconds | pc.rel.ns
))
2305 dbg_printf
(_
("after time adjustment (%+"PRIdMAX
" hours, "
2306 "%+"PRIdMAX
" minutes, "
2307 "%+"PRIdMAX
" seconds, %+d ns),\n"),
2308 pc.rel.hour
, pc.rel.minutes
, pc.rel.seconds
,
2310 dbg_printf
(_
(" new time = %"PRIdMAX
" epoch-seconds\n"), t4
);
2312 /* Warn about crossing DST due to time adjustment.
2313 Example: https://bugs.gnu.org/8357
2314 env TZ=Europe/Helsinki \
2316 -d 'Mon Mar 28 00:36:07 2011 EEST 24 hours ago'
2318 This case is different than DST changes due to days adjustment,
2319 i.e., "1 day ago" vs "24 hours ago" are calculated in different
2322 'tm.tm_isdst' contains the date after date adjustment. */
2324 if
(tm.tm_isdst
!= -1 && localtime_rz
(tz
, &result
->tv_sec
, &lmt
)
2325 && tm.tm_isdst
!= lmt.tm_isdst
)
2326 dbg_printf
(_
("warning: daylight saving time changed after "
2327 "time adjustment\n"));
2332 if
(pc.parse_datetime_debug
)
2334 /* Special case: using 'date -u' simply set TZ=UTC0 */
2336 dbg_printf
(_
("timezone: system default\n"));
2337 else if
(STREQ
(tzstring
, "UTC0"))
2338 dbg_printf
(_
("timezone: Universal Time\n"));
2340 dbg_printf
(_
("timezone: TZ=\"%s\" environment value\n"), tzstring
);
2342 intmax_t sec
= result
->tv_sec
;
2343 int nsec
= result
->tv_nsec
;
2344 dbg_printf
(_
("final: %"PRIdMAX
".%09d (epoch-seconds)\n"),
2348 bool got_utc
= !!gmtime_r
(&result
->tv_sec
, &gmt
);
2350 dbg_printf
(_
("final: %s (UTC)\n"),
2351 debug_strfdatetime
(&gmt
, NULL
,
2352 dbg_tm
, sizeof dbg_tm
));
2353 if
(localtime_rz
(tz
, &result
->tv_sec
, &lmt
))
2355 #ifdef HAVE_TM_GMTOFF
2356 bool got_utcoff
= true
;
2357 long int utcoff
= lmt.tm_gmtoff
;
2359 bool got_utcoff
= got_utc
;
2362 utcoff
= tm_diff
(&lmt
, &gmt
);
2365 dbg_printf
(_
("final: %s (UTC%s)\n"),
2366 debug_strfdatetime
(&lmt
, NULL
, dbg_tm
, sizeof dbg_tm
),
2367 time_zone_str
(utcoff
, time_zone_buf
));
2369 dbg_printf
(_
("final: %s (unknown time zone offset)\n"),
2370 debug_strfdatetime
(&lmt
, NULL
, dbg_tm
, sizeof dbg_tm
));
2377 if
(tz
!= tzdefault
)
2386 main
(int ac
, char **av
)
2390 printf
("Enter date, or blank line to exit.\n\t> ");
2393 buff
[BUFSIZ
- 1] = '\0';
2394 while
(fgets
(buff
, BUFSIZ
- 1, stdin
) && buff
[0])
2397 struct tm
const *tm
;
2398 if
(! parse_datetime
(&d
, buff
, NULL
))
2399 printf
("Bad format - couldn't convert.\n");
2400 else if
(! (tm
= localtime
(&d.tv_sec
)))
2402 intmax_t sec
= d.tv_sec
;
2403 printf
("localtime (%"PRIdMAX
") failed\n", sec
);
2408 char tm_year_buf
[TM_YEAR_BUFSIZE
];
2409 printf
("%s-%02d-%02d %02d:%02d:%02d.%09d\n",
2410 tm_year_str
(tm
->tm_year
, tm_year_buf
),
2411 tm
->tm_mon
+ 1, tm
->tm_mday
,
2412 tm
->tm_hour
, tm
->tm_min
, tm
->tm_sec
, ns
);