This example documents the corner cases of mktime(3), such as what happens during DST transitions, and other jumps in the calendar. Link: <https://www.redhat.com/en/blog/brief-history-mktime> Reported-by: DJ Delorie <dj@xxxxxxxxxx> Cc: Carlos O'Donell <carlos@xxxxxxxxxx> Cc: Paul Eggert <eggert@xxxxxxxxxxx> Signed-off-by: Alejandro Colomar <alx@xxxxxxxxxx> --- Hi DJ, Paul, This is just a resend of v4. Forget v4. I forgot to send it in reply to the previous versions, and forgot to paste the rendered output. Here's how this looks: CAVEATS ... mktime() ... Passing an invalid time to mktime() or an invalid tm‐>tm_isdst value yields an unspecified result. Also, passing the value -1 in tm‐>tm_isdst will result in an ambiguous time during some DST transitions, which will also yield an unspecified result. See the example program in EXAMPLES. EXAMPLES The program below defines a wrapper that allows detecting invalid and ambiguous times, with EINVAL and ENOTUNIQ, respectively. The following shell session shows sample runs of the program: $ TZ=UTC ./a.out 1969 12 31 23 59 59 0; -1 $ $ export TZ=Europe/Madrid; $ $ ./a.out 2147483647 2147483647 00 00 00 00 ‐1; a.out: mktime: Value too large for defined data type $ $ ./a.out 2024 08 23 00 17 53 -1; 1724365073 $ ./a.out 2024 08 23 00 17 53 0; a.out: mktime: Invalid argument 1724368673 $ ./a.out 2024 08 23 00 17 53 1; 1724365073 $ $ ./a.out 2024 02 23 00 17 53 -1; 1708643873 $ ./a.out 2024 02 23 00 17 53 0; 1708643873 $ ./a.out 2024 02 23 00 17 53 1; a.out: mktime: Invalid argument 1708640273 $ $ ./a.out 2023 03 26 02 17 53 -1; a.out: mktime: Invalid argument 1679793473 $ $ ./a.out 2023 10 29 02 17 53 -1; a.out: mktime: Name not unique on network 1698542273 $ ./a.out 2023 10 29 02 17 53 0; 1698542273 $ ./a.out 2023 10 29 02 17 53 1; 1698538673 $ $ ./a.out 2023 02 29 12 00 00 -1; a.out: mktime: Invalid argument 1677668400 Program source: mktime.c #include <err.h> #include <errno.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #define is_signed(T) ((T) -1 < 1) time_t my_mktime(struct tm *tp); int main(int argc, char *argv[]) { char **p; time_t t; struct tm tm; if (argc != 8) { fprintf(stderr, "Usage: %s yyyy mm dd HH MM SS isdst\n", argv[0]); exit(EXIT_FAILURE); } p = &argv[1]; tm.tm_year = atoi(*p++) - 1900; tm.tm_mon = atoi(*p++) - 1; tm.tm_mday = atoi(*p++); tm.tm_hour = atoi(*p++); tm.tm_min = atoi(*p++); tm.tm_sec = atoi(*p++); tm.tm_isdst = atoi(*p++); errno = 0; tm.tm_wday = -1; t = my_mktime(&tm); if (tm.tm_wday == -1) err(EXIT_FAILURE, "mktime"); if (errno == EINVAL || errno == ENOTUNIQ) warn("mktime"); if (is_signed(time_t)) printf("%jd\n", (intmax_t) t); else printf("%ju\n", (uintmax_t) t); exit(EXIT_SUCCESS); } time_t my_mktime(struct tm *tp) { int e, isdst; time_t t; struct tm tm; unsigned char wday[sizeof(tp->tm_wday)]; e = errno; tm = *tp; isdst = tp->tm_isdst; memcpy(wday, &tp->tm_wday, sizeof(wday)); tp->tm_wday = -1; t = mktime(tp); if (tp->tm_wday == -1) { memcpy(&tp->tm_wday, wday, sizeof(wday)); return -1; } if (isdst == -1) tm.tm_isdst = tp->tm_isdst; if ( tm.tm_sec != tp->tm_sec || tm.tm_min != tp->tm_min || tm.tm_hour != tp->tm_hour || tm.tm_mday != tp->tm_mday || tm.tm_mon != tp->tm_mon || tm.tm_year != tp->tm_year || tm.tm_isdst != tp->tm_isdst) { errno = EINVAL; return t; } if (isdst != -1) goto out; tm = *tp; tm.tm_isdst = !tm.tm_isdst; tm.tm_wday = -1; mktime(&tm); if (tm.tm_wday == -1) goto out; if (tm.tm_isdst != tp->tm_isdst) { errno = ENOTUNIQ; return t; } out: errno = e; return t; } Cheers, Alex Range-diff against v3: 1: e9e31a505 < -: --------- ctime.3: EXAMPLES: Add example program -: --------- > 1: b7ed55965 ctime.3: Document how to detect invalid or ambiguous times man/man3/ctime.3 | 93 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/man/man3/ctime.3 b/man/man3/ctime.3 index 0ad2b530f..934da149e 100644 --- a/man/man3/ctime.3 +++ b/man/man3/ctime.3 @@ -427,7 +427,30 @@ .SS mktime() .I tm->tm_wday field. See the example program in EXAMPLES. +.P +Passing an invalid time to +.BR mktime () +or an invalid +.I tm->tm_isdst +value +yields an unspecified result. +Also, +passing the value +.I \-1 +in +.I tm->tm_isdst +will result in an ambiguous time during some DST transitions, +which will also yield an unspecified result. +See the example program in EXAMPLES. .SH EXAMPLES +The program below defines a wrapper that +allows detecting invalid and ambiguous times, +with +.B EINVAL +and +.BR ENOTUNIQ , +respectively. +.P The following shell session shows sample runs of the program: .P .in +4n @@ -443,6 +466,7 @@ .SH EXAMPLES .RB $\~ "./a.out 2024 08 23 00 17 53 \-1" ; 1724365073 .RB $\~ "./a.out 2024 08 23 00 17 53 0" ; +a.out: mktime: Invalid argument 1724368673 .RB $\~ "./a.out 2024 08 23 00 17 53 1" ; 1724365073 @@ -452,12 +476,15 @@ .SH EXAMPLES .RB $\~ "./a.out 2024 02 23 00 17 53 0" ; 1708643873 .RB $\~ "./a.out 2024 02 23 00 17 53 1" ; +a.out: mktime: Invalid argument 1708640273 $ .RB $\~ "./a.out 2023 03 26 02 17 53 \-1" ; +a.out: mktime: Invalid argument 1679793473 $ .RB $\~ "./a.out 2023 10 29 02 17 53 \-1" ; +a.out: mktime: Name not unique on network 1698542273 .RB $\~ "./a.out 2023 10 29 02 17 53 0" ; 1698542273 @@ -465,6 +492,7 @@ .SH EXAMPLES 1698538673 $ .RB $\~ "./a.out 2023 02 29 12 00 00 \-1" ; +a.out: mktime: Invalid argument 1677668400 .EE .SS Program source: mktime.c @@ -472,13 +500,17 @@ .SS Program source: mktime.c .\" SRC BEGIN (mktime.c) .EX #include <err.h> +#include <errno.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> #include <time.h> \& #define is_signed(T) ((T) \-1 < 1) \& +time_t my_mktime(struct tm *tp); +\& int main(int argc, char *argv[]) { @@ -500,10 +532,13 @@ .SS Program source: mktime.c tm.tm_sec = atoi(*p++); tm.tm_isdst = atoi(*p++); \& + errno = 0; tm.tm_wday = \-1; - t = mktime(&tm); + t = my_mktime(&tm); if (tm.tm_wday == \-1) err(EXIT_FAILURE, "mktime"); + if (errno == EINVAL || errno == ENOTUNIQ) + warn("mktime"); \& if (is_signed(time_t)) printf("%jd\[rs]n", (intmax_t) t); @@ -512,6 +547,62 @@ .SS Program source: mktime.c \& exit(EXIT_SUCCESS); } +\& +time_t +my_mktime(struct tm *tp) +{ + int e, isdst; + time_t t; + struct tm tm; + unsigned char wday[sizeof(tp\->tm_wday)]; +\& + e = errno; +\& + tm = *tp; + isdst = tp\->tm_isdst; +\& + memcpy(wday, &tp\->tm_wday, sizeof(wday)); + tp\->tm_wday = \-1; + t = mktime(tp); + if (tp\->tm_wday == \-1) { + memcpy(&tp\->tm_wday, wday, sizeof(wday)); + return \-1; + } +\& + if (isdst == \-1) + tm.tm_isdst = tp\->tm_isdst; +\& + if ( tm.tm_sec != tp\->tm_sec + || tm.tm_min != tp\->tm_min + || tm.tm_hour != tp\->tm_hour + || tm.tm_mday != tp\->tm_mday + || tm.tm_mon != tp\->tm_mon + || tm.tm_year != tp\->tm_year + || tm.tm_isdst != tp\->tm_isdst) + { + errno = EINVAL; + return t; + } +\& + if (isdst != \-1) + goto out; +\& + tm = *tp; + tm.tm_isdst = !tm.tm_isdst; +\& + tm.tm_wday = \-1; + mktime(&tm); + if (tm.tm_wday == \-1) + goto out; +\& + if (tm.tm_isdst != tp\->tm_isdst) { + errno = ENOTUNIQ; + return t; + } +out: + errno = e; + return t; +} .EE .\" SRC END .SH SEE ALSO base-commit: 6a7f1461b0e5474d50ef1920558dec103c0c058f -- 2.45.2
Attachment:
signature.asc
Description: PGP signature