@@ -1075,9 +1075,16 @@ This is a flag that indicates whether daylight saving time is (or was, or
will be) in effect at the time described. The value is positive if
daylight saving time is in effect, zero if it is not, and negative if the
information is not available.
-Although this flag is useful when passing a broken-down time to the
-@code{mktime} function, for other uses this flag should be ignored and
-the @code{tm_gmtoff} and @code{tm_zone} fields should be inspected instead.
+
+It is recommended to set this field to a negative value when calling
+@code{mktime} unless the application has access to the correct value of
+the daylight saving time status at the specified time. This instructs
+@code{mktime} to use time zone data to determine whether or not to apply
+daylight saving time.
+
+Reading the @code{tm_isdst} field after calling @code{localtime}
+and similar functions is not recommended. Instead inspect the
+the @code{tm_gmtoff} and @code{tm_zone} fields.
@item long int tm_gmtoff
This field describes the time zone that was used to compute this
@@ -1292,6 +1299,33 @@ simple time representation. It also normalizes the contents of the
broken-down time structure, and fills in some components based on the
values of the others.
+In general, it is recommended to set the @code{tm_isdst} member of
+@var{brokentime} to a negative value prior to calling this function.
+However, if @code{*@var{brokentime}} was produced by @code{localtime} or
+similar functions, a correct value of @code{tm_isdst} allows to
+disambiguate broken-down time representations during the overlapping
+window after time jumped back during a daylight saving time transition.
+This is a scenario where @code{tm_isdst} can be helpful, and the
+adjustment described below does not produce unexpected @code{mktime}
+results.
+
+If @code{tm_isdst} is negative, @code{mktime} uses time zone data to
+determine whether daylight saving time is in effect at the requested
+time.
+
+In the other case, @code{tm_isdst} is not negative: the value zero
+indicates that no daylight saving time is in effect, and positive value
+means it is in effect. If time zone data and the @code{tm_isdst} flag
+disagree, @code{mktime} will forcefully adjust the returned value, using
+an unspecified heuristic to obtain the daylight saving time offset.
+Typically, this means that @code{mktime} adds one hour if
+@code{tm_isdst} is zero, or subtracts one hour if @code{tm_isdst} is
+positive. This forced adjustment only happens if time zone data
+indicates that there is at least one daylight saving transition (at any
+time). For zones such as @samp{UTC} which never use daylight saving
+time, the adjustment does not happen even if @code{tm_isdst} is
+positive.
+
The @code{mktime} function ignores the specified contents of the
@code{tm_wday}, @code{tm_yday}, @code{tm_gmtoff}, and @code{tm_zone}
members of the broken-down time
@@ -1342,6 +1376,7 @@ available. @code{timelocal} is rather rare.
@c tzfile_compute(!use_localtime) ok
@code{timegm} is functionally identical to @code{mktime} except it
+sets @code{@var{brokentime}->tm_isdst} to zero and
always takes the input values to be UTC
regardless of any local time zone setting.
@@ -64,6 +64,7 @@ tests := \
tst-gmtime \
tst-itimer \
tst-mktime \
+ tst-mktime-dst-adjust \
tst-mktime2 \
tst-mktime3 \
tst-mktime4 \
@@ -433,7 +433,13 @@ __mktime_internal (struct tm *tp, bool local, mktime_offset_t *offset)
/* We have a match. Check whether tm.tm_isdst has the requested
value, if any. */
- if (isdst_differ (isdst, tm.tm_isdst))
+ if (isdst_differ (isdst, tm.tm_isdst)
+#if _LIBC
+ /* Only perform the adjustment if the time zone ever uses DST.
+ This skips the adjustment for UTC, for example. */
+ && __daylight
+#endif
+ )
{
/* tm.tm_isdst has the wrong value. Look for a neighboring
time with the right value, and use its UTC offset.
new file mode 100644
@@ -0,0 +1,156 @@
+/* Test mktime DST adjustment special cases.
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <time.h>
+#include <stdlib.h>
+#include <support/check.h>
+
+static int
+do_test (void)
+{
+ TEST_COMPARE (setenv ("TZ", "UTC", 1), 0);
+
+ {
+ struct tm t =
+ {
+ .tm_year = 124,
+ .tm_mon = 9,
+ .tm_mday = 1,
+ .tm_hour = 9,
+ .tm_min = 20,
+ .tm_sec = 53,
+ .tm_isdst = 1, /* Not actually true. */
+ };
+ TEST_COMPARE (mktime (&t), 1727774453);
+ }
+
+ /* IST used DST at one point, but no longer does. */
+ {
+ char *path = realpath ("../timezone/testdata/IST", NULL);
+ TEST_VERIFY (path != NULL);
+ TEST_COMPARE (setenv ("TZ", path, 1), 0);
+ free (path);
+ }
+
+ {
+ struct tm t =
+ {
+ .tm_year = 124,
+ .tm_mon = 9,
+ .tm_mday = 1,
+ .tm_hour = 9,
+ .tm_min = 20,
+ .tm_sec = 53,
+ .tm_isdst = 0, /* Correct value. */
+ };
+ TEST_COMPARE (mktime (&t), 1727774453 - (int) (5.5 * 3600));
+ TEST_COMPARE (t.tm_gmtoff, (int) (5.5 * 3600));
+ TEST_COMPARE (t.tm_isdst, 0);
+ }
+
+ /* This value is incorrect, but the heuristic ignores historic
+ DST changes. */
+ {
+ struct tm t =
+ {
+ .tm_year = 124,
+ .tm_mon = 9,
+ .tm_mday = 1,
+ .tm_hour = 9,
+ .tm_min = 20,
+ .tm_sec = 53,
+ .tm_isdst = 1, /* Incorrect value. */
+ };
+ TEST_COMPARE (mktime (&t), 1727774453 - (int) (5.5 * 3600));
+ TEST_COMPARE (t.tm_gmtoff, (int) (5.5 * 3600));
+ TEST_COMPARE (t.tm_isdst, 0);
+ }
+
+ /* Test using correct DST. */
+ {
+ struct tm t =
+ {
+ .tm_year = 42,
+ .tm_mon = 9,
+ .tm_mday = 1,
+ .tm_hour = 9,
+ .tm_min = 20,
+ .tm_sec = 53,
+ .tm_isdst = 1, /* Correct value, DST was in effect. */
+ };
+ TEST_COMPARE (mktime (&t), -860015347);
+ TEST_COMPARE (t.tm_gmtoff, (int) (6.5 * 3600));
+ TEST_COMPARE (t.tm_isdst, 1);
+ }
+
+ /* Mismatch: DST incorrectly claimed not in effect. */
+
+ {
+ struct tm t =
+ {
+ .tm_year = 42,
+ .tm_mon = 9,
+ .tm_mday = 1,
+ .tm_hour = 9,
+ .tm_min = 20,
+ .tm_sec = 53,
+ .tm_isdst = 0, /* Incorrect value. */
+ };
+ TEST_COMPARE (mktime (&t), -860015347 + 3600); /* One hour added. */
+ TEST_COMPARE (t.tm_gmtoff, (int) (6.5 * 3600));
+ TEST_COMPARE (t.tm_isdst, 1);
+ }
+
+ /* Test using correct standard time. */
+ {
+ struct tm t =
+ {
+ .tm_year = 42,
+ .tm_mon = 7,
+ .tm_mday = 1,
+ .tm_hour = 9,
+ .tm_min = 20,
+ .tm_sec = 53,
+ .tm_isdst = 0, /* Correct value, standard time in effect. */
+ };
+ TEST_COMPARE (mktime (&t), -865282147);
+ TEST_COMPARE (t.tm_gmtoff, (int) (5.5 * 3600));
+ TEST_COMPARE (t.tm_isdst, 0);
+ }
+
+ /* Test using standard time with mismatch. */
+ {
+ struct tm t =
+ {
+ .tm_year = 42,
+ .tm_mon = 7,
+ .tm_mday = 1,
+ .tm_hour = 9,
+ .tm_min = 20,
+ .tm_sec = 53,
+ .tm_isdst = 1, /* Incorrect value. */
+ };
+ TEST_COMPARE (mktime (&t), -865282147 - 3600); /* One hour subtracted. */
+ TEST_COMPARE (t.tm_gmtoff, (int) (5.5 * 3600));
+ TEST_COMPARE (t.tm_isdst, 0);
+ }
+
+ return 0;
+}
+
+#include <support/test-driver.c>
new file mode 100644
GIT binary patch
literal 285
zcmWHE%1kq2zzf(I7#LU>7#M^a7#JAZ=kD2c>UNLD8P-CHGgFOLTq+To!N|nS#LUFN
z5Of1%j<*{~wQmFi2LnTN1|yG;ZwQ00ZwP~Da0r98ftj%ZLkM9z*%=rZSQ!`?#Qy*P
z|6gsFga(Lqe*W43M1$M_qCxHe(IB^g>8VC5Ks3}%EX*v-Fh>Pl06EIr4df`_2nHUo
W(*!_H^T2Q(muGN@uBnx=0T%#v+DeN6
literal 0
HcmV?d00001
--
2.46.2