Hi Paul, On Sat, Aug 24, 2024 at 10:46:20AM GMT, Alejandro Colomar wrote: > 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> > --- Should I wait for a review, or are you ok with this patch? > > 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. I think the wording is more or less what you suggested, leaving it as unspecified in those corner cases. Only in the example I show more details, for those who need to know what to do in those cases. > > 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 I'll change these error messages to say "my_mktime" instead of "mktime", since it's not a standard error. Have a lovely day! Alex > 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 > -- <https://www.alejandro-colomar.es/>
Attachment:
signature.asc
Description: PGP signature