2 /* Parse a string into an internal timestamp.
4 Copyright (C) 1999-2000, 2002-2024 Free Software Foundation, Inc.
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <https://www.gnu.org/licenses/>. */
19 /* Originally written by Steven M. Bellovin <smb@research.att.com> while
20 at the University of North Carolina at Chapel Hill. Later tweaked by
21 a couple of people on Usenet. Completely overhauled by Rich $alz
22 <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
24 Modified by Assaf Gordon <assafgordon@gmail.com> in 2016 to add
27 Modified by Paul Eggert <eggert@twinsun.com> in 1999 to do the
28 right thing about local DST. Also modified by Paul Eggert
29 <eggert@cs.ucla.edu> in 2004 to support nanosecond-resolution
30 timestamps, in 2004 to support TZ strings in dates, and in 2017 and 2020 to
31 check for integer overflow and to support longer-than-'long'
32 'time_t' and 'tv_nsec'. */
36 #include "parse-datetime.h"
42 /* There's no need to extend the stack, so there's no need to involve
44 #define YYSTACK_USE_ALLOCA 0
46 /* Tell Bison how much stack space is needed. 20 should be plenty for
47 this grammar, which is not right recursive. Beware setting it too
48 high, since that might cause problems on machines whose
49 implementations have lame stack-overflow checking. */
51 #define YYINITDEPTH YYMAXDEPTH
56 #include <stdckdint.h>
63 #define _(str) gettext (str)
65 /* Bison's skeleton tests _STDLIB_H, while some stdlib.h headers
66 use _STDLIB_H_ as witness. Map the latter to the one bison uses. */
67 /* FIXME: this is temporary. Remove when we have a mechanism to ensure
68 that the version we're using is fixed, too. */
74 /* Shift A right by B bits portably, by dividing A by 2**B and
75 truncating towards minus infinity. A and B should be free of side
76 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
77 INT_BITS is the number of useful bits in an int. GNU code can
78 assume that INT_BITS is at least 32.
80 ISO C99 says that A >> B is implementation-defined if A < 0. Some
81 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
82 right in the usual way when A < 0, so SHR falls back on division if
83 ordinary A >> B doesn't seem to be the usual signed shift. */
87 : (a
) / (1 << (b
)) - ((a
) %
(1 << (b
)) < 0))
89 #define HOUR(x) (60 * 60 * (x))
91 #define STREQ(a, b) (strcmp (a, b) == 0)
93 /* Verify that time_t is an integer as POSIX requires, and that every
94 time_t value fits in intmax_t. Please file a bug report if these
95 assumptions are false on your platform. */
96 static_assert
(TYPE_IS_INTEGER
(time_t));
97 static_assert
(!TYPE_SIGNED
(time_t) || INTMAX_MIN
<= TYPE_MINIMUM
(time_t));
98 static_assert
(TYPE_MAXIMUM
(time_t) <= INTMAX_MAX
);
100 /* True if N is out of range for time_t. */
102 time_overflow
(intmax_t n
)
104 return
! ((TYPE_SIGNED
(time_t) ? TYPE_MINIMUM
(time_t) <= n
: 0 <= n
)
105 && n
<= TYPE_MAXIMUM
(time_t));
108 /* Convert a possibly-signed character to an unsigned character. This is
109 a bit safer than casting to unsigned char, since it catches some type
110 errors that the cast doesn't. */
111 static unsigned char to_uchar
(char ch
) { return ch
; }
113 static void _GL_ATTRIBUTE_FORMAT
((__printf__
, 1, 2))
114 dbg_printf
(char const *msg
, ...
)
117 /* TODO: use gnulib's 'program_name' instead? */
118 fputs
("date: ", stderr
);
120 va_start
(args
, msg
);
121 vfprintf
(stderr
, msg
, args
);
126 /* An integer value, and the number of digits in its textual
135 /* An entry in the lexical lookup table. */
143 /* Meridian: am, pm, or 24-hour style. */
144 enum { MERam
, MERpm
, MER24
};
146 /* Maximum length of a time zone abbreviation, plus 1. */
147 enum { TIME_ZONE_BUFSIZE
= INT_STRLEN_BOUND
(intmax_t) + sizeof
":MM:SS" };
149 /* A reasonable upper bound for the buffer used in debug output. */
150 enum { DBGBUFSIZE
= 100 };
152 enum { BILLION
= 1000000000, LOG10_BILLION
= 9 };
154 /* Relative times. */
157 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
167 #if HAVE_COMPOUND_LITERALS
168 # define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
170 static relative_time
const RELATIVE_TIME_0
;
173 /* Information passed to and from the parser. */
176 /* The input string remaining to be parsed. */
179 /* N, if this is the Nth Tuesday. */
180 intmax_t day_ordinal
;
182 /* Day of week; Sunday is 0. */
185 /* tm_isdst flag for the local zone. */
188 /* Time zone, in seconds east of UT. */
191 /* Style used for time. */
194 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
200 struct timespec seconds
; /* includes nanoseconds */
202 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
205 /* Presence or counts of nonterminals of various flavors parsed so far. */
211 idx_t local_zones_seen
;
217 #ifdef GNULIB_PARSE_DATETIME2
218 /* Print debugging output to stderr. */
219 bool parse_datetime_debug
;
222 /* Which of the 'seen' parts have been printed when debugging. */
223 bool debug_dates_seen
;
224 bool debug_days_seen
;
225 bool debug_local_zones_seen
;
226 bool debug_times_seen
;
227 bool debug_zones_seen
;
228 bool debug_year_seen
;
230 /* The user specified explicit ordinal day value. */
231 bool debug_ordinal_day_seen
;
233 /* Table of local time zone abbreviations, terminated by a null entry. */
234 table local_time_zone_table
[3];
236 #if !HAVE_STRUCT_TM_TM_ZONE
237 /* The abbreviations in LOCAL_TIME_ZONE_TABLE. */
238 char tz_abbr
[2][TIME_ZONE_BUFSIZE
];
243 debugging
(parser_control
const *pc
)
245 #ifdef GNULIB_PARSE_DATETIME2
246 return pc
->parse_datetime_debug
;
253 static int yylex (union YYSTYPE *, parser_control
*);
254 static void yyerror (parser_control
const *, char const *);
255 static bool time_zone_hhmm
(parser_control
*, textint
, intmax_t);
257 /* Extract into *PC any date and time info from a string of digits
258 of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
261 digits_to_date_time
(parser_control
*pc
, textint text_int
)
263 if
(pc
->dates_seen
&& ! pc
->year.digits
264 && ! pc
->rels_seen
&& (pc
->times_seen ||
2 < text_int.digits
))
266 pc
->year_seen
= true
;
271 if
(4 < text_int.digits
)
274 pc
->day
= text_int.value %
100;
275 pc
->month
= (text_int.value
/ 100) %
100;
276 pc
->year.value
= text_int.value
/ 10000;
277 pc
->year.digits
= text_int.digits
- 4;
282 if
(text_int.digits
<= 2)
284 pc
->hour
= text_int.value
;
289 pc
->hour
= text_int.value
/ 100;
290 pc
->minutes
= text_int.value %
100;
292 pc
->seconds
= (struct timespec
) {0};
293 pc
->meridian
= MER24
;
298 /* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1). Return true
299 if successful, false if an overflow occurred. */
301 apply_relative_time
(parser_control
*pc
, relative_time rel
, int factor
)
304 ?
(ckd_sub
(&pc
->rel.ns
, pc
->rel.ns
, rel.ns
)
305 | ckd_sub
(&pc
->rel.seconds
, pc
->rel.seconds
, rel.seconds
)
306 | ckd_sub
(&pc
->rel.minutes
, pc
->rel.minutes
, rel.minutes
)
307 | ckd_sub
(&pc
->rel.hour
, pc
->rel.hour
, rel.hour
)
308 | ckd_sub
(&pc
->rel.day
, pc
->rel.day
, rel.day
)
309 | ckd_sub
(&pc
->rel.month
, pc
->rel.month
, rel.month
)
310 | ckd_sub
(&pc
->rel.year
, pc
->rel.year
, rel.year
))
311 : (ckd_add
(&pc
->rel.ns
, pc
->rel.ns
, rel.ns
)
312 | ckd_add
(&pc
->rel.seconds
, pc
->rel.seconds
, rel.seconds
)
313 | ckd_add
(&pc
->rel.minutes
, pc
->rel.minutes
, rel.minutes
)
314 | ckd_add
(&pc
->rel.hour
, pc
->rel.hour
, rel.hour
)
315 | ckd_add
(&pc
->rel.day
, pc
->rel.day
, rel.day
)
316 | ckd_add
(&pc
->rel.month
, pc
->rel.month
, rel.month
)
317 | ckd_add
(&pc
->rel.year
, pc
->rel.year
, rel.year
)))
319 pc
->rels_seen
= true
;
323 /* Set PC-> hour, minutes, seconds and nanoseconds members from arguments. */
325 set_hhmmss
(parser_control
*pc
, intmax_t hour
, intmax_t minutes
,
326 time_t sec
, int nsec
)
329 pc
->minutes
= minutes
;
330 pc
->seconds
= (struct timespec
) { .tv_sec
= sec
, .tv_nsec
= nsec
};
333 /* Return a textual representation of the day ordinal/number values
334 in the parser_control struct (e.g., "last wed", "this tues", "thu"). */
336 str_days
(parser_control
*pc
, char *buffer
, int n
)
338 /* TODO: use relative_time_table for reverse lookup. */
339 static char const ordinal_values
[][11] = {
343 "(SECOND)", /* SECOND is commented out in relative_time_table. */
356 static char const days_values
[][4] = {
368 /* Don't add an ordinal prefix if the user didn't specify it
369 (e.g., "this wed" vs "wed"). */
370 if
(pc
->debug_ordinal_day_seen
)
372 /* Use word description if possible (e.g., -1 = last, 3 = third). */
373 len
= (-1 <= pc
->day_ordinal
&& pc
->day_ordinal
<= 12
374 ? snprintf
(buffer
, n
, "%s", ordinal_values
[pc
->day_ordinal
+ 1])
375 : snprintf
(buffer
, n
, "%"PRIdMAX
, pc
->day_ordinal
));
383 /* Add the day name */
384 if
(0 <= pc
->day_number
&& pc
->day_number
<= 6 && 0 <= len
&& len
< n
)
385 snprintf
(buffer
+ len
, n
- len
, &" %s"[len
== 0],
386 days_values
[pc
->day_number
]);
389 /* invalid day_number value - should never happen */
394 /* Convert a time zone to its string representation. */
397 time_zone_str
(int time_zone
, char time_zone_buf
[TIME_ZONE_BUFSIZE
])
399 char *p
= time_zone_buf
;
400 char sign
= time_zone
< 0 ?
'-' : '+';
401 int hour
= abs
(time_zone
/ (60 * 60));
402 p
+= sprintf
(time_zone_buf
, "%c%02d", sign
, hour
);
403 int offset_from_hour
= abs
(time_zone %
(60 * 60));
404 if
(offset_from_hour
!= 0)
406 int mm
= offset_from_hour
/ 60;
407 int ss
= offset_from_hour %
60;
409 *p
++ = '0' + mm
/ 10;
410 *p
++ = '0' + mm %
10;
414 *p
++ = '0' + ss
/ 10;
415 *p
++ = '0' + ss %
10;
419 return time_zone_buf
;
422 /* debugging: print the current time in the parser_control structure.
423 The parser will increment "*_seen" members for those which were parsed.
424 This function will print only newly seen parts. */
426 debug_print_current_time
(char const *item
, parser_control
*pc
)
433 /* no newline, more items printed below */
434 dbg_printf
(_
("parsed %s part: "), item
);
436 if
(pc
->dates_seen
&& !pc
->debug_dates_seen
)
438 /*TODO: use pc->year.negative? */
439 fprintf
(stderr
, "(Y-M-D) %04"PRIdMAX
"-%02"PRIdMAX
"-%02"PRIdMAX
,
440 pc
->year.value
, pc
->month
, pc
->day
);
441 pc
->debug_dates_seen
= true
;
445 if
(pc
->year_seen
!= pc
->debug_year_seen
)
449 fprintf
(stderr
, _
("year: %04"PRIdMAX
), pc
->year.value
);
451 pc
->debug_year_seen
= pc
->year_seen
;
455 if
(pc
->times_seen
&& !pc
->debug_times_seen
)
457 intmax_t sec
= pc
->seconds.tv_sec
;
458 fprintf
(stderr
, &" %02"PRIdMAX
":%02"PRIdMAX
":%02"PRIdMAX
[!space
],
459 pc
->hour
, pc
->minutes
, sec
);
460 if
(pc
->seconds.tv_nsec
!= 0)
462 int nsec
= pc
->seconds.tv_nsec
;
463 fprintf
(stderr
, ".%09d", nsec
);
465 if
(pc
->meridian
== MERpm
)
466 fputs
("pm", stderr
);
468 pc
->debug_times_seen
= true
;
472 if
(pc
->days_seen
&& !pc
->debug_days_seen
)
476 char tmp
[DBGBUFSIZE
];
477 fprintf
(stderr
, _
("%s (day ordinal=%"PRIdMAX
" number=%d)"),
478 str_days
(pc
, tmp
, sizeof tmp
),
479 pc
->day_ordinal
, pc
->day_number
);
480 pc
->debug_days_seen
= true
;
484 /* local zone strings only change the DST settings,
485 not the timezone value. If seen, inform about the DST. */
486 if
(pc
->local_zones_seen
&& !pc
->debug_local_zones_seen
)
488 fprintf
(stderr
, &" isdst=%d%s"[!space
],
489 pc
->local_isdst
, pc
->dsts_seen ?
" DST" : "");
490 pc
->debug_local_zones_seen
= true
;
494 if
(pc
->zones_seen
&& !pc
->debug_zones_seen
)
496 char time_zone_buf
[TIME_ZONE_BUFSIZE
];
497 fprintf
(stderr
, &" UTC%s"[!space
],
498 time_zone_str
(pc
->time_zone
, time_zone_buf
));
499 pc
->debug_zones_seen
= true
;
503 if
(pc
->timespec_seen
)
505 intmax_t sec
= pc
->seconds.tv_sec
;
508 fprintf
(stderr
, _
("number of seconds: %"PRIdMAX
), sec
);
511 fputc
('\n', stderr
);
514 /* Debugging: print the current relative values. */
517 print_rel_part
(bool space
, intmax_t val
, char const *name
)
521 fprintf
(stderr
, &" %+"PRIdMAX
" %s"[!space
], val
, name
);
526 debug_print_relative_time
(char const *item
, parser_control
const *pc
)
533 /* no newline, more items printed below */
534 dbg_printf
(_
("parsed %s part: "), item
);
536 if
(pc
->rel.year
== 0 && pc
->rel.month
== 0 && pc
->rel.day
== 0
537 && pc
->rel.hour
== 0 && pc
->rel.minutes
== 0 && pc
->rel.seconds
== 0
540 /* Special case: relative time of this/today/now */
541 fputs
(_
("today/this/now\n"), stderr
);
545 space
= print_rel_part
(space
, pc
->rel.year
, "year(s)");
546 space
= print_rel_part
(space
, pc
->rel.month
, "month(s)");
547 space
= print_rel_part
(space
, pc
->rel.day
, "day(s)");
548 space
= print_rel_part
(space
, pc
->rel.hour
, "hour(s)");
549 space
= print_rel_part
(space
, pc
->rel.minutes
, "minutes");
550 space
= print_rel_part
(space
, pc
->rel.seconds
, "seconds");
551 print_rel_part
(space
, pc
->rel.ns
, "nanoseconds");
553 fputc
('\n', stderr
);
560 /* We want a reentrant parser, even if the TZ manipulation and the calls to
561 localtime and gmtime are not reentrant. */
563 %parse
-param
{ parser_control
*pc
}
564 %lex
-param
{ parser_control
*pc
}
566 /* This grammar has 31 shift/reduce conflicts. */
573 struct timespec timespec
;
580 %token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
581 %token
<intval
> tDAY_UNIT tDAY_SHIFT
583 %token
<intval
> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
584 %token
<intval
> tMONTH tORDINAL tZONE
586 %token
<textintval
> tSNUMBER tUNUMBER
587 %token
<timespec
> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
589 %type
<intval
> o_colon_minutes
590 %type
<timespec
> seconds signed_seconds unsigned_seconds
592 %type
<rel
> relunit relunit_snumber dayshift
605 pc
->timespec_seen
= true
;
606 debug_print_current_time
(_
("number of seconds"), pc
);
618 pc
->times_seen
++; pc
->dates_seen
++;
619 debug_print_current_time
(_
("datetime"), pc
);
624 debug_print_current_time
(_
("time"), pc
);
628 pc
->local_zones_seen
++;
629 debug_print_current_time
(_
("local_zone"), pc
);
634 debug_print_current_time
("J", pc
);
639 debug_print_current_time
(_
("zone"), pc
);
644 debug_print_current_time
(_
("date"), pc
);
649 debug_print_current_time
(_
("day"), pc
);
653 debug_print_relative_time
(_
("relative"), pc
);
657 debug_print_current_time
(_
("number"), pc
);
661 debug_print_relative_time
(_
("hybrid"), pc
);
670 iso_8601_date
'T' iso_8601_time
676 set_hhmmss
(pc
, $1.value
, 0, 0, 0);
679 | tUNUMBER
':' tUNUMBER tMERIDIAN
681 set_hhmmss
(pc
, $1.value
, $3.value
, 0, 0);
684 | tUNUMBER
':' tUNUMBER
':' unsigned_seconds tMERIDIAN
686 set_hhmmss
(pc
, $1.value
, $3.value
, $5.tv_sec
, $5.tv_nsec
);
695 set_hhmmss
(pc
, $1.value
, 0, 0, 0);
696 pc
->meridian
= MER24
;
698 | tUNUMBER
':' tUNUMBER o_zone_offset
700 set_hhmmss
(pc
, $1.value
, $3.value
, 0, 0);
701 pc
->meridian
= MER24
;
703 | tUNUMBER
':' tUNUMBER
':' unsigned_seconds o_zone_offset
705 set_hhmmss
(pc
, $1.value
, $3.value
, $5.tv_sec
, $5.tv_nsec
);
706 pc
->meridian
= MER24
;
716 tSNUMBER o_colon_minutes
719 if
(! time_zone_hhmm
(pc
, $1, $2)) YYABORT;
723 /* Local zone strings affect only the DST setting, and take effect
724 only if the current TZ setting is relevant.
727 'EEST' is parsed as tLOCAL_ZONE, as it relates to the effective TZ:
728 TZ='Europe/Helsinki' date -d '2016-06-30 EEST'
731 'EEST' is parsed as tDAYZONE:
732 TZ='Asia/Tokyo' date -d '2016-06-30 EEST'
734 This is implemented by probing the next three calendar quarters
735 of the effective timezone and looking for DST changes -
736 if found, the timezone name (EEST) is inserted into
737 the lexical lookup table with type tLOCAL_ZONE.
738 (Search for 'quarter' comment in 'parse_datetime2'.)
742 { pc
->local_isdst
= $1; }
750 /* Note 'T' is a special case, as it is used as the separator in ISO
751 8601 date and time of day representation. */
754 { pc
->time_zone
= $1; }
756 { pc
->time_zone
= -HOUR
(7); }
757 | tZONE relunit_snumber
758 { pc
->time_zone
= $1;
759 if
(! apply_relative_time
(pc
, $2, 1)) YYABORT;
760 debug_print_relative_time
(_
("relative"), pc
);
762 |
'T' relunit_snumber
763 { pc
->time_zone
= -HOUR
(7);
764 if
(! apply_relative_time
(pc
, $2, 1)) YYABORT;
765 debug_print_relative_time
(_
("relative"), pc
);
767 | tZONE tSNUMBER o_colon_minutes
768 { if
(! time_zone_hhmm
(pc
, $2, $3)) YYABORT;
769 if
(ckd_add
(&pc
->time_zone
, pc
->time_zone
, $1)) YYABORT; }
771 { pc
->time_zone
= $1 + 60 * 60; }
773 { pc
->time_zone
= $1 + 60 * 60; }
789 pc
->day_ordinal
= $1;
791 pc
->debug_ordinal_day_seen
= true
;
795 pc
->day_ordinal
= $1.value
;
797 pc
->debug_ordinal_day_seen
= true
;
802 tUNUMBER
'/' tUNUMBER
804 pc
->month
= $1.value
;
807 | tUNUMBER
'/' tUNUMBER
'/' tUNUMBER
809 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
810 otherwise as MM/DD/YY.
811 The goal in recognizing YYYY/MM/DD is solely to support legacy
812 machine-generated dates like those in an RCS log listing. If
813 you want portability, use the ISO 8601 format. */
818 intmax_t digits
= $1.digits
;
819 dbg_printf
(_
("warning: value %"PRIdMAX
" has %"PRIdMAX
" digits. "
820 "Assuming YYYY/MM/DD\n"),
825 pc
->month
= $3.value
;
831 dbg_printf
(_
("warning: value %"PRIdMAX
" has less than 4 digits. "
832 "Assuming MM/DD/YY[YY]\n"),
835 pc
->month
= $1.value
;
840 | tUNUMBER tMONTH tSNUMBER
842 /* E.g., 17-JUN-1992. */
845 if
(ckd_sub
(&pc
->year.value
, 0, $3.value
)) YYABORT;
846 pc
->year.digits
= $3.digits
;
848 | tMONTH tSNUMBER tSNUMBER
850 /* E.g., JUN-17-1992. */
852 if
(ckd_sub
(&pc
->day
, 0, $2.value
)) YYABORT;
853 if
(ckd_sub
(&pc
->year.value
, 0, $3.value
)) YYABORT;
854 pc
->year.digits
= $3.digits
;
861 | tMONTH tUNUMBER
',' tUNUMBER
872 | tUNUMBER tMONTH tUNUMBER
882 tUNUMBER tSNUMBER tSNUMBER
884 /* ISO 8601 format. YYYY-MM-DD. */
886 if
(ckd_sub
(&pc
->month
, 0, $2.value
)) YYABORT;
887 if
(ckd_sub
(&pc
->day
, 0, $3.value
)) YYABORT;
893 { if
(! apply_relative_time
(pc
, $1, $2)) YYABORT; }
895 { if
(! apply_relative_time
(pc
, $1, 1)) YYABORT; }
897 { if
(! apply_relative_time
(pc
, $1, 1)) YYABORT; }
902 { $$
= RELATIVE_TIME_0
; $$.year
= $1; }
903 | tUNUMBER tYEAR_UNIT
904 { $$
= RELATIVE_TIME_0
; $$.year
= $1.value
; }
906 { $$
= RELATIVE_TIME_0
; $$.year
= 1; }
907 | tORDINAL tMONTH_UNIT
908 { $$
= RELATIVE_TIME_0
; $$.month
= $1; }
909 | tUNUMBER tMONTH_UNIT
910 { $$
= RELATIVE_TIME_0
; $$.month
= $1.value
; }
912 { $$
= RELATIVE_TIME_0
; $$.month
= 1; }
914 { $$
= RELATIVE_TIME_0
;
915 if
(ckd_mul
(&$$.day
, $1, $2)) YYABORT; }
917 { $$
= RELATIVE_TIME_0
;
918 if
(ckd_mul
(&$$.day
, $1.value
, $2)) YYABORT; }
920 { $$
= RELATIVE_TIME_0
; $$.day
= $1; }
921 | tORDINAL tHOUR_UNIT
922 { $$
= RELATIVE_TIME_0
; $$.hour
= $1; }
923 | tUNUMBER tHOUR_UNIT
924 { $$
= RELATIVE_TIME_0
; $$.hour
= $1.value
; }
926 { $$
= RELATIVE_TIME_0
; $$.hour
= 1; }
927 | tORDINAL tMINUTE_UNIT
928 { $$
= RELATIVE_TIME_0
; $$.minutes
= $1; }
929 | tUNUMBER tMINUTE_UNIT
930 { $$
= RELATIVE_TIME_0
; $$.minutes
= $1.value
; }
932 { $$
= RELATIVE_TIME_0
; $$.minutes
= 1; }
934 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1; }
936 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1.value
; }
937 | tSDECIMAL_NUMBER tSEC_UNIT
938 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1.tv_sec
; $$.ns
= $1.tv_nsec
; }
939 | tUDECIMAL_NUMBER tSEC_UNIT
940 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1.tv_sec
; $$.ns
= $1.tv_nsec
; }
942 { $$
= RELATIVE_TIME_0
; $$.seconds
= 1; }
948 { $$
= RELATIVE_TIME_0
; $$.year
= $1.value
; }
949 | tSNUMBER tMONTH_UNIT
950 { $$
= RELATIVE_TIME_0
; $$.month
= $1.value
; }
952 { $$
= RELATIVE_TIME_0
;
953 if
(ckd_mul
(&$$.day
, $1.value
, $2)) YYABORT; }
954 | tSNUMBER tHOUR_UNIT
955 { $$
= RELATIVE_TIME_0
; $$.hour
= $1.value
; }
956 | tSNUMBER tMINUTE_UNIT
957 { $$
= RELATIVE_TIME_0
; $$.minutes
= $1.value
; }
959 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1.value
; }
964 { $$
= RELATIVE_TIME_0
; $$.day
= $1; }
967 seconds: signed_seconds | unsigned_seconds
;
972 { if
(time_overflow
($1.value
)) YYABORT;
973 $$
= (struct timespec
) { .tv_sec
= $1.value
}; }
979 { if
(time_overflow
($1.value
)) YYABORT;
980 $$
= (struct timespec
) { .tv_sec
= $1.value
}; }
985 { digits_to_date_time
(pc
, $1); }
989 tUNUMBER relunit_snumber
991 /* Hybrid all-digit and relative offset, so that we accept e.g.,
992 "YYYYMMDD +N days" as well as "YYYYMMDD N days". */
993 digits_to_date_time
(pc
, $1);
994 if
(! apply_relative_time
(pc
, $2, 1)) YYABORT;
1007 static table
const meridian_table
[] =
1009 { "AM", tMERIDIAN
, MERam
},
1010 { "A.M.", tMERIDIAN
, MERam
},
1011 { "PM", tMERIDIAN
, MERpm
},
1012 { "P.M.", tMERIDIAN
, MERpm
},
1016 static table
const dst_table
[] =
1021 static table
const month_and_day_table
[] =
1023 { "JANUARY", tMONTH
, 1 },
1024 { "FEBRUARY", tMONTH
, 2 },
1025 { "MARCH", tMONTH
, 3 },
1026 { "APRIL", tMONTH
, 4 },
1027 { "MAY", tMONTH
, 5 },
1028 { "JUNE", tMONTH
, 6 },
1029 { "JULY", tMONTH
, 7 },
1030 { "AUGUST", tMONTH
, 8 },
1031 { "SEPTEMBER",tMONTH
, 9 },
1032 { "SEPT", tMONTH
, 9 },
1033 { "OCTOBER", tMONTH
, 10 },
1034 { "NOVEMBER", tMONTH
, 11 },
1035 { "DECEMBER", tMONTH
, 12 },
1036 { "SUNDAY", tDAY
, 0 },
1037 { "MONDAY", tDAY
, 1 },
1038 { "TUESDAY", tDAY
, 2 },
1039 { "TUES", tDAY
, 2 },
1040 { "WEDNESDAY",tDAY
, 3 },
1041 { "WEDNES", tDAY
, 3 },
1042 { "THURSDAY", tDAY
, 4 },
1043 { "THUR", tDAY
, 4 },
1044 { "THURS", tDAY
, 4 },
1045 { "FRIDAY", tDAY
, 5 },
1046 { "SATURDAY", tDAY
, 6 },
1050 static table
const time_units_table
[] =
1052 { "YEAR", tYEAR_UNIT
, 1 },
1053 { "MONTH", tMONTH_UNIT
, 1 },
1054 { "FORTNIGHT",tDAY_UNIT
, 14 },
1055 { "WEEK", tDAY_UNIT
, 7 },
1056 { "DAY", tDAY_UNIT
, 1 },
1057 { "HOUR", tHOUR_UNIT
, 1 },
1058 { "MINUTE", tMINUTE_UNIT
, 1 },
1059 { "MIN", tMINUTE_UNIT
, 1 },
1060 { "SECOND", tSEC_UNIT
, 1 },
1061 { "SEC", tSEC_UNIT
, 1 },
1065 /* Assorted relative-time words. */
1066 static table
const relative_time_table
[] =
1068 { "TOMORROW", tDAY_SHIFT
, 1 },
1069 { "YESTERDAY",tDAY_SHIFT
, -1 },
1070 { "TODAY", tDAY_SHIFT
, 0 },
1071 { "NOW", tDAY_SHIFT
, 0 },
1072 { "LAST", tORDINAL
, -1 },
1073 { "THIS", tORDINAL
, 0 },
1074 { "NEXT", tORDINAL
, 1 },
1075 { "FIRST", tORDINAL
, 1 },
1076 /*{ "SECOND", tORDINAL, 2 }, */
1077 { "THIRD", tORDINAL
, 3 },
1078 { "FOURTH", tORDINAL
, 4 },
1079 { "FIFTH", tORDINAL
, 5 },
1080 { "SIXTH", tORDINAL
, 6 },
1081 { "SEVENTH", tORDINAL
, 7 },
1082 { "EIGHTH", tORDINAL
, 8 },
1083 { "NINTH", tORDINAL
, 9 },
1084 { "TENTH", tORDINAL
, 10 },
1085 { "ELEVENTH", tORDINAL
, 11 },
1086 { "TWELFTH", tORDINAL
, 12 },
1087 { "AGO", tAGO
, -1 },
1088 { "HENCE", tAGO
, 1 },
1092 /* The universal time zone table. These labels can be used even for
1093 timestamps that would not otherwise be valid, e.g., GMT timestamps
1094 oin London during summer. */
1095 static table
const universal_time_zone_table
[] =
1097 { "GMT", tZONE
, HOUR
( 0) }, /* Greenwich Mean */
1098 { "UT", tZONE
, HOUR
( 0) }, /* Universal (Coordinated) */
1099 { "UTC", tZONE
, HOUR
( 0) },
1103 /* The time zone table. This table is necessarily incomplete, as time
1104 zone abbreviations are ambiguous; e.g., Australians interpret "EST"
1105 as Eastern time in Australia, not as US Eastern Standard Time.
1106 You cannot rely on parse_datetime to handle arbitrary time zone
1107 abbreviations; use numeric abbreviations like "-0500" instead. */
1108 static table
const time_zone_table
[] =
1110 { "WET", tZONE
, HOUR
( 0) }, /* Western European */
1111 { "WEST", tDAYZONE
, HOUR
( 0) }, /* Western European Summer */
1112 { "BST", tDAYZONE
, HOUR
( 0) }, /* British Summer */
1113 { "ART", tZONE
, -HOUR
( 3) }, /* Argentina */
1114 { "BRT", tZONE
, -HOUR
( 3) }, /* Brazil */
1115 { "BRST", tDAYZONE
, -HOUR
( 3) }, /* Brazil Summer */
1116 { "NST", tZONE
, -(HOUR
( 3) + 30 * 60) }, /* Newfoundland Standard */
1117 { "NDT", tDAYZONE
,-(HOUR
( 3) + 30 * 60) }, /* Newfoundland Daylight */
1118 { "AST", tZONE
, -HOUR
( 4) }, /* Atlantic Standard */
1119 { "ADT", tDAYZONE
, -HOUR
( 4) }, /* Atlantic Daylight */
1120 { "CLT", tZONE
, -HOUR
( 4) }, /* Chile */
1121 { "CLST", tDAYZONE
, -HOUR
( 4) }, /* Chile Summer */
1122 { "EST", tZONE
, -HOUR
( 5) }, /* Eastern Standard */
1123 { "EDT", tDAYZONE
, -HOUR
( 5) }, /* Eastern Daylight */
1124 { "CST", tZONE
, -HOUR
( 6) }, /* Central Standard */
1125 { "CDT", tDAYZONE
, -HOUR
( 6) }, /* Central Daylight */
1126 { "MST", tZONE
, -HOUR
( 7) }, /* Mountain Standard */
1127 { "MDT", tDAYZONE
, -HOUR
( 7) }, /* Mountain Daylight */
1128 { "PST", tZONE
, -HOUR
( 8) }, /* Pacific Standard */
1129 { "PDT", tDAYZONE
, -HOUR
( 8) }, /* Pacific Daylight */
1130 { "AKST", tZONE
, -HOUR
( 9) }, /* Alaska Standard */
1131 { "AKDT", tDAYZONE
, -HOUR
( 9) }, /* Alaska Daylight */
1132 { "HST", tZONE
, -HOUR
(10) }, /* Hawaii Standard */
1133 { "HAST", tZONE
, -HOUR
(10) }, /* Hawaii-Aleutian Standard */
1134 { "HADT", tDAYZONE
, -HOUR
(10) }, /* Hawaii-Aleutian Daylight */
1135 { "SST", tZONE
, -HOUR
(12) }, /* Samoa Standard */
1136 { "WAT", tZONE
, HOUR
( 1) }, /* West Africa */
1137 { "CET", tZONE
, HOUR
( 1) }, /* Central European */
1138 { "CEST", tDAYZONE
, HOUR
( 1) }, /* Central European Summer */
1139 { "MET", tZONE
, HOUR
( 1) }, /* Middle European */
1140 { "MEZ", tZONE
, HOUR
( 1) }, /* Middle European */
1141 { "MEST", tDAYZONE
, HOUR
( 1) }, /* Middle European Summer */
1142 { "MESZ", tDAYZONE
, HOUR
( 1) }, /* Middle European Summer */
1143 { "EET", tZONE
, HOUR
( 2) }, /* Eastern European */
1144 { "EEST", tDAYZONE
, HOUR
( 2) }, /* Eastern European Summer */
1145 { "CAT", tZONE
, HOUR
( 2) }, /* Central Africa */
1146 { "SAST", tZONE
, HOUR
( 2) }, /* South Africa Standard */
1147 { "EAT", tZONE
, HOUR
( 3) }, /* East Africa */
1148 { "MSK", tZONE
, HOUR
( 3) }, /* Moscow */
1149 { "MSD", tDAYZONE
, HOUR
( 3) }, /* Moscow Daylight */
1150 { "IST", tZONE
, (HOUR
( 5) + 30 * 60) }, /* India Standard */
1151 { "SGT", tZONE
, HOUR
( 8) }, /* Singapore */
1152 { "KST", tZONE
, HOUR
( 9) }, /* Korea Standard */
1153 { "JST", tZONE
, HOUR
( 9) }, /* Japan Standard */
1154 { "GST", tZONE
, HOUR
(10) }, /* Guam Standard */
1155 { "NZST", tZONE
, HOUR
(12) }, /* New Zealand Standard */
1156 { "NZDT", tDAYZONE
, HOUR
(12) }, /* New Zealand Daylight */
1160 /* Military time zone table.
1162 RFC 822 got these backwards, but RFC 5322 makes the incorrect
1163 treatment optional, so do them the right way here.
1165 'J' is special, as it is local time.
1166 'T' is also special, as it is the separator in ISO
1167 8601 date and time of day representation. */
1168 static table
const military_table
[] =
1170 { "A", tZONE
, HOUR
( 1) },
1171 { "B", tZONE
, HOUR
( 2) },
1172 { "C", tZONE
, HOUR
( 3) },
1173 { "D", tZONE
, HOUR
( 4) },
1174 { "E", tZONE
, HOUR
( 5) },
1175 { "F", tZONE
, HOUR
( 6) },
1176 { "G", tZONE
, HOUR
( 7) },
1177 { "H", tZONE
, HOUR
( 8) },
1178 { "I", tZONE
, HOUR
( 9) },
1180 { "K", tZONE
, HOUR
(10) },
1181 { "L", tZONE
, HOUR
(11) },
1182 { "M", tZONE
, HOUR
(12) },
1183 { "N", tZONE
, -HOUR
( 1) },
1184 { "O", tZONE
, -HOUR
( 2) },
1185 { "P", tZONE
, -HOUR
( 3) },
1186 { "Q", tZONE
, -HOUR
( 4) },
1187 { "R", tZONE
, -HOUR
( 5) },
1188 { "S", tZONE
, -HOUR
( 6) },
1190 { "U", tZONE
, -HOUR
( 8) },
1191 { "V", tZONE
, -HOUR
( 9) },
1192 { "W", tZONE
, -HOUR
(10) },
1193 { "X", tZONE
, -HOUR
(11) },
1194 { "Y", tZONE
, -HOUR
(12) },
1195 { "Z", tZONE
, HOUR
( 0) },
1201 /* Convert a time zone expressed as HH:MM into an integer count of
1202 seconds. If MM is negative, then S is of the form HHMM and needs
1203 to be picked apart; otherwise, S is of the form HH. As specified in
1204 https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03, allow
1205 only valid TZ range, and consider first two digits as hours, if no
1206 minutes specified. Return true if successful. */
1209 time_zone_hhmm
(parser_control
*pc
, textint s
, intmax_t mm
)
1212 bool overflow
= false
;
1214 /* If the length of S is 1 or 2 and no minutes are specified,
1215 interpret it as a number of hours. */
1216 if
(s.digits
<= 2 && mm
< 0)
1220 n_minutes
= (s.value
/ 100) * 60 + s.value %
100;
1223 overflow |
= ckd_mul
(&n_minutes
, s.value
, 60);
1224 overflow |
= (s.negative
1225 ? ckd_sub
(&n_minutes
, n_minutes
, mm
)
1226 : ckd_add
(&n_minutes
, n_minutes
, mm
));
1229 if
(overflow ||
! (-24 * 60 <= n_minutes
&& n_minutes
<= 24 * 60))
1231 pc
->time_zone
= n_minutes
* 60;
1236 to_hour
(intmax_t hours
, int meridian
)
1240 default
: /* Pacify GCC. */
1242 return
0 <= hours
&& hours
< 24 ? hours
: -1;
1244 return
0 < hours
&& hours
< 12 ? hours
: hours
== 12 ?
0 : -1;
1246 return
0 < hours
&& hours
< 12 ? hours
+ 12 : hours
== 12 ?
12 : -1;
1250 enum { TM_YEAR_BASE
= 1900 };
1251 enum { TM_YEAR_BUFSIZE
= INT_BUFSIZE_BOUND
(int) + 1 };
1253 /* Convert TM_YEAR, a year minus 1900, to a string that is numerically
1254 correct even if subtracting 1900 would overflow. */
1257 tm_year_str
(int tm_year
, char buf
[TM_YEAR_BUFSIZE
])
1259 static_assert
(TM_YEAR_BASE %
100 == 0);
1260 sprintf
(buf
, &"-%02d%02d"[-TM_YEAR_BASE
<= tm_year
],
1261 abs
(tm_year
/ 100 + TM_YEAR_BASE
/ 100),
1262 abs
(tm_year %
100));
1266 /* Convert a text year number to a year minus 1900, working correctly
1267 even if the input is in the range INT_MAX .. INT_MAX + 1900 - 1. */
1270 to_tm_year
(textint textyear
, bool debug
, int *tm_year
)
1272 intmax_t year
= textyear.value
;
1274 /* XPG4 suggests that years 00-68 map to 2000-2068, and
1275 years 69-99 map to 1969-1999. */
1276 if
(0 <= year
&& textyear.digits
== 2)
1278 year
+= year
< 69 ?
2000 : 1900;
1280 dbg_printf
(_
("warning: adjusting year value %"PRIdMAX
1281 " to %"PRIdMAX
"\n"),
1282 textyear.value
, year
);
1286 ? ckd_sub
(tm_year
, -TM_YEAR_BASE
, year
)
1287 : ckd_sub
(tm_year
, year
, TM_YEAR_BASE
))
1290 dbg_printf
(_
("error: out-of-range year %"PRIdMAX
"\n"), year
);
1297 static table
const * _GL_ATTRIBUTE_PURE
1298 lookup_zone
(parser_control
const *pc
, char const *name
)
1302 for
(tp
= universal_time_zone_table
; tp
->name
; tp
++)
1303 if
(strcmp
(name
, tp
->name
) == 0)
1306 /* Try local zone abbreviations before those in time_zone_table, as
1307 the local ones are more likely to be right. */
1308 for
(tp
= pc
->local_time_zone_table
; tp
->name
; tp
++)
1309 if
(strcmp
(name
, tp
->name
) == 0)
1312 for
(tp
= time_zone_table
; tp
->name
; tp
++)
1313 if
(strcmp
(name
, tp
->name
) == 0)
1319 #if ! HAVE_STRUCT_TM_TM_GMTOFF
1320 /* Yield the difference between *A and *B,
1321 measured in seconds, ignoring leap seconds.
1322 The body of this function is taken directly from the GNU C Library;
1325 tm_diff
(const struct tm
*a
, const struct tm
*b
)
1327 /* Compute intervening leap days correctly even if year is negative.
1328 Take care to avoid int overflow in leap day calculations,
1329 but it's OK to assume that A and B are close to each other. */
1330 int a4
= SHR
(a
->tm_year
, 2) + SHR
(TM_YEAR_BASE
, 2) - ! (a
->tm_year
& 3);
1331 int b4
= SHR
(b
->tm_year
, 2) + SHR
(TM_YEAR_BASE
, 2) - ! (b
->tm_year
& 3);
1332 int a100
= a4
/ 25 - (a4 %
25 < 0);
1333 int b100
= b4
/ 25 - (b4 %
25 < 0);
1334 int a400
= SHR
(a100
, 2);
1335 int b400
= SHR
(b100
, 2);
1336 int intervening_leap_days
= (a4
- b4
) - (a100
- b100
) + (a400
- b400
);
1337 int years
= a
->tm_year
- b
->tm_year
;
1338 int days
= (365 * years
+ intervening_leap_days
1339 + (a
->tm_yday
- b
->tm_yday
));
1340 return
(60 * (60 * (24 * days
+ (a
->tm_hour
- b
->tm_hour
))
1341 + (a
->tm_min
- b
->tm_min
))
1342 + (a
->tm_sec
- b
->tm_sec
));
1346 static table
const *
1347 lookup_word
(parser_control
const *pc
, char *word
)
1356 /* Make it uppercase. */
1357 for
(p
= word
; *p
; p
++)
1358 *p
= c_toupper
(to_uchar
(*p
));
1360 for
(tp
= meridian_table
; tp
->name
; tp
++)
1361 if
(strcmp
(word
, tp
->name
) == 0)
1364 /* See if we have an abbreviation for a month. */
1365 wordlen
= strlen
(word
);
1366 abbrev
= wordlen
== 3 ||
(wordlen
== 4 && word
[3] == '.');
1368 for
(tp
= month_and_day_table
; tp
->name
; tp
++)
1369 if
((abbrev ? strncmp
(word
, tp
->name
, 3) : strcmp
(word
, tp
->name
)) == 0)
1372 if
((tp
= lookup_zone
(pc
, word
)))
1375 if
(strcmp
(word
, dst_table
[0].name
) == 0)
1378 for
(tp
= time_units_table
; tp
->name
; tp
++)
1379 if
(strcmp
(word
, tp
->name
) == 0)
1382 /* Strip off any plural and try the units table again. */
1383 if
(word
[wordlen
- 1] == 'S')
1385 word
[wordlen
- 1] = '\0';
1386 for
(tp
= time_units_table
; tp
->name
; tp
++)
1387 if
(strcmp
(word
, tp
->name
) == 0)
1389 word
[wordlen
- 1] = 'S'; /* For "this" in relative_time_table. */
1392 for
(tp
= relative_time_table
; tp
->name
; tp
++)
1393 if
(strcmp
(word
, tp
->name
) == 0)
1396 /* Military time zones. */
1398 for
(tp
= military_table
; tp
->name
; tp
++)
1399 if
(word
[0] == tp
->name
[0])
1402 /* Drop out any periods and try the time zone table again. */
1403 for
(period_found
= false
, p
= q
= word
; (*p
= *q
); q
++)
1405 period_found
= true
;
1408 if
(period_found
&& (tp
= lookup_zone
(pc
, word
)))
1415 yylex (union YYSTYPE *lvalp
, parser_control
*pc
)
1421 while
(c
= *pc
->input
, c_isspace
(c
))
1424 if
(c_isdigit
(c
) || c
== '-' || c
== '+')
1426 char const *p
= pc
->input
;
1428 if
(c
== '-' || c
== '+')
1430 sign
= c
== '-' ?
-1 : 1;
1431 while
(c
= *(pc
->input
= ++p
), c_isspace
(c
))
1433 if
(! c_isdigit
(c
))
1434 /* skip the '-' sign */
1443 if
(ckd_mul
(&value
, value
, 10))
1445 if
(ckd_add
(&value
, value
, sign
< 0 ?
'0' - c
: c
- '0'))
1449 while
(c_isdigit
(c
));
1451 if
((c
== '.' || c
== ',') && c_isdigit
(p
[1]))
1456 /* Accumulate fraction, to ns precision. */
1458 int ns
= *p
++ - '0';
1459 for
(digits
= 2; digits
<= LOG10_BILLION
; digits
++)
1466 /* Skip excess digits, truncating toward -Infinity. */
1468 for
(; c_isdigit
(*p
); p
++)
1474 while
(c_isdigit
(*p
))
1477 /* Adjust to the timespec convention, which is that
1478 tv_nsec is always a positive offset even if tv_sec is
1482 if
(ckd_sub
(&s
, s
, 1))
1487 lvalp
->timespec
= (struct timespec
) { .tv_sec
= s
,
1490 return sign ? tSDECIMAL_NUMBER
: tUDECIMAL_NUMBER
;
1494 lvalp
->textintval.negative
= sign
< 0;
1495 lvalp
->textintval.value
= value
;
1496 lvalp
->textintval.digits
= p
- pc
->input
;
1498 return sign ? tSNUMBER
: tUNUMBER
;
1510 if
(p
< buff
+ sizeof buff
- 1)
1514 while
(c_isalpha
(c
) || c
== '.');
1517 tp
= lookup_word
(pc
, buff
);
1521 dbg_printf
(_
("error: unknown word '%s'\n"), buff
);
1524 lvalp
->intval
= tp
->value
;
1529 return to_uchar
(*pc
->input
++);
1546 /* Do nothing if the parser reports an error. */
1548 yyerror (_GL_UNUSED parser_control
const *pc
,
1549 _GL_UNUSED
char const *s
)
1553 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1554 passing it to mktime_z, return true if it's OK. It's not OK if
1555 mktime failed or if *TM0 has out-of-range mainline members.
1556 The caller should set TM1->tm_wday to -1 before calling mktime,
1557 as a negative tm_wday is how mktime failure is inferred. */
1560 mktime_ok
(struct tm
const *tm0
, struct tm
const *tm1
)
1562 if
(tm1
->tm_wday
< 0)
1565 return
! ((tm0
->tm_sec ^ tm1
->tm_sec
)
1566 |
(tm0
->tm_min ^ tm1
->tm_min
)
1567 |
(tm0
->tm_hour ^ tm1
->tm_hour
)
1568 |
(tm0
->tm_mday ^ tm1
->tm_mday
)
1569 |
(tm0
->tm_mon ^ tm1
->tm_mon
)
1570 |
(tm0
->tm_year ^ tm1
->tm_year
));
1573 /* Populate PC's local time zone table with information from TM. */
1576 populate_local_time_zone_table
(parser_control
*pc
, struct tm
const *tm
)
1578 bool first_entry_exists
= !!pc
->local_time_zone_table
[0].name
;
1580 /* The table entry to be filled in. There are only two, so this is
1581 the first entry if it is missing, the second entry otherwise. */
1582 table
*e
= &pc
->local_time_zone_table
[first_entry_exists
];
1584 e
->type
= tLOCAL_ZONE
;
1585 e
->value
= tm
->tm_isdst
;
1587 char const *zone
= NULL
;
1588 #if HAVE_STRUCT_TM_TM_ZONE
1592 char *tz_abbr
= pc
->tz_abbr
[first_entry_exists
];
1593 if
(strftime
(tz_abbr
, TIME_ZONE_BUFSIZE
, "%Z", tm
))
1600 /* Debugging: format a 'struct tm' into a buffer, taking the parser's
1601 timezone information into account (if pc != NULL). */
1603 debug_strfdatetime
(struct tm
const *tm
, parser_control
const *pc
,
1607 1. find an optimal way to print date string in a clear and unambiguous
1608 format. Currently, always add '(Y-M-D)' prefix.
1609 Consider '2016y01m10d' or 'year(2016) month(01) day(10)'.
1611 If the user needs debug printing, it means he/she already having
1612 issues with the parsing - better to avoid formats that could
1613 be mis-interpreted (e.g., just YYYY-MM-DD).
1615 2. Print timezone information ?
1617 3. Print DST information ?
1619 4. Print nanosecond information ?
1622 Printed date/time values might not be valid, e.g., '2016-02-31'
1623 or '2016-19-2016' . These are the values as parsed from the user
1624 string, before validation.
1626 int m
= strftime
(buf
, n
, "(Y-M-D) %Y-%m-%d %H:%M:%S", tm
);
1628 /* If parser_control information was provided (for timezone),
1629 and there's enough space in the buffer, add timezone info. */
1630 if
(pc
&& m
< n
&& pc
->zones_seen
)
1632 int tz
= pc
->time_zone
;
1634 /* Account for DST if tLOCAL_ZONE was seen. */
1635 if
(pc
->local_zones_seen
&& !pc
->zones_seen
&& 0 < pc
->local_isdst
)
1638 char time_zone_buf
[TIME_ZONE_BUFSIZE
];
1639 snprintf
(&buf
[m
], n
- m
, " TZ=%s", time_zone_str
(tz
, time_zone_buf
));
1645 debug_strfdate
(struct tm
const *tm
, char *buf
, int n
)
1647 char tm_year_buf
[TM_YEAR_BUFSIZE
];
1648 snprintf
(buf
, n
, "(Y-M-D) %s-%02d-%02d",
1649 tm_year_str
(tm
->tm_year
, tm_year_buf
),
1650 tm
->tm_mon
+ 1, tm
->tm_mday
);
1655 debug_strftime
(struct tm
const *tm
, char *buf
, int n
)
1657 snprintf
(buf
, n
, "%02d:%02d:%02d", tm
->tm_hour
, tm
->tm_min
, tm
->tm_sec
);
1661 /* If mktime_ok failed, display the failed time values,
1662 and provide possible hints. Example output:
1664 date: error: invalid date/time value:
1665 date: user provided time: '(Y-M-D) 2006-04-02 02:45:00'
1666 date: normalized time: '(Y-M-D) 2006-04-02 03:45:00'
1668 date: possible reasons:
1669 date: nonexistent due to daylight-saving time;
1670 date: numeric values overflow;
1671 date: missing timezone;
1674 debug_mktime_not_ok
(struct tm
const *tm0
, struct tm
const *tm1
,
1675 parser_control
const *pc
, bool time_zone_seen
)
1677 /* TODO: handle t == -1 (as in 'mktime_ok'). */
1678 char tmp
[DBGBUFSIZE
];
1680 const bool eq_sec
= (tm0
->tm_sec
== tm1
->tm_sec
);
1681 const bool eq_min
= (tm0
->tm_min
== tm1
->tm_min
);
1682 const bool eq_hour
= (tm0
->tm_hour
== tm1
->tm_hour
);
1683 const bool eq_mday
= (tm0
->tm_mday
== tm1
->tm_mday
);
1684 const bool eq_month
= (tm0
->tm_mon
== tm1
->tm_mon
);
1685 const bool eq_year
= (tm0
->tm_year
== tm1
->tm_year
);
1687 const bool dst_shift
= eq_sec
&& eq_min
&& !eq_hour
1688 && eq_mday
&& eq_month
&& eq_year
;
1690 if
(!debugging
(pc
))
1693 dbg_printf
(_
("error: invalid date/time value:\n"));
1694 dbg_printf
(_
(" user provided time: '%s'\n"),
1695 debug_strfdatetime
(tm0
, pc
, tmp
, sizeof tmp
));
1696 dbg_printf
(_
(" normalized time: '%s'\n"),
1697 debug_strfdatetime
(tm1
, pc
, tmp
, sizeof tmp
));
1698 /* The format must be aligned with debug_strfdatetime and the two
1699 DEBUG statements above. This string is not translated. */
1700 i
= snprintf
(tmp
, sizeof tmp
,
1701 " %4s %2s %2s %2s %2s %2s",
1702 eq_year ?
"" : "----",
1703 eq_month ?
"" : "--",
1704 eq_mday ?
"" : "--",
1705 eq_hour ?
"" : "--",
1707 eq_sec ?
"" : "--");
1708 /* Trim trailing whitespace. */
1711 if
(sizeof tmp
- 1 < i
)
1713 while
(0 < i
&& tmp
[i
- 1] == ' ')
1717 dbg_printf
("%s\n", tmp
);
1719 dbg_printf
(_
(" possible reasons:\n"));
1721 dbg_printf
(_
(" nonexistent due to daylight-saving time;\n"));
1722 if
(!eq_mday
&& !eq_month
)
1723 dbg_printf
(_
(" invalid day/month combination;\n"));
1724 dbg_printf
(_
(" numeric values overflow;\n"));
1725 dbg_printf
(" %s\n", (time_zone_seen ? _
("incorrect timezone")
1726 : _
("missing timezone")));
1729 /* Parse a date/time string, storing the resulting time value into *RESULT.
1730 The string itself is pointed to by P. Return true if successful.
1731 P can be an incomplete or relative time specification; if so, use
1732 *NOW as the basis for the returned time. Default to timezone
1733 TZDEFAULT, which corresponds to tzalloc (TZSTRING). */
1735 parse_datetime_body
(struct timespec
*result
, char const *p
,
1736 struct timespec
const *now
, unsigned int flags
,
1737 timezone_t tzdefault
, char const *tzstring
)
1741 char time_zone_buf
[TIME_ZONE_BUFSIZE
];
1742 char dbg_tm
[DBGBUFSIZE
];
1744 char const *input_sentinel
= p
+ strlen
(p
);
1745 char *tz1alloc
= NULL
;
1747 /* A reasonable upper bound for the size of ordinary TZ strings.
1748 Use heap allocation if TZ's length exceeds this. */
1749 enum { TZBUFSIZE
= 100 };
1750 char tz1buf
[TZBUFSIZE
];
1752 struct timespec gettime_buffer
;
1755 gettime
(&gettime_buffer
);
1756 now
= &gettime_buffer
;
1759 time_t Start
= now
->tv_sec
;
1760 int Start_ns
= now
->tv_nsec
;
1763 while
(c
= *p
, c_isspace
(c
))
1766 timezone_t tz
= tzdefault
;
1768 /* Store a local copy prior to first "goto". Without this, a prior use
1769 below of RELATIVE_TIME_0 on the RHS might translate to an assignment-
1770 to-temporary, which would trigger a -Wjump-misses-init warning. */
1771 const relative_time rel_time_0
= RELATIVE_TIME_0
;
1773 if
(strncmp
(p
, "TZ=\"", 4) == 0)
1775 char const *tzbase
= p
+ 4;
1779 for
(s
= tzbase
; *s
; s
++, tzsize
++)
1783 if
(! (*s
== '\\' ||
*s
== '"'))
1789 char *tz1string
= tz1buf
;
1791 if
(TZBUFSIZE
< tzsize
)
1793 tz1alloc
= malloc
(tzsize
);
1796 tz1string
= tz1alloc
;
1799 for
(s
= tzbase
; *s
!= '"'; s
++)
1800 *z
++ = *(s
+= *s
== '\\');
1802 tz1
= tzalloc
(tz1string
);
1806 tzstring
= tz1string
;
1809 while
(c
= *p
, c_isspace
(c
))
1817 if
(! localtime_rz
(tz
, &now
->tv_sec
, &tmp
))
1820 /* As documented, be careful to treat the empty string just like
1821 a date string of "0". Without this, an empty string would be
1822 declared invalid when parsed during a DST transition. */
1828 #ifdef GNULIB_PARSE_DATETIME2
1829 pc.parse_datetime_debug
= (flags
& PARSE_DATETIME_DEBUG
) != 0;
1831 if
(ckd_add
(&pc.year.value
, tmp.tm_year
, TM_YEAR_BASE
))
1833 if
(debugging
(&pc
))
1834 dbg_printf
(_
("error: initial year out of range\n"));
1838 pc.month
= tmp.tm_mon
+ 1;
1839 pc.day
= tmp.tm_mday
;
1840 pc.hour
= tmp.tm_hour
;
1841 pc.minutes
= tmp.tm_min
;
1842 pc.seconds
= (struct timespec
) { .tv_sec
= tmp.tm_sec
, .tv_nsec
= Start_ns
};
1843 tm.tm_isdst
= tmp.tm_isdst
;
1845 pc.meridian
= MER24
;
1846 pc.rel
= rel_time_0
;
1847 pc.timespec_seen
= false
;
1848 pc.rels_seen
= false
;
1852 pc.J_zones_seen
= 0;
1853 pc.local_zones_seen
= 0;
1856 pc.year_seen
= false
;
1857 pc.debug_dates_seen
= false
;
1858 pc.debug_days_seen
= false
;
1859 pc.debug_times_seen
= false
;
1860 pc.debug_local_zones_seen
= false
;
1861 pc.debug_zones_seen
= false
;
1862 pc.debug_year_seen
= false
;
1863 pc.debug_ordinal_day_seen
= false
;
1865 pc.local_time_zone_table
[0].name
= NULL
;
1866 populate_local_time_zone_table
(&pc
, &tmp
);
1868 /* Probe the names used in the next three calendar quarters, looking
1869 for a tm_isdst different from the one we already have. */
1870 for
(int quarter
= 1; quarter
<= 3; quarter
++)
1873 if
(ckd_add
(&probe
, Start
, quarter
* (90 * 24 * 60 * 60)))
1876 if
(localtime_rz
(tz
, &probe
, &probe_tm
)
1877 && (! pc.local_time_zone_table
[0].name
1878 || probe_tm.tm_isdst
!= pc.local_time_zone_table
[0].value
))
1880 populate_local_time_zone_table
(&pc
, &probe_tm
);
1881 if
(pc.local_time_zone_table
[1].name
)
1883 if
(! strcmp
(pc.local_time_zone_table
[0].name
,
1884 pc.local_time_zone_table
[1].name
))
1886 /* This locale uses the same abbreviation for standard and
1887 daylight times. So if we see that abbreviation, we don't
1888 know whether it's daylight time. */
1889 pc.local_time_zone_table
[0].value
= -1;
1890 pc.local_time_zone_table
[1].name
= NULL
;
1898 if
(yyparse (&pc
) != 0)
1900 if
(debugging
(&pc
))
1901 dbg_printf
((input_sentinel
<= pc.input
1902 ? _
("error: parsing failed\n")
1903 : _
("error: parsing failed, stopped at '%s'\n")),
1909 /* Determine effective timezone source. */
1911 if
(debugging
(&pc
))
1913 dbg_printf
(_
("input timezone: "));
1915 if
(pc.timespec_seen
)
1916 fprintf
(stderr
, _
("'@timespec' - always UTC"));
1917 else if
(pc.zones_seen
)
1918 fprintf
(stderr
, _
("parsed date/time string"));
1921 if
(tz
!= tzdefault
)
1922 fprintf
(stderr
, _
("TZ=\"%s\" in date string"), tzstring
);
1923 else if
(STREQ
(tzstring
, "UTC0"))
1925 /* Special case: 'date -u' sets TZ="UTC0". */
1926 fprintf
(stderr
, _
("TZ=\"UTC0\" environment value or -u"));
1929 fprintf
(stderr
, _
("TZ=\"%s\" environment value"), tzstring
);
1932 fprintf
(stderr
, _
("system default"));
1934 /* Account for DST changes if tLOCAL_ZONE was seen.
1935 local timezone only changes DST and is relative to the
1937 if
(pc.local_zones_seen
&& !pc.zones_seen
&& 0 < pc.local_isdst
)
1938 fprintf
(stderr
, ", dst");
1941 fprintf
(stderr
, " (%s)", time_zone_str
(pc.time_zone
, time_zone_buf
));
1943 fputc
('\n', stderr
);
1946 if
(pc.timespec_seen
)
1947 *result
= pc.seconds
;
1950 if
(1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
1951 |
(pc.J_zones_seen
+ pc.local_zones_seen
+ pc.zones_seen
)))
1953 if
(debugging
(&pc
))
1955 if
(pc.times_seen
> 1)
1956 dbg_printf
("error: seen multiple time parts\n");
1957 if
(pc.dates_seen
> 1)
1958 dbg_printf
("error: seen multiple date parts\n");
1959 if
(pc.days_seen
> 1)
1960 dbg_printf
("error: seen multiple days parts\n");
1961 if
(pc.dsts_seen
> 1)
1962 dbg_printf
("error: seen multiple daylight-saving parts\n");
1963 if
((pc.J_zones_seen
+ pc.local_zones_seen
+ pc.zones_seen
) > 1)
1964 dbg_printf
("error: seen multiple time-zone parts\n");
1969 if
(! to_tm_year
(pc.year
, debugging
(&pc
), &tm.tm_year
)
1970 || ckd_add
(&tm.tm_mon
, pc.month
, -1)
1971 || ckd_add
(&tm.tm_mday
, pc.day
, 0))
1973 if
(debugging
(&pc
))
1974 dbg_printf
(_
("error: year, month, or day overflow\n"));
1977 if
(pc.times_seen ||
(pc.rels_seen
&& ! pc.dates_seen
&& ! pc.days_seen
))
1979 tm.tm_hour
= to_hour
(pc.hour
, pc.meridian
);
1982 char const *mrd
= (pc.meridian
== MERam ?
"am"
1983 : pc.meridian
== MERpm ?
"pm" : "");
1984 if
(debugging
(&pc
))
1985 dbg_printf
(_
("error: invalid hour %"PRIdMAX
"%s\n"),
1989 tm.tm_min
= pc.minutes
;
1990 tm.tm_sec
= pc.seconds.tv_sec
;
1991 if
(debugging
(&pc
))
1992 dbg_printf
((pc.times_seen
1993 ? _
("using specified time as starting value: '%s'\n")
1994 : _
("using current time as starting value: '%s'\n")),
1995 debug_strftime
(&tm
, dbg_tm
, sizeof dbg_tm
));
1999 tm.tm_hour
= tm.tm_min
= tm.tm_sec
= 0;
2000 pc.seconds.tv_nsec
= 0;
2001 if
(debugging
(&pc
))
2002 dbg_printf
("warning: using midnight as starting time: 00:00:00\n");
2005 /* Let mktime deduce tm_isdst if we have an absolute timestamp. */
2006 if
(pc.dates_seen | pc.days_seen | pc.times_seen
)
2009 /* But if the input explicitly specifies local time with or without
2010 DST, give mktime that information. */
2011 if
(pc.local_zones_seen
)
2012 tm.tm_isdst
= pc.local_isdst
;
2014 tm0.tm_sec
= tm.tm_sec
;
2015 tm0.tm_min
= tm.tm_min
;
2016 tm0.tm_hour
= tm.tm_hour
;
2017 tm0.tm_mday
= tm.tm_mday
;
2018 tm0.tm_mon
= tm.tm_mon
;
2019 tm0.tm_year
= tm.tm_year
;
2020 tm0.tm_isdst
= tm.tm_isdst
;
2023 Start
= mktime_z
(tz
, &tm
);
2025 if
(! mktime_ok
(&tm0
, &tm
))
2027 bool repaired
= false
;
2028 bool time_zone_seen
= pc.zones_seen
!= 0;
2031 /* Guard against falsely reporting errors near the time_t
2032 boundaries when parsing times in other time zones. For
2033 example, suppose the input string "1969-12-31 23:00:00 -0100",
2034 the current time zone is 8 hours ahead of UTC, and the min
2035 time_t value is 1970-01-01 00:00:00 UTC. Then the min
2036 localtime value is 1970-01-01 08:00:00, and mktime will
2037 therefore fail on 1969-12-31 23:00:00. To work around the
2038 problem, set the time zone to 1 hour behind UTC temporarily
2039 by setting TZ="XXX1:00" and try mktime again. */
2041 char tz2buf
[sizeof
"XXX" - 1 + TIME_ZONE_BUFSIZE
];
2042 tz2buf
[0] = tz2buf
[1] = tz2buf
[2] = 'X';
2043 time_zone_str
(pc.time_zone
, &tz2buf
[3]);
2044 timezone_t tz2
= tzalloc
(tz2buf
);
2047 if
(debugging
(&pc
))
2048 dbg_printf
(_
("error: tzalloc (\"%s\") failed\n"), tz2buf
);
2051 tm.tm_sec
= tm0.tm_sec
;
2052 tm.tm_min
= tm0.tm_min
;
2053 tm.tm_hour
= tm0.tm_hour
;
2054 tm.tm_mday
= tm0.tm_mday
;
2055 tm.tm_mon
= tm0.tm_mon
;
2056 tm.tm_year
= tm0.tm_year
;
2057 tm.tm_isdst
= tm0.tm_isdst
;
2059 Start
= mktime_z
(tz2
, &tm
);
2060 repaired
= mktime_ok
(&tm0
, &tm
);
2066 debug_mktime_not_ok
(&tm0
, &tm
, &pc
, time_zone_seen
);
2071 char dbg_ord
[DBGBUFSIZE
];
2073 if
(pc.days_seen
&& ! pc.dates_seen
)
2077 intmax_t day_ordinal
= (pc.day_ordinal
2078 - (0 < pc.day_ordinal
2079 && tm.tm_wday
!= pc.day_number
));
2080 if
(! (ckd_mul
(&dayincr
, day_ordinal
, 7)
2081 || ckd_add
(&dayincr
, (pc.day_number
- tm.tm_wday
+ 7) %
7,
2083 || ckd_add
(&tm.tm_mday
, dayincr
, tm.tm_mday
)))
2086 Start
= mktime_z
(tz
, &tm
);
2091 if
(debugging
(&pc
))
2092 dbg_printf
(_
("error: day '%s' "
2093 "(day ordinal=%"PRIdMAX
" number=%d) "
2094 "resulted in an invalid date: '%s'\n"),
2095 str_days
(&pc
, dbg_ord
, sizeof dbg_ord
),
2096 pc.day_ordinal
, pc.day_number
,
2097 debug_strfdatetime
(&tm
, &pc
, dbg_tm
,
2102 if
(debugging
(&pc
))
2103 dbg_printf
(_
("new start date: '%s' is '%s'\n"),
2104 str_days
(&pc
, dbg_ord
, sizeof dbg_ord
),
2105 debug_strfdatetime
(&tm
, &pc
, dbg_tm
, sizeof dbg_tm
));
2109 if
(debugging
(&pc
))
2111 if
(!pc.dates_seen
&& !pc.days_seen
)
2112 dbg_printf
(_
("using current date as starting value: '%s'\n"),
2113 debug_strfdate
(&tm
, dbg_tm
, sizeof dbg_tm
));
2115 if
(pc.days_seen
&& pc.dates_seen
)
2116 dbg_printf
(_
("warning: day (%s) ignored when explicit dates "
2118 str_days
(&pc
, dbg_ord
, sizeof dbg_ord
));
2120 dbg_printf
(_
("starting date/time: '%s'\n"),
2121 debug_strfdatetime
(&tm
, &pc
, dbg_tm
, sizeof dbg_tm
));
2124 /* Add relative date. */
2125 if
(pc.rel.year | pc.rel.month | pc.rel.day
)
2127 if
(debugging
(&pc
))
2129 if
((pc.rel.year
!= 0 || pc.rel.month
!= 0) && tm.tm_mday
!= 15)
2130 dbg_printf
(_
("warning: when adding relative months/years, "
2131 "it is recommended to specify the 15th of the "
2134 if
(pc.rel.day
!= 0 && tm.tm_hour
!= 12)
2135 dbg_printf
(_
("warning: when adding relative days, "
2136 "it is recommended to specify noon\n"));
2139 int year
, month
, day
;
2140 if
(ckd_add
(&year
, tm.tm_year
, pc.rel.year
)
2141 || ckd_add
(&month
, tm.tm_mon
, pc.rel.month
)
2142 || ckd_add
(&day
, tm.tm_mday
, pc.rel.day
))
2144 if
(debugging
(&pc
))
2145 dbg_printf
(_
("error: %s:%d\n"), __FILE__
, __LINE__
);
2151 tm.tm_hour
= tm0.tm_hour
;
2152 tm.tm_min
= tm0.tm_min
;
2153 tm.tm_sec
= tm0.tm_sec
;
2154 tm.tm_isdst
= tm0.tm_isdst
;
2156 Start
= mktime_z
(tz
, &tm
);
2159 if
(debugging
(&pc
))
2160 dbg_printf
(_
("error: adding relative date resulted "
2161 "in an invalid date: '%s'\n"),
2162 debug_strfdatetime
(&tm
, &pc
, dbg_tm
,
2167 if
(debugging
(&pc
))
2169 dbg_printf
(_
("after date adjustment "
2170 "(%+"PRIdMAX
" years, %+"PRIdMAX
" months, "
2171 "%+"PRIdMAX
" days),\n"),
2172 pc.rel.year
, pc.rel.month
, pc.rel.day
);
2173 dbg_printf
(_
(" new date/time = '%s'\n"),
2174 debug_strfdatetime
(&tm
, &pc
, dbg_tm
,
2177 /* Warn about crossing DST due to time adjustment.
2178 Example: https://bugs.gnu.org/8357
2179 env TZ=Europe/Helsinki \
2181 -d 'Mon Mar 28 00:36:07 2011 EEST 1 day ago'
2183 This case is different than DST changes due to time adjustment,
2184 i.e., "1 day ago" vs "24 hours ago" are calculated in different
2187 'tm0.tm_isdst' contains the DST of the input date,
2188 'tm.tm_isdst' is the normalized result after calling
2191 if
(tm0.tm_isdst
!= -1 && tm.tm_isdst
!= tm0.tm_isdst
)
2192 dbg_printf
(_
("warning: daylight saving time changed after "
2193 "date adjustment\n"));
2195 /* Warn if the user did not ask to adjust days but mday changed,
2197 user did not ask to adjust months/days but the month changed.
2199 Example for first case:
2200 2016-05-31 + 1 month => 2016-06-31 => 2016-07-01.
2201 User asked to adjust month, but the day changed from 31 to 01.
2203 Example for second case:
2204 2016-02-29 + 1 year => 2017-02-29 => 2017-03-01.
2205 User asked to adjust year, but the month changed from 02 to 03.
2208 && (tm.tm_mday
!= day
2209 ||
(pc.rel.month
== 0 && tm.tm_mon
!= month
)))
2211 dbg_printf
(_
("warning: month/year adjustment resulted in "
2212 "shifted dates:\n"));
2213 char tm_year_buf
[TM_YEAR_BUFSIZE
];
2214 dbg_printf
(_
(" adjusted Y M D: %s %02d %02d\n"),
2215 tm_year_str
(year
, tm_year_buf
), month
+ 1, day
);
2216 dbg_printf
(_
(" normalized Y M D: %s %02d %02d\n"),
2217 tm_year_str
(tm.tm_year
, tm_year_buf
),
2218 tm.tm_mon
+ 1, tm.tm_mday
);
2224 /* The only "output" of this if-block is an updated Start value,
2225 so this block must follow others that clobber Start. */
2228 bool overflow
= false
;
2229 #ifdef HAVE_STRUCT_TM_TM_GMTOFF
2230 long int utcoff
= tm.tm_gmtoff
;
2234 int utcoff
= (gmtime_r
(&t
, &gmt
)
2235 ? tm_diff
(&tm
, &gmt
)
2236 : (overflow
= true
, 0));
2239 overflow |
= ckd_sub
(&delta
, pc.time_zone
, utcoff
);
2241 overflow |
= ckd_sub
(&t1
, Start
, delta
);
2244 if
(debugging
(&pc
))
2245 dbg_printf
(_
("error: timezone %d caused time_t overflow\n"),
2252 if
(debugging
(&pc
))
2254 intmax_t Starti
= Start
;
2255 dbg_printf
(_
("'%s' = %"PRIdMAX
" epoch-seconds\n"),
2256 debug_strfdatetime
(&tm
, &pc
, dbg_tm
, sizeof dbg_tm
),
2261 /* Add relative hours, minutes, and seconds. On hosts that support
2262 leap seconds, ignore the possibility of leap seconds; e.g.,
2263 "+ 10 minutes" adds 600 seconds, even if one of them is a
2264 leap second. Typically this is not what the user wants, but it's
2265 too hard to do it the other way, because the time zone indicator
2266 must be applied before relative times, and if mktime is applied
2267 again the time zone will be lost. */
2269 intmax_t orig_ns
= pc.seconds.tv_nsec
;
2270 intmax_t sum_ns
= orig_ns
+ pc.rel.ns
;
2271 int normalized_ns
= (sum_ns % BILLION
+ BILLION
) % BILLION
;
2272 int d4
= (sum_ns
- normalized_ns
) / BILLION
;
2273 intmax_t d1
, t1
, d2
, t2
, t3
;
2275 if
(ckd_mul
(&d1
, pc.rel.hour
, 60 * 60)
2276 || ckd_add
(&t1
, Start
, d1
)
2277 || ckd_mul
(&d2
, pc.rel.minutes
, 60)
2278 || ckd_add
(&t2
, t1
, d2
)
2279 || ckd_add
(&t3
, t2
, pc.rel.seconds
)
2280 || ckd_add
(&t4
, t3
, d4
))
2282 if
(debugging
(&pc
))
2283 dbg_printf
(_
("error: adding relative time caused an "
2288 result
->tv_sec
= t4
;
2289 result
->tv_nsec
= normalized_ns
;
2292 && (pc.rel.hour | pc.rel.minutes | pc.rel.seconds | pc.rel.ns
))
2294 dbg_printf
(_
("after time adjustment (%+"PRIdMAX
" hours, "
2295 "%+"PRIdMAX
" minutes, "
2296 "%+"PRIdMAX
" seconds, %+d ns),\n"),
2297 pc.rel.hour
, pc.rel.minutes
, pc.rel.seconds
,
2300 dbg_printf
(_
(" new time = %"PRIdMAX
" epoch-seconds\n"), t4i
);
2302 /* Warn about crossing DST due to time adjustment.
2303 Example: https://bugs.gnu.org/8357
2304 env TZ=Europe/Helsinki \
2306 -d 'Mon Mar 28 00:36:07 2011 EEST 24 hours ago'
2308 This case is different than DST changes due to days adjustment,
2309 i.e., "1 day ago" vs "24 hours ago" are calculated in different
2312 'tm.tm_isdst' contains the date after date adjustment. */
2314 if
(tm.tm_isdst
!= -1 && localtime_rz
(tz
, &result
->tv_sec
, &lmt
)
2315 && tm.tm_isdst
!= lmt.tm_isdst
)
2316 dbg_printf
(_
("warning: daylight saving time changed after "
2317 "time adjustment\n"));
2322 if
(debugging
(&pc
))
2324 /* Special case: using 'date -u' simply set TZ=UTC0 */
2326 dbg_printf
(_
("timezone: system default\n"));
2327 else if
(STREQ
(tzstring
, "UTC0"))
2328 dbg_printf
(_
("timezone: Universal Time\n"));
2330 dbg_printf
(_
("timezone: TZ=\"%s\" environment value\n"), tzstring
);
2332 intmax_t sec
= result
->tv_sec
;
2333 int nsec
= result
->tv_nsec
;
2334 dbg_printf
(_
("final: %"PRIdMAX
".%09d (epoch-seconds)\n"),
2338 bool got_utc
= !!gmtime_r
(&result
->tv_sec
, &gmt
);
2340 dbg_printf
(_
("final: %s (UTC)\n"),
2341 debug_strfdatetime
(&gmt
, NULL
,
2342 dbg_tm
, sizeof dbg_tm
));
2343 if
(localtime_rz
(tz
, &result
->tv_sec
, &lmt
))
2345 #ifdef HAVE_STRUCT_TM_TM_GMTOFF
2346 bool got_utcoff
= true
;
2347 long int utcoff
= lmt.tm_gmtoff
;
2349 bool got_utcoff
= got_utc
;
2352 utcoff
= tm_diff
(&lmt
, &gmt
);
2355 dbg_printf
(_
("final: %s (UTC%s)\n"),
2356 debug_strfdatetime
(&lmt
, NULL
, dbg_tm
, sizeof dbg_tm
),
2357 time_zone_str
(utcoff
, time_zone_buf
));
2359 dbg_printf
(_
("final: %s (unknown time zone offset)\n"),
2360 debug_strfdatetime
(&lmt
, NULL
, dbg_tm
, sizeof dbg_tm
));
2367 if
(tz
!= tzdefault
)
2373 #ifdef GNULIB_PARSE_DATETIME2
2374 /* Parse a date/time string, storing the resulting time value into *RESULT.
2375 The string itself is pointed to by P. Return true if successful.
2376 P can be an incomplete or relative time specification; if so, use
2377 *NOW as the basis for the returned time. Default to timezone
2378 TZDEFAULT, which corresponds to tzalloc (TZSTRING). */
2380 parse_datetime2
(struct timespec
*result
, char const *p
,
2381 struct timespec
const *now
, unsigned int flags
,
2382 timezone_t tzdefault
, char const *tzstring
)
2384 return parse_datetime_body
(result
, p
, now
, flags
, tzdefault
, tzstring
);
2389 /* The plain interface: run with debug=false and the default timezone. */
2391 parse_datetime
(struct timespec
*result
, char const *p
,
2392 struct timespec
const *now
)
2394 char const *tzstring
= getenv
("TZ");
2395 timezone_t tz
= tzalloc
(tzstring
);
2398 bool ok
= parse_datetime_body
(result
, p
, now
, 0, tz
, tzstring
);
2406 main
(int ac
, char **av
)
2410 printf
("Enter date, or blank line to exit.\n\t> ");
2413 buff
[BUFSIZ
- 1] = '\0';
2414 while
(fgets
(buff
, BUFSIZ
- 1, stdin
) && buff
[0])
2417 struct tm
const *tm
;
2418 if
(! parse_datetime
(&d
, buff
, NULL
))
2419 printf
("Bad format - couldn't convert.\n");
2420 else if
(! (tm
= localtime
(&d.tv_sec
)))
2422 intmax_t sec
= d.tv_sec
;
2423 printf
("localtime (%"PRIdMAX
") failed\n", sec
);
2428 char tm_year_buf
[TM_YEAR_BUFSIZE
];
2429 printf
("%s-%02d-%02d %02d:%02d:%02d.%09d\n",
2430 tm_year_str
(tm
->tm_year
, tm_year_buf
),
2431 tm
->tm_mon
+ 1, tm
->tm_mday
,
2432 tm
->tm_hour
, tm
->tm_min
, tm
->tm_sec
, ns
);