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: Paul Eggert <eggert@xxxxxxxxxxx> Signed-off-by: Alejandro Colomar <alx@xxxxxxxxxx> --- Hi DJ, Paul! Below is the rendered text. I've tested this program with several "weird" times, and it all makes sense. Please review. I call it v3, since it supersedes DJ's patches. Have a lovely night! Alex EXAMPLES Passing an invalid time to mktime() or an invalid tm‐>tm_isdst value yields unspecified results. 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. The program below uses a wrapper that allows detecting invalid and ambiguous times, with EINVAL and ENOTUNIQ, respectively. The following shell session shows sample runs of the program: $ export TZ=Europe/Madrid; $ $ ./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 2024 03 26 02 17 53 -1; a.out: mktime: Invalid argument 1679793473 $ $ ./a.out 2024 10 29 02 17 53 -1; a.out: mktime: Name not unique on network 1698542273 $ ./a.out 2024 10 29 02 17 53 0; 1698542273 $ ./a.out 2024 10 29 02 17 53 1; 1698538673 $ $ ./a.out 2024 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 <time.h> 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; t = my_mktime(&tm); if (errno == EOVERFLOW) err(EXIT_FAILURE, "mktime"); if (errno == EINVAL || errno == ENOTUNIQ) warn("mktime"); 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; e = errno; errno = 0; tm = *tp; isdst = tp->tm_isdst; t = mktime(tp); if (t == -1 && errno == EOVERFLOW) 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; if (mktime(&tm) == -1 && errno == EOVERFLOW) goto out; if (tm.tm_isdst != tp->tm_isdst) { errno = ENOTUNIQ; return t; } out: errno = e; return t; } man/man3/ctime.3 | 157 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/man/man3/ctime.3 b/man/man3/ctime.3 index 5aec51b79..5ab881978 100644 --- a/man/man3/ctime.3 +++ b/man/man3/ctime.3 @@ -412,6 +412,163 @@ .SH NOTES object types may overwrite the information in any object of the same type pointed to by the value returned from any previous call to any of them." This can occur in the glibc implementation. +.SH EXAMPLES +Passing an invalid time to +.BR mktime () +or an invalid +.I tm->tm_isdst +value +yields unspecified results. +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. +.P +The program below uses 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 +.EX +.RB $\~ "export TZ=Europe/Madrid" ; +$ +.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 +$ +.RB $\~ "./a.out 2024 02 23 00 17 53 \-1" ; +1708643873 +.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 2024 03 26 02 17 53 \-1" ; +a.out: mktime: Invalid argument +1679793473 +$ +.RB $\~ "./a.out 2024 10 29 02 17 53 \-1" ; +a.out: mktime: Name not unique on network +1698542273 +.RB $\~ "./a.out 2024 10 29 02 17 53 0" ; +1698542273 +.RB $\~ "./a.out 2024 10 29 02 17 53 1" ; +1698538673 +$ +.RB $\~ "./a.out 2024 02 29 12 00 00 \-1" ; +a.out: mktime: Invalid argument +1677668400 +.EE +.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 <time.h> +\& +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\[rs]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; + t = my_mktime(&tm); + if (errno == EOVERFLOW) + err(EXIT_FAILURE, "mktime"); + if (errno == EINVAL || errno == ENOTUNIQ) + warn("mktime"); +\& + printf("%ju\[rs]n", (uintmax_t) t); + exit(EXIT_SUCCESS); +} +\& +time_t +my_mktime(struct tm *tp) +{ + int e, isdst; + time_t t; + struct tm tm; +\& + e = errno; + errno = 0; +\& + tm = *tp; + isdst = tp\->tm_isdst; +\& + t = mktime(tp); + if (t == \-1 && errno == EOVERFLOW) + 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; +\& + if (mktime(&tm) == \-1 && errno == EOVERFLOW) + goto out; +\& + if (tm.tm_isdst != tp\->tm_isdst) { + errno = ENOTUNIQ; + return t; + } +out: + errno = e; + return t; +} +.EE +.\" SRC END .SH SEE ALSO .BR date (1), .BR gettimeofday (2), base-commit: 0813c125d8bf754c40015aa1b31f23e0650584ac -- 2.45.2
Attachment:
signature.asc
Description: PGP signature