[PATCH v4] ctime.3: Document how to detect invalid or ambiguous times

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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,

I've rebased on top of the patch from today which added an example
which showed how to check for errors after mktime(3).

This example extends it to also detect non-error corner-case conditions
that are not always wanted: invalid times, and ambiguous times.

It also documents lightly in CAVEATS that the result is unspecified in
any such call, without much details (because it's just unspecified).

DJ, I expect that the wrapper my_mktime(), which is itself a conforming
implementation of mktime(3), is what you were looking for.

Have a lovely night!
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


[Index of Archives]     [Kernel Documentation]     [Netdev]     [Linux Ethernet Bridging]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux