From: Alejandro Colomar <alx@kernel.org>
To: Alejandro Colomar <alx@kernel.org>
Cc: linux-man@vger.kernel.org, DJ Delorie <dj@redhat.com>,
Carlos O'Donell <carlos@redhat.com>,
Paul Eggert <eggert@cs.ucla.edu>
Subject: [PATCH v5] ctime.3: Document how to detect invalid or ambiguous times
Date: Sat, 24 Aug 2024 10:46:17 +0200 [thread overview]
Message-ID: <20240824084614.4149-1-alx@kernel.org> (raw)
In-Reply-To: <xned6jlywd.fsf@greed.delorie.com>
[-- Attachment #1: Type: text/plain, Size: 9281 bytes --]
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@redhat.com>
Cc: Carlos O'Donell <carlos@redhat.com>
Cc: Paul Eggert <eggert@cs.ucla.edu>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
---
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 #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
next prev parent reply other threads:[~2024-08-24 8:46 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-08-23 23:11 [PATCH v4] ctime.3: Document how to detect invalid or ambiguous times Alejandro Colomar
[not found] ` <xned6jlywd.fsf@greed.delorie.com>
2024-08-22 23:49 ` [PATCH v3] ctime.3: EXAMPLES: Add example program Alejandro Colomar
2024-08-23 4:57 ` Paul Eggert
2024-08-23 7:02 ` Alejandro Colomar
2024-08-23 7:26 ` Xi Ruoyao
2024-08-23 7:57 ` Vincent Lefevre
2024-08-23 12:28 ` Alejandro Colomar
2024-08-23 12:28 ` Xi Ruoyao
2024-08-23 12:53 ` Vincent Lefevre
2024-08-23 13:12 ` Alejandro Colomar
2024-08-23 13:54 ` Vincent Lefevre
2024-08-23 14:18 ` Alejandro Colomar
2024-08-23 15:26 ` Vincent Lefevre
2024-08-23 17:48 ` Alejandro Colomar
2024-08-23 18:46 ` Robert Elz
2024-08-23 19:08 ` Paul Eggert
2024-08-23 20:09 ` Hans Åberg
2024-08-23 20:34 ` Alejandro Colomar
2024-08-23 14:04 ` Brian Inglis
2024-08-23 14:25 ` Alejandro Colomar
2024-08-23 18:31 ` Brian Inglis
2024-08-23 20:27 ` Alejandro Colomar
2024-08-24 8:46 ` Alejandro Colomar [this message]
2024-08-25 18:33 ` [PATCH v5] ctime.3: Document how to detect invalid or ambiguous times Alejandro Colomar
2024-08-30 11:09 ` [PATCH v6] ctime.3: EXAMPLES: " Alejandro Colomar
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20240824084614.4149-1-alx@kernel.org \
--to=alx@kernel.org \
--cc=carlos@redhat.com \
--cc=dj@redhat.com \
--cc=eggert@cs.ucla.edu \
--cc=linux-man@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox