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"
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
57 #include <stdckdint.h>
64 #define _(str) gettext (str)
66 /* Bison's skeleton tests _STDLIB_H, while some stdlib.h headers
67 use _STDLIB_H_ as witness. Map the latter to the one bison uses. */
68 /* FIXME: this is temporary. Remove when we have a mechanism to ensure
69 that the version we're using is fixed, too. */
75 /* Shift A right by B bits portably, by dividing A by 2**B and
76 truncating towards minus infinity. A and B should be free of side
77 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
78 INT_BITS is the number of useful bits in an int. GNU code can
79 assume that INT_BITS is at least 32.
81 ISO C99 says that A >> B is implementation-defined if A < 0. Some
82 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
83 right in the usual way when A < 0, so SHR falls back on division if
84 ordinary A >> B doesn't seem to be the usual signed shift. */
88 : (a
) / (1 << (b
)) - ((a
) %
(1 << (b
)) < 0))
90 #define HOUR(x) (60 * 60 * (x))
92 #define STREQ(a, b) (strcmp (a, b) == 0)
94 /* Verify that time_t is an integer as POSIX requires, and that every
95 time_t value fits in intmax_t. Please file a bug report if these
96 assumptions are false on your platform. */
97 static_assert
(TYPE_IS_INTEGER
(time_t));
98 static_assert
(!TYPE_SIGNED
(time_t) || INTMAX_MIN
<= TYPE_MINIMUM
(time_t));
99 static_assert
(TYPE_MAXIMUM
(time_t) <= INTMAX_MAX
);
101 /* True if N is out of range for time_t. */
103 time_overflow
(intmax_t n
)
105 return
! ((TYPE_SIGNED
(time_t) ? TYPE_MINIMUM
(time_t) <= n
: 0 <= n
)
106 && n
<= TYPE_MAXIMUM
(time_t));
109 /* Convert a possibly-signed character to an unsigned character. This is
110 a bit safer than casting to unsigned char, since it catches some type
111 errors that the cast doesn't. */
112 static unsigned char to_uchar
(char ch
) { return ch
; }
114 static void _GL_ATTRIBUTE_FORMAT
((__printf__
, 1, 2))
115 dbg_printf
(char const *msg
, ...
)
118 /* TODO: use gnulib's 'program_name' instead? */
119 fputs
("date: ", stderr
);
121 va_start
(args
, msg
);
122 vfprintf
(stderr
, msg
, args
);
127 /* An integer value, and the number of digits in its textual
136 /* An entry in the lexical lookup table. */
144 /* Meridian: am, pm, or 24-hour style. */
145 enum { MERam
, MERpm
, MER24
};
147 /* A reasonable upper bound for the buffer used in debug output. */
148 enum { DBGBUFSIZE
= 100 };
150 enum { BILLION
= 1000000000, LOG10_BILLION
= 9 };
152 /* Relative times. */
155 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
165 #if HAVE_COMPOUND_LITERALS
166 # define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
168 static relative_time
const RELATIVE_TIME_0
;
171 /* Information passed to and from the parser. */
174 /* The input string remaining to be parsed. */
177 /* N, if this is the Nth Tuesday. */
178 intmax_t day_ordinal
;
180 /* Day of week; Sunday is 0. */
183 /* tm_isdst flag for the local zone. */
186 /* Time zone, in seconds east of UT. */
189 /* Style used for time. */
192 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
198 struct timespec seconds
; /* includes nanoseconds */
200 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
203 /* Presence or counts of nonterminals of various flavors parsed so far. */
209 idx_t local_zones_seen
;
215 #ifdef GNULIB_PARSE_DATETIME2
216 /* Print debugging output to stderr. */
217 bool parse_datetime_debug
;
220 /* Which of the 'seen' parts have been printed when debugging. */
221 bool debug_dates_seen
;
222 bool debug_days_seen
;
223 bool debug_local_zones_seen
;
224 bool debug_times_seen
;
225 bool debug_zones_seen
;
226 bool debug_year_seen
;
228 /* The user specified explicit ordinal day value. */
229 bool debug_ordinal_day_seen
;
231 /* Table of local time zone abbreviations, terminated by a null entry. */
232 table local_time_zone_table
[3];
236 debugging
(parser_control
const *pc
)
238 #ifdef GNULIB_PARSE_DATETIME2
239 return pc
->parse_datetime_debug
;
246 static int yylex (union YYSTYPE *, parser_control
*);
247 static void yyerror (parser_control
const *, char const *);
248 static bool time_zone_hhmm
(parser_control
*, textint
, intmax_t);
250 /* Extract into *PC any date and time info from a string of digits
251 of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
254 digits_to_date_time
(parser_control
*pc
, textint text_int
)
256 if
(pc
->dates_seen
&& ! pc
->year.digits
257 && ! pc
->rels_seen
&& (pc
->times_seen ||
2 < text_int.digits
))
259 pc
->year_seen
= true
;
264 if
(4 < text_int.digits
)
267 pc
->day
= text_int.value %
100;
268 pc
->month
= (text_int.value
/ 100) %
100;
269 pc
->year.value
= text_int.value
/ 10000;
270 pc
->year.digits
= text_int.digits
- 4;
275 if
(text_int.digits
<= 2)
277 pc
->hour
= text_int.value
;
282 pc
->hour
= text_int.value
/ 100;
283 pc
->minutes
= text_int.value %
100;
285 pc
->seconds
= (struct timespec
) {0};
286 pc
->meridian
= MER24
;
291 /* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1). Return true
292 if successful, false if an overflow occurred. */
294 apply_relative_time
(parser_control
*pc
, relative_time rel
, int factor
)
297 ?
(ckd_sub
(&pc
->rel.ns
, pc
->rel.ns
, rel.ns
)
298 | ckd_sub
(&pc
->rel.seconds
, pc
->rel.seconds
, rel.seconds
)
299 | ckd_sub
(&pc
->rel.minutes
, pc
->rel.minutes
, rel.minutes
)
300 | ckd_sub
(&pc
->rel.hour
, pc
->rel.hour
, rel.hour
)
301 | ckd_sub
(&pc
->rel.day
, pc
->rel.day
, rel.day
)
302 | ckd_sub
(&pc
->rel.month
, pc
->rel.month
, rel.month
)
303 | ckd_sub
(&pc
->rel.year
, pc
->rel.year
, rel.year
))
304 : (ckd_add
(&pc
->rel.ns
, pc
->rel.ns
, rel.ns
)
305 | ckd_add
(&pc
->rel.seconds
, pc
->rel.seconds
, rel.seconds
)
306 | ckd_add
(&pc
->rel.minutes
, pc
->rel.minutes
, rel.minutes
)
307 | ckd_add
(&pc
->rel.hour
, pc
->rel.hour
, rel.hour
)
308 | ckd_add
(&pc
->rel.day
, pc
->rel.day
, rel.day
)
309 | ckd_add
(&pc
->rel.month
, pc
->rel.month
, rel.month
)
310 | ckd_add
(&pc
->rel.year
, pc
->rel.year
, rel.year
)))
312 pc
->rels_seen
= true
;
316 /* Set PC-> hour, minutes, seconds and nanoseconds members from arguments. */
318 set_hhmmss
(parser_control
*pc
, intmax_t hour
, intmax_t minutes
,
319 time_t sec
, int nsec
)
322 pc
->minutes
= minutes
;
323 pc
->seconds
= (struct timespec
) { .tv_sec
= sec
, .tv_nsec
= nsec
};
326 /* Return a textual representation of the day ordinal/number values
327 in the parser_control struct (e.g., "last wed", "this tues", "thu"). */
329 str_days
(parser_control
*pc
, char *buffer
, int n
)
331 /* TODO: use relative_time_table for reverse lookup. */
332 static char const ordinal_values
[][11] = {
336 "(SECOND)", /* SECOND is commented out in relative_time_table. */
349 static char const days_values
[][4] = {
361 /* Don't add an ordinal prefix if the user didn't specify it
362 (e.g., "this wed" vs "wed"). */
363 if
(pc
->debug_ordinal_day_seen
)
365 /* Use word description if possible (e.g., -1 = last, 3 = third). */
366 len
= (-1 <= pc
->day_ordinal
&& pc
->day_ordinal
<= 12
367 ? snprintf
(buffer
, n
, "%s", ordinal_values
[pc
->day_ordinal
+ 1])
368 : snprintf
(buffer
, n
, "%"PRIdMAX
, pc
->day_ordinal
));
376 /* Add the day name */
377 if
(0 <= pc
->day_number
&& pc
->day_number
<= 6 && 0 <= len
&& len
< n
)
378 snprintf
(buffer
+ len
, n
- len
, &" %s"[len
== 0],
379 days_values
[pc
->day_number
]);
382 /* invalid day_number value - should never happen */
387 /* Convert a time zone to its string representation. */
389 enum { TIME_ZONE_BUFSIZE
= INT_STRLEN_BOUND
(intmax_t) + sizeof
":MM:SS" } ;
392 time_zone_str
(int time_zone
, char time_zone_buf
[TIME_ZONE_BUFSIZE
])
394 char *p
= time_zone_buf
;
395 char sign
= time_zone
< 0 ?
'-' : '+';
396 int hour
= abs
(time_zone
/ (60 * 60));
397 p
+= sprintf
(time_zone_buf
, "%c%02d", sign
, hour
);
398 int offset_from_hour
= abs
(time_zone %
(60 * 60));
399 if
(offset_from_hour
!= 0)
401 int mm
= offset_from_hour
/ 60;
402 int ss
= offset_from_hour %
60;
404 *p
++ = '0' + mm
/ 10;
405 *p
++ = '0' + mm %
10;
409 *p
++ = '0' + ss
/ 10;
410 *p
++ = '0' + ss %
10;
414 return time_zone_buf
;
417 /* debugging: print the current time in the parser_control structure.
418 The parser will increment "*_seen" members for those which were parsed.
419 This function will print only newly seen parts. */
421 debug_print_current_time
(char const *item
, parser_control
*pc
)
428 /* no newline, more items printed below */
429 dbg_printf
(_
("parsed %s part: "), item
);
431 if
(pc
->dates_seen
&& !pc
->debug_dates_seen
)
433 /*TODO: use pc->year.negative? */
434 fprintf
(stderr
, "(Y-M-D) %04"PRIdMAX
"-%02"PRIdMAX
"-%02"PRIdMAX
,
435 pc
->year.value
, pc
->month
, pc
->day
);
436 pc
->debug_dates_seen
= true
;
440 if
(pc
->year_seen
!= pc
->debug_year_seen
)
444 fprintf
(stderr
, _
("year: %04"PRIdMAX
), pc
->year.value
);
446 pc
->debug_year_seen
= pc
->year_seen
;
450 if
(pc
->times_seen
&& !pc
->debug_times_seen
)
452 intmax_t sec
= pc
->seconds.tv_sec
;
453 fprintf
(stderr
, &" %02"PRIdMAX
":%02"PRIdMAX
":%02"PRIdMAX
[!space
],
454 pc
->hour
, pc
->minutes
, sec
);
455 if
(pc
->seconds.tv_nsec
!= 0)
457 int nsec
= pc
->seconds.tv_nsec
;
458 fprintf
(stderr
, ".%09d", nsec
);
460 if
(pc
->meridian
== MERpm
)
461 fputs
("pm", stderr
);
463 pc
->debug_times_seen
= true
;
467 if
(pc
->days_seen
&& !pc
->debug_days_seen
)
471 char tmp
[DBGBUFSIZE
];
472 fprintf
(stderr
, _
("%s (day ordinal=%"PRIdMAX
" number=%d)"),
473 str_days
(pc
, tmp
, sizeof tmp
),
474 pc
->day_ordinal
, pc
->day_number
);
475 pc
->debug_days_seen
= true
;
479 /* local zone strings only change the DST settings,
480 not the timezone value. If seen, inform about the DST. */
481 if
(pc
->local_zones_seen
&& !pc
->debug_local_zones_seen
)
483 fprintf
(stderr
, &" isdst=%d%s"[!space
],
484 pc
->local_isdst
, pc
->dsts_seen ?
" DST" : "");
485 pc
->debug_local_zones_seen
= true
;
489 if
(pc
->zones_seen
&& !pc
->debug_zones_seen
)
491 char time_zone_buf
[TIME_ZONE_BUFSIZE
];
492 fprintf
(stderr
, &" UTC%s"[!space
],
493 time_zone_str
(pc
->time_zone
, time_zone_buf
));
494 pc
->debug_zones_seen
= true
;
498 if
(pc
->timespec_seen
)
500 intmax_t sec
= pc
->seconds.tv_sec
;
503 fprintf
(stderr
, _
("number of seconds: %"PRIdMAX
), sec
);
506 fputc
('\n', stderr
);
509 /* Debugging: print the current relative values. */
512 print_rel_part
(bool space
, intmax_t val
, char const *name
)
516 fprintf
(stderr
, &" %+"PRIdMAX
" %s"[!space
], val
, name
);
521 debug_print_relative_time
(char const *item
, parser_control
const *pc
)
528 /* no newline, more items printed below */
529 dbg_printf
(_
("parsed %s part: "), item
);
531 if
(pc
->rel.year
== 0 && pc
->rel.month
== 0 && pc
->rel.day
== 0
532 && pc
->rel.hour
== 0 && pc
->rel.minutes
== 0 && pc
->rel.seconds
== 0
535 /* Special case: relative time of this/today/now */
536 fputs
(_
("today/this/now\n"), stderr
);
540 space
= print_rel_part
(space
, pc
->rel.year
, "year(s)");
541 space
= print_rel_part
(space
, pc
->rel.month
, "month(s)");
542 space
= print_rel_part
(space
, pc
->rel.day
, "day(s)");
543 space
= print_rel_part
(space
, pc
->rel.hour
, "hour(s)");
544 space
= print_rel_part
(space
, pc
->rel.minutes
, "minutes");
545 space
= print_rel_part
(space
, pc
->rel.seconds
, "seconds");
546 print_rel_part
(space
, pc
->rel.ns
, "nanoseconds");
548 fputc
('\n', stderr
);
555 /* We want a reentrant parser, even if the TZ manipulation and the calls to
556 localtime and gmtime are not reentrant. */
558 %parse
-param
{ parser_control
*pc
}
559 %lex
-param
{ parser_control
*pc
}
561 /* This grammar has 31 shift/reduce conflicts. */
568 struct timespec timespec
;
575 %token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
576 %token
<intval
> tDAY_UNIT tDAY_SHIFT
578 %token
<intval
> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
579 %token
<intval
> tMONTH tORDINAL tZONE
581 %token
<textintval
> tSNUMBER tUNUMBER
582 %token
<timespec
> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
584 %type
<intval
> o_colon_minutes
585 %type
<timespec
> seconds signed_seconds unsigned_seconds
587 %type
<rel
> relunit relunit_snumber dayshift
600 pc
->timespec_seen
= true
;
601 debug_print_current_time
(_
("number of seconds"), pc
);
613 pc
->times_seen
++; pc
->dates_seen
++;
614 debug_print_current_time
(_
("datetime"), pc
);
619 debug_print_current_time
(_
("time"), pc
);
623 pc
->local_zones_seen
++;
624 debug_print_current_time
(_
("local_zone"), pc
);
629 debug_print_current_time
("J", pc
);
634 debug_print_current_time
(_
("zone"), pc
);
639 debug_print_current_time
(_
("date"), pc
);
644 debug_print_current_time
(_
("day"), pc
);
648 debug_print_relative_time
(_
("relative"), pc
);
652 debug_print_current_time
(_
("number"), pc
);
656 debug_print_relative_time
(_
("hybrid"), pc
);
665 iso_8601_date
'T' iso_8601_time
671 set_hhmmss
(pc
, $1.value
, 0, 0, 0);
674 | tUNUMBER
':' tUNUMBER tMERIDIAN
676 set_hhmmss
(pc
, $1.value
, $3.value
, 0, 0);
679 | tUNUMBER
':' tUNUMBER
':' unsigned_seconds tMERIDIAN
681 set_hhmmss
(pc
, $1.value
, $3.value
, $5.tv_sec
, $5.tv_nsec
);
690 set_hhmmss
(pc
, $1.value
, 0, 0, 0);
691 pc
->meridian
= MER24
;
693 | tUNUMBER
':' tUNUMBER o_zone_offset
695 set_hhmmss
(pc
, $1.value
, $3.value
, 0, 0);
696 pc
->meridian
= MER24
;
698 | tUNUMBER
':' tUNUMBER
':' unsigned_seconds o_zone_offset
700 set_hhmmss
(pc
, $1.value
, $3.value
, $5.tv_sec
, $5.tv_nsec
);
701 pc
->meridian
= MER24
;
711 tSNUMBER o_colon_minutes
714 if
(! time_zone_hhmm
(pc
, $1, $2)) YYABORT;
718 /* Local zone strings affect only the DST setting, and take effect
719 only if the current TZ setting is relevant.
722 'EEST' is parsed as tLOCAL_ZONE, as it relates to the effective TZ:
723 TZ='Europe/Helsinki' date -d '2016-06-30 EEST'
726 'EEST' is parsed as tDAYZONE:
727 TZ='Asia/Tokyo' date -d '2016-06-30 EEST'
729 This is implemented by probing the next three calendar quarters
730 of the effective timezone and looking for DST changes -
731 if found, the timezone name (EEST) is inserted into
732 the lexical lookup table with type tLOCAL_ZONE.
733 (Search for 'quarter' comment in 'parse_datetime2'.)
737 { pc
->local_isdst
= $1; }
745 /* Note 'T' is a special case, as it is used as the separator in ISO
746 8601 date and time of day representation. */
749 { pc
->time_zone
= $1; }
751 { pc
->time_zone
= -HOUR
(7); }
752 | tZONE relunit_snumber
753 { pc
->time_zone
= $1;
754 if
(! apply_relative_time
(pc
, $2, 1)) YYABORT;
755 debug_print_relative_time
(_
("relative"), pc
);
757 |
'T' relunit_snumber
758 { pc
->time_zone
= -HOUR
(7);
759 if
(! apply_relative_time
(pc
, $2, 1)) YYABORT;
760 debug_print_relative_time
(_
("relative"), pc
);
762 | tZONE tSNUMBER o_colon_minutes
763 { if
(! time_zone_hhmm
(pc
, $2, $3)) YYABORT;
764 if
(ckd_add
(&pc
->time_zone
, pc
->time_zone
, $1)) YYABORT; }
766 { pc
->time_zone
= $1 + 60 * 60; }
768 { pc
->time_zone
= $1 + 60 * 60; }
784 pc
->day_ordinal
= $1;
786 pc
->debug_ordinal_day_seen
= true
;
790 pc
->day_ordinal
= $1.value
;
792 pc
->debug_ordinal_day_seen
= true
;
797 tUNUMBER
'/' tUNUMBER
799 pc
->month
= $1.value
;
802 | tUNUMBER
'/' tUNUMBER
'/' tUNUMBER
804 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
805 otherwise as MM/DD/YY.
806 The goal in recognizing YYYY/MM/DD is solely to support legacy
807 machine-generated dates like those in an RCS log listing. If
808 you want portability, use the ISO 8601 format. */
813 intmax_t digits
= $1.digits
;
814 dbg_printf
(_
("warning: value %"PRIdMAX
" has %"PRIdMAX
" digits. "
815 "Assuming YYYY/MM/DD\n"),
820 pc
->month
= $3.value
;
826 dbg_printf
(_
("warning: value %"PRIdMAX
" has less than 4 digits. "
827 "Assuming MM/DD/YY[YY]\n"),
830 pc
->month
= $1.value
;
835 | tUNUMBER tMONTH tSNUMBER
837 /* E.g., 17-JUN-1992. */
840 if
(ckd_sub
(&pc
->year.value
, 0, $3.value
)) YYABORT;
841 pc
->year.digits
= $3.digits
;
843 | tMONTH tSNUMBER tSNUMBER
845 /* E.g., JUN-17-1992. */
847 if
(ckd_sub
(&pc
->day
, 0, $2.value
)) YYABORT;
848 if
(ckd_sub
(&pc
->year.value
, 0, $3.value
)) YYABORT;
849 pc
->year.digits
= $3.digits
;
856 | tMONTH tUNUMBER
',' tUNUMBER
867 | tUNUMBER tMONTH tUNUMBER
877 tUNUMBER tSNUMBER tSNUMBER
879 /* ISO 8601 format. YYYY-MM-DD. */
881 if
(ckd_sub
(&pc
->month
, 0, $2.value
)) YYABORT;
882 if
(ckd_sub
(&pc
->day
, 0, $3.value
)) YYABORT;
888 { if
(! apply_relative_time
(pc
, $1, $2)) YYABORT; }
890 { if
(! apply_relative_time
(pc
, $1, 1)) YYABORT; }
892 { if
(! apply_relative_time
(pc
, $1, 1)) YYABORT; }
897 { $$
= RELATIVE_TIME_0
; $$.year
= $1; }
898 | tUNUMBER tYEAR_UNIT
899 { $$
= RELATIVE_TIME_0
; $$.year
= $1.value
; }
901 { $$
= RELATIVE_TIME_0
; $$.year
= 1; }
902 | tORDINAL tMONTH_UNIT
903 { $$
= RELATIVE_TIME_0
; $$.month
= $1; }
904 | tUNUMBER tMONTH_UNIT
905 { $$
= RELATIVE_TIME_0
; $$.month
= $1.value
; }
907 { $$
= RELATIVE_TIME_0
; $$.month
= 1; }
909 { $$
= RELATIVE_TIME_0
;
910 if
(ckd_mul
(&$$.day
, $1, $2)) YYABORT; }
912 { $$
= RELATIVE_TIME_0
;
913 if
(ckd_mul
(&$$.day
, $1.value
, $2)) YYABORT; }
915 { $$
= RELATIVE_TIME_0
; $$.day
= $1; }
916 | tORDINAL tHOUR_UNIT
917 { $$
= RELATIVE_TIME_0
; $$.hour
= $1; }
918 | tUNUMBER tHOUR_UNIT
919 { $$
= RELATIVE_TIME_0
; $$.hour
= $1.value
; }
921 { $$
= RELATIVE_TIME_0
; $$.hour
= 1; }
922 | tORDINAL tMINUTE_UNIT
923 { $$
= RELATIVE_TIME_0
; $$.minutes
= $1; }
924 | tUNUMBER tMINUTE_UNIT
925 { $$
= RELATIVE_TIME_0
; $$.minutes
= $1.value
; }
927 { $$
= RELATIVE_TIME_0
; $$.minutes
= 1; }
929 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1; }
931 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1.value
; }
932 | tSDECIMAL_NUMBER tSEC_UNIT
933 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1.tv_sec
; $$.ns
= $1.tv_nsec
; }
934 | tUDECIMAL_NUMBER tSEC_UNIT
935 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1.tv_sec
; $$.ns
= $1.tv_nsec
; }
937 { $$
= RELATIVE_TIME_0
; $$.seconds
= 1; }
943 { $$
= RELATIVE_TIME_0
; $$.year
= $1.value
; }
944 | tSNUMBER tMONTH_UNIT
945 { $$
= RELATIVE_TIME_0
; $$.month
= $1.value
; }
947 { $$
= RELATIVE_TIME_0
;
948 if
(ckd_mul
(&$$.day
, $1.value
, $2)) YYABORT; }
949 | tSNUMBER tHOUR_UNIT
950 { $$
= RELATIVE_TIME_0
; $$.hour
= $1.value
; }
951 | tSNUMBER tMINUTE_UNIT
952 { $$
= RELATIVE_TIME_0
; $$.minutes
= $1.value
; }
954 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1.value
; }
959 { $$
= RELATIVE_TIME_0
; $$.day
= $1; }
962 seconds: signed_seconds | unsigned_seconds
;
967 { if
(time_overflow
($1.value
)) YYABORT;
968 $$
= (struct timespec
) { .tv_sec
= $1.value
}; }
974 { if
(time_overflow
($1.value
)) YYABORT;
975 $$
= (struct timespec
) { .tv_sec
= $1.value
}; }
980 { digits_to_date_time
(pc
, $1); }
984 tUNUMBER relunit_snumber
986 /* Hybrid all-digit and relative offset, so that we accept e.g.,
987 "YYYYMMDD +N days" as well as "YYYYMMDD N days". */
988 digits_to_date_time
(pc
, $1);
989 if
(! apply_relative_time
(pc
, $2, 1)) YYABORT;
1002 static table
const meridian_table
[] =
1004 { "AM", tMERIDIAN
, MERam
},
1005 { "A.M.", tMERIDIAN
, MERam
},
1006 { "PM", tMERIDIAN
, MERpm
},
1007 { "P.M.", tMERIDIAN
, MERpm
},
1011 static table
const dst_table
[] =
1016 static table
const month_and_day_table
[] =
1018 { "JANUARY", tMONTH
, 1 },
1019 { "FEBRUARY", tMONTH
, 2 },
1020 { "MARCH", tMONTH
, 3 },
1021 { "APRIL", tMONTH
, 4 },
1022 { "MAY", tMONTH
, 5 },
1023 { "JUNE", tMONTH
, 6 },
1024 { "JULY", tMONTH
, 7 },
1025 { "AUGUST", tMONTH
, 8 },
1026 { "SEPTEMBER",tMONTH
, 9 },
1027 { "SEPT", tMONTH
, 9 },
1028 { "OCTOBER", tMONTH
, 10 },
1029 { "NOVEMBER", tMONTH
, 11 },
1030 { "DECEMBER", tMONTH
, 12 },
1031 { "SUNDAY", tDAY
, 0 },
1032 { "MONDAY", tDAY
, 1 },
1033 { "TUESDAY", tDAY
, 2 },
1034 { "TUES", tDAY
, 2 },
1035 { "WEDNESDAY",tDAY
, 3 },
1036 { "WEDNES", tDAY
, 3 },
1037 { "THURSDAY", tDAY
, 4 },
1038 { "THUR", tDAY
, 4 },
1039 { "THURS", tDAY
, 4 },
1040 { "FRIDAY", tDAY
, 5 },
1041 { "SATURDAY", tDAY
, 6 },
1045 static table
const time_units_table
[] =
1047 { "YEAR", tYEAR_UNIT
, 1 },
1048 { "MONTH", tMONTH_UNIT
, 1 },
1049 { "FORTNIGHT",tDAY_UNIT
, 14 },
1050 { "WEEK", tDAY_UNIT
, 7 },
1051 { "DAY", tDAY_UNIT
, 1 },
1052 { "HOUR", tHOUR_UNIT
, 1 },
1053 { "MINUTE", tMINUTE_UNIT
, 1 },
1054 { "MIN", tMINUTE_UNIT
, 1 },
1055 { "SECOND", tSEC_UNIT
, 1 },
1056 { "SEC", tSEC_UNIT
, 1 },
1060 /* Assorted relative-time words. */
1061 static table
const relative_time_table
[] =
1063 { "TOMORROW", tDAY_SHIFT
, 1 },
1064 { "YESTERDAY",tDAY_SHIFT
, -1 },
1065 { "TODAY", tDAY_SHIFT
, 0 },
1066 { "NOW", tDAY_SHIFT
, 0 },
1067 { "LAST", tORDINAL
, -1 },
1068 { "THIS", tORDINAL
, 0 },
1069 { "NEXT", tORDINAL
, 1 },
1070 { "FIRST", tORDINAL
, 1 },
1071 /*{ "SECOND", tORDINAL, 2 }, */
1072 { "THIRD", tORDINAL
, 3 },
1073 { "FOURTH", tORDINAL
, 4 },
1074 { "FIFTH", tORDINAL
, 5 },
1075 { "SIXTH", tORDINAL
, 6 },
1076 { "SEVENTH", tORDINAL
, 7 },
1077 { "EIGHTH", tORDINAL
, 8 },
1078 { "NINTH", tORDINAL
, 9 },
1079 { "TENTH", tORDINAL
, 10 },
1080 { "ELEVENTH", tORDINAL
, 11 },
1081 { "TWELFTH", tORDINAL
, 12 },
1082 { "AGO", tAGO
, -1 },
1083 { "HENCE", tAGO
, 1 },
1087 /* The universal time zone table. These labels can be used even for
1088 timestamps that would not otherwise be valid, e.g., GMT timestamps
1089 oin London during summer. */
1090 static table
const universal_time_zone_table
[] =
1092 { "GMT", tZONE
, HOUR
( 0) }, /* Greenwich Mean */
1093 { "UT", tZONE
, HOUR
( 0) }, /* Universal (Coordinated) */
1094 { "UTC", tZONE
, HOUR
( 0) },
1098 /* The time zone table. This table is necessarily incomplete, as time
1099 zone abbreviations are ambiguous; e.g., Australians interpret "EST"
1100 as Eastern time in Australia, not as US Eastern Standard Time.
1101 You cannot rely on parse_datetime to handle arbitrary time zone
1102 abbreviations; use numeric abbreviations like "-0500" instead. */
1103 static table
const time_zone_table
[] =
1105 { "WET", tZONE
, HOUR
( 0) }, /* Western European */
1106 { "WEST", tDAYZONE
, HOUR
( 0) }, /* Western European Summer */
1107 { "BST", tDAYZONE
, HOUR
( 0) }, /* British Summer */
1108 { "ART", tZONE
, -HOUR
( 3) }, /* Argentina */
1109 { "BRT", tZONE
, -HOUR
( 3) }, /* Brazil */
1110 { "BRST", tDAYZONE
, -HOUR
( 3) }, /* Brazil Summer */
1111 { "NST", tZONE
, -(HOUR
( 3) + 30 * 60) }, /* Newfoundland Standard */
1112 { "NDT", tDAYZONE
,-(HOUR
( 3) + 30 * 60) }, /* Newfoundland Daylight */
1113 { "AST", tZONE
, -HOUR
( 4) }, /* Atlantic Standard */
1114 { "ADT", tDAYZONE
, -HOUR
( 4) }, /* Atlantic Daylight */
1115 { "CLT", tZONE
, -HOUR
( 4) }, /* Chile */
1116 { "CLST", tDAYZONE
, -HOUR
( 4) }, /* Chile Summer */
1117 { "EST", tZONE
, -HOUR
( 5) }, /* Eastern Standard */
1118 { "EDT", tDAYZONE
, -HOUR
( 5) }, /* Eastern Daylight */
1119 { "CST", tZONE
, -HOUR
( 6) }, /* Central Standard */
1120 { "CDT", tDAYZONE
, -HOUR
( 6) }, /* Central Daylight */
1121 { "MST", tZONE
, -HOUR
( 7) }, /* Mountain Standard */
1122 { "MDT", tDAYZONE
, -HOUR
( 7) }, /* Mountain Daylight */
1123 { "PST", tZONE
, -HOUR
( 8) }, /* Pacific Standard */
1124 { "PDT", tDAYZONE
, -HOUR
( 8) }, /* Pacific Daylight */
1125 { "AKST", tZONE
, -HOUR
( 9) }, /* Alaska Standard */
1126 { "AKDT", tDAYZONE
, -HOUR
( 9) }, /* Alaska Daylight */
1127 { "HST", tZONE
, -HOUR
(10) }, /* Hawaii Standard */
1128 { "HAST", tZONE
, -HOUR
(10) }, /* Hawaii-Aleutian Standard */
1129 { "HADT", tDAYZONE
, -HOUR
(10) }, /* Hawaii-Aleutian Daylight */
1130 { "SST", tZONE
, -HOUR
(12) }, /* Samoa Standard */
1131 { "WAT", tZONE
, HOUR
( 1) }, /* West Africa */
1132 { "CET", tZONE
, HOUR
( 1) }, /* Central European */
1133 { "CEST", tDAYZONE
, HOUR
( 1) }, /* Central European Summer */
1134 { "MET", tZONE
, HOUR
( 1) }, /* Middle European */
1135 { "MEZ", tZONE
, HOUR
( 1) }, /* Middle European */
1136 { "MEST", tDAYZONE
, HOUR
( 1) }, /* Middle European Summer */
1137 { "MESZ", tDAYZONE
, HOUR
( 1) }, /* Middle European Summer */
1138 { "EET", tZONE
, HOUR
( 2) }, /* Eastern European */
1139 { "EEST", tDAYZONE
, HOUR
( 2) }, /* Eastern European Summer */
1140 { "CAT", tZONE
, HOUR
( 2) }, /* Central Africa */
1141 { "SAST", tZONE
, HOUR
( 2) }, /* South Africa Standard */
1142 { "EAT", tZONE
, HOUR
( 3) }, /* East Africa */
1143 { "MSK", tZONE
, HOUR
( 3) }, /* Moscow */
1144 { "MSD", tDAYZONE
, HOUR
( 3) }, /* Moscow Daylight */
1145 { "IST", tZONE
, (HOUR
( 5) + 30 * 60) }, /* India Standard */
1146 { "SGT", tZONE
, HOUR
( 8) }, /* Singapore */
1147 { "KST", tZONE
, HOUR
( 9) }, /* Korea Standard */
1148 { "JST", tZONE
, HOUR
( 9) }, /* Japan Standard */
1149 { "GST", tZONE
, HOUR
(10) }, /* Guam Standard */
1150 { "NZST", tZONE
, HOUR
(12) }, /* New Zealand Standard */
1151 { "NZDT", tDAYZONE
, HOUR
(12) }, /* New Zealand Daylight */
1155 /* Military time zone table.
1157 RFC 822 got these backwards, but RFC 5322 makes the incorrect
1158 treatment optional, so do them the right way here.
1160 'J' is special, as it is local time.
1161 'T' is also special, as it is the separator in ISO
1162 8601 date and time of day representation. */
1163 static table
const military_table
[] =
1165 { "A", tZONE
, HOUR
( 1) },
1166 { "B", tZONE
, HOUR
( 2) },
1167 { "C", tZONE
, HOUR
( 3) },
1168 { "D", tZONE
, HOUR
( 4) },
1169 { "E", tZONE
, HOUR
( 5) },
1170 { "F", tZONE
, HOUR
( 6) },
1171 { "G", tZONE
, HOUR
( 7) },
1172 { "H", tZONE
, HOUR
( 8) },
1173 { "I", tZONE
, HOUR
( 9) },
1175 { "K", tZONE
, HOUR
(10) },
1176 { "L", tZONE
, HOUR
(11) },
1177 { "M", tZONE
, HOUR
(12) },
1178 { "N", tZONE
, -HOUR
( 1) },
1179 { "O", tZONE
, -HOUR
( 2) },
1180 { "P", tZONE
, -HOUR
( 3) },
1181 { "Q", tZONE
, -HOUR
( 4) },
1182 { "R", tZONE
, -HOUR
( 5) },
1183 { "S", tZONE
, -HOUR
( 6) },
1185 { "U", tZONE
, -HOUR
( 8) },
1186 { "V", tZONE
, -HOUR
( 9) },
1187 { "W", tZONE
, -HOUR
(10) },
1188 { "X", tZONE
, -HOUR
(11) },
1189 { "Y", tZONE
, -HOUR
(12) },
1190 { "Z", tZONE
, HOUR
( 0) },
1196 /* Convert a time zone expressed as HH:MM into an integer count of
1197 seconds. If MM is negative, then S is of the form HHMM and needs
1198 to be picked apart; otherwise, S is of the form HH. As specified in
1199 https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03, allow
1200 only valid TZ range, and consider first two digits as hours, if no
1201 minutes specified. Return true if successful. */
1204 time_zone_hhmm
(parser_control
*pc
, textint s
, intmax_t mm
)
1207 bool overflow
= false
;
1209 /* If the length of S is 1 or 2 and no minutes are specified,
1210 interpret it as a number of hours. */
1211 if
(s.digits
<= 2 && mm
< 0)
1215 n_minutes
= (s.value
/ 100) * 60 + s.value %
100;
1218 overflow |
= ckd_mul
(&n_minutes
, s.value
, 60);
1219 overflow |
= (s.negative
1220 ? ckd_sub
(&n_minutes
, n_minutes
, mm
)
1221 : ckd_add
(&n_minutes
, n_minutes
, mm
));
1224 if
(overflow ||
! (-24 * 60 <= n_minutes
&& n_minutes
<= 24 * 60))
1226 pc
->time_zone
= n_minutes
* 60;
1231 to_hour
(intmax_t hours
, int meridian
)
1235 default
: /* Pacify GCC. */
1237 return
0 <= hours
&& hours
< 24 ? hours
: -1;
1239 return
0 < hours
&& hours
< 12 ? hours
: hours
== 12 ?
0 : -1;
1241 return
0 < hours
&& hours
< 12 ? hours
+ 12 : hours
== 12 ?
12 : -1;
1245 enum { TM_YEAR_BASE
= 1900 };
1246 enum { TM_YEAR_BUFSIZE
= INT_BUFSIZE_BOUND
(int) + 1 };
1248 /* Convert TM_YEAR, a year minus 1900, to a string that is numerically
1249 correct even if subtracting 1900 would overflow. */
1252 tm_year_str
(int tm_year
, char buf
[TM_YEAR_BUFSIZE
])
1254 static_assert
(TM_YEAR_BASE %
100 == 0);
1255 sprintf
(buf
, &"-%02d%02d"[-TM_YEAR_BASE
<= tm_year
],
1256 abs
(tm_year
/ 100 + TM_YEAR_BASE
/ 100),
1257 abs
(tm_year %
100));
1261 /* Convert a text year number to a year minus 1900, working correctly
1262 even if the input is in the range INT_MAX .. INT_MAX + 1900 - 1. */
1265 to_tm_year
(textint textyear
, bool debug
, int *tm_year
)
1267 intmax_t year
= textyear.value
;
1269 /* XPG4 suggests that years 00-68 map to 2000-2068, and
1270 years 69-99 map to 1969-1999. */
1271 if
(0 <= year
&& textyear.digits
== 2)
1273 year
+= year
< 69 ?
2000 : 1900;
1275 dbg_printf
(_
("warning: adjusting year value %"PRIdMAX
1276 " to %"PRIdMAX
"\n"),
1277 textyear.value
, year
);
1281 ? ckd_sub
(tm_year
, -TM_YEAR_BASE
, year
)
1282 : ckd_sub
(tm_year
, year
, TM_YEAR_BASE
))
1285 dbg_printf
(_
("error: out-of-range year %"PRIdMAX
"\n"), year
);
1292 static table
const * _GL_ATTRIBUTE_PURE
1293 lookup_zone
(parser_control
const *pc
, char const *name
)
1297 for
(tp
= universal_time_zone_table
; tp
->name
; tp
++)
1298 if
(strcmp
(name
, tp
->name
) == 0)
1301 /* Try local zone abbreviations before those in time_zone_table, as
1302 the local ones are more likely to be right. */
1303 for
(tp
= pc
->local_time_zone_table
; tp
->name
; tp
++)
1304 if
(strcmp
(name
, tp
->name
) == 0)
1307 for
(tp
= time_zone_table
; tp
->name
; tp
++)
1308 if
(strcmp
(name
, tp
->name
) == 0)
1314 #if ! HAVE_TM_GMTOFF
1315 /* Yield the difference between *A and *B,
1316 measured in seconds, ignoring leap seconds.
1317 The body of this function is taken directly from the GNU C Library;
1320 tm_diff
(const struct tm
*a
, const struct tm
*b
)
1322 /* Compute intervening leap days correctly even if year is negative.
1323 Take care to avoid int overflow in leap day calculations,
1324 but it's OK to assume that A and B are close to each other. */
1325 int a4
= SHR
(a
->tm_year
, 2) + SHR
(TM_YEAR_BASE
, 2) - ! (a
->tm_year
& 3);
1326 int b4
= SHR
(b
->tm_year
, 2) + SHR
(TM_YEAR_BASE
, 2) - ! (b
->tm_year
& 3);
1327 int a100
= a4
/ 25 - (a4 %
25 < 0);
1328 int b100
= b4
/ 25 - (b4 %
25 < 0);
1329 int a400
= SHR
(a100
, 2);
1330 int b400
= SHR
(b100
, 2);
1331 int intervening_leap_days
= (a4
- b4
) - (a100
- b100
) + (a400
- b400
);
1332 int years
= a
->tm_year
- b
->tm_year
;
1333 int days
= (365 * years
+ intervening_leap_days
1334 + (a
->tm_yday
- b
->tm_yday
));
1335 return
(60 * (60 * (24 * days
+ (a
->tm_hour
- b
->tm_hour
))
1336 + (a
->tm_min
- b
->tm_min
))
1337 + (a
->tm_sec
- b
->tm_sec
));
1339 #endif /* ! HAVE_TM_GMTOFF */
1341 static table
const *
1342 lookup_word
(parser_control
const *pc
, char *word
)
1351 /* Make it uppercase. */
1352 for
(p
= word
; *p
; p
++)
1353 *p
= c_toupper
(to_uchar
(*p
));
1355 for
(tp
= meridian_table
; tp
->name
; tp
++)
1356 if
(strcmp
(word
, tp
->name
) == 0)
1359 /* See if we have an abbreviation for a month. */
1360 wordlen
= strlen
(word
);
1361 abbrev
= wordlen
== 3 ||
(wordlen
== 4 && word
[3] == '.');
1363 for
(tp
= month_and_day_table
; tp
->name
; tp
++)
1364 if
((abbrev ? strncmp
(word
, tp
->name
, 3) : strcmp
(word
, tp
->name
)) == 0)
1367 if
((tp
= lookup_zone
(pc
, word
)))
1370 if
(strcmp
(word
, dst_table
[0].name
) == 0)
1373 for
(tp
= time_units_table
; tp
->name
; tp
++)
1374 if
(strcmp
(word
, tp
->name
) == 0)
1377 /* Strip off any plural and try the units table again. */
1378 if
(word
[wordlen
- 1] == 'S')
1380 word
[wordlen
- 1] = '\0';
1381 for
(tp
= time_units_table
; tp
->name
; tp
++)
1382 if
(strcmp
(word
, tp
->name
) == 0)
1384 word
[wordlen
- 1] = 'S'; /* For "this" in relative_time_table. */
1387 for
(tp
= relative_time_table
; tp
->name
; tp
++)
1388 if
(strcmp
(word
, tp
->name
) == 0)
1391 /* Military time zones. */
1393 for
(tp
= military_table
; tp
->name
; tp
++)
1394 if
(word
[0] == tp
->name
[0])
1397 /* Drop out any periods and try the time zone table again. */
1398 for
(period_found
= false
, p
= q
= word
; (*p
= *q
); q
++)
1400 period_found
= true
;
1403 if
(period_found
&& (tp
= lookup_zone
(pc
, word
)))
1410 yylex (union YYSTYPE *lvalp
, parser_control
*pc
)
1416 while
(c
= *pc
->input
, c_isspace
(c
))
1419 if
(c_isdigit
(c
) || c
== '-' || c
== '+')
1421 char const *p
= pc
->input
;
1423 if
(c
== '-' || c
== '+')
1425 sign
= c
== '-' ?
-1 : 1;
1426 while
(c
= *(pc
->input
= ++p
), c_isspace
(c
))
1428 if
(! c_isdigit
(c
))
1429 /* skip the '-' sign */
1438 if
(ckd_mul
(&value
, value
, 10))
1440 if
(ckd_add
(&value
, value
, sign
< 0 ?
'0' - c
: c
- '0'))
1444 while
(c_isdigit
(c
));
1446 if
((c
== '.' || c
== ',') && c_isdigit
(p
[1]))
1451 /* Accumulate fraction, to ns precision. */
1453 int ns
= *p
++ - '0';
1454 for
(digits
= 2; digits
<= LOG10_BILLION
; digits
++)
1461 /* Skip excess digits, truncating toward -Infinity. */
1463 for
(; c_isdigit
(*p
); p
++)
1469 while
(c_isdigit
(*p
))
1472 /* Adjust to the timespec convention, which is that
1473 tv_nsec is always a positive offset even if tv_sec is
1477 if
(ckd_sub
(&s
, s
, 1))
1482 lvalp
->timespec
= (struct timespec
) { .tv_sec
= s
,
1485 return sign ? tSDECIMAL_NUMBER
: tUDECIMAL_NUMBER
;
1489 lvalp
->textintval.negative
= sign
< 0;
1490 lvalp
->textintval.value
= value
;
1491 lvalp
->textintval.digits
= p
- pc
->input
;
1493 return sign ? tSNUMBER
: tUNUMBER
;
1505 if
(p
< buff
+ sizeof buff
- 1)
1509 while
(c_isalpha
(c
) || c
== '.');
1512 tp
= lookup_word
(pc
, buff
);
1516 dbg_printf
(_
("error: unknown word '%s'\n"), buff
);
1519 lvalp
->intval
= tp
->value
;
1524 return to_uchar
(*pc
->input
++);
1541 /* Do nothing if the parser reports an error. */
1543 yyerror (_GL_UNUSED parser_control
const *pc
,
1544 _GL_UNUSED
char const *s
)
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: nonexistent 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
(!debugging
(pc
))
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
(_
(" nonexistent 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 /* Parse a date/time string, storing the resulting time value into *RESULT.
1701 The string itself is pointed to by P. Return true if successful.
1702 P can be an incomplete or relative time specification; if so, use
1703 *NOW as the basis for the returned time. Default to timezone
1704 TZDEFAULT, which corresponds to tzalloc (TZSTRING). */
1706 parse_datetime_body
(struct timespec
*result
, char const *p
,
1707 struct timespec
const *now
, unsigned int flags
,
1708 timezone_t tzdefault
, char const *tzstring
)
1712 char time_zone_buf
[TIME_ZONE_BUFSIZE
];
1713 char dbg_tm
[DBGBUFSIZE
];
1715 char const *input_sentinel
= p
+ strlen
(p
);
1716 char *tz1alloc
= NULL
;
1718 /* A reasonable upper bound for the size of ordinary TZ strings.
1719 Use heap allocation if TZ's length exceeds this. */
1720 enum { TZBUFSIZE
= 100 };
1721 char tz1buf
[TZBUFSIZE
];
1723 struct timespec gettime_buffer
;
1726 gettime
(&gettime_buffer
);
1727 now
= &gettime_buffer
;
1730 time_t Start
= now
->tv_sec
;
1731 int Start_ns
= now
->tv_nsec
;
1734 while
(c
= *p
, c_isspace
(c
))
1737 timezone_t tz
= tzdefault
;
1739 /* Store a local copy prior to first "goto". Without this, a prior use
1740 below of RELATIVE_TIME_0 on the RHS might translate to an assignment-
1741 to-temporary, which would trigger a -Wjump-misses-init warning. */
1742 const relative_time rel_time_0
= RELATIVE_TIME_0
;
1744 if
(strncmp
(p
, "TZ=\"", 4) == 0)
1746 char const *tzbase
= p
+ 4;
1750 for
(s
= tzbase
; *s
; s
++, tzsize
++)
1754 if
(! (*s
== '\\' ||
*s
== '"'))
1760 char *tz1string
= tz1buf
;
1762 if
(TZBUFSIZE
< tzsize
)
1764 tz1alloc
= malloc
(tzsize
);
1767 tz1string
= tz1alloc
;
1770 for
(s
= tzbase
; *s
!= '"'; s
++)
1771 *z
++ = *(s
+= *s
== '\\');
1773 tz1
= tzalloc
(tz1string
);
1777 tzstring
= tz1string
;
1780 while
(c
= *p
, c_isspace
(c
))
1788 if
(! localtime_rz
(tz
, &now
->tv_sec
, &tmp
))
1791 /* As documented, be careful to treat the empty string just like
1792 a date string of "0". Without this, an empty string would be
1793 declared invalid when parsed during a DST transition. */
1799 #ifdef GNULIB_PARSE_DATETIME2
1800 pc.parse_datetime_debug
= (flags
& PARSE_DATETIME_DEBUG
) != 0;
1802 if
(ckd_add
(&pc.year.value
, tmp.tm_year
, TM_YEAR_BASE
))
1804 if
(debugging
(&pc
))
1805 dbg_printf
(_
("error: initial year out of range\n"));
1809 pc.month
= tmp.tm_mon
+ 1;
1810 pc.day
= tmp.tm_mday
;
1811 pc.hour
= tmp.tm_hour
;
1812 pc.minutes
= tmp.tm_min
;
1813 pc.seconds
= (struct timespec
) { .tv_sec
= tmp.tm_sec
, .tv_nsec
= Start_ns
};
1814 tm.tm_isdst
= tmp.tm_isdst
;
1816 pc.meridian
= MER24
;
1817 pc.rel
= rel_time_0
;
1818 pc.timespec_seen
= false
;
1819 pc.rels_seen
= false
;
1823 pc.J_zones_seen
= 0;
1824 pc.local_zones_seen
= 0;
1827 pc.year_seen
= false
;
1828 pc.debug_dates_seen
= false
;
1829 pc.debug_days_seen
= false
;
1830 pc.debug_times_seen
= false
;
1831 pc.debug_local_zones_seen
= false
;
1832 pc.debug_zones_seen
= false
;
1833 pc.debug_year_seen
= false
;
1834 pc.debug_ordinal_day_seen
= false
;
1836 #if HAVE_STRUCT_TM_TM_ZONE
1837 pc.local_time_zone_table
[0].name
= tmp.tm_zone
;
1838 pc.local_time_zone_table
[0].type
= tLOCAL_ZONE
;
1839 pc.local_time_zone_table
[0].value
= tmp.tm_isdst
;
1840 pc.local_time_zone_table
[1].name
= NULL
;
1842 /* Probe the names used in the next three calendar quarters, looking
1843 for a tm_isdst different from the one we already have. */
1846 for
(quarter
= 1; quarter
<= 3; quarter
++)
1849 if
(ckd_add
(&probe
, Start
, quarter
* (90 * 24 * 60 * 60)))
1852 if
(localtime_rz
(tz
, &probe
, &probe_tm
) && probe_tm.tm_zone
1853 && probe_tm.tm_isdst
!= pc.local_time_zone_table
[0].value
)
1856 pc.local_time_zone_table
[1].name
= probe_tm.tm_zone
;
1857 pc.local_time_zone_table
[1].type
= tLOCAL_ZONE
;
1858 pc.local_time_zone_table
[1].value
= probe_tm.tm_isdst
;
1859 pc.local_time_zone_table
[2].name
= NULL
;
1868 # if !HAVE_DECL_TZNAME
1869 extern
char *tzname
[];
1872 for
(i
= 0; i
< 2; i
++)
1874 pc.local_time_zone_table
[i
].name
= tzname
[i
];
1875 pc.local_time_zone_table
[i
].type
= tLOCAL_ZONE
;
1876 pc.local_time_zone_table
[i
].value
= i
;
1878 pc.local_time_zone_table
[i
].name
= NULL
;
1881 pc.local_time_zone_table
[0].name
= NULL
;
1885 if
(pc.local_time_zone_table
[0].name
&& pc.local_time_zone_table
[1].name
1886 && ! strcmp
(pc.local_time_zone_table
[0].name
,
1887 pc.local_time_zone_table
[1].name
))
1889 /* This locale uses the same abbreviation for standard and
1890 daylight times. So if we see that abbreviation, we don't
1891 know whether it's daylight time. */
1892 pc.local_time_zone_table
[0].value
= -1;
1893 pc.local_time_zone_table
[1].name
= NULL
;
1896 if
(yyparse (&pc
) != 0)
1898 if
(debugging
(&pc
))
1899 dbg_printf
((input_sentinel
<= pc.input
1900 ? _
("error: parsing failed\n")
1901 : _
("error: parsing failed, stopped at '%s'\n")),
1907 /* Determine effective timezone source. */
1909 if
(debugging
(&pc
))
1911 dbg_printf
(_
("input timezone: "));
1913 if
(pc.timespec_seen
)
1914 fprintf
(stderr
, _
("'@timespec' - always UTC"));
1915 else if
(pc.zones_seen
)
1916 fprintf
(stderr
, _
("parsed date/time string"));
1919 if
(tz
!= tzdefault
)
1920 fprintf
(stderr
, _
("TZ=\"%s\" in date string"), tzstring
);
1921 else if
(STREQ
(tzstring
, "UTC0"))
1923 /* Special case: 'date -u' sets TZ="UTC0". */
1924 fprintf
(stderr
, _
("TZ=\"UTC0\" environment value or -u"));
1927 fprintf
(stderr
, _
("TZ=\"%s\" environment value"), tzstring
);
1930 fprintf
(stderr
, _
("system default"));
1932 /* Account for DST changes if tLOCAL_ZONE was seen.
1933 local timezone only changes DST and is relative to the
1935 if
(pc.local_zones_seen
&& !pc.zones_seen
&& 0 < pc.local_isdst
)
1936 fprintf
(stderr
, ", dst");
1939 fprintf
(stderr
, " (%s)", time_zone_str
(pc.time_zone
, time_zone_buf
));
1941 fputc
('\n', stderr
);
1944 if
(pc.timespec_seen
)
1945 *result
= pc.seconds
;
1948 if
(1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
1949 |
(pc.J_zones_seen
+ pc.local_zones_seen
+ pc.zones_seen
)))
1951 if
(debugging
(&pc
))
1953 if
(pc.times_seen
> 1)
1954 dbg_printf
("error: seen multiple time parts\n");
1955 if
(pc.dates_seen
> 1)
1956 dbg_printf
("error: seen multiple date parts\n");
1957 if
(pc.days_seen
> 1)
1958 dbg_printf
("error: seen multiple days parts\n");
1959 if
(pc.dsts_seen
> 1)
1960 dbg_printf
("error: seen multiple daylight-saving parts\n");
1961 if
((pc.J_zones_seen
+ pc.local_zones_seen
+ pc.zones_seen
) > 1)
1962 dbg_printf
("error: seen multiple time-zone parts\n");
1967 if
(! to_tm_year
(pc.year
, debugging
(&pc
), &tm.tm_year
)
1968 || ckd_add
(&tm.tm_mon
, pc.month
, -1)
1969 || ckd_add
(&tm.tm_mday
, pc.day
, 0))
1971 if
(debugging
(&pc
))
1972 dbg_printf
(_
("error: year, month, or day overflow\n"));
1975 if
(pc.times_seen ||
(pc.rels_seen
&& ! pc.dates_seen
&& ! pc.days_seen
))
1977 tm.tm_hour
= to_hour
(pc.hour
, pc.meridian
);
1980 char const *mrd
= (pc.meridian
== MERam ?
"am"
1981 : pc.meridian
== MERpm ?
"pm" : "");
1982 if
(debugging
(&pc
))
1983 dbg_printf
(_
("error: invalid hour %"PRIdMAX
"%s\n"),
1987 tm.tm_min
= pc.minutes
;
1988 tm.tm_sec
= pc.seconds.tv_sec
;
1989 if
(debugging
(&pc
))
1990 dbg_printf
((pc.times_seen
1991 ? _
("using specified time as starting value: '%s'\n")
1992 : _
("using current time as starting value: '%s'\n")),
1993 debug_strftime
(&tm
, dbg_tm
, sizeof dbg_tm
));
1997 tm.tm_hour
= tm.tm_min
= tm.tm_sec
= 0;
1998 pc.seconds.tv_nsec
= 0;
1999 if
(debugging
(&pc
))
2000 dbg_printf
("warning: using midnight as starting time: 00:00:00\n");
2003 /* Let mktime deduce tm_isdst if we have an absolute timestamp. */
2004 if
(pc.dates_seen | pc.days_seen | pc.times_seen
)
2007 /* But if the input explicitly specifies local time with or without
2008 DST, give mktime that information. */
2009 if
(pc.local_zones_seen
)
2010 tm.tm_isdst
= pc.local_isdst
;
2012 tm0.tm_sec
= tm.tm_sec
;
2013 tm0.tm_min
= tm.tm_min
;
2014 tm0.tm_hour
= tm.tm_hour
;
2015 tm0.tm_mday
= tm.tm_mday
;
2016 tm0.tm_mon
= tm.tm_mon
;
2017 tm0.tm_year
= tm.tm_year
;
2018 tm0.tm_isdst
= tm.tm_isdst
;
2021 Start
= mktime_z
(tz
, &tm
);
2023 if
(! mktime_ok
(&tm0
, &tm
))
2025 bool repaired
= false
;
2026 bool time_zone_seen
= pc.zones_seen
!= 0;
2029 /* Guard against falsely reporting errors near the time_t
2030 boundaries when parsing times in other time zones. For
2031 example, suppose the input string "1969-12-31 23:00:00 -0100",
2032 the current time zone is 8 hours ahead of UTC, and the min
2033 time_t value is 1970-01-01 00:00:00 UTC. Then the min
2034 localtime value is 1970-01-01 08:00:00, and mktime will
2035 therefore fail on 1969-12-31 23:00:00. To work around the
2036 problem, set the time zone to 1 hour behind UTC temporarily
2037 by setting TZ="XXX1:00" and try mktime again. */
2039 char tz2buf
[sizeof
"XXX" - 1 + TIME_ZONE_BUFSIZE
];
2040 tz2buf
[0] = tz2buf
[1] = tz2buf
[2] = 'X';
2041 time_zone_str
(pc.time_zone
, &tz2buf
[3]);
2042 timezone_t tz2
= tzalloc
(tz2buf
);
2045 if
(debugging
(&pc
))
2046 dbg_printf
(_
("error: tzalloc (\"%s\") failed\n"), tz2buf
);
2049 tm.tm_sec
= tm0.tm_sec
;
2050 tm.tm_min
= tm0.tm_min
;
2051 tm.tm_hour
= tm0.tm_hour
;
2052 tm.tm_mday
= tm0.tm_mday
;
2053 tm.tm_mon
= tm0.tm_mon
;
2054 tm.tm_year
= tm0.tm_year
;
2055 tm.tm_isdst
= tm0.tm_isdst
;
2057 Start
= mktime_z
(tz2
, &tm
);
2058 repaired
= mktime_ok
(&tm0
, &tm
);
2064 debug_mktime_not_ok
(&tm0
, &tm
, &pc
, time_zone_seen
);
2069 char dbg_ord
[DBGBUFSIZE
];
2071 if
(pc.days_seen
&& ! pc.dates_seen
)
2075 intmax_t day_ordinal
= (pc.day_ordinal
2076 - (0 < pc.day_ordinal
2077 && tm.tm_wday
!= pc.day_number
));
2078 if
(! (ckd_mul
(&dayincr
, day_ordinal
, 7)
2079 || ckd_add
(&dayincr
, (pc.day_number
- tm.tm_wday
+ 7) %
7,
2081 || ckd_add
(&tm.tm_mday
, dayincr
, tm.tm_mday
)))
2084 Start
= mktime_z
(tz
, &tm
);
2089 if
(debugging
(&pc
))
2090 dbg_printf
(_
("error: day '%s' "
2091 "(day ordinal=%"PRIdMAX
" number=%d) "
2092 "resulted in an invalid date: '%s'\n"),
2093 str_days
(&pc
, dbg_ord
, sizeof dbg_ord
),
2094 pc.day_ordinal
, pc.day_number
,
2095 debug_strfdatetime
(&tm
, &pc
, dbg_tm
,
2100 if
(debugging
(&pc
))
2101 dbg_printf
(_
("new start date: '%s' is '%s'\n"),
2102 str_days
(&pc
, dbg_ord
, sizeof dbg_ord
),
2103 debug_strfdatetime
(&tm
, &pc
, dbg_tm
, sizeof dbg_tm
));
2107 if
(debugging
(&pc
))
2109 if
(!pc.dates_seen
&& !pc.days_seen
)
2110 dbg_printf
(_
("using current date as starting value: '%s'\n"),
2111 debug_strfdate
(&tm
, dbg_tm
, sizeof dbg_tm
));
2113 if
(pc.days_seen
&& pc.dates_seen
)
2114 dbg_printf
(_
("warning: day (%s) ignored when explicit dates "
2116 str_days
(&pc
, dbg_ord
, sizeof dbg_ord
));
2118 dbg_printf
(_
("starting date/time: '%s'\n"),
2119 debug_strfdatetime
(&tm
, &pc
, dbg_tm
, sizeof dbg_tm
));
2122 /* Add relative date. */
2123 if
(pc.rel.year | pc.rel.month | pc.rel.day
)
2125 if
(debugging
(&pc
))
2127 if
((pc.rel.year
!= 0 || pc.rel.month
!= 0) && tm.tm_mday
!= 15)
2128 dbg_printf
(_
("warning: when adding relative months/years, "
2129 "it is recommended to specify the 15th of the "
2132 if
(pc.rel.day
!= 0 && tm.tm_hour
!= 12)
2133 dbg_printf
(_
("warning: when adding relative days, "
2134 "it is recommended to specify noon\n"));
2137 int year
, month
, day
;
2138 if
(ckd_add
(&year
, tm.tm_year
, pc.rel.year
)
2139 || ckd_add
(&month
, tm.tm_mon
, pc.rel.month
)
2140 || ckd_add
(&day
, tm.tm_mday
, pc.rel.day
))
2142 if
(debugging
(&pc
))
2143 dbg_printf
(_
("error: %s:%d\n"), __FILE__
, __LINE__
);
2149 tm.tm_hour
= tm0.tm_hour
;
2150 tm.tm_min
= tm0.tm_min
;
2151 tm.tm_sec
= tm0.tm_sec
;
2152 tm.tm_isdst
= tm0.tm_isdst
;
2154 Start
= mktime_z
(tz
, &tm
);
2157 if
(debugging
(&pc
))
2158 dbg_printf
(_
("error: adding relative date resulted "
2159 "in an invalid date: '%s'\n"),
2160 debug_strfdatetime
(&tm
, &pc
, dbg_tm
,
2165 if
(debugging
(&pc
))
2167 dbg_printf
(_
("after date adjustment "
2168 "(%+"PRIdMAX
" years, %+"PRIdMAX
" months, "
2169 "%+"PRIdMAX
" days),\n"),
2170 pc.rel.year
, pc.rel.month
, pc.rel.day
);
2171 dbg_printf
(_
(" new date/time = '%s'\n"),
2172 debug_strfdatetime
(&tm
, &pc
, dbg_tm
,
2175 /* Warn about crossing DST due to time adjustment.
2176 Example: https://bugs.gnu.org/8357
2177 env TZ=Europe/Helsinki \
2179 -d 'Mon Mar 28 00:36:07 2011 EEST 1 day ago'
2181 This case is different than DST changes due to time adjustment,
2182 i.e., "1 day ago" vs "24 hours ago" are calculated in different
2185 'tm0.tm_isdst' contains the DST of the input date,
2186 'tm.tm_isdst' is the normalized result after calling
2189 if
(tm0.tm_isdst
!= -1 && tm.tm_isdst
!= tm0.tm_isdst
)
2190 dbg_printf
(_
("warning: daylight saving time changed after "
2191 "date adjustment\n"));
2193 /* Warn if the user did not ask to adjust days but mday changed,
2195 user did not ask to adjust months/days but the month changed.
2197 Example for first case:
2198 2016-05-31 + 1 month => 2016-06-31 => 2016-07-01.
2199 User asked to adjust month, but the day changed from 31 to 01.
2201 Example for second case:
2202 2016-02-29 + 1 year => 2017-02-29 => 2017-03-01.
2203 User asked to adjust year, but the month changed from 02 to 03.
2206 && (tm.tm_mday
!= day
2207 ||
(pc.rel.month
== 0 && tm.tm_mon
!= month
)))
2209 dbg_printf
(_
("warning: month/year adjustment resulted in "
2210 "shifted dates:\n"));
2211 char tm_year_buf
[TM_YEAR_BUFSIZE
];
2212 dbg_printf
(_
(" adjusted Y M D: %s %02d %02d\n"),
2213 tm_year_str
(year
, tm_year_buf
), month
+ 1, day
);
2214 dbg_printf
(_
(" normalized Y M D: %s %02d %02d\n"),
2215 tm_year_str
(tm.tm_year
, tm_year_buf
),
2216 tm.tm_mon
+ 1, tm.tm_mday
);
2222 /* The only "output" of this if-block is an updated Start value,
2223 so this block must follow others that clobber Start. */
2226 bool overflow
= false
;
2227 #ifdef HAVE_TM_GMTOFF
2228 long int utcoff
= tm.tm_gmtoff
;
2232 int utcoff
= (gmtime_r
(&t
, &gmt
)
2233 ? tm_diff
(&tm
, &gmt
)
2234 : (overflow
= true
, 0));
2237 overflow |
= ckd_sub
(&delta
, pc.time_zone
, utcoff
);
2239 overflow |
= ckd_sub
(&t1
, Start
, delta
);
2242 if
(debugging
(&pc
))
2243 dbg_printf
(_
("error: timezone %d caused time_t overflow\n"),
2250 if
(debugging
(&pc
))
2252 intmax_t Starti
= Start
;
2253 dbg_printf
(_
("'%s' = %"PRIdMAX
" epoch-seconds\n"),
2254 debug_strfdatetime
(&tm
, &pc
, dbg_tm
, sizeof dbg_tm
),
2259 /* Add relative hours, minutes, and seconds. On hosts that support
2260 leap seconds, ignore the possibility of leap seconds; e.g.,
2261 "+ 10 minutes" adds 600 seconds, even if one of them is a
2262 leap second. Typically this is not what the user wants, but it's
2263 too hard to do it the other way, because the time zone indicator
2264 must be applied before relative times, and if mktime is applied
2265 again the time zone will be lost. */
2267 intmax_t orig_ns
= pc.seconds.tv_nsec
;
2268 intmax_t sum_ns
= orig_ns
+ pc.rel.ns
;
2269 int normalized_ns
= (sum_ns % BILLION
+ BILLION
) % BILLION
;
2270 int d4
= (sum_ns
- normalized_ns
) / BILLION
;
2271 intmax_t d1
, t1
, d2
, t2
, t3
;
2273 if
(ckd_mul
(&d1
, pc.rel.hour
, 60 * 60)
2274 || ckd_add
(&t1
, Start
, d1
)
2275 || ckd_mul
(&d2
, pc.rel.minutes
, 60)
2276 || ckd_add
(&t2
, t1
, d2
)
2277 || ckd_add
(&t3
, t2
, pc.rel.seconds
)
2278 || ckd_add
(&t4
, t3
, d4
))
2280 if
(debugging
(&pc
))
2281 dbg_printf
(_
("error: adding relative time caused an "
2286 result
->tv_sec
= t4
;
2287 result
->tv_nsec
= normalized_ns
;
2290 && (pc.rel.hour | pc.rel.minutes | pc.rel.seconds | pc.rel.ns
))
2292 dbg_printf
(_
("after time adjustment (%+"PRIdMAX
" hours, "
2293 "%+"PRIdMAX
" minutes, "
2294 "%+"PRIdMAX
" seconds, %+d ns),\n"),
2295 pc.rel.hour
, pc.rel.minutes
, pc.rel.seconds
,
2298 dbg_printf
(_
(" new time = %"PRIdMAX
" epoch-seconds\n"), t4i
);
2300 /* Warn about crossing DST due to time adjustment.
2301 Example: https://bugs.gnu.org/8357
2302 env TZ=Europe/Helsinki \
2304 -d 'Mon Mar 28 00:36:07 2011 EEST 24 hours ago'
2306 This case is different than DST changes due to days adjustment,
2307 i.e., "1 day ago" vs "24 hours ago" are calculated in different
2310 'tm.tm_isdst' contains the date after date adjustment. */
2312 if
(tm.tm_isdst
!= -1 && localtime_rz
(tz
, &result
->tv_sec
, &lmt
)
2313 && tm.tm_isdst
!= lmt.tm_isdst
)
2314 dbg_printf
(_
("warning: daylight saving time changed after "
2315 "time adjustment\n"));
2320 if
(debugging
(&pc
))
2322 /* Special case: using 'date -u' simply set TZ=UTC0 */
2324 dbg_printf
(_
("timezone: system default\n"));
2325 else if
(STREQ
(tzstring
, "UTC0"))
2326 dbg_printf
(_
("timezone: Universal Time\n"));
2328 dbg_printf
(_
("timezone: TZ=\"%s\" environment value\n"), tzstring
);
2330 intmax_t sec
= result
->tv_sec
;
2331 int nsec
= result
->tv_nsec
;
2332 dbg_printf
(_
("final: %"PRIdMAX
".%09d (epoch-seconds)\n"),
2336 bool got_utc
= !!gmtime_r
(&result
->tv_sec
, &gmt
);
2338 dbg_printf
(_
("final: %s (UTC)\n"),
2339 debug_strfdatetime
(&gmt
, NULL
,
2340 dbg_tm
, sizeof dbg_tm
));
2341 if
(localtime_rz
(tz
, &result
->tv_sec
, &lmt
))
2343 #ifdef HAVE_TM_GMTOFF
2344 bool got_utcoff
= true
;
2345 long int utcoff
= lmt.tm_gmtoff
;
2347 bool got_utcoff
= got_utc
;
2350 utcoff
= tm_diff
(&lmt
, &gmt
);
2353 dbg_printf
(_
("final: %s (UTC%s)\n"),
2354 debug_strfdatetime
(&lmt
, NULL
, dbg_tm
, sizeof dbg_tm
),
2355 time_zone_str
(utcoff
, time_zone_buf
));
2357 dbg_printf
(_
("final: %s (unknown time zone offset)\n"),
2358 debug_strfdatetime
(&lmt
, NULL
, dbg_tm
, sizeof dbg_tm
));
2365 if
(tz
!= tzdefault
)
2371 #ifdef GNULIB_PARSE_DATETIME2
2372 /* Parse a date/time string, storing the resulting time value into *RESULT.
2373 The string itself is pointed to by P. Return true if successful.
2374 P can be an incomplete or relative time specification; if so, use
2375 *NOW as the basis for the returned time. Default to timezone
2376 TZDEFAULT, which corresponds to tzalloc (TZSTRING). */
2378 parse_datetime2
(struct timespec
*result
, char const *p
,
2379 struct timespec
const *now
, unsigned int flags
,
2380 timezone_t tzdefault
, char const *tzstring
)
2382 return parse_datetime_body
(result
, p
, now
, flags
, tzdefault
, tzstring
);
2387 /* The plain interface: run with debug=false and the default timezone. */
2389 parse_datetime
(struct timespec
*result
, char const *p
,
2390 struct timespec
const *now
)
2392 char const *tzstring
= getenv
("TZ");
2393 timezone_t tz
= tzalloc
(tzstring
);
2396 bool ok
= parse_datetime_body
(result
, p
, now
, 0, tz
, tzstring
);
2404 main
(int ac
, char **av
)
2408 printf
("Enter date, or blank line to exit.\n\t> ");
2411 buff
[BUFSIZ
- 1] = '\0';
2412 while
(fgets
(buff
, BUFSIZ
- 1, stdin
) && buff
[0])
2415 struct tm
const *tm
;
2416 if
(! parse_datetime
(&d
, buff
, NULL
))
2417 printf
("Bad format - couldn't convert.\n");
2418 else if
(! (tm
= localtime
(&d.tv_sec
)))
2420 intmax_t sec
= d.tv_sec
;
2421 printf
("localtime (%"PRIdMAX
") failed\n", sec
);
2426 char tm_year_buf
[TM_YEAR_BUFSIZE
];
2427 printf
("%s-%02d-%02d %02d:%02d:%02d.%09d\n",
2428 tm_year_str
(tm
->tm_year
, tm_year_buf
),
2429 tm
->tm_mon
+ 1, tm
->tm_mday
,
2430 tm
->tm_hour
, tm
->tm_min
, tm
->tm_sec
, ns
);