2 /* Parse a string into an internal timestamp.
4 Copyright (C) 1999-2000, 2002-2018 Free Software Foundation, Inc.
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <https://www.gnu.org/licenses/>. */
19 /* Originally written by Steven M. Bellovin <smb@research.att.com> while
20 at the University of North Carolina at Chapel Hill. Later tweaked by
21 a couple of people on Usenet. Completely overhauled by Rich $alz
22 <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
24 Modified by Assaf Gordon <assafgordon@gmail.com> in 2016 to add
27 Modified by Paul Eggert <eggert@twinsun.com> in 1999 to do the
28 right thing about local DST. Also modified by Paul Eggert
29 <eggert@cs.ucla.edu> in 2004 to support nanosecond-resolution
30 timestamps, in 2004 to support TZ strings in dates, and in 2017 to
31 check for integer overflow and to support longer-than-'long'
32 'time_t' and 'tv_nsec'. */
36 #include "parse-datetime.h"
43 /* There's no need to extend the stack, so there's no need to involve
45 #define YYSTACK_USE_ALLOCA 0
47 /* Tell Bison how much stack space is needed. 20 should be plenty for
48 this grammar, which is not right recursive. Beware setting it too
49 high, since that might cause problems on machines whose
50 implementations have lame stack-overflow checking. */
52 #define YYINITDEPTH YYMAXDEPTH
54 /* Since the code of parse-datetime.y is not included in the Emacs executable
55 itself, there is no need to #define static in this file. Even if
56 the code were included in the Emacs executable, it probably
57 wouldn't do any harm to #undef it here; this will only cause
58 problems if we try to write to a static variable, which I don't
59 think this code needs to do. */
74 #define _(str) gettext (str)
76 /* Bison's skeleton tests _STDLIB_H, while some stdlib.h headers
77 use _STDLIB_H_ as witness. Map the latter to the one bison uses. */
78 /* FIXME: this is temporary. Remove when we have a mechanism to ensure
79 that the version we're using is fixed, too. */
85 /* The __attribute__ feature is available in gcc versions 2.5 and later.
86 The __-protected variants of the attributes 'format' and 'printf' are
87 accepted by gcc versions 2.6.4 (effectively 2.7) and later.
88 Enable _GL_ATTRIBUTE_FORMAT only if these are supported too, because
89 gnulib and libintl do '#define printf __printf__' when they override
90 the 'printf' function. */
91 #if 2 < __GNUC__ + (7 <= __GNUC_MINOR__)
92 # define _GL_ATTRIBUTE_FORMAT(spec) __attribute__ ((__format__ spec))
94 # define _GL_ATTRIBUTE_FORMAT(spec) /* empty */
97 /* Shift A right by B bits portably, by dividing A by 2**B and
98 truncating towards minus infinity. A and B should be free of side
99 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
100 INT_BITS is the number of useful bits in an int. GNU code can
101 assume that INT_BITS is at least 32.
103 ISO C99 says that A >> B is implementation-defined if A < 0. Some
104 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
105 right in the usual way when A < 0, so SHR falls back on division if
106 ordinary A >> B doesn't seem to be the usual signed shift. */
110 : (a
) / (1 << (b
)) - ((a
) %
(1 << (b
)) < 0))
112 #define HOUR(x) (60 * 60 * (x))
114 #define STREQ(a, b) (strcmp (a, b) == 0)
116 /* Verify that time_t is an integer as POSIX requires, and that every
117 time_t value fits in intmax_t. Please file a bug report if these
118 assumptions are false on your platform. */
119 verify
(TYPE_IS_INTEGER
(time_t));
120 verify
(!TYPE_SIGNED
(time_t) || INTMAX_MIN
<= TYPE_MINIMUM
(time_t));
121 verify
(TYPE_MAXIMUM
(time_t) <= INTMAX_MAX
);
123 /* True if N is out of range for time_t. */
125 time_overflow
(intmax_t n
)
127 return
! ((TYPE_SIGNED
(time_t) ? TYPE_MINIMUM
(time_t) <= n
: 0 <= n
)
128 && n
<= TYPE_MAXIMUM
(time_t));
131 /* Convert a possibly-signed character to an unsigned character. This is
132 a bit safer than casting to unsigned char, since it catches some type
133 errors that the cast doesn't. */
134 static unsigned char to_uchar
(char ch
) { return ch
; }
136 static void _GL_ATTRIBUTE_FORMAT
((__printf__
, 1, 2))
137 dbg_printf
(char const *msg
, ...
)
140 /* TODO: use gnulib's 'program_name' instead? */
141 fputs
("date: ", stderr
);
143 va_start
(args
, msg
);
144 vfprintf
(stderr
, msg
, args
);
149 /* An integer value, and the number of digits in its textual
158 /* An entry in the lexical lookup table. */
166 /* Meridian: am, pm, or 24-hour style. */
167 enum { MERam
, MERpm
, MER24
};
169 /* A reasonable upper bound for the buffer used in debug output. */
170 enum { DBGBUFSIZE
= 100 };
172 enum { BILLION
= 1000000000, LOG10_BILLION
= 9 };
174 /* Relative times. */
177 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
187 #if HAVE_COMPOUND_LITERALS
188 # define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
190 static relative_time
const RELATIVE_TIME_0
;
193 /* Information passed to and from the parser. */
196 /* The input string remaining to be parsed. */
199 /* N, if this is the Nth Tuesday. */
200 intmax_t day_ordinal
;
202 /* Day of week; Sunday is 0. */
205 /* tm_isdst flag for the local zone. */
208 /* Time zone, in seconds east of UT. */
211 /* Style used for time. */
214 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
220 struct timespec seconds
; /* includes nanoseconds */
222 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
225 /* Presence or counts of nonterminals of various flavors parsed so far. */
228 ptrdiff_t dates_seen
;
230 ptrdiff_t local_zones_seen
;
232 ptrdiff_t times_seen
;
233 ptrdiff_t zones_seen
;
236 /* Print debugging output to stderr. */
237 bool parse_datetime_debug
;
239 /* Which of the 'seen' parts have been printed when debugging. */
240 bool debug_dates_seen
;
241 bool debug_days_seen
;
242 bool debug_local_zones_seen
;
243 bool debug_times_seen
;
244 bool debug_zones_seen
;
245 bool debug_year_seen
;
247 /* The user specified explicit ordinal day value. */
248 bool debug_ordinal_day_seen
;
250 /* Table of local time zone abbreviations, terminated by a null entry. */
251 table local_time_zone_table
[3];
255 static int yylex (union YYSTYPE *, parser_control
*);
256 static int yyerror (parser_control
const *, char const *);
257 static bool time_zone_hhmm
(parser_control
*, textint
, intmax_t);
259 /* Extract into *PC any date and time info from a string of digits
260 of the form e.g., YYYYMMDD, YYMMDD, HHMM, HH (and sometimes YYY,
263 digits_to_date_time
(parser_control
*pc
, textint text_int
)
265 if
(pc
->dates_seen
&& ! pc
->year.digits
266 && ! pc
->rels_seen
&& (pc
->times_seen ||
2 < text_int.digits
))
268 pc
->year_seen
= true
;
273 if
(4 < text_int.digits
)
276 pc
->day
= text_int.value %
100;
277 pc
->month
= (text_int.value
/ 100) %
100;
278 pc
->year.value
= text_int.value
/ 10000;
279 pc
->year.digits
= text_int.digits
- 4;
284 if
(text_int.digits
<= 2)
286 pc
->hour
= text_int.value
;
291 pc
->hour
= text_int.value
/ 100;
292 pc
->minutes
= text_int.value %
100;
294 pc
->seconds.tv_sec
= 0;
295 pc
->seconds.tv_nsec
= 0;
296 pc
->meridian
= MER24
;
301 /* Increment PC->rel by FACTOR * REL (FACTOR is 1 or -1). Return true
302 if successful, false if an overflow occurred. */
304 apply_relative_time
(parser_control
*pc
, relative_time rel
, int factor
)
307 ?
(INT_SUBTRACT_WRAPV
(pc
->rel.ns
, rel.ns
, &pc
->rel.ns
)
308 | INT_SUBTRACT_WRAPV
(pc
->rel.seconds
, rel.seconds
, &pc
->rel.seconds
)
309 | INT_SUBTRACT_WRAPV
(pc
->rel.minutes
, rel.minutes
, &pc
->rel.minutes
)
310 | INT_SUBTRACT_WRAPV
(pc
->rel.hour
, rel.hour
, &pc
->rel.hour
)
311 | INT_SUBTRACT_WRAPV
(pc
->rel.day
, rel.day
, &pc
->rel.day
)
312 | INT_SUBTRACT_WRAPV
(pc
->rel.month
, rel.month
, &pc
->rel.month
)
313 | INT_SUBTRACT_WRAPV
(pc
->rel.year
, rel.year
, &pc
->rel.year
))
314 : (INT_ADD_WRAPV
(pc
->rel.ns
, rel.ns
, &pc
->rel.ns
)
315 | INT_ADD_WRAPV
(pc
->rel.seconds
, rel.seconds
, &pc
->rel.seconds
)
316 | INT_ADD_WRAPV
(pc
->rel.minutes
, rel.minutes
, &pc
->rel.minutes
)
317 | INT_ADD_WRAPV
(pc
->rel.hour
, rel.hour
, &pc
->rel.hour
)
318 | INT_ADD_WRAPV
(pc
->rel.day
, rel.day
, &pc
->rel.day
)
319 | INT_ADD_WRAPV
(pc
->rel.month
, rel.month
, &pc
->rel.month
)
320 | INT_ADD_WRAPV
(pc
->rel.year
, rel.year
, &pc
->rel.year
)))
322 pc
->rels_seen
= true
;
326 /* Set PC-> hour, minutes, seconds and nanoseconds members from arguments. */
328 set_hhmmss
(parser_control
*pc
, intmax_t hour
, intmax_t minutes
,
329 time_t sec
, int nsec
)
332 pc
->minutes
= minutes
;
333 pc
->seconds.tv_sec
= sec
;
334 pc
->seconds.tv_nsec
= nsec
;
337 /* Return a textual representation of the day ordinal/number values
338 in the parser_control struct (e.g., "last wed", "this tues", "thu"). */
340 str_days
(parser_control
*pc
, char *buffer
, int n
)
342 /* TODO: use relative_time_table for reverse lookup. */
343 static char const ordinal_values
[][11] = {
347 "(SECOND)", /* SECOND is commented out in relative_time_table. */
360 static char const days_values
[][4] = {
372 /* Don't add an ordinal prefix if the user didn't specify it
373 (e.g., "this wed" vs "wed"). */
374 if
(pc
->debug_ordinal_day_seen
)
376 /* Use word description if possible (e.g., -1 = last, 3 = third). */
377 len
= (-1 <= pc
->day_ordinal
&& pc
->day_ordinal
<= 12
378 ? snprintf
(buffer
, n
, "%s", ordinal_values
[pc
->day_ordinal
+ 1])
379 : snprintf
(buffer
, n
, "%"PRIdMAX
, pc
->day_ordinal
));
387 /* Add the day name */
388 if
(0 <= pc
->day_number
&& pc
->day_number
<= 6 && 0 <= len
&& len
< n
)
389 snprintf
(buffer
+ len
, n
- len
, &" %s"[len
== 0],
390 days_values
[pc
->day_number
]);
393 /* invalid day_number value - should never happen */
398 /* Convert a time zone to its string representation. */
400 enum { TIME_ZONE_BUFSIZE
= INT_STRLEN_BOUND
(intmax_t) + sizeof
":MM:SS" } ;
403 time_zone_str
(int time_zone
, char time_zone_buf
[TIME_ZONE_BUFSIZE
])
405 char *p
= time_zone_buf
;
406 char sign
= time_zone
< 0 ?
'-' : '+';
407 int hour
= abs
(time_zone
/ (60 * 60));
408 p
+= sprintf
(time_zone_buf
, "%c%02d", sign
, hour
);
409 int offset_from_hour
= abs
(time_zone %
(60 * 60));
410 if
(offset_from_hour
!= 0)
412 int mm
= offset_from_hour
/ 60;
413 int ss
= offset_from_hour %
60;
415 *p
++ = '0' + mm
/ 10;
416 *p
++ = '0' + mm %
10;
420 *p
++ = '0' + ss
/ 10;
421 *p
++ = '0' + ss %
10;
425 return time_zone_buf
;
428 /* debugging: print the current time in the parser_control structure.
429 The parser will increment "*_seen" members for those which were parsed.
430 This function will print only newly seen parts. */
432 debug_print_current_time
(char const *item
, parser_control
*pc
)
436 if
(!pc
->parse_datetime_debug
)
439 /* no newline, more items printed below */
440 dbg_printf
(_
("parsed %s part: "), item
);
442 if
(pc
->dates_seen
&& !pc
->debug_dates_seen
)
444 /*TODO: use pc->year.negative? */
445 fprintf
(stderr
, "(Y-M-D) %04"PRIdMAX
"-%02"PRIdMAX
"-%02"PRIdMAX
,
446 pc
->year.value
, pc
->month
, pc
->day
);
447 pc
->debug_dates_seen
= true
;
451 if
(pc
->year_seen
!= pc
->debug_year_seen
)
455 fprintf
(stderr
, _
("year: %04"PRIdMAX
), pc
->year.value
);
457 pc
->debug_year_seen
= pc
->year_seen
;
461 if
(pc
->times_seen
&& !pc
->debug_times_seen
)
463 intmax_t sec
= pc
->seconds.tv_sec
;
464 fprintf
(stderr
, &" %02"PRIdMAX
":%02"PRIdMAX
":%02"PRIdMAX
[!space
],
465 pc
->hour
, pc
->minutes
, sec
);
466 if
(pc
->seconds.tv_nsec
!= 0)
468 int nsec
= pc
->seconds.tv_nsec
;
469 fprintf
(stderr
, ".%09d", nsec
);
471 if
(pc
->meridian
== MERpm
)
472 fputs
("pm", stderr
);
474 pc
->debug_times_seen
= true
;
478 if
(pc
->days_seen
&& !pc
->debug_days_seen
)
482 char tmp
[DBGBUFSIZE
];
483 fprintf
(stderr
, _
("%s (day ordinal=%"PRIdMAX
" number=%d)"),
484 str_days
(pc
, tmp
, sizeof tmp
),
485 pc
->day_ordinal
, pc
->day_number
);
486 pc
->debug_days_seen
= true
;
490 /* local zone strings only change the DST settings,
491 not the timezone value. If seen, inform about the DST. */
492 if
(pc
->local_zones_seen
&& !pc
->debug_local_zones_seen
)
494 fprintf
(stderr
, &" isdst=%d%s"[!space
],
495 pc
->local_isdst
, pc
->dsts_seen ?
" DST" : "");
496 pc
->debug_local_zones_seen
= true
;
500 if
(pc
->zones_seen
&& !pc
->debug_zones_seen
)
502 char time_zone_buf
[TIME_ZONE_BUFSIZE
];
503 fprintf
(stderr
, &" UTC%s"[!space
],
504 time_zone_str
(pc
->time_zone
, time_zone_buf
));
505 pc
->debug_zones_seen
= true
;
509 if
(pc
->timespec_seen
)
511 intmax_t sec
= pc
->seconds.tv_sec
;
514 fprintf
(stderr
, _
("number of seconds: %"PRIdMAX
), sec
);
517 fputc
('\n', stderr
);
520 /* Debugging: print the current relative values. */
523 print_rel_part
(bool space
, intmax_t val
, char const *name
)
527 fprintf
(stderr
, &" %+"PRIdMAX
" %s"[!space
], val
, name
);
532 debug_print_relative_time
(char const *item
, parser_control
const *pc
)
536 if
(!pc
->parse_datetime_debug
)
539 /* no newline, more items printed below */
540 dbg_printf
(_
("parsed %s part: "), item
);
542 if
(pc
->rel.year
== 0 && pc
->rel.month
== 0 && pc
->rel.day
== 0
543 && pc
->rel.hour
== 0 && pc
->rel.minutes
== 0 && pc
->rel.seconds
== 0
546 /* Special case: relative time of this/today/now */
547 fputs
(_
("today/this/now\n"), stderr
);
551 space
= print_rel_part
(space
, pc
->rel.year
, "year(s)");
552 space
= print_rel_part
(space
, pc
->rel.month
, "month(s)");
553 space
= print_rel_part
(space
, pc
->rel.day
, "day(s)");
554 space
= print_rel_part
(space
, pc
->rel.hour
, "hour(s)");
555 space
= print_rel_part
(space
, pc
->rel.minutes
, "minutes");
556 space
= print_rel_part
(space
, pc
->rel.seconds
, "seconds");
557 print_rel_part
(space
, pc
->rel.ns
, "nanoseconds");
559 fputc
('\n', stderr
);
566 /* We want a reentrant parser, even if the TZ manipulation and the calls to
567 localtime and gmtime are not reentrant. */
569 %parse
-param
{ parser_control
*pc
}
570 %lex
-param
{ parser_control
*pc
}
572 /* This grammar has 31 shift/reduce conflicts. */
579 struct timespec timespec
;
586 %token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
587 %token
<intval
> tDAY_UNIT tDAY_SHIFT
589 %token
<intval
> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
590 %token
<intval
> tMONTH tORDINAL tZONE
592 %token
<textintval
> tSNUMBER tUNUMBER
593 %token
<timespec
> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
595 %type
<intval
> o_colon_minutes
596 %type
<timespec
> seconds signed_seconds unsigned_seconds
598 %type
<rel
> relunit relunit_snumber dayshift
611 pc
->timespec_seen
= true
;
612 debug_print_current_time
(_
("number of seconds"), pc
);
624 pc
->times_seen
++; pc
->dates_seen
++;
625 debug_print_current_time
(_
("datetime"), pc
);
630 debug_print_current_time
(_
("time"), pc
);
634 pc
->local_zones_seen
++;
635 debug_print_current_time
(_
("local_zone"), pc
);
640 debug_print_current_time
(_
("zone"), pc
);
645 debug_print_current_time
(_
("date"), pc
);
650 debug_print_current_time
(_
("day"), pc
);
654 debug_print_relative_time
(_
("relative"), pc
);
658 debug_print_current_time
(_
("number"), pc
);
662 debug_print_relative_time
(_
("hybrid"), pc
);
671 iso_8601_date
'T' iso_8601_time
677 set_hhmmss
(pc
, $1.value
, 0, 0, 0);
680 | tUNUMBER
':' tUNUMBER tMERIDIAN
682 set_hhmmss
(pc
, $1.value
, $3.value
, 0, 0);
685 | tUNUMBER
':' tUNUMBER
':' unsigned_seconds tMERIDIAN
687 set_hhmmss
(pc
, $1.value
, $3.value
, $5.tv_sec
, $5.tv_nsec
);
696 set_hhmmss
(pc
, $1.value
, 0, 0, 0);
697 pc
->meridian
= MER24
;
699 | tUNUMBER
':' tUNUMBER o_zone_offset
701 set_hhmmss
(pc
, $1.value
, $3.value
, 0, 0);
702 pc
->meridian
= MER24
;
704 | tUNUMBER
':' tUNUMBER
':' unsigned_seconds o_zone_offset
706 set_hhmmss
(pc
, $1.value
, $3.value
, $5.tv_sec
, $5.tv_nsec
);
707 pc
->meridian
= MER24
;
717 tSNUMBER o_colon_minutes
720 if
(! time_zone_hhmm
(pc
, $1, $2)) YYABORT;
724 /* Local zone strings affect only the DST setting, and take effect
725 only if the current TZ setting is relevant.
728 'EEST' is parsed as tLOCAL_ZONE, as it relates to the effective TZ:
729 TZ='Europe/Helsinki' date -d '2016-06-30 EEST'
732 'EEST' is parsed as tDAYZONE:
733 TZ='Asia/Tokyo' date -d '2016-06-30 EEST'
735 This is implemented by probing the next three calendar quarters
736 of the effective timezone and looking for DST changes -
737 if found, the timezone name (EEST) is inserted into
738 the lexical lookup table with type tLOCAL_ZONE.
739 (Search for 'quarter' comment in 'parse_datetime2'.)
743 { pc
->local_isdst
= $1; }
751 /* Note 'T' is a special case, as it is used as the separator in ISO
752 8601 date and time of day representation. */
755 { pc
->time_zone
= $1; }
757 { pc
->time_zone
= HOUR
(7); }
758 | tZONE relunit_snumber
759 { pc
->time_zone
= $1;
760 if
(! apply_relative_time
(pc
, $2, 1)) YYABORT;
761 debug_print_relative_time
(_
("relative"), pc
);
763 |
'T' relunit_snumber
764 { pc
->time_zone
= HOUR
(7);
765 if
(! apply_relative_time
(pc
, $2, 1)) YYABORT;
766 debug_print_relative_time
(_
("relative"), pc
);
768 | tZONE tSNUMBER o_colon_minutes
769 { if
(! time_zone_hhmm
(pc
, $2, $3)) YYABORT;
770 if
(INT_ADD_WRAPV
(pc
->time_zone
, $1, &pc
->time_zone
)) YYABORT; }
772 { pc
->time_zone
= $1 + 60 * 60; }
774 { pc
->time_zone
= $1 + 60 * 60; }
790 pc
->day_ordinal
= $1;
792 pc
->debug_ordinal_day_seen
= true
;
796 pc
->day_ordinal
= $1.value
;
798 pc
->debug_ordinal_day_seen
= true
;
803 tUNUMBER
'/' tUNUMBER
805 pc
->month
= $1.value
;
808 | tUNUMBER
'/' tUNUMBER
'/' tUNUMBER
810 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
811 otherwise as MM/DD/YY.
812 The goal in recognizing YYYY/MM/DD is solely to support legacy
813 machine-generated dates like those in an RCS log listing. If
814 you want portability, use the ISO 8601 format. */
817 if
(pc
->parse_datetime_debug
)
819 intmax_t digits
= $1.digits
;
820 dbg_printf
(_
("warning: value %"PRIdMAX
" has %"PRIdMAX
" digits. "
821 "Assuming YYYY/MM/DD\n"),
826 pc
->month
= $3.value
;
831 if
(pc
->parse_datetime_debug
)
832 dbg_printf
(_
("warning: value %"PRIdMAX
" has less than 4 digits. "
833 "Assuming MM/DD/YY[YY]\n"),
836 pc
->month
= $1.value
;
841 | tUNUMBER tMONTH tSNUMBER
843 /* E.g., 17-JUN-1992. */
846 if
(INT_SUBTRACT_WRAPV
(0, $3.value
, &pc
->year.value
)) YYABORT;
847 pc
->year.digits
= $3.digits
;
849 | tMONTH tSNUMBER tSNUMBER
851 /* E.g., JUN-17-1992. */
853 if
(INT_SUBTRACT_WRAPV
(0, $2.value
, &pc
->day
)) YYABORT;
854 if
(INT_SUBTRACT_WRAPV
(0, $3.value
, &pc
->year.value
)) YYABORT;
855 pc
->year.digits
= $3.digits
;
862 | tMONTH tUNUMBER
',' tUNUMBER
873 | tUNUMBER tMONTH tUNUMBER
883 tUNUMBER tSNUMBER tSNUMBER
885 /* ISO 8601 format. YYYY-MM-DD. */
887 if
(INT_SUBTRACT_WRAPV
(0, $2.value
, &pc
->month
)) YYABORT;
888 if
(INT_SUBTRACT_WRAPV
(0, $3.value
, &pc
->day
)) YYABORT;
894 { if
(! apply_relative_time
(pc
, $1, $2)) YYABORT; }
896 { if
(! apply_relative_time
(pc
, $1, 1)) YYABORT; }
898 { if
(! apply_relative_time
(pc
, $1, 1)) YYABORT; }
903 { $$
= RELATIVE_TIME_0
; $$.year
= $1; }
904 | tUNUMBER tYEAR_UNIT
905 { $$
= RELATIVE_TIME_0
; $$.year
= $1.value
; }
907 { $$
= RELATIVE_TIME_0
; $$.year
= 1; }
908 | tORDINAL tMONTH_UNIT
909 { $$
= RELATIVE_TIME_0
; $$.month
= $1; }
910 | tUNUMBER tMONTH_UNIT
911 { $$
= RELATIVE_TIME_0
; $$.month
= $1.value
; }
913 { $$
= RELATIVE_TIME_0
; $$.month
= 1; }
915 { $$
= RELATIVE_TIME_0
;
916 if
(INT_MULTIPLY_WRAPV
($1, $2, &$$.day
)) YYABORT; }
918 { $$
= RELATIVE_TIME_0
;
919 if
(INT_MULTIPLY_WRAPV
($1.value
, $2, &$$.day
)) YYABORT; }
921 { $$
= RELATIVE_TIME_0
; $$.day
= $1; }
922 | tORDINAL tHOUR_UNIT
923 { $$
= RELATIVE_TIME_0
; $$.hour
= $1; }
924 | tUNUMBER tHOUR_UNIT
925 { $$
= RELATIVE_TIME_0
; $$.hour
= $1.value
; }
927 { $$
= RELATIVE_TIME_0
; $$.hour
= 1; }
928 | tORDINAL tMINUTE_UNIT
929 { $$
= RELATIVE_TIME_0
; $$.minutes
= $1; }
930 | tUNUMBER tMINUTE_UNIT
931 { $$
= RELATIVE_TIME_0
; $$.minutes
= $1.value
; }
933 { $$
= RELATIVE_TIME_0
; $$.minutes
= 1; }
935 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1; }
937 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1.value
; }
938 | tSDECIMAL_NUMBER tSEC_UNIT
939 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1.tv_sec
; $$.ns
= $1.tv_nsec
; }
940 | tUDECIMAL_NUMBER tSEC_UNIT
941 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1.tv_sec
; $$.ns
= $1.tv_nsec
; }
943 { $$
= RELATIVE_TIME_0
; $$.seconds
= 1; }
949 { $$
= RELATIVE_TIME_0
; $$.year
= $1.value
; }
950 | tSNUMBER tMONTH_UNIT
951 { $$
= RELATIVE_TIME_0
; $$.month
= $1.value
; }
953 { $$
= RELATIVE_TIME_0
;
954 if
(INT_MULTIPLY_WRAPV
($1.value
, $2, &$$.day
)) YYABORT; }
955 | tSNUMBER tHOUR_UNIT
956 { $$
= RELATIVE_TIME_0
; $$.hour
= $1.value
; }
957 | tSNUMBER tMINUTE_UNIT
958 { $$
= RELATIVE_TIME_0
; $$.minutes
= $1.value
; }
960 { $$
= RELATIVE_TIME_0
; $$.seconds
= $1.value
; }
965 { $$
= RELATIVE_TIME_0
; $$.day
= $1; }
968 seconds: signed_seconds | unsigned_seconds
;
973 { if
(time_overflow
($1.value
)) YYABORT;
974 $$.tv_sec
= $1.value
; $$.tv_nsec
= 0; }
980 { if
(time_overflow
($1.value
)) YYABORT;
981 $$.tv_sec
= $1.value
; $$.tv_nsec
= 0; }
986 { digits_to_date_time
(pc
, $1); }
990 tUNUMBER relunit_snumber
992 /* Hybrid all-digit and relative offset, so that we accept e.g.,
993 "YYYYMMDD +N days" as well as "YYYYMMDD N days". */
994 digits_to_date_time
(pc
, $1);
995 if
(! apply_relative_time
(pc
, $2, 1)) YYABORT;
1008 static table
const meridian_table
[] =
1010 { "AM", tMERIDIAN
, MERam
},
1011 { "A.M.", tMERIDIAN
, MERam
},
1012 { "PM", tMERIDIAN
, MERpm
},
1013 { "P.M.", tMERIDIAN
, MERpm
},
1017 static table
const dst_table
[] =
1022 static table
const month_and_day_table
[] =
1024 { "JANUARY", tMONTH
, 1 },
1025 { "FEBRUARY", tMONTH
, 2 },
1026 { "MARCH", tMONTH
, 3 },
1027 { "APRIL", tMONTH
, 4 },
1028 { "MAY", tMONTH
, 5 },
1029 { "JUNE", tMONTH
, 6 },
1030 { "JULY", tMONTH
, 7 },
1031 { "AUGUST", tMONTH
, 8 },
1032 { "SEPTEMBER",tMONTH
, 9 },
1033 { "SEPT", tMONTH
, 9 },
1034 { "OCTOBER", tMONTH
, 10 },
1035 { "NOVEMBER", tMONTH
, 11 },
1036 { "DECEMBER", tMONTH
, 12 },
1037 { "SUNDAY", tDAY
, 0 },
1038 { "MONDAY", tDAY
, 1 },
1039 { "TUESDAY", tDAY
, 2 },
1040 { "TUES", tDAY
, 2 },
1041 { "WEDNESDAY",tDAY
, 3 },
1042 { "WEDNES", tDAY
, 3 },
1043 { "THURSDAY", tDAY
, 4 },
1044 { "THUR", tDAY
, 4 },
1045 { "THURS", tDAY
, 4 },
1046 { "FRIDAY", tDAY
, 5 },
1047 { "SATURDAY", tDAY
, 6 },
1051 static table
const time_units_table
[] =
1053 { "YEAR", tYEAR_UNIT
, 1 },
1054 { "MONTH", tMONTH_UNIT
, 1 },
1055 { "FORTNIGHT",tDAY_UNIT
, 14 },
1056 { "WEEK", tDAY_UNIT
, 7 },
1057 { "DAY", tDAY_UNIT
, 1 },
1058 { "HOUR", tHOUR_UNIT
, 1 },
1059 { "MINUTE", tMINUTE_UNIT
, 1 },
1060 { "MIN", tMINUTE_UNIT
, 1 },
1061 { "SECOND", tSEC_UNIT
, 1 },
1062 { "SEC", tSEC_UNIT
, 1 },
1066 /* Assorted relative-time words. */
1067 static table
const relative_time_table
[] =
1069 { "TOMORROW", tDAY_SHIFT
, 1 },
1070 { "YESTERDAY",tDAY_SHIFT
, -1 },
1071 { "TODAY", tDAY_SHIFT
, 0 },
1072 { "NOW", tDAY_SHIFT
, 0 },
1073 { "LAST", tORDINAL
, -1 },
1074 { "THIS", tORDINAL
, 0 },
1075 { "NEXT", tORDINAL
, 1 },
1076 { "FIRST", tORDINAL
, 1 },
1077 /*{ "SECOND", tORDINAL, 2 }, */
1078 { "THIRD", tORDINAL
, 3 },
1079 { "FOURTH", tORDINAL
, 4 },
1080 { "FIFTH", tORDINAL
, 5 },
1081 { "SIXTH", tORDINAL
, 6 },
1082 { "SEVENTH", tORDINAL
, 7 },
1083 { "EIGHTH", tORDINAL
, 8 },
1084 { "NINTH", tORDINAL
, 9 },
1085 { "TENTH", tORDINAL
, 10 },
1086 { "ELEVENTH", tORDINAL
, 11 },
1087 { "TWELFTH", tORDINAL
, 12 },
1088 { "AGO", tAGO
, -1 },
1089 { "HENCE", tAGO
, 1 },
1093 /* The universal time zone table. These labels can be used even for
1094 timestamps that would not otherwise be valid, e.g., GMT timestamps
1095 oin London during summer. */
1096 static table
const universal_time_zone_table
[] =
1098 { "GMT", tZONE
, HOUR
( 0) }, /* Greenwich Mean */
1099 { "UT", tZONE
, HOUR
( 0) }, /* Universal (Coordinated) */
1100 { "UTC", tZONE
, HOUR
( 0) },
1104 /* The time zone table. This table is necessarily incomplete, as time
1105 zone abbreviations are ambiguous; e.g., Australians interpret "EST"
1106 as Eastern time in Australia, not as US Eastern Standard Time.
1107 You cannot rely on parse_datetime to handle arbitrary time zone
1108 abbreviations; use numeric abbreviations like "-0500" instead. */
1109 static table
const time_zone_table
[] =
1111 { "WET", tZONE
, HOUR
( 0) }, /* Western European */
1112 { "WEST", tDAYZONE
, HOUR
( 0) }, /* Western European Summer */
1113 { "BST", tDAYZONE
, HOUR
( 0) }, /* British Summer */
1114 { "ART", tZONE
, -HOUR
( 3) }, /* Argentina */
1115 { "BRT", tZONE
, -HOUR
( 3) }, /* Brazil */
1116 { "BRST", tDAYZONE
, -HOUR
( 3) }, /* Brazil Summer */
1117 { "NST", tZONE
, -(HOUR
( 3) + 30 * 60) }, /* Newfoundland Standard */
1118 { "NDT", tDAYZONE
,-(HOUR
( 3) + 30 * 60) }, /* Newfoundland Daylight */
1119 { "AST", tZONE
, -HOUR
( 4) }, /* Atlantic Standard */
1120 { "ADT", tDAYZONE
, -HOUR
( 4) }, /* Atlantic Daylight */
1121 { "CLT", tZONE
, -HOUR
( 4) }, /* Chile */
1122 { "CLST", tDAYZONE
, -HOUR
( 4) }, /* Chile Summer */
1123 { "EST", tZONE
, -HOUR
( 5) }, /* Eastern Standard */
1124 { "EDT", tDAYZONE
, -HOUR
( 5) }, /* Eastern Daylight */
1125 { "CST", tZONE
, -HOUR
( 6) }, /* Central Standard */
1126 { "CDT", tDAYZONE
, -HOUR
( 6) }, /* Central Daylight */
1127 { "MST", tZONE
, -HOUR
( 7) }, /* Mountain Standard */
1128 { "MDT", tDAYZONE
, -HOUR
( 7) }, /* Mountain Daylight */
1129 { "PST", tZONE
, -HOUR
( 8) }, /* Pacific Standard */
1130 { "PDT", tDAYZONE
, -HOUR
( 8) }, /* Pacific Daylight */
1131 { "AKST", tZONE
, -HOUR
( 9) }, /* Alaska Standard */
1132 { "AKDT", tDAYZONE
, -HOUR
( 9) }, /* Alaska Daylight */
1133 { "HST", tZONE
, -HOUR
(10) }, /* Hawaii Standard */
1134 { "HAST", tZONE
, -HOUR
(10) }, /* Hawaii-Aleutian Standard */
1135 { "HADT", tDAYZONE
, -HOUR
(10) }, /* Hawaii-Aleutian Daylight */
1136 { "SST", tZONE
, -HOUR
(12) }, /* Samoa Standard */
1137 { "WAT", tZONE
, HOUR
( 1) }, /* West Africa */
1138 { "CET", tZONE
, HOUR
( 1) }, /* Central European */
1139 { "CEST", tDAYZONE
, HOUR
( 1) }, /* Central European Summer */
1140 { "MET", tZONE
, HOUR
( 1) }, /* Middle European */
1141 { "MEZ", tZONE
, HOUR
( 1) }, /* Middle European */
1142 { "MEST", tDAYZONE
, HOUR
( 1) }, /* Middle European Summer */
1143 { "MESZ", tDAYZONE
, HOUR
( 1) }, /* Middle European Summer */
1144 { "EET", tZONE
, HOUR
( 2) }, /* Eastern European */
1145 { "EEST", tDAYZONE
, HOUR
( 2) }, /* Eastern European Summer */
1146 { "CAT", tZONE
, HOUR
( 2) }, /* Central Africa */
1147 { "SAST", tZONE
, HOUR
( 2) }, /* South Africa Standard */
1148 { "EAT", tZONE
, HOUR
( 3) }, /* East Africa */
1149 { "MSK", tZONE
, HOUR
( 3) }, /* Moscow */
1150 { "MSD", tDAYZONE
, HOUR
( 3) }, /* Moscow Daylight */
1151 { "IST", tZONE
, (HOUR
( 5) + 30 * 60) }, /* India Standard */
1152 { "SGT", tZONE
, HOUR
( 8) }, /* Singapore */
1153 { "KST", tZONE
, HOUR
( 9) }, /* Korea Standard */
1154 { "JST", tZONE
, HOUR
( 9) }, /* Japan Standard */
1155 { "GST", tZONE
, HOUR
(10) }, /* Guam Standard */
1156 { "NZST", tZONE
, HOUR
(12) }, /* New Zealand Standard */
1157 { "NZDT", tDAYZONE
, HOUR
(12) }, /* New Zealand Daylight */
1161 /* Military time zone table.
1163 Note 'T' is a special case, as it is used as the separator in ISO
1164 8601 date and time of day representation. */
1165 static table
const military_table
[] =
1167 { "A", tZONE
, -HOUR
( 1) },
1168 { "B", tZONE
, -HOUR
( 2) },
1169 { "C", tZONE
, -HOUR
( 3) },
1170 { "D", tZONE
, -HOUR
( 4) },
1171 { "E", tZONE
, -HOUR
( 5) },
1172 { "F", tZONE
, -HOUR
( 6) },
1173 { "G", tZONE
, -HOUR
( 7) },
1174 { "H", tZONE
, -HOUR
( 8) },
1175 { "I", tZONE
, -HOUR
( 9) },
1176 { "K", tZONE
, -HOUR
(10) },
1177 { "L", tZONE
, -HOUR
(11) },
1178 { "M", tZONE
, -HOUR
(12) },
1179 { "N", tZONE
, HOUR
( 1) },
1180 { "O", tZONE
, HOUR
( 2) },
1181 { "P", tZONE
, HOUR
( 3) },
1182 { "Q", tZONE
, HOUR
( 4) },
1183 { "R", tZONE
, HOUR
( 5) },
1184 { "S", tZONE
, HOUR
( 6) },
1186 { "U", tZONE
, HOUR
( 8) },
1187 { "V", tZONE
, HOUR
( 9) },
1188 { "W", tZONE
, HOUR
(10) },
1189 { "X", tZONE
, HOUR
(11) },
1190 { "Y", tZONE
, HOUR
(12) },
1191 { "Z", tZONE
, HOUR
( 0) },
1197 /* Convert a time zone expressed as HH:MM into an integer count of
1198 seconds. If MM is negative, then S is of the form HHMM and needs
1199 to be picked apart; otherwise, S is of the form HH. As specified in
1200 http://www.opengroup.org/susv3xbd/xbd_chap08.html#tag_08_03, allow
1201 only valid TZ range, and consider first two digits as hours, if no
1202 minutes specified. Return true if successful. */
1205 time_zone_hhmm
(parser_control
*pc
, textint s
, intmax_t mm
)
1208 bool overflow
= false
;
1210 /* If the length of S is 1 or 2 and no minutes are specified,
1211 interpret it as a number of hours. */
1212 if
(s.digits
<= 2 && mm
< 0)
1216 n_minutes
= (s.value
/ 100) * 60 + s.value %
100;
1219 overflow |
= INT_MULTIPLY_WRAPV
(s.value
, 60, &n_minutes
);
1220 overflow |
= (s.negative
1221 ? INT_SUBTRACT_WRAPV
(n_minutes
, mm
, &n_minutes
)
1222 : INT_ADD_WRAPV
(n_minutes
, mm
, &n_minutes
));
1225 if
(overflow ||
! (-24 * 60 <= n_minutes
&& n_minutes
<= 24 * 60))
1227 pc
->time_zone
= n_minutes
* 60;
1232 to_hour
(intmax_t hours
, int meridian
)
1236 default
: /* Pacify GCC. */
1238 return
0 <= hours
&& hours
< 24 ? hours
: -1;
1240 return
0 < hours
&& hours
< 12 ? hours
: hours
== 12 ?
0 : -1;
1242 return
0 < hours
&& hours
< 12 ? hours
+ 12 : hours
== 12 ?
12 : -1;
1246 enum { TM_YEAR_BASE
= 1900 };
1247 enum { TM_YEAR_BUFSIZE
= INT_BUFSIZE_BOUND
(int) + 1 };
1249 /* Convert TM_YEAR, a year minus 1900, to a string that is numerically
1250 correct even if subtracting 1900 would overflow. */
1253 tm_year_str
(int tm_year
, char buf
[TM_YEAR_BUFSIZE
])
1255 verify
(TM_YEAR_BASE %
100 == 0);
1256 sprintf
(buf
, &"-%02d%02d"[-TM_YEAR_BASE
<= tm_year
],
1257 abs
(tm_year
/ 100 + TM_YEAR_BASE
/ 100),
1258 abs
(tm_year %
100));
1262 /* Convert a text year number to a year minus 1900, working correctly
1263 even if the input is in the range INT_MAX .. INT_MAX + 1900 - 1. */
1266 to_tm_year
(textint textyear
, bool debug
, int *tm_year
)
1268 intmax_t year
= textyear.value
;
1270 /* XPG4 suggests that years 00-68 map to 2000-2068, and
1271 years 69-99 map to 1969-1999. */
1272 if
(0 <= year
&& textyear.digits
== 2)
1274 year
+= year
< 69 ?
2000 : 1900;
1276 dbg_printf
(_
("warning: adjusting year value %"PRIdMAX
1277 " to %"PRIdMAX
"\n"),
1278 textyear.value
, year
);
1282 ? INT_SUBTRACT_WRAPV
(-TM_YEAR_BASE
, year
, tm_year
)
1283 : INT_SUBTRACT_WRAPV
(year
, TM_YEAR_BASE
, tm_year
))
1286 dbg_printf
(_
("error: out-of-range year %"PRIdMAX
"\n"), year
);
1293 static table
const * _GL_ATTRIBUTE_PURE
1294 lookup_zone
(parser_control
const *pc
, char const *name
)
1298 for
(tp
= universal_time_zone_table
; tp
->name
; tp
++)
1299 if
(strcmp
(name
, tp
->name
) == 0)
1302 /* Try local zone abbreviations before those in time_zone_table, as
1303 the local ones are more likely to be right. */
1304 for
(tp
= pc
->local_time_zone_table
; tp
->name
; tp
++)
1305 if
(strcmp
(name
, tp
->name
) == 0)
1308 for
(tp
= time_zone_table
; tp
->name
; tp
++)
1309 if
(strcmp
(name
, tp
->name
) == 0)
1315 #if ! HAVE_TM_GMTOFF
1316 /* Yield the difference between *A and *B,
1317 measured in seconds, ignoring leap seconds.
1318 The body of this function is taken directly from the GNU C Library;
1321 tm_diff
(const struct tm
*a
, const struct tm
*b
)
1323 /* Compute intervening leap days correctly even if year is negative.
1324 Take care to avoid int overflow in leap day calculations,
1325 but it's OK to assume that A and B are close to each other. */
1326 int a4
= SHR
(a
->tm_year
, 2) + SHR
(TM_YEAR_BASE
, 2) - ! (a
->tm_year
& 3);
1327 int b4
= SHR
(b
->tm_year
, 2) + SHR
(TM_YEAR_BASE
, 2) - ! (b
->tm_year
& 3);
1328 int a100
= a4
/ 25 - (a4 %
25 < 0);
1329 int b100
= b4
/ 25 - (b4 %
25 < 0);
1330 int a400
= SHR
(a100
, 2);
1331 int b400
= SHR
(b100
, 2);
1332 int intervening_leap_days
= (a4
- b4
) - (a100
- b100
) + (a400
- b400
);
1333 int years
= a
->tm_year
- b
->tm_year
;
1334 int days
= (365 * years
+ intervening_leap_days
1335 + (a
->tm_yday
- b
->tm_yday
));
1336 return
(60 * (60 * (24 * days
+ (a
->tm_hour
- b
->tm_hour
))
1337 + (a
->tm_min
- b
->tm_min
))
1338 + (a
->tm_sec
- b
->tm_sec
));
1340 #endif /* ! HAVE_TM_GMTOFF */
1342 static table
const *
1343 lookup_word
(parser_control
const *pc
, char *word
)
1352 /* Make it uppercase. */
1353 for
(p
= word
; *p
; p
++)
1354 *p
= c_toupper
(to_uchar
(*p
));
1356 for
(tp
= meridian_table
; tp
->name
; tp
++)
1357 if
(strcmp
(word
, tp
->name
) == 0)
1360 /* See if we have an abbreviation for a month. */
1361 wordlen
= strlen
(word
);
1362 abbrev
= wordlen
== 3 ||
(wordlen
== 4 && word
[3] == '.');
1364 for
(tp
= month_and_day_table
; tp
->name
; tp
++)
1365 if
((abbrev ? strncmp
(word
, tp
->name
, 3) : strcmp
(word
, tp
->name
)) == 0)
1368 if
((tp
= lookup_zone
(pc
, word
)))
1371 if
(strcmp
(word
, dst_table
[0].name
) == 0)
1374 for
(tp
= time_units_table
; tp
->name
; tp
++)
1375 if
(strcmp
(word
, tp
->name
) == 0)
1378 /* Strip off any plural and try the units table again. */
1379 if
(word
[wordlen
- 1] == 'S')
1381 word
[wordlen
- 1] = '\0';
1382 for
(tp
= time_units_table
; tp
->name
; tp
++)
1383 if
(strcmp
(word
, tp
->name
) == 0)
1385 word
[wordlen
- 1] = 'S'; /* For "this" in relative_time_table. */
1388 for
(tp
= relative_time_table
; tp
->name
; tp
++)
1389 if
(strcmp
(word
, tp
->name
) == 0)
1392 /* Military time zones. */
1394 for
(tp
= military_table
; tp
->name
; tp
++)
1395 if
(word
[0] == tp
->name
[0])
1398 /* Drop out any periods and try the time zone table again. */
1399 for
(period_found
= false
, p
= q
= word
; (*p
= *q
); q
++)
1401 period_found
= true
;
1404 if
(period_found
&& (tp
= lookup_zone
(pc
, word
)))
1411 yylex (union YYSTYPE *lvalp
, parser_control
*pc
)
1417 while
(c
= *pc
->input
, c_isspace
(c
))
1420 if
(c_isdigit
(c
) || c
== '-' || c
== '+')
1425 if
(c
== '-' || c
== '+')
1427 sign
= c
== '-' ?
-1 : 1;
1428 while
(c
= *++pc
->input
, c_isspace
(c
))
1430 if
(! c_isdigit
(c
))
1431 /* skip the '-' sign */
1440 if
(INT_MULTIPLY_WRAPV
(value
, 10, &value
))
1442 if
(INT_ADD_WRAPV
(value
, sign
< 0 ?
'0' - c
: c
- '0', &value
))
1446 while
(c_isdigit
(c
));
1448 if
((c
== '.' || c
== ',') && c_isdigit
(p
[1]))
1454 if
(time_overflow
(value
))
1458 /* Accumulate fraction, to ns precision. */
1461 for
(digits
= 2; digits
<= LOG10_BILLION
; digits
++)
1468 /* Skip excess digits, truncating toward -Infinity. */
1470 for
(; c_isdigit
(*p
); p
++)
1476 while
(c_isdigit
(*p
))
1479 /* Adjust to the timespec convention, which is that
1480 tv_nsec is always a positive offset even if tv_sec is
1484 if
(s
== TYPE_MINIMUM
(time_t))
1490 lvalp
->timespec.tv_sec
= s
;
1491 lvalp
->timespec.tv_nsec
= ns
;
1493 return sign ? tSDECIMAL_NUMBER
: tUDECIMAL_NUMBER
;
1497 lvalp
->textintval.negative
= sign
< 0;
1498 lvalp
->textintval.value
= value
;
1499 lvalp
->textintval.digits
= p
- pc
->input
;
1501 return sign ? tSNUMBER
: tUNUMBER
;
1513 if
(p
< buff
+ sizeof buff
- 1)
1517 while
(c_isalpha
(c
) || c
== '.');
1520 tp
= lookup_word
(pc
, buff
);
1523 if
(pc
->parse_datetime_debug
)
1524 dbg_printf
(_
("error: unknown word '%s'\n"), buff
);
1527 lvalp
->intval
= tp
->value
;
1532 return to_uchar
(*pc
->input
++);
1534 ptrdiff_t count
= 0;
1549 /* Do nothing if the parser reports an error. */
1551 yyerror (parser_control
const *pc _GL_UNUSED
,
1552 char const *s _GL_UNUSED
)
1557 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1558 passing it to mktime_z, return true if it's OK. It's not OK if
1559 mktime failed or if *TM0 has out-of-range mainline members.
1560 The caller should set TM1->tm_wday to -1 before calling mktime,
1561 as a negative tm_wday is how mktime failure is inferred. */
1564 mktime_ok
(struct tm
const *tm0
, struct tm
const *tm1
)
1566 if
(tm1
->tm_wday
< 0)
1569 return
! ((tm0
->tm_sec ^ tm1
->tm_sec
)
1570 |
(tm0
->tm_min ^ tm1
->tm_min
)
1571 |
(tm0
->tm_hour ^ tm1
->tm_hour
)
1572 |
(tm0
->tm_mday ^ tm1
->tm_mday
)
1573 |
(tm0
->tm_mon ^ tm1
->tm_mon
)
1574 |
(tm0
->tm_year ^ tm1
->tm_year
));
1577 /* Debugging: format a 'struct tm' into a buffer, taking the parser's
1578 timezone information into account (if pc != NULL). */
1580 debug_strfdatetime
(struct tm
const *tm
, parser_control
const *pc
,
1584 1. find an optimal way to print date string in a clear and unambiguous
1585 format. Currently, always add '(Y-M-D)' prefix.
1586 Consider '2016y01m10d' or 'year(2016) month(01) day(10)'.
1588 If the user needs debug printing, it means he/she already having
1589 issues with the parsing - better to avoid formats that could
1590 be mis-interpreted (e.g., just YYYY-MM-DD).
1592 2. Can strftime be used instead?
1593 depends if it is portable and can print invalid dates on all systems.
1595 3. Print timezone information ?
1597 4. Print DST information ?
1599 5. Print nanosecond information ?
1602 Printed date/time values might not be valid, e.g., '2016-02-31'
1603 or '2016-19-2016' . These are the values as parsed from the user
1604 string, before validation.
1606 int m
= nstrftime
(buf
, n
, "(Y-M-D) %Y-%m-%d %H:%M:%S", tm
, 0, 0);
1608 /* If parser_control information was provided (for timezone),
1609 and there's enough space in the buffer, add timezone info. */
1610 if
(pc
&& m
< n
&& pc
->zones_seen
)
1612 int tz
= pc
->time_zone
;
1614 /* Account for DST if tLOCAL_ZONE was seen. */
1615 if
(pc
->local_zones_seen
&& !pc
->zones_seen
&& 0 < pc
->local_isdst
)
1618 char time_zone_buf
[TIME_ZONE_BUFSIZE
];
1619 snprintf
(&buf
[m
], n
- m
, " TZ=%s", time_zone_str
(tz
, time_zone_buf
));
1625 debug_strfdate
(struct tm
const *tm
, char *buf
, int n
)
1627 char tm_year_buf
[TM_YEAR_BUFSIZE
];
1628 snprintf
(buf
, n
, "(Y-M-D) %s-%02d-%02d",
1629 tm_year_str
(tm
->tm_year
, tm_year_buf
),
1630 tm
->tm_mon
+ 1, tm
->tm_mday
);
1635 debug_strftime
(struct tm
const *tm
, char *buf
, int n
)
1637 snprintf
(buf
, n
, "%02d:%02d:%02d", tm
->tm_hour
, tm
->tm_min
, tm
->tm_sec
);
1641 /* If mktime_ok failed, display the failed time values,
1642 and provide possible hints. Example output:
1644 date: error: invalid date/time value:
1645 date: user provided time: '(Y-M-D) 2006-04-02 02:45:00'
1646 date: normalized time: '(Y-M-D) 2006-04-02 03:45:00'
1648 date: possible reasons:
1649 date: non-existing due to daylight-saving time;
1650 date: numeric values overflow;
1651 date: missing timezone;
1654 debug_mktime_not_ok
(struct tm
const *tm0
, struct tm
const *tm1
,
1655 parser_control
const *pc
, bool time_zone_seen
)
1657 /* TODO: handle t == -1 (as in 'mktime_ok'). */
1658 char tmp
[DBGBUFSIZE
];
1660 const bool eq_sec
= (tm0
->tm_sec
== tm1
->tm_sec
);
1661 const bool eq_min
= (tm0
->tm_min
== tm1
->tm_min
);
1662 const bool eq_hour
= (tm0
->tm_hour
== tm1
->tm_hour
);
1663 const bool eq_mday
= (tm0
->tm_mday
== tm1
->tm_mday
);
1664 const bool eq_month
= (tm0
->tm_mon
== tm1
->tm_mon
);
1665 const bool eq_year
= (tm0
->tm_year
== tm1
->tm_year
);
1667 const bool dst_shift
= eq_sec
&& eq_min
&& !eq_hour
1668 && eq_mday
&& eq_month
&& eq_year
;
1670 if
(!pc
->parse_datetime_debug
)
1673 dbg_printf
(_
("error: invalid date/time value:\n"));
1674 dbg_printf
(_
(" user provided time: '%s'\n"),
1675 debug_strfdatetime
(tm0
, pc
, tmp
, sizeof tmp
));
1676 dbg_printf
(_
(" normalized time: '%s'\n"),
1677 debug_strfdatetime
(tm1
, pc
, tmp
, sizeof tmp
));
1678 /* The format must be aligned with debug_strfdatetime and the two
1679 DEBUG statements above. This string is not translated. */
1680 i
= snprintf
(tmp
, sizeof tmp
,
1681 " %4s %2s %2s %2s %2s %2s",
1682 eq_year ?
"" : "----",
1683 eq_month ?
"" : "--",
1684 eq_mday ?
"" : "--",
1685 eq_hour ?
"" : "--",
1687 eq_sec ?
"" : "--");
1688 /* Trim trailing whitespace. */
1691 if
(sizeof tmp
- 1 < i
)
1693 while
(0 < i
&& tmp
[i
- 1] == ' ')
1697 dbg_printf
("%s\n", tmp
);
1699 dbg_printf
(_
(" possible reasons:\n"));
1701 dbg_printf
(_
(" non-existing due to daylight-saving time;\n"));
1702 if
(!eq_mday
&& !eq_month
)
1703 dbg_printf
(_
(" invalid day/month combination;\n"));
1704 dbg_printf
(_
(" numeric values overflow;\n"));
1705 dbg_printf
(" %s\n", (time_zone_seen ? _
("incorrect timezone")
1706 : _
("missing timezone")));
1709 /* The original interface: run with debug=false and the default timezone. */
1711 parse_datetime
(struct timespec
*result
, char const *p
,
1712 struct timespec
const *now
)
1714 char const *tzstring
= getenv
("TZ");
1715 timezone_t tz
= tzalloc
(tzstring
);
1718 bool ok
= parse_datetime2
(result
, p
, now
, 0, tz
, tzstring
);
1723 /* Parse a date/time string, storing the resulting time value into *RESULT.
1724 The string itself is pointed to by P. Return true if successful.
1725 P can be an incomplete or relative time specification; if so, use
1726 *NOW as the basis for the returned time. Default to timezone
1727 TZDEFAULT, which corresponds to tzalloc (TZSTRING). */
1729 parse_datetime2
(struct timespec
*result
, char const *p
,
1730 struct timespec
const *now
, unsigned int flags
,
1731 timezone_t tzdefault
, char const *tzstring
)
1735 char time_zone_buf
[TIME_ZONE_BUFSIZE
];
1736 char dbg_tm
[DBGBUFSIZE
];
1738 char const *input_sentinel
= p
+ strlen
(p
);
1739 char *tz1alloc
= NULL
;
1741 /* A reasonable upper bound for the size of ordinary TZ strings.
1742 Use heap allocation if TZ's length exceeds this. */
1743 enum { TZBUFSIZE
= 100 };
1744 char tz1buf
[TZBUFSIZE
];
1746 struct timespec gettime_buffer
;
1749 gettime
(&gettime_buffer
);
1750 now
= &gettime_buffer
;
1753 time_t Start
= now
->tv_sec
;
1754 int Start_ns
= now
->tv_nsec
;
1757 while
(c
= *p
, c_isspace
(c
))
1760 timezone_t tz
= tzdefault
;
1762 /* Store a local copy prior to first "goto". Without this, a prior use
1763 below of RELATIVE_TIME_0 on the RHS might translate to an assignment-
1764 to-temporary, which would trigger a -Wjump-misses-init warning. */
1765 const relative_time rel_time_0
= RELATIVE_TIME_0
;
1767 if
(strncmp
(p
, "TZ=\"", 4) == 0)
1769 char const *tzbase
= p
+ 4;
1770 ptrdiff_t tzsize
= 1;
1773 for
(s
= tzbase
; *s
; s
++, tzsize
++)
1777 if
(! (*s
== '\\' ||
*s
== '"'))
1783 char *tz1string
= tz1buf
;
1785 if
(TZBUFSIZE
< tzsize
)
1787 tz1alloc
= malloc
(tzsize
);
1790 tz1string
= tz1alloc
;
1793 for
(s
= tzbase
; *s
!= '"'; s
++)
1794 *z
++ = *(s
+= *s
== '\\');
1796 tz1
= tzalloc
(tz1string
);
1800 tzstring
= tz1string
;
1803 while
(c
= *p
, c_isspace
(c
))
1811 if
(! localtime_rz
(tz
, &now
->tv_sec
, &tmp
))
1814 /* As documented, be careful to treat the empty string just like
1815 a date string of "0". Without this, an empty string would be
1816 declared invalid when parsed during a DST transition. */
1822 pc.parse_datetime_debug
= (flags
& PARSE_DATETIME_DEBUG
) != 0;
1823 if
(INT_ADD_WRAPV
(tmp.tm_year
, TM_YEAR_BASE
, &pc.year.value
))
1825 if
(pc.parse_datetime_debug
)
1826 dbg_printf
(_
("error: initial year out of range\n"));
1830 pc.month
= tmp.tm_mon
+ 1;
1831 pc.day
= tmp.tm_mday
;
1832 pc.hour
= tmp.tm_hour
;
1833 pc.minutes
= tmp.tm_min
;
1834 pc.seconds.tv_sec
= tmp.tm_sec
;
1835 pc.seconds.tv_nsec
= Start_ns
;
1836 tm.tm_isdst
= tmp.tm_isdst
;
1838 pc.meridian
= MER24
;
1839 pc.rel
= rel_time_0
;
1840 pc.timespec_seen
= false
;
1841 pc.rels_seen
= false
;
1845 pc.local_zones_seen
= 0;
1848 pc.year_seen
= false
;
1849 pc.debug_dates_seen
= false
;
1850 pc.debug_days_seen
= false
;
1851 pc.debug_times_seen
= false
;
1852 pc.debug_local_zones_seen
= false
;
1853 pc.debug_zones_seen
= false
;
1854 pc.debug_year_seen
= false
;
1855 pc.debug_ordinal_day_seen
= false
;
1857 #if HAVE_STRUCT_TM_TM_ZONE
1858 pc.local_time_zone_table
[0].name
= tmp.tm_zone
;
1859 pc.local_time_zone_table
[0].type
= tLOCAL_ZONE
;
1860 pc.local_time_zone_table
[0].value
= tmp.tm_isdst
;
1861 pc.local_time_zone_table
[1].name
= NULL
;
1863 /* Probe the names used in the next three calendar quarters, looking
1864 for a tm_isdst different from the one we already have. */
1867 for
(quarter
= 1; quarter
<= 3; quarter
++)
1870 if
(INT_ADD_WRAPV
(Start
, quarter
* (90 * 24 * 60 * 60), &iprobe
)
1871 || time_overflow
(iprobe
))
1873 time_t probe
= iprobe
;
1875 if
(localtime_rz
(tz
, &probe
, &probe_tm
) && probe_tm.tm_zone
1876 && probe_tm.tm_isdst
!= pc.local_time_zone_table
[0].value
)
1879 pc.local_time_zone_table
[1].name
= probe_tm.tm_zone
;
1880 pc.local_time_zone_table
[1].type
= tLOCAL_ZONE
;
1881 pc.local_time_zone_table
[1].value
= probe_tm.tm_isdst
;
1882 pc.local_time_zone_table
[2].name
= NULL
;
1891 # if !HAVE_DECL_TZNAME
1892 extern
char *tzname
[];
1895 for
(i
= 0; i
< 2; i
++)
1897 pc.local_time_zone_table
[i
].name
= tzname
[i
];
1898 pc.local_time_zone_table
[i
].type
= tLOCAL_ZONE
;
1899 pc.local_time_zone_table
[i
].value
= i
;
1901 pc.local_time_zone_table
[i
].name
= NULL
;
1904 pc.local_time_zone_table
[0].name
= NULL
;
1908 if
(pc.local_time_zone_table
[0].name
&& pc.local_time_zone_table
[1].name
1909 && ! strcmp
(pc.local_time_zone_table
[0].name
,
1910 pc.local_time_zone_table
[1].name
))
1912 /* This locale uses the same abbreviation for standard and
1913 daylight times. So if we see that abbreviation, we don't
1914 know whether it's daylight time. */
1915 pc.local_time_zone_table
[0].value
= -1;
1916 pc.local_time_zone_table
[1].name
= NULL
;
1919 if
(yyparse (&pc
) != 0)
1921 if
(pc.parse_datetime_debug
)
1922 dbg_printf
((input_sentinel
<= pc.input
1923 ? _
("error: parsing failed\n")
1924 : _
("error: parsing failed, stopped at '%s'\n")),
1930 /* Determine effective timezone source. */
1932 if
(pc.parse_datetime_debug
)
1934 dbg_printf
(_
("input timezone: "));
1936 if
(pc.timespec_seen
)
1937 fprintf
(stderr
, _
("'@timespec' - always UTC"));
1938 else if
(pc.zones_seen
)
1939 fprintf
(stderr
, _
("parsed date/time string"));
1942 if
(tz
!= tzdefault
)
1943 fprintf
(stderr
, _
("TZ=\"%s\" in date string"), tzstring
);
1944 else if
(STREQ
(tzstring
, "UTC0"))
1946 /* Special case: 'date -u' sets TZ="UTC0". */
1947 fprintf
(stderr
, _
("TZ=\"UTC0\" environment value or -u"));
1950 fprintf
(stderr
, _
("TZ=\"%s\" environment value"), tzstring
);
1953 fprintf
(stderr
, _
("system default"));
1955 /* Account for DST changes if tLOCAL_ZONE was seen.
1956 local timezone only changes DST and is relative to the
1958 if
(pc.local_zones_seen
&& !pc.zones_seen
&& 0 < pc.local_isdst
)
1959 fprintf
(stderr
, ", dst");
1962 fprintf
(stderr
, " (%s)", time_zone_str
(pc.time_zone
, time_zone_buf
));
1964 fputc
('\n', stderr
);
1967 if
(pc.timespec_seen
)
1968 *result
= pc.seconds
;
1971 if
(1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
1972 |
(pc.local_zones_seen
+ pc.zones_seen
)))
1974 if
(pc.parse_datetime_debug
)
1976 if
(pc.times_seen
> 1)
1977 dbg_printf
("error: seen multiple time parts\n");
1978 if
(pc.dates_seen
> 1)
1979 dbg_printf
("error: seen multiple date parts\n");
1980 if
(pc.days_seen
> 1)
1981 dbg_printf
("error: seen multiple days parts\n");
1982 if
(pc.dsts_seen
> 1)
1983 dbg_printf
("error: seen multiple daylight-saving parts\n");
1984 if
((pc.local_zones_seen
+ pc.zones_seen
) > 1)
1985 dbg_printf
("error: seen multiple time-zone parts\n");
1990 if
(! to_tm_year
(pc.year
, pc.parse_datetime_debug
, &tm.tm_year
)
1991 || INT_ADD_WRAPV
(pc.month
, -1, &tm.tm_mon
)
1992 || INT_ADD_WRAPV
(pc.day
, 0, &tm.tm_mday
))
1994 if
(pc.parse_datetime_debug
)
1995 dbg_printf
(_
("error: year, month, or day overflow\n"));
1998 if
(pc.times_seen ||
(pc.rels_seen
&& ! pc.dates_seen
&& ! pc.days_seen
))
2000 tm.tm_hour
= to_hour
(pc.hour
, pc.meridian
);
2003 char const *mrd
= (pc.meridian
== MERam ?
"am"
2004 : pc.meridian
== MERpm ?
"pm" : "");
2005 if
(pc.parse_datetime_debug
)
2006 dbg_printf
(_
("error: invalid hour %"PRIdMAX
"%s\n"),
2010 tm.tm_min
= pc.minutes
;
2011 tm.tm_sec
= pc.seconds.tv_sec
;
2012 if
(pc.parse_datetime_debug
)
2013 dbg_printf
((pc.times_seen
2014 ? _
("using specified time as starting value: '%s'\n")
2015 : _
("using current time as starting value: '%s'\n")),
2016 debug_strftime
(&tm
, dbg_tm
, sizeof dbg_tm
));
2020 tm.tm_hour
= tm.tm_min
= tm.tm_sec
= 0;
2021 pc.seconds.tv_nsec
= 0;
2022 if
(pc.parse_datetime_debug
)
2023 dbg_printf
("warning: using midnight as starting time: 00:00:00\n");
2026 /* Let mktime deduce tm_isdst if we have an absolute timestamp. */
2027 if
(pc.dates_seen | pc.days_seen | pc.times_seen
)
2030 /* But if the input explicitly specifies local time with or without
2031 DST, give mktime that information. */
2032 if
(pc.local_zones_seen
)
2033 tm.tm_isdst
= pc.local_isdst
;
2035 tm0.tm_sec
= tm.tm_sec
;
2036 tm0.tm_min
= tm.tm_min
;
2037 tm0.tm_hour
= tm.tm_hour
;
2038 tm0.tm_mday
= tm.tm_mday
;
2039 tm0.tm_mon
= tm.tm_mon
;
2040 tm0.tm_year
= tm.tm_year
;
2041 tm0.tm_isdst
= tm.tm_isdst
;
2044 Start
= mktime_z
(tz
, &tm
);
2046 if
(! mktime_ok
(&tm0
, &tm
))
2048 bool repaired
= false
;
2049 bool time_zone_seen
= pc.zones_seen
!= 0;
2052 /* Guard against falsely reporting errors near the time_t
2053 boundaries when parsing times in other time zones. For
2054 example, suppose the input string "1969-12-31 23:00:00 -0100",
2055 the current time zone is 8 hours ahead of UTC, and the min
2056 time_t value is 1970-01-01 00:00:00 UTC. Then the min
2057 localtime value is 1970-01-01 08:00:00, and mktime will
2058 therefore fail on 1969-12-31 23:00:00. To work around the
2059 problem, set the time zone to 1 hour behind UTC temporarily
2060 by setting TZ="XXX1:00" and try mktime again. */
2062 char tz2buf
[sizeof
"XXX" - 1 + TIME_ZONE_BUFSIZE
];
2063 tz2buf
[0] = tz2buf
[1] = tz2buf
[2] = 'X';
2064 time_zone_str
(pc.time_zone
, &tz2buf
[3]);
2065 timezone_t tz2
= tzalloc
(tz2buf
);
2068 if
(pc.parse_datetime_debug
)
2069 dbg_printf
(_
("error: tzalloc (\"%s\") failed\n"), tz2buf
);
2072 tm.tm_sec
= tm0.tm_sec
;
2073 tm.tm_min
= tm0.tm_min
;
2074 tm.tm_hour
= tm0.tm_hour
;
2075 tm.tm_mday
= tm0.tm_mday
;
2076 tm.tm_mon
= tm0.tm_mon
;
2077 tm.tm_year
= tm0.tm_year
;
2078 tm.tm_isdst
= tm0.tm_isdst
;
2080 Start
= mktime_z
(tz2
, &tm
);
2081 repaired
= mktime_ok
(&tm0
, &tm
);
2087 debug_mktime_not_ok
(&tm0
, &tm
, &pc
, time_zone_seen
);
2092 char dbg_ord
[DBGBUFSIZE
];
2094 if
(pc.days_seen
&& ! pc.dates_seen
)
2097 if
(INT_MULTIPLY_WRAPV
((pc.day_ordinal
2098 - (0 < pc.day_ordinal
2099 && tm.tm_wday
!= pc.day_number
)),
2101 || INT_ADD_WRAPV
((pc.day_number
- tm.tm_wday
+ 7) %
7,
2103 || INT_ADD_WRAPV
(dayincr
, tm.tm_mday
, &tm.tm_mday
))
2108 Start
= mktime_z
(tz
, &tm
);
2111 if
(Start
== (time_t) -1)
2113 if
(pc.parse_datetime_debug
)
2114 dbg_printf
(_
("error: day '%s' "
2115 "(day ordinal=%"PRIdMAX
" number=%d) "
2116 "resulted in an invalid date: '%s'\n"),
2117 str_days
(&pc
, dbg_ord
, sizeof dbg_ord
),
2118 pc.day_ordinal
, pc.day_number
,
2119 debug_strfdatetime
(&tm
, &pc
, dbg_tm
,
2124 if
(pc.parse_datetime_debug
)
2125 dbg_printf
(_
("new start date: '%s' is '%s'\n"),
2126 str_days
(&pc
, dbg_ord
, sizeof dbg_ord
),
2127 debug_strfdatetime
(&tm
, &pc
, dbg_tm
, sizeof dbg_tm
));
2131 if
(pc.parse_datetime_debug
)
2133 if
(!pc.dates_seen
&& !pc.days_seen
)
2134 dbg_printf
(_
("using current date as starting value: '%s'\n"),
2135 debug_strfdate
(&tm
, dbg_tm
, sizeof dbg_tm
));
2137 if
(pc.days_seen
&& pc.dates_seen
)
2138 dbg_printf
(_
("warning: day (%s) ignored when explicit dates "
2140 str_days
(&pc
, dbg_ord
, sizeof dbg_ord
));
2142 dbg_printf
(_
("starting date/time: '%s'\n"),
2143 debug_strfdatetime
(&tm
, &pc
, dbg_tm
, sizeof dbg_tm
));
2146 /* Add relative date. */
2147 if
(pc.rel.year | pc.rel.month | pc.rel.day
)
2149 if
(pc.parse_datetime_debug
)
2151 if
((pc.rel.year
!= 0 || pc.rel.month
!= 0) && tm.tm_mday
!= 15)
2152 dbg_printf
(_
("warning: when adding relative months/years, "
2153 "it is recommended to specify the 15th of the "
2156 if
(pc.rel.day
!= 0 && tm.tm_hour
!= 12)
2157 dbg_printf
(_
("warning: when adding relative days, "
2158 "it is recommended to specify noon\n"));
2161 int year
, month
, day
;
2162 if
(INT_ADD_WRAPV
(tm.tm_year
, pc.rel.year
, &year
)
2163 || INT_ADD_WRAPV
(tm.tm_mon
, pc.rel.month
, &month
)
2164 || INT_ADD_WRAPV
(tm.tm_mday
, pc.rel.day
, &day
))
2166 if
(pc.parse_datetime_debug
)
2167 dbg_printf
(_
("error: %s:%d\n"), __FILE__
, __LINE__
);
2173 tm.tm_hour
= tm0.tm_hour
;
2174 tm.tm_min
= tm0.tm_min
;
2175 tm.tm_sec
= tm0.tm_sec
;
2176 tm.tm_isdst
= tm0.tm_isdst
;
2177 Start
= mktime_z
(tz
, &tm
);
2178 if
(Start
== (time_t) -1)
2180 if
(pc.parse_datetime_debug
)
2181 dbg_printf
(_
("error: adding relative date resulted "
2182 "in an invalid date: '%s'\n"),
2183 debug_strfdatetime
(&tm
, &pc
, dbg_tm
,
2188 if
(pc.parse_datetime_debug
)
2190 dbg_printf
(_
("after date adjustment "
2191 "(%+"PRIdMAX
" years, %+"PRIdMAX
" months, "
2192 "%+"PRIdMAX
" days),\n"),
2193 pc.rel.year
, pc.rel.month
, pc.rel.day
);
2194 dbg_printf
(_
(" new date/time = '%s'\n"),
2195 debug_strfdatetime
(&tm
, &pc
, dbg_tm
,
2198 /* Warn about crossing DST due to time adjustment.
2199 Example: https://bugs.gnu.org/8357
2200 env TZ=Europe/Helsinki \
2202 -d 'Mon Mar 28 00:36:07 2011 EEST 1 day ago'
2204 This case is different than DST changes due to time adjustment,
2205 i.e., "1 day ago" vs "24 hours ago" are calculated in different
2208 'tm0.tm_isdst' contains the DST of the input date,
2209 'tm.tm_isdst' is the normalized result after calling
2212 if
(tm0.tm_isdst
!= -1 && tm.tm_isdst
!= tm0.tm_isdst
)
2213 dbg_printf
(_
("warning: daylight saving time changed after "
2214 "date adjustment\n"));
2216 /* Warn if the user did not ask to adjust days but mday changed,
2218 user did not ask to adjust months/days but the month changed.
2220 Example for first case:
2221 2016-05-31 + 1 month => 2016-06-31 => 2016-07-01.
2222 User asked to adjust month, but the day changed from 31 to 01.
2224 Example for second case:
2225 2016-02-29 + 1 year => 2017-02-29 => 2017-03-01.
2226 User asked to adjust year, but the month changed from 02 to 03.
2229 && (tm.tm_mday
!= day
2230 ||
(pc.rel.month
== 0 && tm.tm_mon
!= month
)))
2232 dbg_printf
(_
("warning: month/year adjustment resulted in "
2233 "shifted dates:\n"));
2234 char tm_year_buf
[TM_YEAR_BUFSIZE
];
2235 dbg_printf
(_
(" adjusted Y M D: %s %02d %02d\n"),
2236 tm_year_str
(year
, tm_year_buf
), month
+ 1, day
);
2237 dbg_printf
(_
(" normalized Y M D: %s %02d %02d\n"),
2238 tm_year_str
(tm.tm_year
, tm_year_buf
),
2239 tm.tm_mon
+ 1, tm.tm_mday
);
2245 /* The only "output" of this if-block is an updated Start value,
2246 so this block must follow others that clobber Start. */
2249 intmax_t delta
= pc.time_zone
, t1
;
2250 bool overflow
= false
;
2251 #ifdef HAVE_TM_GMTOFF
2252 long int utcoff
= tm.tm_gmtoff
;
2256 int utcoff
= (gmtime_r
(&t
, &gmt
)
2257 ? tm_diff
(&tm
, &gmt
)
2258 : (overflow
= true
, 0));
2260 overflow |
= INT_SUBTRACT_WRAPV
(delta
, utcoff
, &delta
);
2261 overflow |
= INT_SUBTRACT_WRAPV
(Start
, delta
, &t1
);
2262 if
(overflow || time_overflow
(t1
))
2264 if
(pc.parse_datetime_debug
)
2265 dbg_printf
(_
("error: timezone %d caused time_t overflow\n"),
2272 if
(pc.parse_datetime_debug
)
2274 intmax_t Starti
= Start
;
2275 dbg_printf
(_
("'%s' = %"PRIdMAX
" epoch-seconds\n"),
2276 debug_strfdatetime
(&tm
, &pc
, dbg_tm
, sizeof dbg_tm
),
2281 /* Add relative hours, minutes, and seconds. On hosts that support
2282 leap seconds, ignore the possibility of leap seconds; e.g.,
2283 "+ 10 minutes" adds 600 seconds, even if one of them is a
2284 leap second. Typically this is not what the user wants, but it's
2285 too hard to do it the other way, because the time zone indicator
2286 must be applied before relative times, and if mktime is applied
2287 again the time zone will be lost. */
2289 intmax_t orig_ns
= pc.seconds.tv_nsec
;
2290 intmax_t sum_ns
= orig_ns
+ pc.rel.ns
;
2291 int normalized_ns
= (sum_ns % BILLION
+ BILLION
) % BILLION
;
2292 int d4
= (sum_ns
- normalized_ns
) / BILLION
;
2293 intmax_t d1
, t1
, d2
, t2
, t3
, t4
;
2294 if
(INT_MULTIPLY_WRAPV
(pc.rel.hour
, 60 * 60, &d1
)
2295 || INT_ADD_WRAPV
(Start
, d1
, &t1
)
2296 || INT_MULTIPLY_WRAPV
(pc.rel.minutes
, 60, &d2
)
2297 || INT_ADD_WRAPV
(t1
, d2
, &t2
)
2298 || INT_ADD_WRAPV
(t2
, pc.rel.seconds
, &t3
)
2299 || INT_ADD_WRAPV
(t3
, d4
, &t4
)
2300 || time_overflow
(t4
))
2302 if
(pc.parse_datetime_debug
)
2303 dbg_printf
(_
("error: adding relative time caused an "
2308 result
->tv_sec
= t4
;
2309 result
->tv_nsec
= normalized_ns
;
2311 if
(pc.parse_datetime_debug
2312 && (pc.rel.hour | pc.rel.minutes | pc.rel.seconds | pc.rel.ns
))
2314 dbg_printf
(_
("after time adjustment (%+"PRIdMAX
" hours, "
2315 "%+"PRIdMAX
" minutes, "
2316 "%+"PRIdMAX
" seconds, %+d ns),\n"),
2317 pc.rel.hour
, pc.rel.minutes
, pc.rel.seconds
,
2319 dbg_printf
(_
(" new time = %"PRIdMAX
" epoch-seconds\n"), t4
);
2321 /* Warn about crossing DST due to time adjustment.
2322 Example: https://bugs.gnu.org/8357
2323 env TZ=Europe/Helsinki \
2325 -d 'Mon Mar 28 00:36:07 2011 EEST 24 hours ago'
2327 This case is different than DST changes due to days adjustment,
2328 i.e., "1 day ago" vs "24 hours ago" are calculated in different
2331 'tm.tm_isdst' contains the date after date adjustment. */
2333 if
(tm.tm_isdst
!= -1 && localtime_rz
(tz
, &result
->tv_sec
, &lmt
)
2334 && tm.tm_isdst
!= lmt.tm_isdst
)
2335 dbg_printf
(_
("warning: daylight saving time changed after "
2336 "time adjustment\n"));
2341 if
(pc.parse_datetime_debug
)
2343 /* Special case: using 'date -u' simply set TZ=UTC0 */
2345 dbg_printf
(_
("timezone: system default\n"));
2346 else if
(STREQ
(tzstring
, "UTC0"))
2347 dbg_printf
(_
("timezone: Universal Time\n"));
2349 dbg_printf
(_
("timezone: TZ=\"%s\" environment value\n"), tzstring
);
2351 intmax_t sec
= result
->tv_sec
;
2352 int nsec
= result
->tv_nsec
;
2353 dbg_printf
(_
("final: %"PRIdMAX
".%09d (epoch-seconds)\n"),
2357 bool got_utc
= !!gmtime_r
(&result
->tv_sec
, &gmt
);
2359 dbg_printf
(_
("final: %s (UTC)\n"),
2360 debug_strfdatetime
(&gmt
, NULL
,
2361 dbg_tm
, sizeof dbg_tm
));
2362 if
(localtime_rz
(tz
, &result
->tv_sec
, &lmt
))
2364 #ifdef HAVE_TM_GMTOFF
2365 bool got_utcoff
= true
;
2366 long int utcoff
= lmt.tm_gmtoff
;
2368 bool got_utcoff
= got_utc
;
2371 utcoff
= tm_diff
(&lmt
, &gmt
);
2374 dbg_printf
(_
("final: %s (UTC%s)\n"),
2375 debug_strfdatetime
(&lmt
, NULL
, dbg_tm
, sizeof dbg_tm
),
2376 time_zone_str
(utcoff
, time_zone_buf
));
2378 dbg_printf
(_
("final: %s (unknown time zone offset)\n"),
2379 debug_strfdatetime
(&lmt
, NULL
, dbg_tm
, sizeof dbg_tm
));
2386 if
(tz
!= tzdefault
)
2395 main
(int ac
, char **av
)
2399 printf
("Enter date, or blank line to exit.\n\t> ");
2402 buff
[BUFSIZ
- 1] = '\0';
2403 while
(fgets
(buff
, BUFSIZ
- 1, stdin
) && buff
[0])
2406 struct tm
const *tm
;
2407 if
(! parse_datetime
(&d
, buff
, NULL
))
2408 printf
("Bad format - couldn't convert.\n");
2409 else if
(! (tm
= localtime
(&d.tv_sec
)))
2411 intmax_t sec
= d.tv_sec
;
2412 printf
("localtime (%"PRIdMAX
") failed\n", sec
);
2417 char tm_year_buf
[TM_YEAR_BUFSIZE
];
2418 printf
("%s-%02d-%02d %02d:%02d:%02d.%09d\n",
2419 tm_year_str
(tm
->tm_year
, tm_year_buf
),
2420 tm
->tm_mon
+ 1, tm
->tm_mday
,
2421 tm
->tm_hour
, tm
->tm_min
, tm
->tm_sec
, ns
);