* [PATCH 00/59] time: sync mktime from Gnulib
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-06 14:30 ` Carlos O'Donell
2025-01-05 5:56 ` [PATCH 01/59] Split intprops.h into two Paul Eggert
` (59 subsequent siblings)
60 siblings, 1 reply; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
This patch series syncs mktime from Gnulib. Among the issues it fixes
is the problem reported by Florian a couple of days ago
in "mktime tm_isdst compatibility improvements"
<https://sourceware.org/pipermail/libc-alpha/2025-January/163342.html>.
This patch series fixes that problem differently from Florian's proposal,
without introducing a new glibc tunable.
Florian Weimer (1):
time: test DST adjustment for DST-less zones
Paul Eggert (58):
Split intprops.h into two
Support stdckdint.h internally if older GCC
Fix mishandling of default DST rule
Don't worry about !TZDEFAULT
Support static_assert in pre-C23 C
Sync mktime.c from Gnulib 2024-10-04
Document mktime out-of-range + tm_isdst
Push tzset lock into callers of time functions
Omit parse_offset unnecessary *tz == '\0'
Don’t assume TZif bloat is likely
tzset no longer calls ftello
Check TZif files more carefully
Remove alignment assumption from __tzfile_read
Treat oddball UTC offset strings as zero
Simplify __tz_compute
Improve performance in 2 BC
Don’t mishandle years < 1970 in compute_change
Match RFC 9636 names
Trivial tzset_internal speedup
Fix obscure bug when stdoffset == dstoffset
Document assumption that NO_DST == 0
__use_tzfile is boolean
Minor tzfile.c clarity improvements
Stop using zic's -y option
Fix mishandling of default DST rule
Simplify treatment of missing and empty TZ
TZ="" always means UTC sans leap seconds
Prefer "UTC" for UTC
Simplify setting tz_rules to UTC
Update tzname etc. even if TZ is unchanged
"POSIX TZ" -> "proleptic TZ"
Reject invalid TZ strings in TZif files
Reject TZif files containing '\0' in TZ string
mktime should not consult 'daylight'
Remove __use_tzfile
Remove arbitrary limit on TZ, TZDIR lengths
Tighten setuid TZif file name check
Avoid a malloc in __tzfile_read
Free earlier in __tzfile_read
Improve __tzfile_read TZ="" comment
Refactor enum tz_rule_type
__offtime now returns struct tm *
Fix overflow checking in offtime
Handle TZif time type 0 correctly
Remove invalid tzset tests from tst-bz29951.c
tzset, not localtime, now parses TZif TZ spec
Minor compute_change widening tweak
Minor SECSPERDAY widening tweak
Remove leap-second goto from __tzfile_compute
Remove use_last label from __tzfile_compute
Refactor ‘if’ in __tzfile_compute
Refactor localtime ‘if’ in __tzfile_compute
Refactor post-transition-table ‘if’
Refactor __tzfile_compute to avoid two gotos
Refactor __tzfile_compute index local
mktime: prefer bool to int
mktime: port __mktime64 locking to Gnulib
mktime: improve tm_isdst heuristic
CONTRIBUTED-BY | 9 +
Makeconfig | 16 +-
NEWS | 18 +
SHARED-FILES | 6 +-
include/intprops-internal.h | 400 ++++++++++++
include/intprops.h | 356 +----------
include/stdckdint.h | 6 +
include/stdckdint.in.h | 35 +
include/sys/cdefs.h | 8 +
include/time.h | 37 +-
manual/time.texi | 69 +-
resolv/ns_date.c | 2 +-
sysdeps/generic/netinet/in_systm.h | 2 +-
sysdeps/nptl/pthread.h | 2 +-
time/Makefile | 1 +
time/bits/types/struct_timeb.h | 2 +-
time/gmtime.c | 8 +-
time/localtime.c | 8 +-
time/mktime-internal.h | 14 +-
time/mktime.c | 172 ++---
time/offtime.c | 70 +-
time/strftime_l.c | 3 +-
time/sys/time.h | 2 +-
time/timegm.c | 10 +-
time/tst-mktime-dst-adjust.c | 156 +++++
time/tst-mktime2.c | 4 +-
time/tst-posixtz.c | 65 +-
time/tst-tzname.c | 110 +++-
time/tzfile.c | 983 +++++++++++++----------------
time/tzset.c | 510 ++++++++-------
time/tzset.h | 29 +
timezone/Makefile | 5 +-
timezone/private.h | 2 +-
timezone/test-tz.c | 1 +
timezone/testdata/IST | Bin 0 -> 285 bytes
timezone/tst-bz29951.c | 8 -
timezone/tst-timezone.c | 64 +-
timezone/version | 2 +-
38 files changed, 1865 insertions(+), 1330 deletions(-)
create mode 100644 include/intprops-internal.h
create mode 100644 include/stdckdint.h
create mode 100644 include/stdckdint.in.h
create mode 100644 time/tst-mktime-dst-adjust.c
create mode 100644 time/tzset.h
create mode 100644 timezone/testdata/IST
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* Re: [PATCH 00/59] time: sync mktime from Gnulib
2025-01-05 5:56 ` [PATCH 00/59] time: sync mktime from Gnulib Paul Eggert
@ 2025-01-06 14:30 ` Carlos O'Donell
2025-01-06 21:34 ` Joseph Myers
0 siblings, 1 reply; 72+ messages in thread
From: Carlos O'Donell @ 2025-01-06 14:30 UTC (permalink / raw)
To: Paul Eggert, libc-alpha
On 1/5/25 12:56 AM, Paul Eggert wrote:
> This patch series syncs mktime from Gnulib. Among the issues it fixes
> is the problem reported by Florian a couple of days ago
> in "mktime tm_isdst compatibility improvements"
> <https://sourceware.org/pipermail/libc-alpha/2025-January/163342.html>.
> This patch series fixes that problem differently from Florian's proposal,
> without introducing a new glibc tunable.
Please do not push this while we are preparing for glibc 2.41 to go out
on February 1st (I see you mention this in your other email, but I'll note
the version and date here).
Please note that pre-commit CI has failed for this series and the patches
introduce link namespace failures.
Link namespace failures can be seen here:
https://patchwork.sourceware.org/project/glibc/patch/20250105055750.1668721-60-eggert@cs.ucla.edu/
--
Cheers,
Carlos.
^ permalink raw reply [flat|nested] 72+ messages in thread
* Re: [PATCH 00/59] time: sync mktime from Gnulib
2025-01-06 14:30 ` Carlos O'Donell
@ 2025-01-06 21:34 ` Joseph Myers
2025-01-06 21:43 ` Paul Eggert
0 siblings, 1 reply; 72+ messages in thread
From: Joseph Myers @ 2025-01-06 21:34 UTC (permalink / raw)
To: Carlos O'Donell; +Cc: Paul Eggert, libc-alpha
On Mon, 6 Jan 2025, Carlos O'Donell wrote:
> On 1/5/25 12:56 AM, Paul Eggert wrote:
> > This patch series syncs mktime from Gnulib. Among the issues it fixes
> > is the problem reported by Florian a couple of days ago
> > in "mktime tm_isdst compatibility improvements"
> > <https://sourceware.org/pipermail/libc-alpha/2025-January/163342.html>.
> > This patch series fixes that problem differently from Florian's proposal,
> > without introducing a new glibc tunable.
>
> Please do not push this while we are preparing for glibc 2.41 to go out
> on February 1st (I see you mention this in your other email, but I'll note
> the version and date here).
>
> Please note that pre-commit CI has failed for this series and the patches
> introduce link namespace failures.
>
> Link namespace failures can be seen here:
> https://patchwork.sourceware.org/project/glibc/patch/20250105055750.1668721-60-eggert@cs.ucla.edu/
Those look like conform (compile-time) namespace failures, not link
namespace. Concretely, in patch 5, the static_assert definition in the
sys/cdefs.h wrapper needs to be under a !_ISOMAC conditional.
--
Joseph S. Myers
josmyers@redhat.com
^ permalink raw reply [flat|nested] 72+ messages in thread
* Re: [PATCH 00/59] time: sync mktime from Gnulib
2025-01-06 21:34 ` Joseph Myers
@ 2025-01-06 21:43 ` Paul Eggert
0 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-06 21:43 UTC (permalink / raw)
To: Joseph Myers, Carlos O'Donell; +Cc: libc-alpha
On 1/6/25 13:34, Joseph Myers wrote:
> in patch 5, the static_assert definition in the
> sys/cdefs.h wrapper needs to be under a !_ISOMAC conditional
Thanks for diagnosing that. I'll put that into v2 (no rush since the
patchset won't be committed before the next release).
The static_assert stuff is because Gnulib arranges for C23-style
single-argument static_assert to work regardless of what standard files
are included, whereas glibc code doesn't assume C23 yet.
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 01/59] Split intprops.h into two
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
2025-01-05 5:56 ` [PATCH 00/59] time: sync mktime from Gnulib Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:56 ` [PATCH 02/59] Support stdckdint.h internally if older GCC Paul Eggert
` (58 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
This syncs from Gnulib, and prepares for stdckdint.h support.
* include/intprops-internal.h: New file, copied from Gnulib.
* include/intprops.h: Sync from Gnulib.
---
CONTRIBUTED-BY | 3 +
SHARED-FILES | 4 +-
include/intprops-internal.h | 400 ++++++++++++++++++++++++++++++++++++
include/intprops.h | 356 +++-----------------------------
4 files changed, 432 insertions(+), 331 deletions(-)
create mode 100644 include/intprops-internal.h
diff --git a/CONTRIBUTED-BY b/CONTRIBUTED-BY
index 0cb379cc9d..652642fe86 100644
--- a/CONTRIBUTED-BY
+++ b/CONTRIBUTED-BY
@@ -1406,6 +1406,9 @@ include/atomic.h:
include/inline-hashtab.h:
Contributed by Alexandre Oliva <aoliva@redhat.com>
+include/intprops-internal.h:
+ Written by Paul Eggert.
+
include/intprops.h:
Written by Paul Eggert.
diff --git a/SHARED-FILES b/SHARED-FILES
index 032c407881..444eff5141 100644
--- a/SHARED-FILES
+++ b/SHARED-FILES
@@ -29,7 +29,9 @@ gnulib:
argp/argp.h
dirent/alphasort.c
dirent/scandir.c
- # Merged from gnulib 2021-09-21
+ # Merged from gnulib 2024-10-14
+ include/intprops-internal.h
+ # Merged from gnulib 2024-10-14
include/intprops.h
# Merged from gnulib 2021-09-21
include/regex.h
diff --git a/include/intprops-internal.h b/include/intprops-internal.h
new file mode 100644
index 0000000000..62de3c889e
--- /dev/null
+++ b/include/intprops-internal.h
@@ -0,0 +1,400 @@
+/* intprops-internal.h -- properties of integer types not visible to users
+
+ Copyright (C) 2001-2025 Free Software Foundation, Inc.
+
+ This program 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.
+
+ This program 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 this program. If not, see <https://www.gnu.org/licenses/>. */
+
+#ifndef _GL_INTPROPS_INTERNAL_H
+#define _GL_INTPROPS_INTERNAL_H
+
+#include <limits.h>
+
+/* Pacify GCC 13.2 in some calls to _GL_EXPR_SIGNED. */
+#if 4 < __GNUC__ + (3 <= __GNUC_MINOR__) && !defined __clang__
+# pragma GCC diagnostic ignored "-Wtype-limits"
+#endif
+
+/* Return a value with the common real type of E and V and the value of V.
+ Do not evaluate E. */
+#define _GL_INT_CONVERT(e, v) ((1 ? 0 : (e)) + (v))
+
+/* Act like _GL_INT_CONVERT (E, -V) but work around a bug in IRIX 6.5 cc; see
+ <https://lists.gnu.org/r/bug-gnulib/2011-05/msg00406.html>. */
+#define _GL_INT_NEGATE_CONVERT(e, v) ((1 ? 0 : (e)) - (v))
+
+/* The extra casts in the following macros work around compiler bugs,
+ e.g., in Cray C 5.0.3.0. */
+
+/* True if the real type T is signed. */
+#define _GL_TYPE_SIGNED(t) (! ((t) 0 < (t) -1))
+
+/* Return 1 if the real expression E, after promotion, has a
+ signed or floating type. Do not evaluate E. */
+#define _GL_EXPR_SIGNED(e) (_GL_INT_NEGATE_CONVERT (e, 1) < 0)
+
+
+/* Minimum and maximum values for integer types and expressions. */
+
+/* The width in bits of the integer type or expression T.
+ Do not evaluate T. T must not be a bit-field expression.
+ Padding bits are not supported; this is checked at compile-time below. */
+#define _GL_TYPE_WIDTH(t) (sizeof (t) * CHAR_BIT)
+
+/* The maximum and minimum values for the type of the expression E,
+ after integer promotion. E is not evaluated. */
+#define _GL_INT_MINIMUM(e) \
+ (_GL_EXPR_SIGNED (e) \
+ ? ~ _GL_SIGNED_INT_MAXIMUM (e) \
+ : _GL_INT_CONVERT (e, 0))
+#define _GL_INT_MAXIMUM(e) \
+ (_GL_EXPR_SIGNED (e) \
+ ? _GL_SIGNED_INT_MAXIMUM (e) \
+ : _GL_INT_NEGATE_CONVERT (e, 1))
+#define _GL_SIGNED_INT_MAXIMUM(e) \
+ (((_GL_INT_CONVERT (e, 1) << (_GL_TYPE_WIDTH (+ (e)) - 2)) - 1) * 2 + 1)
+
+/* Work around OpenVMS incompatibility with C99. */
+#if !defined LLONG_MAX && defined __INT64_MAX
+# define LLONG_MAX __INT64_MAX
+# define LLONG_MIN __INT64_MIN
+#endif
+
+/* This include file assumes that signed types are two's complement without
+ padding bits; the above macros have undefined behavior otherwise.
+ If this is a problem for you, please let us know how to fix it for your host.
+ This assumption is tested by the intprops-tests module. */
+
+/* Does the __typeof__ keyword work? This could be done by
+ 'configure', but for now it's easier to do it by hand. */
+#if ((defined __GNUC__ && 2 <= __GNUC__) \
+ || (defined __clang_major__ && 4 <= __clang_major__) \
+ || (defined __IBMC__ && 1210 <= __IBMC__ && defined __IBM__TYPEOF__) \
+ || (defined __SUNPRO_C && 0x5110 <= __SUNPRO_C && !__STDC__) \
+ || (defined _MSC_VER && 1939 <= _MSC_VER))
+# define _GL_HAVE___TYPEOF__ 1
+#else
+# define _GL_HAVE___TYPEOF__ 0
+#endif
+
+/* Return 1 if the integer type or expression T might be signed. Return 0
+ if it is definitely unsigned. T must not be a bit-field expression.
+ This macro does not evaluate its argument, and expands to an
+ integer constant expression. */
+#if _GL_HAVE___TYPEOF__
+# define _GL_SIGNED_TYPE_OR_EXPR(t) _GL_TYPE_SIGNED (__typeof__ (t))
+#else
+# define _GL_SIGNED_TYPE_OR_EXPR(t) 1
+#endif
+
+/* Return 1 if - A would overflow in [MIN,MAX] arithmetic.
+ A should not have side effects, and A's type should be an
+ integer with minimum value MIN and maximum MAX. */
+#define _GL_INT_NEGATE_RANGE_OVERFLOW(a, min, max) \
+ ((min) < 0 ? (a) < - (max) : 0 < (a))
+
+/* True if __builtin_add_overflow (A, B, P) and __builtin_sub_overflow
+ (A, B, P) work when P is non-null. */
+#ifdef __EDG__
+/* EDG-based compilers like nvc 22.1 cannot add 64-bit signed to unsigned
+ <https://bugs.gnu.org/53256>. */
+# define _GL_HAS_BUILTIN_ADD_OVERFLOW 0
+#elif defined __has_builtin
+# define _GL_HAS_BUILTIN_ADD_OVERFLOW __has_builtin (__builtin_add_overflow)
+/* __builtin_{add,sub}_overflow exists but is not reliable in GCC 5.x and 6.x,
+ see <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98269>. */
+#elif 7 <= __GNUC__
+# define _GL_HAS_BUILTIN_ADD_OVERFLOW 1
+#else
+# define _GL_HAS_BUILTIN_ADD_OVERFLOW 0
+#endif
+
+/* True if __builtin_mul_overflow (A, B, P) works when P is non-null. */
+#if defined __clang_major__ && __clang_major__ < 14
+/* Work around Clang bug <https://bugs.llvm.org/show_bug.cgi?id=16404>. */
+# define _GL_HAS_BUILTIN_MUL_OVERFLOW 0
+#else
+# define _GL_HAS_BUILTIN_MUL_OVERFLOW _GL_HAS_BUILTIN_ADD_OVERFLOW
+#endif
+
+/* True if __builtin_add_overflow_p (A, B, C) works, and similarly for
+ __builtin_sub_overflow_p and __builtin_mul_overflow_p. */
+#ifdef __EDG__
+/* In EDG-based compilers like ICC 2021.3 and earlier,
+ __builtin_add_overflow_p etc. are not treated as integral constant
+ expressions even when all arguments are. */
+# define _GL_HAS_BUILTIN_OVERFLOW_P 0
+#elif defined __has_builtin
+# define _GL_HAS_BUILTIN_OVERFLOW_P __has_builtin (__builtin_mul_overflow_p)
+#else
+# define _GL_HAS_BUILTIN_OVERFLOW_P (7 <= __GNUC__)
+#endif
+
+#if (!defined _GL_STDCKDINT_H && 202311 <= __STDC_VERSION__ \
+ && ! (_GL_HAS_BUILTIN_ADD_OVERFLOW && _GL_HAS_BUILTIN_MUL_OVERFLOW))
+# include <stdckdint.h>
+#endif
+
+/* Store the low-order bits of A + B, A - B, A * B, respectively, into *R.
+ Return 1 if the result overflows. Arguments should not have side
+ effects and A, B and *R can be of any integer type other than char,
+ bool, a bit-precise integer type, or an enumeration type. */
+#if _GL_HAS_BUILTIN_ADD_OVERFLOW
+# define _GL_INT_ADD_WRAPV(a, b, r) __builtin_add_overflow (a, b, r)
+# define _GL_INT_SUBTRACT_WRAPV(a, b, r) __builtin_sub_overflow (a, b, r)
+#elif defined ckd_add && defined ckd_sub && !defined _GL_STDCKDINT_H
+# define _GL_INT_ADD_WRAPV(a, b, r) ckd_add (r, + (a), + (b))
+# define _GL_INT_SUBTRACT_WRAPV(a, b, r) ckd_sub (r, + (a), + (b))
+#else
+# define _GL_INT_ADD_WRAPV(a, b, r) \
+ _GL_INT_OP_WRAPV (a, b, r, +, _GL_INT_ADD_RANGE_OVERFLOW)
+# define _GL_INT_SUBTRACT_WRAPV(a, b, r) \
+ _GL_INT_OP_WRAPV (a, b, r, -, _GL_INT_SUBTRACT_RANGE_OVERFLOW)
+#endif
+#if _GL_HAS_BUILTIN_MUL_OVERFLOW
+# if ((9 < __GNUC__ + (3 <= __GNUC_MINOR__) \
+ || (__GNUC__ == 8 && 4 <= __GNUC_MINOR__)) \
+ && !defined __clang__ && !defined __EDG__)
+# define _GL_INT_MULTIPLY_WRAPV(a, b, r) __builtin_mul_overflow (a, b, r)
+# else
+ /* Work around GCC bug 91450. */
+# define _GL_INT_MULTIPLY_WRAPV(a, b, r) \
+ ((!_GL_SIGNED_TYPE_OR_EXPR (*(r)) && _GL_EXPR_SIGNED (a) && _GL_EXPR_SIGNED (b) \
+ && _GL_INT_MULTIPLY_RANGE_OVERFLOW (a, b, \
+ (__typeof__ (*(r))) 0, \
+ (__typeof__ (*(r))) -1)) \
+ ? ((void) __builtin_mul_overflow (a, b, r), 1) \
+ : __builtin_mul_overflow (a, b, r))
+# endif
+#elif defined ckd_mul && !defined _GL_STDCKDINT_H
+# define _GL_INT_MULTIPLY_WRAPV(a, b, r) ckd_mul (r, + (a), + (b))
+#else
+# define _GL_INT_MULTIPLY_WRAPV(a, b, r) \
+ _GL_INT_OP_WRAPV (a, b, r, *, _GL_INT_MULTIPLY_RANGE_OVERFLOW)
+#endif
+
+/* Nonzero if this compiler has GCC bug 68193 or Clang bug 25390. See:
+ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68193
+ https://llvm.org/bugs/show_bug.cgi?id=25390
+ For now, assume GCC < 14 and all Clang versions generate bogus
+ warnings for _Generic. This matters only for compilers that
+ lack relevant builtins. */
+#if (__GNUC__ && __GNUC__ < 14) || defined __clang__
+# define _GL__GENERIC_BOGUS 1
+#else
+# define _GL__GENERIC_BOGUS 0
+#endif
+
+/* Store the low-order bits of A <op> B into *R, where OP specifies
+ the operation and OVERFLOW the overflow predicate. Return 1 if the
+ result overflows. Arguments should not have side effects,
+ and A, B and *R can be of any integer type other than char, bool, a
+ bit-precise integer type, or an enumeration type. */
+#if 201112 <= __STDC_VERSION__ && !_GL__GENERIC_BOGUS
+# define _GL_INT_OP_WRAPV(a, b, r, op, overflow) \
+ (_Generic \
+ (*(r), \
+ signed char: \
+ _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
+ signed char, SCHAR_MIN, SCHAR_MAX), \
+ unsigned char: \
+ _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
+ unsigned char, 0, UCHAR_MAX), \
+ short int: \
+ _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
+ short int, SHRT_MIN, SHRT_MAX), \
+ unsigned short int: \
+ _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
+ unsigned short int, 0, USHRT_MAX), \
+ int: \
+ _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
+ int, INT_MIN, INT_MAX), \
+ unsigned int: \
+ _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
+ unsigned int, 0, UINT_MAX), \
+ long int: \
+ _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long int, \
+ long int, LONG_MIN, LONG_MAX), \
+ unsigned long int: \
+ _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long int, \
+ unsigned long int, 0, ULONG_MAX), \
+ long long int: \
+ _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long long int, \
+ long long int, LLONG_MIN, LLONG_MAX), \
+ unsigned long long int: \
+ _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long long int, \
+ unsigned long long int, 0, ULLONG_MAX)))
+#else
+/* Store the low-order bits of A <op> B into *R, where OP specifies
+ the operation and OVERFLOW the overflow predicate. If *R is
+ signed, its type is ST with bounds SMIN..SMAX; otherwise its type
+ is UT with bounds U..UMAX. ST and UT are narrower than int.
+ Return 1 if the result overflows. Arguments should not have side
+ effects, and A, B and *R can be of any integer type other than
+ char, bool, a bit-precise integer type, or an enumeration type. */
+# if _GL_HAVE___TYPEOF__
+# define _GL_INT_OP_WRAPV_SMALLISH(a,b,r,op,overflow,st,smin,smax,ut,umax) \
+ (_GL_TYPE_SIGNED (__typeof__ (*(r))) \
+ ? _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, st, smin, smax) \
+ : _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, ut, 0, umax))
+# else
+# define _GL_INT_OP_WRAPV_SMALLISH(a,b,r,op,overflow,st,smin,smax,ut,umax) \
+ (overflow (a, b, smin, smax) \
+ ? (overflow (a, b, 0, umax) \
+ ? (*(r) = _GL_INT_OP_WRAPV_VIA_UNSIGNED (a,b,op,unsigned,st), 1) \
+ : (*(r) = _GL_INT_OP_WRAPV_VIA_UNSIGNED (a,b,op,unsigned,st)) < 0) \
+ : (overflow (a, b, 0, umax) \
+ ? (*(r) = _GL_INT_OP_WRAPV_VIA_UNSIGNED (a,b,op,unsigned,st)) >= 0 \
+ : (*(r) = _GL_INT_OP_WRAPV_VIA_UNSIGNED (a,b,op,unsigned,st), 0)))
+# endif
+
+# define _GL_INT_OP_WRAPV(a, b, r, op, overflow) \
+ (sizeof *(r) == sizeof (signed char) \
+ ? _GL_INT_OP_WRAPV_SMALLISH (a, b, r, op, overflow, \
+ signed char, SCHAR_MIN, SCHAR_MAX, \
+ unsigned char, UCHAR_MAX) \
+ : sizeof *(r) == sizeof (short int) \
+ ? _GL_INT_OP_WRAPV_SMALLISH (a, b, r, op, overflow, \
+ short int, SHRT_MIN, SHRT_MAX, \
+ unsigned short int, USHRT_MAX) \
+ : sizeof *(r) == sizeof (int) \
+ ? (_GL_EXPR_SIGNED (*(r)) \
+ ? _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
+ int, INT_MIN, INT_MAX) \
+ : _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
+ unsigned int, 0, UINT_MAX)) \
+ : _GL_INT_OP_WRAPV_LONGISH(a, b, r, op, overflow))
+# ifdef LLONG_MAX
+# define _GL_INT_OP_WRAPV_LONGISH(a, b, r, op, overflow) \
+ (sizeof *(r) == sizeof (long int) \
+ ? (_GL_EXPR_SIGNED (*(r)) \
+ ? _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long int, \
+ long int, LONG_MIN, LONG_MAX) \
+ : _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long int, \
+ unsigned long int, 0, ULONG_MAX)) \
+ : (_GL_EXPR_SIGNED (*(r)) \
+ ? _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long long int, \
+ long long int, LLONG_MIN, LLONG_MAX) \
+ : _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long long int, \
+ unsigned long long int, 0, ULLONG_MAX)))
+# else
+# define _GL_INT_OP_WRAPV_LONGISH(a, b, r, op, overflow) \
+ (_GL_EXPR_SIGNED (*(r)) \
+ ? _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long int, \
+ long int, LONG_MIN, LONG_MAX) \
+ : _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long int, \
+ unsigned long int, 0, ULONG_MAX))
+# endif
+#endif
+
+/* Store the low-order bits of A <op> B into *R, where the operation
+ is given by OP. Use the unsigned type UT for calculation to avoid
+ overflow problems. *R's type is T, with extrema TMIN and TMAX.
+ T can be any signed integer type other than char, bool, a
+ bit-precise integer type, or an enumeration type.
+ Return 1 if the result overflows. */
+#define _GL_INT_OP_CALC(a, b, r, op, overflow, ut, t, tmin, tmax) \
+ (overflow (a, b, tmin, tmax) \
+ ? (*(r) = _GL_INT_OP_WRAPV_VIA_UNSIGNED (a, b, op, ut, t), 1) \
+ : (*(r) = _GL_INT_OP_WRAPV_VIA_UNSIGNED (a, b, op, ut, t), 0))
+
+/* Return 1 if the integer expressions A - B and -A would overflow,
+ respectively. Arguments should not have side effects,
+ and can be any signed integer type other than char, bool, a
+ bit-precise integer type, or an enumeration type.
+ These macros are tuned for their last input argument being a constant. */
+
+#if _GL_HAS_BUILTIN_OVERFLOW_P
+# define _GL_INT_NEGATE_OVERFLOW(a) \
+ __builtin_sub_overflow_p (0, a, (__typeof__ (- (a))) 0)
+#else
+# define _GL_INT_NEGATE_OVERFLOW(a) \
+ _GL_INT_NEGATE_RANGE_OVERFLOW (a, _GL_INT_MINIMUM (a), _GL_INT_MAXIMUM (a))
+#endif
+
+/* Return the low-order bits of A <op> B, where the operation is given
+ by OP. Use the unsigned type UT for calculation to avoid undefined
+ behavior on signed integer overflow, and convert the result to type T.
+ UT is at least as wide as T and is no narrower than unsigned int,
+ T is two's complement, and there is no padding or trap representations.
+ Assume that converting UT to T yields the low-order bits, as is
+ done in all known two's-complement C compilers. E.g., see:
+ https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html
+
+ According to the C standard, converting UT to T yields an
+ implementation-defined result or signal for values outside T's
+ range. However, code that works around this theoretical problem
+ runs afoul of a compiler bug in Oracle Studio 12.3 x86. See:
+ https://lists.gnu.org/r/bug-gnulib/2017-04/msg00049.html
+ As the compiler bug is real, don't try to work around the
+ theoretical problem. */
+
+#define _GL_INT_OP_WRAPV_VIA_UNSIGNED(a, b, op, ut, t) \
+ ((t) ((ut) (a) op (ut) (b)))
+
+/* Return true if the numeric values A + B, A - B, A * B fall outside
+ the range TMIN..TMAX. Arguments should not have side effects
+ and can be any integer type other than char, bool,
+ a bit-precise integer type, or an enumeration type.
+ TMIN should be signed and nonpositive.
+ TMAX should be positive, and should be signed unless TMIN is zero. */
+#define _GL_INT_ADD_RANGE_OVERFLOW(a, b, tmin, tmax) \
+ ((b) < 0 \
+ ? (((tmin) \
+ ? ((_GL_EXPR_SIGNED (_GL_INT_CONVERT (a, (tmin) - (b))) || (b) < (tmin)) \
+ && (a) < (tmin) - (b)) \
+ : (a) <= -1 - (b)) \
+ || ((_GL_EXPR_SIGNED (a) ? 0 <= (a) : (tmax) < (a)) && (tmax) < (a) + (b))) \
+ : (a) < 0 \
+ ? (((tmin) \
+ ? ((_GL_EXPR_SIGNED (_GL_INT_CONVERT (b, (tmin) - (a))) || (a) < (tmin)) \
+ && (b) < (tmin) - (a)) \
+ : (b) <= -1 - (a)) \
+ || ((_GL_EXPR_SIGNED (_GL_INT_CONVERT (a, b)) || (tmax) < (b)) \
+ && (tmax) < (a) + (b))) \
+ : (tmax) < (b) || (tmax) - (b) < (a))
+#define _GL_INT_SUBTRACT_RANGE_OVERFLOW(a, b, tmin, tmax) \
+ (((a) < 0) == ((b) < 0) \
+ ? ((a) < (b) \
+ ? !(tmin) || -1 - (tmin) < (b) - (a) - 1 \
+ : (tmax) < (a) - (b)) \
+ : (a) < 0 \
+ ? ((!_GL_EXPR_SIGNED (_GL_INT_CONVERT ((a) - (tmin), b)) && (a) - (tmin) < 0) \
+ || (a) - (tmin) < (b)) \
+ : ((! (_GL_EXPR_SIGNED (_GL_INT_CONVERT (tmax, b)) \
+ && _GL_EXPR_SIGNED (_GL_INT_CONVERT ((tmax) + (b), a))) \
+ && (tmax) <= -1 - (b)) \
+ || (tmax) + (b) < (a)))
+#define _GL_INT_MULTIPLY_RANGE_OVERFLOW(a, b, tmin, tmax) \
+ ((b) < 0 \
+ ? ((a) < 0 \
+ ? (_GL_EXPR_SIGNED (_GL_INT_CONVERT (tmax, b)) \
+ ? (a) < (tmax) / (b) \
+ : ((_GL_INT_NEGATE_OVERFLOW (b) \
+ ? _GL_INT_CONVERT (b, tmax) >> (_GL_TYPE_WIDTH (+ (b)) - 1) \
+ : (tmax) / -(b)) \
+ <= -1 - (a))) \
+ : _GL_INT_NEGATE_OVERFLOW (_GL_INT_CONVERT (b, tmin)) && (b) == -1 \
+ ? (_GL_EXPR_SIGNED (a) \
+ ? 0 < (a) + (tmin) \
+ : 0 < (a) && -1 - (tmin) < (a) - 1) \
+ : (tmin) / (b) < (a)) \
+ : (b) == 0 \
+ ? 0 \
+ : ((a) < 0 \
+ ? (_GL_INT_NEGATE_OVERFLOW (_GL_INT_CONVERT (a, tmin)) && (a) == -1 \
+ ? (_GL_EXPR_SIGNED (b) ? 0 < (b) + (tmin) : -1 - (tmin) < (b) - 1) \
+ : (tmin) / (a) < (b)) \
+ : (tmax) / (b) < (a)))
+
+#endif /* _GL_INTPROPS_INTERNAL_H */
diff --git a/include/intprops.h b/include/intprops.h
index 140e51054a..92dfef2500 100644
--- a/include/intprops.h
+++ b/include/intprops.h
@@ -15,19 +15,10 @@
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
-
#ifndef _GL_INTPROPS_H
#define _GL_INTPROPS_H
-#include <limits.h>
-
-/* Return a value with the common real type of E and V and the value of V.
- Do not evaluate E. */
-#define _GL_INT_CONVERT(e, v) ((1 ? 0 : (e)) + (v))
-
-/* Act like _GL_INT_CONVERT (E, -V) but work around a bug in IRIX 6.5 cc; see
- <https://lists.gnu.org/r/bug-gnulib/2011-05/msg00406.html>. */
-#define _GL_INT_NEGATE_CONVERT(e, v) ((1 ? 0 : (e)) - (v))
+#include "intprops-internal.h"
/* The extra casts in the following macros work around compiler bugs,
e.g., in Cray C 5.0.3.0. */
@@ -37,11 +28,11 @@
#define TYPE_IS_INTEGER(t) ((t) 1.5 == 1)
/* True if the real type T is signed. */
-#define TYPE_SIGNED(t) (! ((t) 0 < (t) -1))
+#define TYPE_SIGNED(t) _GL_TYPE_SIGNED (t)
/* Return 1 if the real expression E, after promotion, has a
signed or floating type. Do not evaluate E. */
-#define EXPR_SIGNED(e) (_GL_INT_NEGATE_CONVERT (e, 1) < 0)
+#define EXPR_SIGNED(e) _GL_EXPR_SIGNED (e)
/* Minimum and maximum values for integer types and expressions. */
@@ -49,7 +40,7 @@
/* The width in bits of the integer type or expression T.
Do not evaluate T. T must not be a bit-field expression.
Padding bits are not supported; this is checked at compile-time below. */
-#define TYPE_WIDTH(t) (sizeof (t) * CHAR_BIT)
+#define TYPE_WIDTH(t) _GL_TYPE_WIDTH (t)
/* The maximum and minimum values for the integer type T. */
#define TYPE_MINIMUM(t) ((t) ~ TYPE_MAXIMUM (t))
@@ -58,51 +49,6 @@
? (t) -1 \
: ((((t) 1 << (TYPE_WIDTH (t) - 2)) - 1) * 2 + 1)))
-/* The maximum and minimum values for the type of the expression E,
- after integer promotion. E is not evaluated. */
-#define _GL_INT_MINIMUM(e) \
- (EXPR_SIGNED (e) \
- ? ~ _GL_SIGNED_INT_MAXIMUM (e) \
- : _GL_INT_CONVERT (e, 0))
-#define _GL_INT_MAXIMUM(e) \
- (EXPR_SIGNED (e) \
- ? _GL_SIGNED_INT_MAXIMUM (e) \
- : _GL_INT_NEGATE_CONVERT (e, 1))
-#define _GL_SIGNED_INT_MAXIMUM(e) \
- (((_GL_INT_CONVERT (e, 1) << (TYPE_WIDTH (+ (e)) - 2)) - 1) * 2 + 1)
-
-/* Work around OpenVMS incompatibility with C99. */
-#if !defined LLONG_MAX && defined __INT64_MAX
-# define LLONG_MAX __INT64_MAX
-# define LLONG_MIN __INT64_MIN
-#endif
-
-/* This include file assumes that signed types are two's complement without
- padding bits; the above macros have undefined behavior otherwise.
- If this is a problem for you, please let us know how to fix it for your host.
- This assumption is tested by the intprops-tests module. */
-
-/* Does the __typeof__ keyword work? This could be done by
- 'configure', but for now it's easier to do it by hand. */
-#if (2 <= __GNUC__ \
- || (4 <= __clang_major__) \
- || (1210 <= __IBMC__ && defined __IBM__TYPEOF__) \
- || (0x5110 <= __SUNPRO_C && !__STDC__))
-# define _GL_HAVE___TYPEOF__ 1
-#else
-# define _GL_HAVE___TYPEOF__ 0
-#endif
-
-/* Return 1 if the integer type or expression T might be signed. Return 0
- if it is definitely unsigned. T must not be a bit-field expression.
- This macro does not evaluate its argument, and expands to an
- integer constant expression. */
-#if _GL_HAVE___TYPEOF__
-# define _GL_SIGNED_TYPE_OR_EXPR(t) TYPE_SIGNED (__typeof__ (t))
-#else
-# define _GL_SIGNED_TYPE_OR_EXPR(t) 1
-#endif
-
/* Bound on length of the string representing an unsigned integer
value representable in B bits. log10 (2.0) < 146/485. The
smallest value of B where this bound is not tight is 2621. */
@@ -129,12 +75,11 @@
/* Range overflow checks.
The INT_<op>_RANGE_OVERFLOW macros return 1 if the corresponding C
- operators might not yield numerically correct answers due to
- arithmetic overflow. They do not rely on undefined or
- implementation-defined behavior. Their implementations are simple
- and straightforward, but they are harder to use and may be less
- efficient than the INT_<op>_WRAPV, INT_<op>_OK, and
- INT_<op>_OVERFLOW macros described below.
+ operators overflow arithmetically when given the same arguments.
+ These macros do not rely on undefined or implementation-defined behavior.
+ Although their implementations are simple and straightforward,
+ they are harder to use and may be less efficient than the
+ INT_<op>_WRAPV, INT_<op>_OK, and INT_<op>_OVERFLOW macros described below.
Example usage:
@@ -181,9 +126,7 @@
/* Return 1 if - A would overflow in [MIN,MAX] arithmetic.
See above for restrictions. */
#define INT_NEGATE_RANGE_OVERFLOW(a, min, max) \
- ((min) < 0 \
- ? (a) < - (max) \
- : 0 < (a))
+ _GL_INT_NEGATE_RANGE_OVERFLOW (a, min, max)
/* Return 1 if A * B would overflow in [MIN,MAX] arithmetic.
See above for restrictions. Avoid && and || as they tickle
@@ -227,40 +170,6 @@
? (a) < (min) >> (b) \
: (max) >> (b) < (a))
-/* True if __builtin_add_overflow (A, B, P) and __builtin_sub_overflow
- (A, B, P) work when P is non-null. */
-/* __builtin_{add,sub}_overflow exists but is not reliable in GCC 5.x and 6.x,
- see <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98269>. */
-#if 7 <= __GNUC__ && !defined __ICC
-# define _GL_HAS_BUILTIN_ADD_OVERFLOW 1
-#elif defined __has_builtin
-# define _GL_HAS_BUILTIN_ADD_OVERFLOW __has_builtin (__builtin_add_overflow)
-#else
-# define _GL_HAS_BUILTIN_ADD_OVERFLOW 0
-#endif
-
-/* True if __builtin_mul_overflow (A, B, P) works when P is non-null. */
-#ifdef __clang__
-/* Work around Clang bug <https://bugs.llvm.org/show_bug.cgi?id=16404>. */
-# define _GL_HAS_BUILTIN_MUL_OVERFLOW 0
-#else
-# define _GL_HAS_BUILTIN_MUL_OVERFLOW _GL_HAS_BUILTIN_ADD_OVERFLOW
-#endif
-
-/* True if __builtin_add_overflow_p (A, B, C) works, and similarly for
- __builtin_sub_overflow_p and __builtin_mul_overflow_p. */
-#if defined __clang__ || defined __ICC
-/* Clang 11 lacks __builtin_mul_overflow_p, and even if it did it
- would presumably run afoul of Clang bug 16404. ICC 2021.1's
- __builtin_add_overflow_p etc. are not treated as integral constant
- expressions even when all arguments are. */
-# define _GL_HAS_BUILTIN_OVERFLOW_P 0
-#elif defined __has_builtin
-# define _GL_HAS_BUILTIN_OVERFLOW_P __has_builtin (__builtin_mul_overflow_p)
-#else
-# define _GL_HAS_BUILTIN_OVERFLOW_P (7 <= __GNUC__)
-#endif
-
/* The _GL*_OVERFLOW macros have the same restrictions as the
*_RANGE_OVERFLOW macros, except that they do not assume that operands
(e.g., A and B) have the same type as MIN and MAX. Instead, they assume
@@ -347,13 +256,18 @@
Because the WRAPV macros convert the result, they report overflow
in different circumstances than the OVERFLOW macros do. For
example, in the typical case with 16-bit 'short' and 32-bit 'int',
- if A, B and R are all of type 'short' then INT_ADD_OVERFLOW (A, B)
+ if A, B and *R are all of type 'short' then INT_ADD_OVERFLOW (A, B)
returns false because the addition cannot overflow after A and B
- are converted to 'int', whereas INT_ADD_WRAPV (A, B, &R) returns
+ are converted to 'int', whereas INT_ADD_WRAPV (A, B, R) returns
true or false depending on whether the sum fits into 'short'.
These macros are tuned for their last input argument being a constant.
+ A, B, and *R should be integers; they need not be the same type,
+ and they need not be all signed or all unsigned.
+ However, none of the integer types should be bit-precise,
+ and *R's type should not be char, bool, or an enumeration type.
+
Return 1 if the integer expressions A * B, A - B, -A, A * B, A / B,
A % B, and A << B would overflow, respectively. */
@@ -361,12 +275,7 @@
_GL_BINARY_OP_OVERFLOW (a, b, _GL_ADD_OVERFLOW)
#define INT_SUBTRACT_OVERFLOW(a, b) \
_GL_BINARY_OP_OVERFLOW (a, b, _GL_SUBTRACT_OVERFLOW)
-#if _GL_HAS_BUILTIN_OVERFLOW_P
-# define INT_NEGATE_OVERFLOW(a) INT_SUBTRACT_OVERFLOW (0, a)
-#else
-# define INT_NEGATE_OVERFLOW(a) \
- INT_NEGATE_RANGE_OVERFLOW (a, _GL_INT_MINIMUM (a), _GL_INT_MAXIMUM (a))
-#endif
+#define INT_NEGATE_OVERFLOW(a) _GL_INT_NEGATE_OVERFLOW (a)
#define INT_MULTIPLY_OVERFLOW(a, b) \
_GL_BINARY_OP_OVERFLOW (a, b, _GL_MULTIPLY_OVERFLOW)
#define INT_DIVIDE_OVERFLOW(a, b) \
@@ -388,224 +297,9 @@
/* Store the low-order bits of A + B, A - B, A * B, respectively, into *R.
Return 1 if the result overflows. See above for restrictions. */
-#if _GL_HAS_BUILTIN_ADD_OVERFLOW
-# define INT_ADD_WRAPV(a, b, r) __builtin_add_overflow (a, b, r)
-# define INT_SUBTRACT_WRAPV(a, b, r) __builtin_sub_overflow (a, b, r)
-#else
-# define INT_ADD_WRAPV(a, b, r) \
- _GL_INT_OP_WRAPV (a, b, r, +, _GL_INT_ADD_RANGE_OVERFLOW)
-# define INT_SUBTRACT_WRAPV(a, b, r) \
- _GL_INT_OP_WRAPV (a, b, r, -, _GL_INT_SUBTRACT_RANGE_OVERFLOW)
-#endif
-#if _GL_HAS_BUILTIN_MUL_OVERFLOW
-# if ((9 < __GNUC__ + (3 <= __GNUC_MINOR__) \
- || (__GNUC__ == 8 && 4 <= __GNUC_MINOR__)) \
- && !defined __ICC)
-# define INT_MULTIPLY_WRAPV(a, b, r) __builtin_mul_overflow (a, b, r)
-# else
- /* Work around GCC bug 91450. */
-# define INT_MULTIPLY_WRAPV(a, b, r) \
- ((!_GL_SIGNED_TYPE_OR_EXPR (*(r)) && EXPR_SIGNED (a) && EXPR_SIGNED (b) \
- && _GL_INT_MULTIPLY_RANGE_OVERFLOW (a, b, 0, (__typeof__ (*(r))) -1)) \
- ? ((void) __builtin_mul_overflow (a, b, r), 1) \
- : __builtin_mul_overflow (a, b, r))
-# endif
-#else
-# define INT_MULTIPLY_WRAPV(a, b, r) \
- _GL_INT_OP_WRAPV (a, b, r, *, _GL_INT_MULTIPLY_RANGE_OVERFLOW)
-#endif
-
-/* Nonzero if this compiler has GCC bug 68193 or Clang bug 25390. See:
- https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68193
- https://llvm.org/bugs/show_bug.cgi?id=25390
- For now, assume all versions of GCC-like compilers generate bogus
- warnings for _Generic. This matters only for compilers that
- lack relevant builtins. */
-#if __GNUC__ || defined __clang__
-# define _GL__GENERIC_BOGUS 1
-#else
-# define _GL__GENERIC_BOGUS 0
-#endif
-
-/* Store the low-order bits of A <op> B into *R, where OP specifies
- the operation and OVERFLOW the overflow predicate. Return 1 if the
- result overflows. See above for restrictions. */
-#if 201112 <= __STDC_VERSION__ && !_GL__GENERIC_BOGUS
-# define _GL_INT_OP_WRAPV(a, b, r, op, overflow) \
- (_Generic \
- (*(r), \
- signed char: \
- _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
- signed char, SCHAR_MIN, SCHAR_MAX), \
- unsigned char: \
- _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
- unsigned char, 0, UCHAR_MAX), \
- short int: \
- _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
- short int, SHRT_MIN, SHRT_MAX), \
- unsigned short int: \
- _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
- unsigned short int, 0, USHRT_MAX), \
- int: \
- _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
- int, INT_MIN, INT_MAX), \
- unsigned int: \
- _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
- unsigned int, 0, UINT_MAX), \
- long int: \
- _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long int, \
- long int, LONG_MIN, LONG_MAX), \
- unsigned long int: \
- _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long int, \
- unsigned long int, 0, ULONG_MAX), \
- long long int: \
- _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long long int, \
- long long int, LLONG_MIN, LLONG_MAX), \
- unsigned long long int: \
- _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long long int, \
- unsigned long long int, 0, ULLONG_MAX)))
-#else
-/* Store the low-order bits of A <op> B into *R, where OP specifies
- the operation and OVERFLOW the overflow predicate. If *R is
- signed, its type is ST with bounds SMIN..SMAX; otherwise its type
- is UT with bounds U..UMAX. ST and UT are narrower than int.
- Return 1 if the result overflows. See above for restrictions. */
-# if _GL_HAVE___TYPEOF__
-# define _GL_INT_OP_WRAPV_SMALLISH(a,b,r,op,overflow,st,smin,smax,ut,umax) \
- (TYPE_SIGNED (__typeof__ (*(r))) \
- ? _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, st, smin, smax) \
- : _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, ut, 0, umax))
-# else
-# define _GL_INT_OP_WRAPV_SMALLISH(a,b,r,op,overflow,st,smin,smax,ut,umax) \
- (overflow (a, b, smin, smax) \
- ? (overflow (a, b, 0, umax) \
- ? (*(r) = _GL_INT_OP_WRAPV_VIA_UNSIGNED (a,b,op,unsigned,st), 1) \
- : (*(r) = _GL_INT_OP_WRAPV_VIA_UNSIGNED (a,b,op,unsigned,st)) < 0) \
- : (overflow (a, b, 0, umax) \
- ? (*(r) = _GL_INT_OP_WRAPV_VIA_UNSIGNED (a,b,op,unsigned,st)) >= 0 \
- : (*(r) = _GL_INT_OP_WRAPV_VIA_UNSIGNED (a,b,op,unsigned,st), 0)))
-# endif
-
-# define _GL_INT_OP_WRAPV(a, b, r, op, overflow) \
- (sizeof *(r) == sizeof (signed char) \
- ? _GL_INT_OP_WRAPV_SMALLISH (a, b, r, op, overflow, \
- signed char, SCHAR_MIN, SCHAR_MAX, \
- unsigned char, UCHAR_MAX) \
- : sizeof *(r) == sizeof (short int) \
- ? _GL_INT_OP_WRAPV_SMALLISH (a, b, r, op, overflow, \
- short int, SHRT_MIN, SHRT_MAX, \
- unsigned short int, USHRT_MAX) \
- : sizeof *(r) == sizeof (int) \
- ? (EXPR_SIGNED (*(r)) \
- ? _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
- int, INT_MIN, INT_MAX) \
- : _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned int, \
- unsigned int, 0, UINT_MAX)) \
- : _GL_INT_OP_WRAPV_LONGISH(a, b, r, op, overflow))
-# ifdef LLONG_MAX
-# define _GL_INT_OP_WRAPV_LONGISH(a, b, r, op, overflow) \
- (sizeof *(r) == sizeof (long int) \
- ? (EXPR_SIGNED (*(r)) \
- ? _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long int, \
- long int, LONG_MIN, LONG_MAX) \
- : _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long int, \
- unsigned long int, 0, ULONG_MAX)) \
- : (EXPR_SIGNED (*(r)) \
- ? _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long long int, \
- long long int, LLONG_MIN, LLONG_MAX) \
- : _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long long int, \
- unsigned long long int, 0, ULLONG_MAX)))
-# else
-# define _GL_INT_OP_WRAPV_LONGISH(a, b, r, op, overflow) \
- (EXPR_SIGNED (*(r)) \
- ? _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long int, \
- long int, LONG_MIN, LONG_MAX) \
- : _GL_INT_OP_CALC (a, b, r, op, overflow, unsigned long int, \
- unsigned long int, 0, ULONG_MAX))
-# endif
-#endif
-
-/* Store the low-order bits of A <op> B into *R, where the operation
- is given by OP. Use the unsigned type UT for calculation to avoid
- overflow problems. *R's type is T, with extrema TMIN and TMAX.
- T must be a signed integer type. Return 1 if the result overflows. */
-#define _GL_INT_OP_CALC(a, b, r, op, overflow, ut, t, tmin, tmax) \
- (overflow (a, b, tmin, tmax) \
- ? (*(r) = _GL_INT_OP_WRAPV_VIA_UNSIGNED (a, b, op, ut, t), 1) \
- : (*(r) = _GL_INT_OP_WRAPV_VIA_UNSIGNED (a, b, op, ut, t), 0))
-
-/* Return the low-order bits of A <op> B, where the operation is given
- by OP. Use the unsigned type UT for calculation to avoid undefined
- behavior on signed integer overflow, and convert the result to type T.
- UT is at least as wide as T and is no narrower than unsigned int,
- T is two's complement, and there is no padding or trap representations.
- Assume that converting UT to T yields the low-order bits, as is
- done in all known two's-complement C compilers. E.g., see:
- https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html
-
- According to the C standard, converting UT to T yields an
- implementation-defined result or signal for values outside T's
- range. However, code that works around this theoretical problem
- runs afoul of a compiler bug in Oracle Studio 12.3 x86. See:
- https://lists.gnu.org/r/bug-gnulib/2017-04/msg00049.html
- As the compiler bug is real, don't try to work around the
- theoretical problem. */
-
-#define _GL_INT_OP_WRAPV_VIA_UNSIGNED(a, b, op, ut, t) \
- ((t) ((ut) (a) op (ut) (b)))
-
-/* Return true if the numeric values A + B, A - B, A * B fall outside
- the range TMIN..TMAX. Arguments should be integer expressions
- without side effects. TMIN should be signed and nonpositive.
- TMAX should be positive, and should be signed unless TMIN is zero. */
-#define _GL_INT_ADD_RANGE_OVERFLOW(a, b, tmin, tmax) \
- ((b) < 0 \
- ? (((tmin) \
- ? ((EXPR_SIGNED (_GL_INT_CONVERT (a, (tmin) - (b))) || (b) < (tmin)) \
- && (a) < (tmin) - (b)) \
- : (a) <= -1 - (b)) \
- || ((EXPR_SIGNED (a) ? 0 <= (a) : (tmax) < (a)) && (tmax) < (a) + (b))) \
- : (a) < 0 \
- ? (((tmin) \
- ? ((EXPR_SIGNED (_GL_INT_CONVERT (b, (tmin) - (a))) || (a) < (tmin)) \
- && (b) < (tmin) - (a)) \
- : (b) <= -1 - (a)) \
- || ((EXPR_SIGNED (_GL_INT_CONVERT (a, b)) || (tmax) < (b)) \
- && (tmax) < (a) + (b))) \
- : (tmax) < (b) || (tmax) - (b) < (a))
-#define _GL_INT_SUBTRACT_RANGE_OVERFLOW(a, b, tmin, tmax) \
- (((a) < 0) == ((b) < 0) \
- ? ((a) < (b) \
- ? !(tmin) || -1 - (tmin) < (b) - (a) - 1 \
- : (tmax) < (a) - (b)) \
- : (a) < 0 \
- ? ((!EXPR_SIGNED (_GL_INT_CONVERT ((a) - (tmin), b)) && (a) - (tmin) < 0) \
- || (a) - (tmin) < (b)) \
- : ((! (EXPR_SIGNED (_GL_INT_CONVERT (tmax, b)) \
- && EXPR_SIGNED (_GL_INT_CONVERT ((tmax) + (b), a))) \
- && (tmax) <= -1 - (b)) \
- || (tmax) + (b) < (a)))
-#define _GL_INT_MULTIPLY_RANGE_OVERFLOW(a, b, tmin, tmax) \
- ((b) < 0 \
- ? ((a) < 0 \
- ? (EXPR_SIGNED (_GL_INT_CONVERT (tmax, b)) \
- ? (a) < (tmax) / (b) \
- : ((INT_NEGATE_OVERFLOW (b) \
- ? _GL_INT_CONVERT (b, tmax) >> (TYPE_WIDTH (+ (b)) - 1) \
- : (tmax) / -(b)) \
- <= -1 - (a))) \
- : INT_NEGATE_OVERFLOW (_GL_INT_CONVERT (b, tmin)) && (b) == -1 \
- ? (EXPR_SIGNED (a) \
- ? 0 < (a) + (tmin) \
- : 0 < (a) && -1 - (tmin) < (a) - 1) \
- : (tmin) / (b) < (a)) \
- : (b) == 0 \
- ? 0 \
- : ((a) < 0 \
- ? (INT_NEGATE_OVERFLOW (_GL_INT_CONVERT (a, tmin)) && (a) == -1 \
- ? (EXPR_SIGNED (b) ? 0 < (b) + (tmin) : -1 - (tmin) < (b) - 1) \
- : (tmin) / (a) < (b)) \
- : (tmax) / (b) < (a)))
+#define INT_ADD_WRAPV(a, b, r) _GL_INT_ADD_WRAPV (a, b, r)
+#define INT_SUBTRACT_WRAPV(a, b, r) _GL_INT_SUBTRACT_WRAPV (a, b, r)
+#define INT_MULTIPLY_WRAPV(a, b, r) _GL_INT_MULTIPLY_WRAPV (a, b, r)
/* The following macros compute A + B, A - B, and A * B, respectively.
If no overflow occurs, they set *R to the result and return 1;
@@ -621,6 +315,8 @@
A, B, and *R should be integers; they need not be the same type,
and they need not be all signed or all unsigned.
+ However, none of the integer types should be bit-precise,
+ and *R's type should not be char, bool, or an enumeration type.
These macros work correctly on all known practical hosts, and do not rely
on undefined behavior due to signed arithmetic overflow.
@@ -632,8 +328,8 @@
These macros are tuned for B being a constant. */
-#define INT_ADD_OK(a, b, r) ! INT_ADD_WRAPV (a, b, r)
-#define INT_SUBTRACT_OK(a, b, r) ! INT_SUBTRACT_WRAPV (a, b, r)
-#define INT_MULTIPLY_OK(a, b, r) ! INT_MULTIPLY_WRAPV (a, b, r)
+#define INT_ADD_OK(a, b, r) (! INT_ADD_WRAPV (a, b, r))
+#define INT_SUBTRACT_OK(a, b, r) (! INT_SUBTRACT_WRAPV (a, b, r))
+#define INT_MULTIPLY_OK(a, b, r) (! INT_MULTIPLY_WRAPV (a, b, r))
#endif /* _GL_INTPROPS_H */
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 02/59] Support stdckdint.h internally if older GCC
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
2025-01-05 5:56 ` [PATCH 00/59] time: sync mktime from Gnulib Paul Eggert
2025-01-05 5:56 ` [PATCH 01/59] Split intprops.h into two Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:56 ` [PATCH 03/59] Fix mishandling of default DST rule Paul Eggert
` (57 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* include/stdckdint.h: New file.
* include/stdckdint.in.h: New file, copied from Gnulib.
---
CONTRIBUTED-BY | 6 ++++++
SHARED-FILES | 2 ++
include/stdckdint.h | 6 ++++++
include/stdckdint.in.h | 35 +++++++++++++++++++++++++++++++++++
4 files changed, 49 insertions(+)
create mode 100644 include/stdckdint.h
create mode 100644 include/stdckdint.in.h
diff --git a/CONTRIBUTED-BY b/CONTRIBUTED-BY
index 652642fe86..87ba0dfc58 100644
--- a/CONTRIBUTED-BY
+++ b/CONTRIBUTED-BY
@@ -1418,6 +1418,12 @@ include/list.h:
include/list_t.h:
Contributed by Ulrich Drepper <drepper@redhat.com>, 2002.
+include/stdckdint.h:
+ Written by Paul Eggert.
+
+include/stdckdint.in.h:
+ Written by Paul Eggert.
+
inet/bug-if1.c:
Contributed by Ulrich Drepper <drepper@redhat.com>, 2004.
diff --git a/SHARED-FILES b/SHARED-FILES
index 444eff5141..2edeef7f03 100644
--- a/SHARED-FILES
+++ b/SHARED-FILES
@@ -35,6 +35,8 @@ gnulib:
include/intprops.h
# Merged from gnulib 2021-09-21
include/regex.h
+ # Merged from gnulib 2024-10-14
+ include/stdckdint.in.h
locale/programs/3level.h
# Merged from gnulib 2014-6-23
malloc/obstack.c
diff --git a/include/stdckdint.h b/include/stdckdint.h
new file mode 100644
index 0000000000..713fb4559d
--- /dev/null
+++ b/include/stdckdint.h
@@ -0,0 +1,6 @@
+#if __GNUC__ < 14
+# include <stdbool.h>
+# include "stdckdint.in.h"
+#else
+# include_next <stdckdint.h>
+#endif
diff --git a/include/stdckdint.in.h b/include/stdckdint.in.h
new file mode 100644
index 0000000000..83277b728e
--- /dev/null
+++ b/include/stdckdint.in.h
@@ -0,0 +1,35 @@
+/* stdckdint.h -- checked integer arithmetic
+
+ Copyright 2022-2025 Free Software Foundation, Inc.
+
+ This program 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.
+
+ This program 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 this program. If not, see <https://www.gnu.org/licenses/>. */
+
+#ifndef _GL_STDCKDINT_H
+#define _GL_STDCKDINT_H
+
+#include "intprops-internal.h"
+
+/* Store into *R the low-order bits of A + B, A - B, A * B, respectively.
+ Return 1 if the result overflows, 0 otherwise.
+ A, B, and *R can have any integer type other than char, bool, a
+ bit-precise integer type, or an enumeration type.
+
+ These are like the standard macros introduced in C23, except that
+ arguments should not have side effects. */
+
+#define ckd_add(r, a, b) ((bool) _GL_INT_ADD_WRAPV (a, b, r))
+#define ckd_sub(r, a, b) ((bool) _GL_INT_SUBTRACT_WRAPV (a, b, r))
+#define ckd_mul(r, a, b) ((bool) _GL_INT_MULTIPLY_WRAPV (a, b, r))
+
+#endif /* _GL_STDCKDINT_H */
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 03/59] Fix mishandling of default DST rule
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (2 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 02/59] Support stdckdint.h internally if older GCC Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:56 ` [PATCH 04/59] Don't worry about !TZDEFAULT Paul Eggert
` (56 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
For timezone settings like TZ="AST4ADT" where POSIX says the
default is implementation-defined, time/tzfile.c has long had
vestiges of an old attempt to be upward compatible with UNIX
System V. This code has not worked for decades and evidently is
not being used, so remove it and use the simpler fallback code
already present in time/tzset.c, which uses US DST. Also, add a
few test cases to demonstrate some bugs in the removed code.
* time/tst-posixtz.c: Include stdckdint.h.
(tests): WHEN is now intmax_t, not time_t, to support
skipping tests after 2038 on 32-bit time_t. All uses changed.
Make it const while we're at it. Add several tests for AST4ADT.
* time/tzfile.c (rule_dstoff): Remove. All uses removed.
(__tzfile_read): Remove args EXTRA, EXTRAP. All uses changed.
(__tzfile_default): Remove. All uses removed.
(__tzfile_compute): Remove no-longer-needed code dealing
with __tzfile_default.
* timezone/Makefile (build-testdata):
Add '-p America/New_York' option if compiling 'northamerica',
so that we can test non-use of posixrules.
* timezone/tst-timezone.c: Include stdbool.h, stdckdint.h.
(failed): Now bool, not int. All uses changed.
(do_test): Test AST4ADT too.
---
NEWS | 7 +++
include/time.h | 6 +--
manual/time.texi | 2 +-
time/tst-posixtz.c | 56 ++++++++++++++++++--
time/tzfile.c | 112 ++--------------------------------------
time/tzset.c | 19 +------
timezone/Makefile | 3 +-
timezone/tst-timezone.c | 64 ++++++++++++++++++++---
8 files changed, 124 insertions(+), 145 deletions(-)
diff --git a/NEWS b/NEWS
index 00c569fe85..3e6227a448 100644
--- a/NEWS
+++ b/NEWS
@@ -84,6 +84,13 @@ Deprecated and removed features, and other changes affecting compatibility:
explicitly because of the executable bit in GNU_STACK, and the stack is
not already executable. Instead, loading such objects will fail.
+* The default daylight saving rules for old Unix System V style TZ
+ strings like TZ="AST4ADT" are now those of current US DST. Although
+ the rules were supposed to be those of /usr/share/zoneinfo/posixrules,
+ this feature, which has been disabled by default and marked obsolete
+ upstream in tzcode, did not work in either glibc or tzcode, and in
+ practice posixrules invariably specified current US DST anyway.
+
Changes to build and runtime requirements:
* On recent Linux kernels with vDSO getrandom support, getrandom does not
diff --git a/include/time.h b/include/time.h
index f599eeed4e..52bb22abd9 100644
--- a/include/time.h
+++ b/include/time.h
@@ -54,14 +54,10 @@ extern char *__tzstring (const char *string) attribute_hidden;
extern int __use_tzfile attribute_hidden;
-extern void __tzfile_read (const char *file, size_t extra,
- char **extrap) attribute_hidden;
+extern void __tzfile_read (const char *file) attribute_hidden;
extern void __tzfile_compute (__time64_t timer, int use_localtime,
long int *leap_correct, int *leap_hit,
struct tm *tp) attribute_hidden;
-extern void __tzfile_default (const char *std, const char *dst,
- int stdoff, int dstoff)
- attribute_hidden;
extern void __tzset_parse_tz (const char *tz) attribute_hidden;
extern void __tz_compute (__time64_t timer, struct tm *tm, int use_localtime)
__THROW attribute_hidden;
diff --git a/manual/time.texi b/manual/time.texi
index 90bc9a2566..899128ca7f 100644
--- a/manual/time.texi
+++ b/manual/time.texi
@@ -2724,7 +2724,7 @@ and offset for the corresponding daylight saving time zone; if the
The remainder of the proleptic format, which starts with the first comma,
describes when daylight saving time is in effect. This remainder is
-optional and if omitted, @theglibc{} defaults to the daylight saving
+optional and if omitted, @theglibc{} defaults to US daylight saving rules.
rules that would be used if @env{TZ} had the value @t{"posixrules"}.
However, other POSIX implementations default to different daylight
saving rules, so portable @env{TZ} settings should not omit the
diff --git a/time/tst-posixtz.c b/time/tst-posixtz.c
index 9bec7ae4bb..936c5b8fc6 100644
--- a/time/tst-posixtz.c
+++ b/time/tst-posixtz.c
@@ -1,3 +1,4 @@
+#include <stdckdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -6,10 +7,10 @@
struct
{
- time_t when;
+ intmax_t when;
const char *tz;
const char *result;
-} tests[] =
+} const tests[] =
{
{ 909312849L, "AEST-10AEDST-11,M10.5.0,M3.5.0",
"1998/10/25 21:54:09 dst=1 zone=AEDST" },
@@ -27,6 +28,43 @@ struct
"1999/04/23 06:54:09 dst=1 zone=EDT" },
{ 919973892L, "EST+5EDT,M4.1.0/2,M10.5.0/2",
"1999/02/25 15:18:12 dst=0 zone=EST" },
+
+ /* Test Atlantic Standard / Atlantic Daylight transitions in 2024 and 2038,
+ with explicit DST rule. */
+ { 1710050399, "AST4ADT,M3.2.0,M11.1.0",
+ "2024/03/10 01:59:59 dst=0 zone=AST" },
+ { 1710050400, "AST4ADT,M3.2.0/2,M11.1.0/2",
+ "2024/03/10 03:00:00 dst=1 zone=ADT" },
+ { 1730609999, "AST4ADT,M3.2.0/02:00,M11.1.0/02:00",
+ "2024/11/03 01:59:59 dst=1 zone=ADT" },
+ { 1730610000, "AST4ADT,M3.2.0/02:00:00,M11.1.0/02:00:00",
+ "2024/11/03 01:00:00 dst=0 zone=AST" },
+ { 2152159199, "AST4ADT,M3.2.0,M11.1.0",
+ "2038/03/14 01:59:59 dst=0 zone=AST" },
+ { 2152159200, "AST4ADT,M3.2.0/2,M11.1.0/2",
+ "2038/03/14 03:00:00 dst=1 zone=ADT" },
+ { 2172718799, "AST4ADT,M3.2.0/02:00,M11.1.0/02:00",
+ "2038/11/07 01:59:59 dst=1 zone=ADT" },
+ { 2172718800, "AST4ADT,M3.2.0/02:00:00,M11.1.0/02:00:00",
+ "2038/11/07 01:00:00 dst=0 zone=AST" },
+
+ /* Likewise, but with default DST rule. */
+ { 1710050399, "AST4ADT",
+ "2024/03/10 01:59:59 dst=0 zone=AST" },
+ { 1710050400, "AST4ADT",
+ "2024/03/10 03:00:00 dst=1 zone=ADT" },
+ { 1730609999, "AST4ADT",
+ "2024/11/03 01:59:59 dst=1 zone=ADT" },
+ { 1730610000, "AST4ADT",
+ "2024/11/03 01:00:00 dst=0 zone=AST" },
+ { 2152159199, "AST4ADT",
+ "2038/03/14 01:59:59 dst=0 zone=AST" },
+ { 2152159200, "AST4ADT",
+ "2038/03/14 03:00:00 dst=1 zone=ADT" },
+ { 2172718799, "AST4ADT",
+ "2038/11/07 01:59:59 dst=1 zone=ADT" },
+ { 2172718800, "AST4ADT",
+ "2038/11/07 01:00:00 dst=0 zone=AST" },
};
static int
@@ -34,6 +72,7 @@ do_test (void)
{
int result = 0;
size_t cnt;
+ time_t when;
for (cnt = 0; cnt < sizeof (tests) / sizeof (tests[0]); ++cnt)
{
@@ -41,12 +80,18 @@ do_test (void)
struct tm *tmp;
printf ("TZ = \"%s\", time = %jd => ", tests[cnt].tz,
- (intmax_t) tests[cnt].when);
+ tests[cnt].when);
fflush (stdout);
+ if (ckd_add (&when, tests[cnt].when, 0))
+ {
+ puts ("SKIPPED [time_t out of range for this platform]");
+ continue;
+ }
+
setenv ("TZ", tests[cnt].tz, 1);
- tmp = localtime (&tests[cnt].when);
+ tmp = localtime (&when);
snprintf (buf, sizeof (buf),
"%04d/%02d/%02d %02d:%02d:%02d dst=%d zone=%s",
@@ -66,7 +111,8 @@ do_test (void)
}
setenv ("TZ", "Universal", 1);
- localtime (&tests[0].when);
+ when = tests[0].when;
+ localtime (&when);
printf ("TZ = \"Universal\" daylight %d tzname = { \"%s\", \"%s\" }",
daylight, tzname[0], tzname[1]);
if (! daylight)
diff --git a/time/tzfile.c b/time/tzfile.c
index edb5643335..bca8b3f4ef 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -57,7 +57,6 @@ static size_t num_types;
static struct ttinfo *types;
static char *zone_names;
static long int rule_stdoff;
-static long int rule_dstoff;
static size_t num_leaps;
static struct leap *leaps;
static char *tzspec;
@@ -103,7 +102,7 @@ decode64 (const void *ptr)
void
-__tzfile_read (const char *file, size_t extra, char **extrap)
+__tzfile_read (const char *file)
{
static const char default_tzdir[] = TZDIR;
size_t num_isstd, num_isgmt;
@@ -257,7 +256,6 @@ __tzfile_read (const char *file, size_t extra, char **extrap)
unsigned char type_idxs[num_types];
char zone_names[chars];
char tzspec[tzspec_len];
- char extra_array[extra]; // Stored into *pextras if requested.
The piece-wise allocations from buf below verify that no
overflow/wraparound occurred in these computations.
@@ -277,7 +275,7 @@ __tzfile_read (const char *file, size_t extra, char **extrap)
+ num_types * sizeof (struct ttinfo)
+ num_transitions /* type_idxs */
+ chars /* zone_names */
- + tzspec_len + extra);
+ + tzspec_len);
transitions = malloc (total_size);
if (transitions == NULL)
goto lose;
@@ -295,8 +293,6 @@ __tzfile_read (const char *file, size_t extra, char **extrap)
tzspec = alloc_buffer_alloc_array (&buf, char, tzspec_len);
else
tzspec = NULL;
- if (extra > 0)
- *extrap = alloc_buffer_alloc_array (&buf, char, extra);
if (alloc_buffer_has_failed (&buf))
goto lose;
@@ -447,7 +443,7 @@ __tzfile_read (const char *file, size_t extra, char **extrap)
daylight_saved = 0;
if (num_transitions == 0)
/* Use the first rule (which should also be the only one). */
- rule_stdoff = rule_dstoff = types[0].offset;
+ rule_stdoff = types[0].offset;
else
{
rule_stdoff = 0;
@@ -488,98 +484,6 @@ __tzfile_read (const char *file, size_t extra, char **extrap)
transitions = NULL;
}
\f
-/* The user specified a hand-made timezone, but not its DST rules.
- We will use the names and offsets from the user, and the rules
- from the TZDEFRULES file. */
-
-void
-__tzfile_default (const char *std, const char *dst,
- int stdoff, int dstoff)
-{
- size_t stdlen = strlen (std) + 1;
- size_t dstlen = strlen (dst) + 1;
- size_t i;
- int isdst;
- char *cp;
-
- __tzfile_read (TZDEFRULES, stdlen + dstlen, &cp);
- if (!__use_tzfile)
- return;
-
- if (num_types < 2)
- {
- __use_tzfile = 0;
- return;
- }
-
- /* Ignore the zone names read from the file and use the given ones
- instead. */
- __mempcpy (__mempcpy (cp, std, stdlen), dst, dstlen);
- zone_names = cp;
-
- /* Now there are only two zones, regardless of what the file contained. */
- num_types = 2;
-
- /* Now correct the transition times for the user-specified standard and
- daylight offsets from GMT. */
- isdst = 0;
- for (i = 0; i < num_transitions; ++i)
- {
- struct ttinfo *trans_type = &types[type_idxs[i]];
-
- /* We will use only types 0 (standard) and 1 (daylight).
- Fix up this transition to point to whichever matches
- the flavor of its original type. */
- type_idxs[i] = trans_type->isdst;
-
- if (trans_type->isgmt)
- /* The transition time is in GMT. No correction to apply. */ ;
- else if (isdst && !trans_type->isstd)
- /* The type says this transition is in "local wall clock time", and
- wall clock time as of the previous transition was DST. Correct
- for the difference between the rule's DST offset and the user's
- DST offset. */
- transitions[i] += dstoff - rule_dstoff;
- else
- /* This transition is in "local wall clock time", and wall clock
- time as of this iteration is non-DST. Correct for the
- difference between the rule's standard offset and the user's
- standard offset. */
- transitions[i] += stdoff - rule_stdoff;
-
- /* The DST state of "local wall clock time" for the next iteration is
- as specified by this transition. */
- isdst = trans_type->isdst;
- }
-
- /* Now that we adjusted the transitions to the requested offsets,
- reset the rule_stdoff and rule_dstoff values appropriately. They
- are used elsewhere. */
- rule_stdoff = stdoff;
- rule_dstoff = dstoff;
-
- /* Reset types 0 and 1 to describe the user's settings. */
- types[0].idx = 0;
- types[0].offset = stdoff;
- types[0].isdst = 0;
- types[1].idx = stdlen;
- types[1].offset = dstoff;
- types[1].isdst = 1;
-
- /* Reset time zone abbreviations to point to the user's abbreviations. */
- __tzname[0] = (char *) std;
- __tzname[1] = (char *) dst;
-
- /* Set the timezone. */
- __timezone = -types[0].offset;
-
- /* Invalidate the tzfile attribute cache to force rereading
- TZDEFRULES the next time it is used. */
- tzfile_dev = 0;
- tzfile_ino = 0;
- tzfile_mtime = 0;
-}
-\f
void
__tzfile_compute (__time64_t timer, int use_localtime,
long int *leap_correct, int *leap_hit,
@@ -642,16 +546,6 @@ __tzfile_compute (__time64_t timer, int use_localtime,
/* Use the rules from the TZ string to compute the change. */
__tz_compute (timer, tp, 1);
- /* If tzspec comes from posixrules loaded by __tzfile_default,
- override the STD and DST zone names with the ones user
- requested in TZ envvar. */
- if (__glibc_unlikely (zone_names == (char *) &leaps[num_leaps]))
- {
- assert (num_types == 2);
- __tzname[0] = __tzstring (zone_names);
- __tzname[1] = __tzstring (&zone_names[strlen (zone_names) + 1]);
- }
-
goto leap;
}
else
diff --git a/time/tzset.c b/time/tzset.c
index 0ddc203287..e178742222 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -331,22 +331,7 @@ __tzset_parse_tz (const char *tz)
if (*tz != '\0')
{
if (parse_tzname (&tz, 1))
- {
- parse_offset (&tz, 1);
- if (*tz == '\0' || (tz[0] == ',' && tz[1] == '\0'))
- {
- /* There is no rule. See if there is a default rule
- file. */
- __tzfile_default (tz_rules[0].name, tz_rules[1].name,
- tz_rules[0].offset, tz_rules[1].offset);
- if (__use_tzfile)
- {
- free (old_tz);
- old_tz = NULL;
- return;
- }
- }
- }
+ parse_offset (&tz, 1);
/* Figure out the standard <-> DST rules. */
if (parse_rule (&tz, 0))
parse_rule (&tz, 1);
@@ -402,7 +387,7 @@ tzset_internal (int always)
old_tz = tz ? __strdup (tz) : NULL;
/* Try to read a data file. */
- __tzfile_read (tz, 0, NULL);
+ __tzfile_read (tz);
if (__use_tzfile)
return;
diff --git a/timezone/Makefile b/timezone/Makefile
index ebe5cf73a1..6e28a2e670 100644
--- a/timezone/Makefile
+++ b/timezone/Makefile
@@ -80,7 +80,8 @@ CFLAGS-zic.c += $(tz-cflags) -Wno-unused-variable
# Don't add leapseconds here since test-tz made checks that work only without
# leapseconds.
define build-testdata
-$(built-program-cmd) -d $(testdata) -y ./yearistype $<; \
+$(built-program-cmd) -d $(testdata) \
+ $(if $(findstring northamerica,$<),-p America/New_York) $<; \
$(evaluate-test)
endef
diff --git a/timezone/tst-timezone.c b/timezone/tst-timezone.c
index 2d90be305d..882b3b8fd6 100644
--- a/timezone/tst-timezone.c
+++ b/timezone/tst-timezone.c
@@ -16,13 +16,15 @@
<https://www.gnu.org/licenses/>. */
#include <time.h>
+#include <stdbool.h>
+#include <stdckdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
-int failed = 0;
+bool failed = false;
struct test_times
{
@@ -69,20 +71,20 @@ check_tzvars (const char *name, int dayl, int timez, const char *const tznam[])
{
printf ("*** Timezone: %s, daylight is: %d but should be: %d\n",
name, daylight, dayl);
- ++failed;
+ failed = true;
}
if (timezone != timez)
{
printf ("*** Timezone: %s, timezone is: %ld but should be: %d\n",
name, timezone, timez);
- ++failed;
+ failed = true;
}
for (i = 0; i <= 1; ++i)
if (strcmp (tzname[i], tznam[i]) != 0)
{
printf ("*** Timezone: %s, tzname[%d] is: %s but should be: %s\n",
name, i, tzname[i], tznam[i]);
- ++failed;
+ failed = true;
}
}
@@ -106,7 +108,7 @@ do_test (void)
if (putenv (buf))
{
puts ("putenv failed.");
- failed = 1;
+ failed = true;
}
tzset ();
print_tzvars ();
@@ -136,7 +138,7 @@ do_test (void)
puts ("TZ=Europe/London 892162800 0 0 0 10 3 98 5 99 1");
if (strcmp (buf, "TZ=Europe/London 892162800 0 0 0 10 3 98 5 99 1") != 0)
{
- failed = 1;
+ failed = true;
fputs (" FAILED ***", stdout);
}
}
@@ -159,11 +161,59 @@ do_test (void)
puts ("TZ=GMT 892166400 0 0 0 10 3 98 5 99 0");
if (strcmp (buf, "TZ=GMT 892166400 0 0 0 10 3 98 5 99 0") != 0)
{
- failed = 1;
+ failed = true;
fputs (" FAILED ***", stdout);
}
}
+ /* Test AST4ADT here, as well as in ../time/tst-posixtz.c, because
+ formerly the tzset implementation read the posixrules file
+ generated as part of the timezone data, and some bugs of that old
+ implementation are covered by this test. */
+ {
+ static struct
+ {
+ intmax_t when;
+ char const *tz;
+ char const *result;
+ } const default_dst_test[] =
+ {
+ /* Test Atlantic Standard / Atlantic Daylight transitions in
+ 2024 and 2038, with default DST rule. */
+ { 1710050399, "AST4ADT", "2024-03-10 01:59:59 -0400 (AST)" },
+ { 1710050400, "AST4ADT", "2024-03-10 03:00:00 -0300 (ADT)" },
+ { 1730609999, "AST4ADT", "2024-11-03 01:59:59 -0300 (ADT)" },
+ { 1730610000, "AST4ADT", "2024-11-03 01:00:00 -0400 (AST)" },
+ { 2152159199, "AST4ADT", "2038-03-14 01:59:59 -0400 (AST)" },
+ { 2152159200, "AST4ADT", "2038-03-14 03:00:00 -0300 (ADT)" },
+ { 2172718799, "AST4ADT", "2038-11-07 01:59:59 -0300 (ADT)" },
+ { 2172718800, "AST4ADT", "2038-11-07 01:00:00 -0400 (AST)" },
+ };
+
+ for (int i = 0; i < sizeof default_dst_test / sizeof *default_dst_test; i++)
+ {
+ sprintf (envstring, "TZ=%s", default_dst_test[i].tz);
+ /* No putenv call needed! */
+
+ time_t when;
+ if (ckd_add (&when, default_dst_test[i].when, 0))
+ {
+ printf ("time=%jd out of range for test; test skipped\n",
+ default_dst_test[i].when);
+ continue;
+ }
+ char buf[100];
+ strftime (buf, sizeof buf, "%Y-%m-%d %H:%M:%S %z (%Z)",
+ localtime (&when));
+ bool bad = strcmp (buf, default_dst_test[i].result) != 0;
+ printf ("%s should be\n%s when time=%jd TZ=%s%s\n\n",
+ buf, default_dst_test[i].result,
+ default_dst_test[i].when, default_dst_test[i].tz,
+ bad ? " FAILED ***" : "");
+ failed |= bad;
+ }
+ }
+
return failed ? EXIT_FAILURE : EXIT_SUCCESS;
}
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 04/59] Don't worry about !TZDEFAULT
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (3 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 03/59] Fix mishandling of default DST rule Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:56 ` [PATCH 05/59] Support static_assert in pre-C23 C Paul Eggert
` (55 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzset.c (tzset_internal): Simplify due to the fact that
TZDEFAULT is not a null pointer (tzfile.c is already assuming this).
---
time/tzset.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/time/tzset.c b/time/tzset.c
index e178742222..1a9a03a436 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -384,17 +384,17 @@ tzset_internal (int always)
/* Save the value of `tz'. */
free (old_tz);
- old_tz = tz ? __strdup (tz) : NULL;
+ old_tz = __strdup (tz);
/* Try to read a data file. */
__tzfile_read (tz);
if (__use_tzfile)
return;
- /* No data file found. Default to UTC if nothing specified. */
+ /* No data file found. Default to UTC if nothing specified or if
+ TZDEFAULT is broken. */
- if (tz == NULL || *tz == '\0'
- || (TZDEFAULT != NULL && strcmp (tz, TZDEFAULT) == 0))
+ if (*tz == '\0' || strcmp (tz, TZDEFAULT) == 0)
{
memset (tz_rules, '\0', sizeof tz_rules);
tz_rules[0].name = tz_rules[1].name = "UTC";
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 05/59] Support static_assert in pre-C23 C
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (4 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 04/59] Don't worry about !TZDEFAULT Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:56 ` [PATCH 06/59] Sync mktime.c from Gnulib 2024-10-04 Paul Eggert
` (54 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* include/sys/cdefs.h (static_assert) [!C++ && __STDC_VERSION__ < 202311]:
New macro.
* timezone/private.h, timezone/version: Copy from tzcode git.
---
include/sys/cdefs.h | 8 ++++++++
timezone/private.h | 2 +-
timezone/version | 2 +-
3 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/include/sys/cdefs.h b/include/sys/cdefs.h
index a676f75f62..cd3127aaca 100644
--- a/include/sys/cdefs.h
+++ b/include/sys/cdefs.h
@@ -7,6 +7,14 @@
# define _Static_assert(expr, diagnostic) _Static_assert (expr, diagnostic)
#endif
+/* Support single-arg static_assert in C code even on pre-C23 compilers,
+ for compatibility with Gnulib. */
+#if (!defined __cplusplus \
+ && (!defined __STDC_VERSION__ || __STDC_VERSION__ < 202311) \
+ && !defined static_assert)
+# define static_assert(expr) _Static_assert (expr, #expr)
+#endif
+
#include <misc/sys/cdefs.h>
#ifndef _ISOMAC
diff --git a/timezone/private.h b/timezone/private.h
index c33041049f..3db0121135 100644
--- a/timezone/private.h
+++ b/timezone/private.h
@@ -50,7 +50,7 @@
# include <stdbool.h>
#endif
-#if __STDC_VERSION__ < 202311
+#if __STDC_VERSION__ < 202311 && !defined static_assert
# define static_assert(cond) extern int static_assert_check[(cond) ? 1 : -1]
#endif
diff --git a/timezone/version b/timezone/version
index 04fe674443..c6db4af447 100644
--- a/timezone/version
+++ b/timezone/version
@@ -1 +1 @@
-2024a
+2024b-11-gaf54a9e
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 06/59] Sync mktime.c from Gnulib 2024-10-04
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (5 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 05/59] Support static_assert in pre-C23 C Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:56 ` [PATCH 07/59] Document mktime out-of-range + tm_isdst Paul Eggert
` (53 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
Refactor by syncing from Gnulib commit
8e0c8d148117086894fe26576d9255ea7f047a6d (Oct 4 21:07:09 2024 -0700),
which now uses C23's stdckdint.h for integer overflow checking,
uses static_assert instead of verify, uses a convert_time function
that is closer to what glibc wants, does not try to match tm_isdst in
timezones that never observe DST, and fixes a minor bug when timegm
fails on 32-bit time_t platforms.
* time/mktime-internal.h, time/mktime.c, time/timegm.c: Copy from Gnulib.
---
time/mktime-internal.h | 9 ++-
time/mktime.c | 124 ++++++++++++++++++++++-------------------
time/timegm.c | 3 +-
3 files changed, 73 insertions(+), 63 deletions(-)
diff --git a/time/mktime-internal.h b/time/mktime-internal.h
index 5fbcc0ec60..1da98b4373 100644
--- a/time/mktime-internal.h
+++ b/time/mktime-internal.h
@@ -71,9 +71,8 @@ typedef int mktime_offset_t;
#endif
/* Subroutine of mktime. Return the time_t representation of TP and
- normalize TP, given that a struct tm * maps to a time_t as performed
- by FUNC. Record next guess for localtime-gmtime offset in *OFFSET. */
-extern __time64_t __mktime_internal (struct tm *tp,
- struct tm *(*func) (__time64_t const *,
- struct tm *),
+ normalize TP, given that a struct tm * maps to a time_t. If
+ LOCAL, the mapping is performed by localtime_r, otherwise by gmtime_r.
+ Record next guess for localtime-gmtime offset in *OFFSET. */
+extern __time64_t __mktime_internal (struct tm *tp, bool local,
mktime_offset_t *offset) attribute_hidden;
diff --git a/time/mktime.c b/time/mktime.c
index 86d496ca65..7f7d33492b 100644
--- a/time/mktime.c
+++ b/time/mktime.c
@@ -46,11 +46,11 @@
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
+#include <stdckdint.h>
#include <stdlib.h>
#include <string.h>
#include <intprops.h>
-#include <verify.h>
#ifndef NEED_MKTIME_INTERNAL
# define NEED_MKTIME_INTERNAL 0
@@ -118,12 +118,12 @@ my_tzset (void)
__time64_t values that mktime can generate even on platforms where
__time64_t is wider than the int components of struct tm. */
-#if INT_MAX <= LONG_MAX / 4 / 366 / 24 / 60 / 60
+# if INT_MAX <= LONG_MAX / 4 / 366 / 24 / 60 / 60
typedef long int long_int;
-#else
+# else
typedef long long int long_int;
-#endif
-verify (INT_MAX <= TYPE_MAXIMUM (long_int) / 4 / 366 / 24 / 60 / 60);
+# endif
+static_assert (INT_MAX <= TYPE_MAXIMUM (long_int) / 4 / 366 / 24 / 60 / 60);
/* Shift A right by B bits portably, by dividing A by 2**B and
truncating towards minus infinity. B should be in the range 0 <= B
@@ -154,9 +154,9 @@ static long_int const mktime_max
= (TYPE_MAXIMUM (long_int) < TYPE_MAXIMUM (__time64_t)
? TYPE_MAXIMUM (long_int) : TYPE_MAXIMUM (__time64_t));
-#define EPOCH_YEAR 1970
-#define TM_YEAR_BASE 1900
-verify (TM_YEAR_BASE % 100 == 0);
+# define EPOCH_YEAR 1970
+# define TM_YEAR_BASE 1900
+static_assert (TM_YEAR_BASE % 100 == 0);
/* Is YEAR + TM_YEAR_BASE a leap year? */
static bool
@@ -171,9 +171,9 @@ leapyear (long_int year)
}
/* How many days come before each month (0-12). */
-#ifndef _LIBC
+# ifndef _LIBC
static
-#endif
+# endif
const unsigned short int __mon_yday[2][13] =
{
/* Normal years. */
@@ -205,7 +205,7 @@ static long_int
ydhms_diff (long_int year1, long_int yday1, int hour1, int min1, int sec1,
int year0, int yday0, int hour0, int min0, int sec0)
{
- verify (-1 / 2 == 0);
+ static_assert (-1 / 2 == 0);
/* Compute intervening leap days correctly even if year is negative.
Take care to avoid integer overflow here. */
@@ -250,29 +250,33 @@ tm_diff (long_int year, long_int yday, int hour, int min, int sec,
tp->tm_hour, tp->tm_min, tp->tm_sec);
}
-/* Use CONVERT to convert T to a struct tm value in *TM. T must be in
- range for __time64_t. Return TM if successful, NULL (setting errno) on
- failure. */
+/* Convert T to a struct tm value in *TM. Use localtime64_r if LOCAL,
+ otherwise gmtime64_r. T must be in range for __time64_t. Return
+ TM if successful, NULL (setting errno) on failure. */
static struct tm *
-convert_time (struct tm *(*convert) (const __time64_t *, struct tm *),
- long_int t, struct tm *tm)
+convert_time (long_int t, bool local, struct tm *tm)
{
__time64_t x = t;
- return convert (&x, tm);
+ if (local)
+ return __localtime64_r (&x, tm);
+ else
+ return __gmtime64_r (&x, tm);
}
-
-/* Use CONVERT to convert *T to a broken down time in *TP.
- If *T is out of range for conversion, adjust it so that
- it is the nearest in-range value and then convert that.
- A value is in range if it fits in both __time64_t and long_int.
- Return TP on success, NULL (setting errno) on failure. */
+/* Call it __tzconvert to sync with other parts of glibc. */
+#define __tz_convert convert_time
+
+/* Convert *T to a broken down time in *TP (as if by localtime if
+ LOCAL, otherwise as if by gmtime). If *T is out of range for
+ conversion, adjust it so that it is the nearest in-range value and
+ then convert that. A value is in range if it fits in both
+ __time64_t and long_int. Return TP on success, NULL (setting
+ errno) on failure. */
static struct tm *
-ranged_convert (struct tm *(*convert) (const __time64_t *, struct tm *),
- long_int *t, struct tm *tp)
+ranged_convert (bool local, long_int *t, struct tm *tp)
{
long_int t1 = (*t < mktime_min ? mktime_min
: *t <= mktime_max ? *t : mktime_max);
- struct tm *r = convert_time (convert, t1, tp);
+ struct tm *r = __tz_convert (t1, local, tp);
if (r)
{
*t = t1;
@@ -293,7 +297,7 @@ ranged_convert (struct tm *(*convert) (const __time64_t *, struct tm *),
long_int mid = long_int_avg (ok, bad);
if (mid == ok || mid == bad)
break;
- if (convert_time (convert, mid, tp))
+ if (__tz_convert (mid, local, tp))
ok = mid, oktm = *tp;
else if (errno != EOVERFLOW)
return NULL;
@@ -309,36 +313,45 @@ ranged_convert (struct tm *(*convert) (const __time64_t *, struct tm *),
}
-/* Convert *TP to a __time64_t value, inverting
- the monotonic and mostly-unit-linear conversion function CONVERT.
- Use *OFFSET to keep track of a guess at the offset of the result,
+/* Convert *TP to a __time64_t value. If LOCAL, the reverse mapping
+ is performed as if localtime, otherwise as if by gmtime. Use
+ *OFFSET to keep track of a guess at the offset of the result,
compared to what the result would be for UTC without leap seconds.
- If *OFFSET's guess is correct, only one CONVERT call is needed.
- If successful, set *TP to the canonicalized struct tm;
+ If *OFFSET's guess is correct, only one reverse mapping call is
+ needed. If successful, set *TP to the canonicalized struct tm;
otherwise leave *TP alone, return ((time_t) -1) and set errno.
This function is external because it is used also by timegm.c. */
__time64_t
-__mktime_internal (struct tm *tp,
- struct tm *(*convert) (const __time64_t *, struct tm *),
- mktime_offset_t *offset)
+__mktime_internal (struct tm *tp, bool local, mktime_offset_t *offset)
{
struct tm tm;
- /* The maximum number of probes (calls to CONVERT) should be enough
- to handle any combinations of time zone rule changes, solar time,
- leap seconds, and oscillations around a spring-forward gap.
- POSIX.1 prohibits leap seconds, but some hosts have them anyway. */
+ /* The maximum number of probes should be enough to handle any
+ combinations of time zone rule changes, solar time, leap seconds,
+ and oscillations around a spring-forward gap. POSIX.1 prohibits
+ leap seconds, but some hosts have them anyway. */
int remaining_probes = 6;
- /* Time requested. Copy it in case CONVERT modifies *TP; this can
- occur if TP is localtime's returned value and CONVERT is localtime. */
+#ifndef _LIBC
+ /* Gnulib mktime doesn't lock the tz state, so it may need to probe
+ more often if some other thread changes local time while
+ __mktime_internal is probing. Double the number of probes; this
+ should suffice for practical cases that are at all likely. */
+ remaining_probes *= 2;
+#endif
+
+ /* Time requested. Copy it in case gmtime/localtime modify *TP;
+ this can occur if TP is localtime's returned value and CONVERT is
+ localtime. */
int sec = tp->tm_sec;
int min = tp->tm_min;
int hour = tp->tm_hour;
int mday = tp->tm_mday;
int mon = tp->tm_mon;
int year_requested = tp->tm_year;
- int isdst = tp->tm_isdst;
+
+ /* If the timezone never observes DST, ignore any tm_isdst request. */
+ int isdst = local && __daylight ? tp->tm_isdst : 0;
/* 1 if the previous probe was DST. */
int dst2 = 0;
@@ -379,7 +392,7 @@ __mktime_internal (struct tm *tp,
/* Invert CONVERT by probing. First assume the same offset as last
time. */
- INT_SUBTRACT_WRAPV (0, off, &negative_offset_guess);
+ ckd_sub (&negative_offset_guess, 0, off);
long_int t0 = ydhms_diff (year, yday, hour, min, sec,
EPOCH_YEAR - TM_YEAR_BASE, 0, 0, 0,
negative_offset_guess);
@@ -389,7 +402,7 @@ __mktime_internal (struct tm *tp,
while (true)
{
- if (! ranged_convert (convert, &t, &tm))
+ if (! ranged_convert (local, &t, &tm))
return -1;
long_int dt = tm_diff (year, yday, hour, min, sec, &tm);
if (dt == 0)
@@ -465,10 +478,10 @@ __mktime_internal (struct tm *tp,
for (direction = -1; direction <= 1; direction += 2)
{
long_int ot;
- if (! INT_ADD_WRAPV (t, delta * direction, &ot))
+ if (! ckd_add (&ot, t, delta * direction))
{
struct tm otm;
- if (! ranged_convert (convert, &ot, &otm))
+ if (! ranged_convert (local, &ot, &otm))
return -1;
if (! isdst_differ (isdst, otm.tm_isdst))
{
@@ -478,7 +491,7 @@ __mktime_internal (struct tm *tp,
&otm);
if (mktime_min <= gt && gt <= mktime_max)
{
- if (convert_time (convert, gt, &tm))
+ if (__tz_convert (gt, local, &tm))
{
t = gt;
goto offset_found;
@@ -492,7 +505,7 @@ __mktime_internal (struct tm *tp,
/* No unusual DST offset was found nearby. Assume one-hour DST. */
t += 60 * 60 * dst_difference;
- if (mktime_min <= t && t <= mktime_max && convert_time (convert, t, &tm))
+ if (mktime_min <= t && t <= mktime_max && __tz_convert (t, local, &tm))
goto offset_found;
__set_errno (EOVERFLOW);
@@ -503,8 +516,8 @@ __mktime_internal (struct tm *tp,
/* Set *OFFSET to the low-order bits of T - T0 - NEGATIVE_OFFSET_GUESS.
This is just a heuristic to speed up the next mktime call, and
correctness is unaffected if integer overflow occurs here. */
- INT_SUBTRACT_WRAPV (t, t0, offset);
- INT_SUBTRACT_WRAPV (*offset, negative_offset_guess, offset);
+ ckd_sub (offset, t, t0);
+ ckd_sub (offset, *offset, negative_offset_guess);
if (LEAP_SECONDS_POSSIBLE && sec_requested != tm.tm_sec)
{
@@ -513,13 +526,13 @@ __mktime_internal (struct tm *tp,
long_int sec_adjustment = sec == 0 && tm.tm_sec == 60;
sec_adjustment -= sec;
sec_adjustment += sec_requested;
- if (INT_ADD_WRAPV (t, sec_adjustment, &t)
+ if (ckd_add (&t, t, sec_adjustment)
|| ! (mktime_min <= t && t <= mktime_max))
{
__set_errno (EOVERFLOW);
return -1;
}
- if (! convert_time (convert, t, &tm))
+ if (! __tz_convert (t, local, &tm))
return -1;
}
@@ -535,14 +548,13 @@ __mktime_internal (struct tm *tp,
__time64_t
__mktime64 (struct tm *tp)
{
- /* POSIX.1 8.1.1 requires that whenever mktime() is called, the
- time zone abbreviations contained in the external variable 'tzname' shall
- be set as if the tzset() function had been called. */
+ /* POSIX.1 requires mktime to set external variables like 'tzname'
+ as though tzset had been called. */
__tzset ();
# if defined _LIBC || NEED_MKTIME_WORKING
static mktime_offset_t localtime_offset;
- return __mktime_internal (tp, __localtime64_r, &localtime_offset);
+ return __mktime_internal (tp, true, &localtime_offset);
# else
# undef mktime
return mktime (tp);
diff --git a/time/timegm.c b/time/timegm.c
index 5e5fa0127f..4c2615b9fa 100644
--- a/time/timegm.c
+++ b/time/timegm.c
@@ -30,8 +30,7 @@ __time64_t
__timegm64 (struct tm *tmp)
{
static mktime_offset_t gmtime_offset;
- tmp->tm_isdst = 0;
- return __mktime_internal (tmp, __gmtime64_r, &gmtime_offset);
+ return __mktime_internal (tmp, false, &gmtime_offset);
}
#if defined _LIBC && __TIMESIZE != 64
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 07/59] Document mktime out-of-range + tm_isdst
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (6 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 06/59] Sync mktime.c from Gnulib 2024-10-04 Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 8:03 ` Florian Weimer
2025-01-05 5:56 ` [PATCH 08/59] Push tzset lock into callers of time functions Paul Eggert
` (52 subsequent siblings)
60 siblings, 1 reply; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* manual/time.texi (Broken-down Time): Document mktime better
when dealing with out-of-range inputs and nonnegative tm_isdst.
Documentation problem reported by Florian Weimer in:
https://sourceware.org/pipermail/libc-alpha/2024-October/160310.html
---
manual/time.texi | 51 ++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 43 insertions(+), 8 deletions(-)
diff --git a/manual/time.texi b/manual/time.texi
index 899128ca7f..28d8912446 100644
--- a/manual/time.texi
+++ b/manual/time.texi
@@ -1075,9 +1075,11 @@ 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.
+This flag should not be inspected; the @code{tm_gmtoff} and
+@code{tm_zone} fields should be inspected instead.
+However, before calling @code{mktime} this flag should be set,
+and set to a negative value unless the application already has a
+correct value for the specified time.
@item long int tm_gmtoff
This field describes the time zone that was used to compute this
@@ -1297,13 +1299,46 @@ The @code{mktime} function ignores the specified contents of the
members of the broken-down time
structure. It uses the values of the other components to determine the
calendar time; it's permissible for these components to have
-unnormalized values outside their normal ranges. The last thing that
-@code{mktime} does is adjust the components of the @var{brokentime}
+unnormalized values outside their normal ranges.
+Out-of-range members are brought into range in reverse order of
+significance: for example, an hour equal to @minus{}1 is first
+increased to 23 and the day decreased by 1, before an out-of-range day
+is brought into range.
+
+If local time ever observes daylight saving time, the @code{tm_isdst}
+member is inspected after other members are brought into range. If
+negative, @code{mktime} uses time zone data to determine whether
+daylight saving time is in effect at the requested time. Then members
+are adjusted if they would otherwise be inconsistent, e.g., by adding
+or subtracting an hour if the broken-down time would otherwise have
+the wrong @code{tm_isdst} member or would occur during a
+spring-forward gap.
+The heuristic used for these adjustments is unspecified.
+@c The heuristic conforms to POSIX.1-2024 for non-geographic timezones.
+@c POSIX-1.2024 is incorrect (and unclear to boot) for geographic timezones,
+@c which is the normal use cases for non-UTC timezones these days.
+
+Ordinarily callers should specify a negative @code{tm_isdst} member.
+The main practical use for nonnegative @code{tm_isdst} is when a the
+broken-down time was produced by @code{localtime} or similar functions
+so its @code{tm_isdst} is already known to be correct. This might
+happen if the original simple time has been lost, in which case the
+nonnegative @code{tm_isdst} usually (but not always) lets
+@code{mktime} disambiguate broken-down times within an overlapping
+window after the clock jumps back during a daylight saving or other
+transition. A nonnegative @code{tm_isdst} might also happen when
+doing broken-down time arithmetic by calling @code{localtime}, adding
+or subtracting to (say) @code{tm_mday}, and then calling
+@code{mktime}; in this case, though, if the time arithmetic crosses a
+time transition boundary the results are unspecified.
+
+If successful, @code{mktime} uses the equivalent of @code{localtime}
+to set all the components of the @code{*@var{brokentime}}
structure, including the members that were initially ignored.
If the specified broken-down time cannot be represented as a simple time,
@code{mktime} returns a value of @code{(time_t)(-1)} and does not modify
-the contents of @var{brokentime}.
+@code{*@var{brokentime}}.
Calling @code{mktime} also sets the time zone state as if
@code{tzset} were called; @code{mktime} uses this information instead
@@ -1342,8 +1377,8 @@ available. @code{timelocal} is rather rare.
@c tzfile_compute(!use_localtime) ok
@code{timegm} is functionally identical to @code{mktime} except it
-always takes the input values to be UTC
-regardless of any local time zone setting.
+operates in UTC, so it ignores @code{tm_isdst} other than setting it
+to zero when successful.
Note that @code{timegm} is the inverse of @code{gmtime}.
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* Re: [PATCH 07/59] Document mktime out-of-range + tm_isdst
2025-01-05 5:56 ` [PATCH 07/59] Document mktime out-of-range + tm_isdst Paul Eggert
@ 2025-01-05 8:03 ` Florian Weimer
2025-01-05 14:03 ` Paul Eggert
0 siblings, 1 reply; 72+ messages in thread
From: Florian Weimer @ 2025-01-05 8:03 UTC (permalink / raw)
To: Paul Eggert; +Cc: libc-alpha
* Paul Eggert:
> +If local time ever observes daylight saving time, the @code{tm_isdst}
> +member is inspected after other members are brought into range. If
> +negative, @code{mktime} uses time zone data to determine whether
> +daylight saving time is in effect at the requested time. Then members
> +are adjusted if they would otherwise be inconsistent, e.g., by adding
> +or subtracting an hour if the broken-down time would otherwise have
> +the wrong @code{tm_isdst} member or would occur during a
> +spring-forward gap.
The “Then members are adjusted” part seems unclear to me. If tm_isdst
is negative, is there adjustment based on time zone offset? Would
that even be possible, given that tm_gmtoff may not have been
initialized?
> Ordinarily callers should specify a negative @code{tm_isdst} member.
> The main practical use for nonnegative @code{tm_isdst} is when a the
> broken-down time was produced by @code{localtime} or similar functions
> so its @code{tm_isdst} is already known to be correct.
Please clarify that gmtime does not count as a similar function in
this context.
^ permalink raw reply [flat|nested] 72+ messages in thread
* Re: [PATCH 07/59] Document mktime out-of-range + tm_isdst
2025-01-05 8:03 ` Florian Weimer
@ 2025-01-05 14:03 ` Paul Eggert
0 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 14:03 UTC (permalink / raw)
To: Florian Weimer; +Cc: libc-alpha
[-- Attachment #1: Type: text/plain, Size: 1032 bytes --]
On 2025-01-05 00:03, Florian Weimer wrote:
> The “Then members are adjusted” part seems unclear to me. If tm_isdst
> is negative, is there adjustment based on time zone offset?
Yes.
> Would that even be possible, given that tm_gmtoff may not have been
> initialized?
Yes, because mktime knows the timezone. It ignores tm_gmtoff when
initially determining the UTC offset; instread, it inspects the timezone
data and infers the UTC offset from that.
The attached followup patch attempts to clarify this.
>> Ordinarily callers should specify a negative @code{tm_isdst} member.
>> The main practical use for nonnegative @code{tm_isdst} is when a the
>> broken-down time was produced by @code{localtime} or similar functions
>> so its @code{tm_isdst} is already known to be correct.
>
> Please clarify that gmtime does not count as a similar function in
> this context.
Also done in the attached, which says that gmtime counts as similar only
if local time is UTC.
Thanks for the review.
[-- Attachment #2: 0001-Clarify-mktime-and-timegm-doc.patch --]
[-- Type: text/x-patch, Size: 4383 bytes --]
From 8953b72766ec97f744ec3eff155e8c87a22fc8ba Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Sun, 5 Jan 2025 06:01:12 -0800
Subject: [PATCH] Clarify mktime and timegm doc
* manual/time.texi (Broken-down Time):
Clarify based on comments from Florian Weimer in:
https://sourceware.org/pipermail/libc-alpha/2025-January/163505.html
---
manual/time.texi | 32 ++++++++++++++++++++++----------
1 file changed, 22 insertions(+), 10 deletions(-)
diff --git a/manual/time.texi b/manual/time.texi
index d5619534a5..4d7e8a75eb 100644
--- a/manual/time.texi
+++ b/manual/time.texi
@@ -1306,9 +1306,16 @@ increased to 23 and the day decreased by 1, before an out-of-range day
is brought into range.
If local time ever observes daylight saving time, the @code{tm_isdst}
-member is inspected after other members are brought into range. If
-negative, @code{mktime} uses time zone data to determine whether
-daylight saving time is in effect at the requested time. Then members
+member is inspected after other members are brought into range
+and time has been corrected for the offset from UTC@.
+If @code{tm_isdst} is negative, @code{mktime} determines whether
+daylight saving time is in effect at the requested time,
+using an unspecified heuristic if the requested time is ambiguous
+due to being in a fallback overlap, i.e., an overlapping window before
+and after the clock falls back during a daylight saving or other transition.
+
+If local time ever observes daylight saving or has other transitions
+that result in spring-forward gaps or fallback overlaps, members then
are adjusted if they would otherwise be inconsistent, e.g., by adding
or subtracting an hour if the broken-down time would otherwise have
the wrong @code{tm_isdst} member or would occur during a
@@ -1319,19 +1326,22 @@ The heuristic used for these adjustments is unspecified.
@c which is the normal use cases for non-UTC timezones these days.
Ordinarily callers should specify a negative @code{tm_isdst} member.
-The main practical use for nonnegative @code{tm_isdst} is when a the
+The main practical use for nonnegative @code{tm_isdst} is when a
broken-down time was produced by @code{localtime} or similar functions
so its @code{tm_isdst} is already known to be correct. This might
happen if the original simple time has been lost, in which case the
nonnegative @code{tm_isdst} usually (but not always) lets
-@code{mktime} disambiguate broken-down times within an overlapping
-window after the clock jumps back during a daylight saving or other
-transition. A nonnegative @code{tm_isdst} might also happen when
+@code{mktime} disambiguate broken-down times within a fallback
+overlap. A nonnegative @code{tm_isdst} might also happen when
doing broken-down time arithmetic by calling @code{localtime}, adding
or subtracting to (say) @code{tm_mday}, and then calling
@code{mktime}; in this case, though, if the time arithmetic crosses a
time transition boundary the results are unspecified.
+Unless local time is UTC, a broken-down time that was produced by
+@code{gmtime} or similar functions should be given to @code{timegm},
+not to @code{mktime}.
+
If successful, @code{mktime} uses the equivalent of @code{localtime}
to set all the components of the @code{*@var{brokentime}}
structure, including the members that were initially ignored.
@@ -1352,8 +1362,8 @@ members. @xref{Time Zone State}.
@c Alias to mktime.
@code{timelocal} is functionally identical to @code{mktime}, but more
-mnemonically named. Note that it is the inverse of the @code{localtime}
-function.
+mnemonically named. Like @code{mktime}, it is roughly the inverse of
+the @code{localtime} function.
@strong{Portability note:} @code{mktime} is essentially universally
available. @code{timelocal} is rather rare.
@@ -1380,7 +1390,9 @@ available. @code{timelocal} is rather rare.
operates in UTC, so it ignores @code{tm_isdst} other than setting it
to zero when successful.
-Note that @code{timegm} is the inverse of @code{gmtime}.
+Because @code{timegm} is the inverse of @code{gmtime}, a broken-down
+time that was produced by @code{localtime} or similar functions should
+be given to @code{mktime}, not to @code{timegm}.
@strong{Portability note:} @code{mktime} is essentially universally
available. Although @code{timegm} is standardized by C23, some
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 08/59] Push tzset lock into callers of time functions
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (7 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 07/59] Document mktime out-of-range + tm_isdst Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:56 ` [PATCH 09/59] Omit parse_offset unnecessary *tz == '\0' Paul Eggert
` (51 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert, Florian Weimer
This makes sure no other thread changes the time zone data while
mktime is running, which could confuse its search probes.
Likewise for timegm, though admittedly this is overkill unless leap
seconds are being used and the leap seconds table changes (!).
Although this means mktime and timegm will be more of a bottleneck
for other threads, mktime and timegm are called so rarely that
let's hope it's not a significant performance issue.
This patch also fixes a harmless data race on the cached offset
used by __mktime_internal.
Co-authored-by: Florian Weimer <fweimer@redhat.com>
* include/time.h: Move tzset-related declarations out of here ...
* time/tzset.h: ... and into this new file. All .c files that
use these declarations changed to include the new file.
* time/gmtime.c (__gmtime64_r, __gmtime64):
* time/localtime.c (__localtime64_r, __localtime64):
* time/mktime.c (__mktime64):
* time/timegm.c (__timegm64):
Lock __tzset_lock while calling __tz_convert.
* time/gmtime.c (__timegm64):
Invoke __tzset_unlocked here; it is needed for leap seconds.
* time/mktime-internal.h (__libc_lock_lock, __libc_lock_unlock)
(__tzset_unlocked) [!_LIBC]: New no-op macros.
* time/mktime.c (convert_time, __tz_convert) [_LIBC]:
Remove; now done by glibc/time/tzset.c's __tz_convert.
(__mktime_internal) [!_LIBC]: Double the number of probes.
---
include/time.h | 19 -------------------
time/gmtime.c | 8 ++++++--
time/localtime.c | 8 ++++++--
time/mktime-internal.h | 7 ++++++-
time/mktime.c | 23 +++++++++++++++--------
time/strftime_l.c | 1 +
time/timegm.c | 9 ++++++++-
time/tzfile.c | 1 +
time/tzset.c | 21 +++++++++++----------
time/tzset.h | 30 ++++++++++++++++++++++++++++++
10 files changed, 84 insertions(+), 43 deletions(-)
create mode 100644 time/tzset.h
diff --git a/include/time.h b/include/time.h
index 52bb22abd9..e9c083646a 100644
--- a/include/time.h
+++ b/include/time.h
@@ -49,20 +49,6 @@ extern const unsigned short int __mon_yday[2][13] attribute_hidden;
/* Defined in localtime.c. */
extern struct tm _tmbuf attribute_hidden;
-/* Defined in tzset.c. */
-extern char *__tzstring (const char *string) attribute_hidden;
-
-extern int __use_tzfile attribute_hidden;
-
-extern void __tzfile_read (const char *file) attribute_hidden;
-extern void __tzfile_compute (__time64_t timer, int use_localtime,
- long int *leap_correct, int *leap_hit,
- struct tm *tp) attribute_hidden;
-extern void __tzset_parse_tz (const char *tz) attribute_hidden;
-extern void __tz_compute (__time64_t timer, struct tm *tm, int use_localtime)
- __THROW attribute_hidden;
-
-
#if __TIMESIZE == 64
# define __itimerspec64 itimerspec
#else
@@ -271,11 +257,6 @@ extern int __offtime (__time64_t __timer,
extern char *__asctime_r (const struct tm *__tp, char *__buf)
attribute_hidden;
-extern void __tzset (void) attribute_hidden;
-
-/* Prototype for the internal function to get information based on TZ. */
-extern struct tm *__tz_convert (__time64_t timer, int use_localtime,
- struct tm *tp) attribute_hidden;
extern int __nanosleep (const struct timespec *__requested_time,
struct timespec *__remaining);
diff --git a/time/gmtime.c b/time/gmtime.c
index ad6e8eb352..9d0af33401 100644
--- a/time/gmtime.c
+++ b/time/gmtime.c
@@ -17,13 +17,17 @@
<https://www.gnu.org/licenses/>. */
#include <time.h>
+#include <tzset.h>
/* Return the `struct tm' representation of *T in UTC,
using *TP to store the result. */
struct tm *
__gmtime64_r (const __time64_t *t, struct tm *tp)
{
- return __tz_convert (*t, 0, tp);
+ __libc_lock_lock (__tzset_lock);
+ struct tm *result = __tz_convert (*t, 0, tp);
+ __libc_lock_unlock (__tzset_lock);
+ return result;
}
/* Provide a 32-bit variant if needed. */
@@ -48,7 +52,7 @@ weak_alias (__gmtime_r, gmtime_r)
struct tm *
__gmtime64 (const __time64_t *t)
{
- return __tz_convert (*t, 0, &_tmbuf);
+ return __gmtime64_r (t, &_tmbuf);
}
/* Provide a 32-bit variant if needed. */
diff --git a/time/localtime.c b/time/localtime.c
index d1967ff11b..7f865a7053 100644
--- a/time/localtime.c
+++ b/time/localtime.c
@@ -17,6 +17,7 @@
<https://www.gnu.org/licenses/>. */
#include <time.h>
+#include <tzset.h>
/* C89 says that localtime and gmtime return the same pointer.
Although C99 and later relax this to let localtime and gmtime
@@ -30,7 +31,10 @@ struct tm _tmbuf;
struct tm *
__localtime64_r (const __time64_t *t, struct tm *tp)
{
- return __tz_convert (*t, 1, tp);
+ __libc_lock_lock (__tzset_lock);
+ struct tm *result = __tz_convert (*t, 1, tp);
+ __libc_lock_unlock (__tzset_lock);
+ return result;
}
/* Provide a 32-bit variant if needed. */
@@ -54,7 +58,7 @@ weak_alias (__localtime_r, localtime_r)
struct tm *
__localtime64 (const __time64_t *t)
{
- return __tz_convert (*t, 1, &_tmbuf);
+ return __localtime64_r (t, &_tmbuf);
}
libc_hidden_def (__localtime64)
diff --git a/time/mktime-internal.h b/time/mktime-internal.h
index 1da98b4373..215be914c2 100644
--- a/time/mktime-internal.h
+++ b/time/mktime-internal.h
@@ -19,6 +19,9 @@
#ifndef _LIBC
# include <time.h>
+# define __libc_lock_lock(lock) ((void) 0)
+# define __libc_lock_unlock(lock) ((void) 0)
+# define __tzset_unlocked() tzset ()
#endif
/* mktime_offset_t is a signed type wide enough to hold a UTC offset
@@ -73,6 +76,8 @@ typedef int mktime_offset_t;
/* Subroutine of mktime. Return the time_t representation of TP and
normalize TP, given that a struct tm * maps to a time_t. If
LOCAL, the mapping is performed by localtime_r, otherwise by gmtime_r.
- Record next guess for localtime-gmtime offset in *OFFSET. */
+ Record next guess for localtime-gmtime offset in *OFFSET.
+
+ If _LIBC, the caller must lock __tzset_lock. */
extern __time64_t __mktime_internal (struct tm *tp, bool local,
mktime_offset_t *offset) attribute_hidden;
diff --git a/time/mktime.c b/time/mktime.c
index 7f7d33492b..3a951b2c8c 100644
--- a/time/mktime.c
+++ b/time/mktime.c
@@ -62,6 +62,9 @@
# define NEED_MKTIME_WORKING 0
#endif
+#ifdef _LIBC
+# include <tzset.h>
+#endif
#include "mktime-internal.h"
#if !defined _LIBC && (NEED_MKTIME_WORKING || NEED_MKTIME_WINDOWS)
@@ -250,6 +253,7 @@ tm_diff (long_int year, long_int yday, int hour, int min, int sec,
tp->tm_hour, tp->tm_min, tp->tm_sec);
}
+#ifndef _LIBC
/* Convert T to a struct tm value in *TM. Use localtime64_r if LOCAL,
otherwise gmtime64_r. T must be in range for __time64_t. Return
TM if successful, NULL (setting errno) on failure. */
@@ -262,8 +266,8 @@ convert_time (long_int t, bool local, struct tm *tm)
else
return __gmtime64_r (&x, tm);
}
-/* Call it __tzconvert to sync with other parts of glibc. */
-#define __tz_convert convert_time
+# define __tz_convert convert_time
+#endif
/* Convert *T to a broken down time in *TP (as if by localtime if
LOCAL, otherwise as if by gmtime). If *T is out of range for
@@ -320,7 +324,9 @@ ranged_convert (bool local, long_int *t, struct tm *tp)
If *OFFSET's guess is correct, only one reverse mapping call is
needed. If successful, set *TP to the canonicalized struct tm;
otherwise leave *TP alone, return ((time_t) -1) and set errno.
- This function is external because it is used also by timegm.c. */
+ This function is external because it is used also by timegm.c.
+
+ If _LIBC, the caller must lock __tzset_lock. */
__time64_t
__mktime_internal (struct tm *tp, bool local, mktime_offset_t *offset)
{
@@ -548,14 +554,15 @@ __mktime_internal (struct tm *tp, bool local, mktime_offset_t *offset)
__time64_t
__mktime64 (struct tm *tp)
{
- /* POSIX.1 requires mktime to set external variables like 'tzname'
- as though tzset had been called. */
- __tzset ();
-
# if defined _LIBC || NEED_MKTIME_WORKING
+ __libc_lock_lock (__tzset_lock);
+ __tzset_unlocked ();
static mktime_offset_t localtime_offset;
- return __mktime_internal (tp, true, &localtime_offset);
+ __time64_t result = __mktime_internal (tp, true, &localtime_offset);
+ __libc_lock_unlock (__tzset_lock);
+ return result;
# else
+ __tzset ();
# undef mktime
return mktime (tp);
# endif
diff --git a/time/strftime_l.c b/time/strftime_l.c
index f51d926b46..584df83d15 100644
--- a/time/strftime_l.c
+++ b/time/strftime_l.c
@@ -33,6 +33,7 @@
# define MULTIBYTE_IS_FORMAT_SAFE 1
# define STDC_HEADERS 1
# include "../locale/localeinfo.h"
+# include <tzset.h>
#endif
#if defined emacs && !defined HAVE_BCOPY
diff --git a/time/timegm.c b/time/timegm.c
index 4c2615b9fa..42115a5d7f 100644
--- a/time/timegm.c
+++ b/time/timegm.c
@@ -24,13 +24,20 @@
#include <time.h>
#include <errno.h>
+#ifdef _LIBC
+# include <tzset.h>
+#endif
#include "mktime-internal.h"
__time64_t
__timegm64 (struct tm *tmp)
{
+ __libc_lock_lock (__tzset_lock);
+ __tzset_unlocked ();
static mktime_offset_t gmtime_offset;
- return __mktime_internal (tmp, false, &gmtime_offset);
+ __time64_t result = __mktime_internal (tmp, false, &gmtime_offset);
+ __libc_lock_unlock (__tzset_lock);
+ return result;
}
#if defined _LIBC && __TIMESIZE != 64
diff --git a/time/tzfile.c b/time/tzfile.c
index bca8b3f4ef..fa2be56580 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -27,6 +27,7 @@
#include <stdint.h>
#include <alloc_buffer.h>
#include <set-freeres.h>
+#include <tzset.h>
#include <timezone/tzfile.h>
diff --git a/time/tzset.c b/time/tzset.c
index 1a9a03a436..a0bf3382ba 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -16,13 +16,13 @@
<https://www.gnu.org/licenses/>. */
#include <ctype.h>
-#include <libc-lock.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
+#include <tzset.h>
#include <timezone/tzfile.h>
@@ -37,7 +37,7 @@ weak_alias (__daylight, daylight)
weak_alias (__timezone, timezone)
/* This locks all the state variables in tzfile.c and this file. */
-__libc_lock_define_initialized (static, tzset_lock)
+__libc_lock_define_initialized (, __tzset_lock)
/* This structure contains all the information about a
timezone given in the POSIX standard TZ envariable. */
@@ -529,10 +529,8 @@ __tz_compute (__time64_t timer, struct tm *tm, int use_localtime)
#undef tzset
void
-__tzset (void)
+__tzset_unlocked (void)
{
- __libc_lock_lock (tzset_lock);
-
tzset_internal (1);
if (!__use_tzfile)
@@ -541,9 +539,16 @@ __tzset (void)
__tzname[0] = (char *) tz_rules[0].name;
__tzname[1] = (char *) tz_rules[1].name;
}
+}
- __libc_lock_unlock (tzset_lock);
+void
+__tzset (void)
+{
+ __libc_lock_lock (__tzset_lock);
+ __tzset_unlocked ();
+ __libc_lock_unlock (__tzset_lock);
}
+
weak_alias (__tzset, tzset)
\f
/* Return the `struct tm' representation of TIMER in the local timezone.
@@ -554,8 +559,6 @@ __tz_convert (__time64_t timer, int use_localtime, struct tm *tp)
long int leap_correction;
int leap_extra_secs;
- __libc_lock_lock (tzset_lock);
-
/* Update internal database according to current TZ setting.
POSIX.1 8.3.7.2 says that localtime_r is not required to set tzname.
This is a good idea since this allows at least a bit more parallelism. */
@@ -574,8 +577,6 @@ __tz_convert (__time64_t timer, int use_localtime, struct tm *tp)
leap_extra_secs = 0;
}
- __libc_lock_unlock (tzset_lock);
-
if (tp)
{
if (! use_localtime)
diff --git a/time/tzset.h b/time/tzset.h
new file mode 100644
index 0000000000..fb7984563e
--- /dev/null
+++ b/time/tzset.h
@@ -0,0 +1,30 @@
+#ifndef _TZSET_H
+#define _TZSET_H
+
+#include <time.h>
+#include <libc-lock.h>
+
+/* Defined in tzset.c. */
+extern char *__tzstring (const char *string) attribute_hidden;
+
+extern int __use_tzfile attribute_hidden;
+
+extern void __tzfile_read (const char *file) attribute_hidden;
+extern void __tzfile_compute (__time64_t timer, int use_localtime,
+ long int *leap_correct, int *leap_hit,
+ struct tm *tp) attribute_hidden;
+extern void __tzset_parse_tz (const char *tz) attribute_hidden;
+extern void __tz_compute (__time64_t timer, struct tm *tm, int use_localtime)
+ __THROW attribute_hidden;
+
+__libc_lock_define (extern, __tzset_lock attribute_hidden);
+
+extern void __tzset (void) attribute_hidden;
+extern void __tzset_unlocked (void) attribute_hidden;
+
+/* Prototype for the internal function to get information based on TZ.
+ Caller needs to lock __tzset_lock. */
+extern struct tm *__tz_convert (__time64_t timer, int use_localtime,
+ struct tm *tp) attribute_hidden;
+
+#endif /* _TZSET_H */
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 09/59] Omit parse_offset unnecessary *tz == '\0'
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (8 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 08/59] Push tzset lock into callers of time functions Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:56 ` [PATCH 10/59] Don’t assume TZif bloat is likely Paul Eggert
` (50 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzset.c (parse_offset): Simplify by omitting unnecessary
(*tz == '\0') test.
---
time/tzset.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/time/tzset.c b/time/tzset.c
index a0bf3382ba..9fb3a127f7 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -189,12 +189,13 @@ static bool
parse_offset (const char **tzp, int whichrule)
{
const char *tz = *tzp;
+ bool leading_sign = *tz == '-' || *tz == '+';
if (whichrule == 0
- && (*tz == '\0' || (*tz != '+' && *tz != '-' && !isdigit (*tz))))
+ && (!leading_sign && !isdigit (*tz)))
return false;
int sign;
- if (*tz == '-' || *tz == '+')
+ if (leading_sign)
sign = *tz++ == '-' ? 1 : -1;
else
sign = -1;
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 10/59] Don’t assume TZif bloat is likely
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (9 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 09/59] Omit parse_offset unnecessary *tz == '\0' Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:56 ` [PATCH 11/59] tzset no longer calls ftello Paul Eggert
` (49 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzfile.c (__tzfile_compute): It’s no longer unlikely
that timer >= transitions[num_transitions - 1], now that
‘zic -b slim’ is commonly the default.
---
time/tzfile.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index fa2be56580..9d5f428f47 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -527,7 +527,7 @@ __tzfile_compute (__time64_t timer, int use_localtime,
++j;
}
}
- else if (__glibc_unlikely (timer >= transitions[num_transitions - 1]))
+ else if (timer >= transitions[num_transitions - 1])
{
if (__glibc_unlikely (tzspec == NULL))
{
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 11/59] tzset no longer calls ftello
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (10 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 10/59] Don’t assume TZif bloat is likely Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:56 ` [PATCH 12/59] Check TZif files more carefully Paul Eggert
` (48 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzfile.c (__tzfile_read): Compute file offset directly
rather than invoking ftello.
---
time/tzfile.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index 9d5f428f47..15d1890892 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -168,6 +168,7 @@ __tzfile_read (const char *file)
f = fopen (file, "rce");
if (f == NULL)
goto ret_free_transitions;
+ off_t f_offset = 0;
/* Get information about the file we are actually using. */
if (__fstat64_time64 (__fileno (f), &st) != 0)
@@ -190,6 +191,7 @@ __tzfile_read (const char *file)
|| memcmp (tzhead.tzh_magic, TZ_MAGIC, sizeof (tzhead.tzh_magic)) != 0)
goto lose;
+ f_offset += sizeof tzhead;
num_transitions = (size_t) decode (tzhead.tzh_timecnt);
num_types = (size_t) decode (tzhead.tzh_typecnt);
chars = (size_t) decode (tzhead.tzh_charcnt);
@@ -215,6 +217,7 @@ __tzfile_read (const char *file)
if (fseek (f, to_skip, SEEK_CUR) != 0)
goto lose;
+ f_offset += to_skip;
goto read_again;
}
@@ -223,7 +226,7 @@ __tzfile_read (const char *file)
size_t tzspec_len;
if (trans_width == 8)
{
- off_t rem = st.st_size - __ftello (f);
+ off_t rem = st.st_size - f_offset;
if (__builtin_expect (rem < 0
|| (size_t) rem < (num_transitions * (8 + 1)
+ num_types * 6
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 12/59] Check TZif files more carefully
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (11 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 11/59] tzset no longer calls ftello Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:56 ` [PATCH 13/59] Remove alignment assumption from __tzfile_read Paul Eggert
` (47 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
This tests the contents of TZif files more carefully for
conformance to RFC 9636, in areas where non-conformance
might plausibly mess up glibc or application behavior.
It also removes some nonportable assumptions
about alignment and arithmetic.
* time/tzfile.c: Include stdalign.h, stdckdint.h.
(tzidx): New type. Prefer it to size_t for TZif-related counts
that must fit in 32-bit unsigned integer. This is mostly for clarity.
(tzidx_decode): New function, for obtaining tzidx values.
All uses of decode changed, when tzidx values are wanted.
This fixes some potential misuse of values with the top bit set.
(decode): Reimplement using tzidx_decode.
(decode64): Don’t assume sizeof (int64_t) == 8; admittedly
this is theoretical. Simplify the usual case.
(__tzfile_read): Use unions to guarantee proper alignment.
Check that there is at least one time type, since we always use at
least the zeroth entry. Check for overflow in size and offset
computations. Check that the TZif's file TZ string is either
empty or at least 4 bytes long (not counting newlines).
Avoid aliasing issues when unpacking transitions.
Fix off-by-one bug when checking for bogus index.
Check that all time zone abbreviations are null-terminated.
Check that leap seconds are in ascending order, that they are all
either insertions or deletions of a single second, and that they
are at least 2419199 seconds apart.
(__tzfile_default): Defend against negating INT_MIN.
(__tzfile_compute): Args are now int * and bool *, not
long int * and int *. All uses changed. Defend against
unlikely integer overflows leading to misbehavior.
Do not worry about adjacent leap seconds, as it cannot happen
even though a bug in the original C standard said it could;
this is why we can now use bool * not int * for leap hits.
---
time/tzfile.c | 369 ++++++++++++++++++++++++++++++--------------------
time/tzset.c | 21 ++-
time/tzset.h | 3 +-
3 files changed, 241 insertions(+), 152 deletions(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index 15d1890892..e60c3c0b9c 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -17,6 +17,8 @@
#include <assert.h>
#include <limits.h>
+#include <stdalign.h>
+#include <stdckdint.h>
#include <stdio.h>
#include <stdio_ext.h>
#include <stdlib.h>
@@ -48,17 +50,23 @@ struct ttinfo
struct leap
{
__time64_t transition; /* Time the transition takes effect. */
- long int change; /* Seconds of correction to apply. */
+ int change; /* Seconds of correction to apply. */
};
-static size_t num_transitions;
+/* Internal index derived from a count taken from a TZif file.
+ These counts are at most 2**32 - 1; see Internet RFC 9636.
+ Although this type is currently unsigned to save time and/or space,
+ code ordinarily should not assume it is unsigned. */
+typedef unsigned int tzidx;
+
+static tzidx num_transitions;
static __time64_t *transitions;
static unsigned char *type_idxs;
-static size_t num_types;
+static tzidx num_types;
static struct ttinfo *types;
static char *zone_names;
static long int rule_stdoff;
-static size_t num_leaps;
+static tzidx num_leaps;
static struct leap *leaps;
static char *tzspec;
@@ -69,19 +77,25 @@ static int daylight_saved;
#include <endian.h>
#include <byteswap.h>
-/* Decode the four bytes at PTR as a signed integer in network byte order. */
-static inline int
+/* Decode the four bytes at PTR as a tzidx in network byte order. */
+static inline tzidx
__attribute ((always_inline))
-decode (const void *ptr)
+tzidx_decode (const void *ptr)
{
- if (BYTE_ORDER == BIG_ENDIAN && sizeof (int) == 4)
- return *(const int *) ptr;
- if (sizeof (int) == 4)
- return bswap_32 (*(const int *) ptr);
-
- const unsigned char *p = ptr;
- int result = *p & (1 << (CHAR_BIT - 1)) ? ~0 : 0;
+ if (sizeof (tzidx) == 4
+ && offsetof (struct tzhead, tzh_timecnt) % alignof (tzidx) == 0
+ && offsetof (struct tzhead, tzh_typecnt) % alignof (tzidx) == 0
+ && offsetof (struct tzhead, tzh_charcnt) % alignof (tzidx) == 0
+ && offsetof (struct tzhead, tzh_leapcnt) % alignof (tzidx) == 0
+ && offsetof (struct tzhead, tzh_ttisstdcnt) % alignof (tzidx) == 0
+ && offsetof (struct tzhead, tzh_ttisutcnt) % alignof (tzidx) == 0)
+ {
+ tzidx const *uptr = ptr, i = *uptr;
+ return BYTE_ORDER == BIG_ENDIAN ? i : bswap_32 (i);
+ }
+ unsigned char const *p = ptr;
+ unsigned long result = 0;
result = (result << 8) | *p++;
result = (result << 8) | *p++;
result = (result << 8) | *p++;
@@ -90,15 +104,37 @@ decode (const void *ptr)
return result;
}
+/* Decode the four bytes at PTR as a signed int in network byte order. */
+static inline int
+__attribute ((always_inline))
+decode (const void *ptr)
+{
+ tzidx i = tzidx_decode (ptr);
+ if ((UINT_MAX >> 31) == 1 && INT_MAX == UINT_MAX / 2 && (int) UINT_MAX == -1)
+ return i;
+
+ /* An unusual platform. Explicitly extend the sign. */
+ if (i <= INT_MAX)
+ return i;
+ int r = ~i;
+ return ~r;
+}
static inline int64_t
__attribute ((always_inline))
decode64 (const void *ptr)
{
- if ((BYTE_ORDER == BIG_ENDIAN))
- return *(const int64_t *) ptr;
+ if (sizeof (int64_t) == 8)
+ {
+ int64_t const *p = ptr, i = *p;
+ return BYTE_ORDER == BIG_ENDIAN ? i : bswap_64 (i);
+ }
- return bswap_64 (*(const int64_t *) ptr);
+ unsigned char const *p = ptr;
+ uint64_t result = 0; /* uint64_t avoids undefined behavior with <<. */
+ for (int i = 0; i < 8; i++)
+ result = (result << 8) | p[i];
+ return result;
}
@@ -106,11 +142,11 @@ void
__tzfile_read (const char *file)
{
static const char default_tzdir[] = TZDIR;
- size_t num_isstd, num_isgmt;
+ tzidx num_isstd, num_isgmt;
FILE *f;
- struct tzhead tzhead;
- size_t chars;
- size_t i;
+ union { struct tzhead tzhead; tzidx tzidx_aligned; } u;
+ tzidx chars;
+ tzidx i;
int was_using_tzfile = __use_tzfile;
int trans_width = 4;
char *new = NULL;
@@ -174,7 +210,7 @@ __tzfile_read (const char *file)
if (__fstat64_time64 (__fileno (f), &st) != 0)
goto lose;
- free ((void *) transitions);
+ free (transitions);
transitions = NULL;
/* Remember the inode and device number and modification time. */
@@ -186,34 +222,41 @@ __tzfile_read (const char *file)
__fsetlocking (f, FSETLOCKING_BYCALLER);
read_again:
- if (__builtin_expect (__fread_unlocked ((void *) &tzhead, sizeof (tzhead),
- 1, f) != 1, 0)
- || memcmp (tzhead.tzh_magic, TZ_MAGIC, sizeof (tzhead.tzh_magic)) != 0)
+ if (__builtin_expect (__fread_unlocked (&u, sizeof u.tzhead, 1, f) != 1, 0)
+ || memcmp (u.tzhead.tzh_magic, TZ_MAGIC, sizeof u.tzhead.tzh_magic) != 0)
goto lose;
+ f_offset += sizeof u.tzhead;
- f_offset += sizeof tzhead;
- num_transitions = (size_t) decode (tzhead.tzh_timecnt);
- num_types = (size_t) decode (tzhead.tzh_typecnt);
- chars = (size_t) decode (tzhead.tzh_charcnt);
- num_leaps = (size_t) decode (tzhead.tzh_leapcnt);
- num_isstd = (size_t) decode (tzhead.tzh_ttisstdcnt);
- num_isgmt = (size_t) decode (tzhead.tzh_ttisutcnt);
+ num_transitions = tzidx_decode (u.tzhead.tzh_timecnt);
+ num_types = tzidx_decode (u.tzhead.tzh_typecnt);
+ chars = tzidx_decode (u.tzhead.tzh_charcnt);
+ num_leaps = tzidx_decode (u.tzhead.tzh_leapcnt);
+ num_isstd = tzidx_decode (u.tzhead.tzh_ttisstdcnt);
+ num_isgmt = tzidx_decode (u.tzhead.tzh_ttisutcnt);
- if (__glibc_unlikely (num_isstd > num_types || num_isgmt > num_types))
+ if (__glibc_unlikely (num_isstd > num_types || num_isgmt > num_types
+ || !num_types))
goto lose;
- if (trans_width == 4 && tzhead.tzh_version[0] != '\0')
+ if (trans_width == 4 && u.tzhead.tzh_version[0] != '\0')
{
/* We use the 8-byte format. */
trans_width = 8;
/* Position the stream before the second header. */
- size_t to_skip = (num_transitions * (4 + 1)
- + num_types * 6
- + chars
- + num_leaps * 8
- + num_isstd
- + num_isgmt);
+ off_t to_skip, product;
+ bool v = false;
+ v |= ckd_mul (&to_skip, num_transitions, 4 + 1);
+ v |= ckd_mul (&product, num_types, 6);
+ v |= ckd_add (&to_skip, to_skip, product);
+ v |= ckd_add (&to_skip, to_skip, chars);
+ v |= ckd_mul (&product, num_leaps, 8);
+ v |= ckd_add (&to_skip, to_skip, product);
+ v |= ckd_add (&to_skip, to_skip, num_isstd);
+ v |= ckd_add (&to_skip, to_skip, num_isgmt);
+ if (v)
+ goto lose;
+
if (fseek (f, to_skip, SEEK_CUR) != 0)
goto lose;
@@ -222,34 +265,29 @@ __tzfile_read (const char *file)
}
/* Compute the size of the POSIX time zone specification in the
- file. */
- size_t tzspec_len;
+ file. This includes the trailing but not the leading newline. */
+ size_t tzspec_size;
if (trans_width == 8)
{
- off_t rem = st.st_size - f_offset;
- if (__builtin_expect (rem < 0
- || (size_t) rem < (num_transitions * (8 + 1)
- + num_types * 6
- + chars), 0))
- goto lose;
- tzspec_len = (size_t) rem - (num_transitions * (8 + 1)
- + num_types * 6
- + chars);
- if (__builtin_expect (num_leaps > SIZE_MAX / 12
- || tzspec_len < num_leaps * 12, 0))
- goto lose;
- tzspec_len -= num_leaps * 12;
- if (__glibc_unlikely (tzspec_len < num_isstd))
- goto lose;
- tzspec_len -= num_isstd;
- if (__glibc_unlikely (tzspec_len == 0 || tzspec_len - 1 < num_isgmt))
- goto lose;
- tzspec_len -= num_isgmt + 1;
- if (tzspec_len == 0)
+ off_t rem = st.st_size - f_offset, product;
+ bool v = false;
+ v |= ckd_mul (&product, num_transitions, 8 + 1);
+ v |= ckd_sub (&rem, rem, product);
+ v |= ckd_mul (&product, num_types, 6);
+ v |= ckd_sub (&rem, rem, product);
+ v |= ckd_sub (&rem, rem, chars);
+ v |= ckd_mul (&product, num_leaps, 12);
+ v |= ckd_sub (&rem, rem, product);
+ v |= ckd_sub (&rem, rem, num_isstd);
+ v |= ckd_sub (&rem, rem, num_isgmt);
+ v |= rem <= 1;
+ v |= 3 <= rem && rem < sizeof "XYZ0\n";
+ if (v)
goto lose;
+ tzspec_size = rem - 1;
}
else
- tzspec_len = 0;
+ tzspec_size = 0;
/* The file is parsed into a single heap allocation, comprising of
the following arrays:
@@ -259,7 +297,7 @@ __tzfile_read (const char *file)
struct ttinfo types[num_types];
unsigned char type_idxs[num_types];
char zone_names[chars];
- char tzspec[tzspec_len];
+ char tzspec[tzspec_size];
The piece-wise allocations from buf below verify that no
overflow/wraparound occurred in these computations.
@@ -274,12 +312,18 @@ __tzfile_read (const char *file)
"alignment of struct leap");
struct alloc_buffer buf;
{
- size_t total_size = (num_transitions * sizeof (__time64_t)
- + num_leaps * sizeof (struct leap)
- + num_types * sizeof (struct ttinfo)
- + num_transitions /* type_idxs */
- + chars /* zone_names */
- + tzspec_len);
+ size_t total_size, product;
+ bool v = false;
+ v |= ckd_mul (&total_size, num_transitions, sizeof (__time64_t));
+ v |= ckd_mul (&product, num_leaps, sizeof (struct leap));
+ v |= ckd_add (&total_size, total_size, product);
+ v |= ckd_mul (&product, num_types, sizeof (struct ttinfo));
+ v |= ckd_add (&total_size, total_size, product);
+ v |= ckd_add (&total_size, total_size, num_transitions); /* type_idxs */
+ v |= ckd_add (&total_size, total_size, chars); /* zone_names */
+ v |= ckd_add (&total_size, total_size, tzspec_size);
+ if (v)
+ goto lose;
transitions = malloc (total_size);
if (transitions == NULL)
goto lose;
@@ -294,18 +338,53 @@ __tzfile_read (const char *file)
type_idxs = alloc_buffer_alloc_array (&buf, unsigned char, num_transitions);
zone_names = alloc_buffer_alloc_array (&buf, char, chars);
if (trans_width == 8)
- tzspec = alloc_buffer_alloc_array (&buf, char, tzspec_len);
+ tzspec = alloc_buffer_alloc_array (&buf, char, tzspec_size);
else
tzspec = NULL;
if (alloc_buffer_has_failed (&buf))
goto lose;
- if (__glibc_unlikely (__fread_unlocked (transitions, trans_width,
- num_transitions, f)
- != num_transitions)
- || __glibc_unlikely (__fread_unlocked (type_idxs, 1, num_transitions, f)
- != num_transitions))
+ if (trans_width == 4)
+ {
+ for (i = 0; i < num_transitions; i++)
+ {
+ union { unsigned char c[4]; tzidx tzidx_aligned; } x;
+ if (__fread_unlocked (&x, 1, 4, f) != 4)
+ goto lose;
+ transitions[i] = decode (&x);
+ if (i && transitions[i] <= transitions[i - 1])
+ goto lose;
+ }
+ }
+ else if (sizeof (int64_t) == 8)
+ {
+ if (__fread_unlocked (transitions, 8, num_transitions, f)
+ != num_transitions)
goto lose;
+ for (i = 0; i < num_transitions; i++)
+ {
+ if (BYTE_ORDER != BIG_ENDIAN)
+ transitions[i] = decode64 (&transitions[i]);
+ if (i && transitions[i] <= transitions[i - 1])
+ goto lose;
+ }
+ }
+ else
+ {
+ for (i = 0; i < num_transitions; i++)
+ {
+ union { unsigned char c[8]; int64_t int64_t_aligned; } x;
+ if (__fread_unlocked (&x, 1, 8, f) != 8)
+ goto lose;
+ transitions[i] = decode64 (&x);
+ if (i && transitions[i] <= transitions[i - 1])
+ goto lose;
+ }
+ }
+
+ if (__glibc_unlikely (__fread_unlocked (type_idxs, 1, num_transitions, f)
+ != num_transitions))
+ goto lose;
/* Check for bogus indices in the data file, so we can hereafter
safely use type_idxs[T] as indices into `types' and never crash. */
@@ -313,61 +392,67 @@ __tzfile_read (const char *file)
if (__glibc_unlikely (type_idxs[i] >= num_types))
goto lose;
- if (trans_width == 4)
- {
- /* Decode the transition times, stored as 4-byte integers in
- network (big-endian) byte order. We work from the end of the
- array so as not to clobber the next element to be
- processed. */
- i = num_transitions;
- while (i-- > 0)
- transitions[i] = decode ((char *) transitions + i * 4);
- }
- else if (BYTE_ORDER != BIG_ENDIAN)
- {
- /* Decode the transition times, stored as 8-byte integers in
- network (big-endian) byte order. */
- for (i = 0; i < num_transitions; ++i)
- transitions[i] = decode64 ((char *) transitions + i * 8);
- }
-
+ unsigned char max_idx = 0;
for (i = 0; i < num_types; ++i)
{
- unsigned char x[4];
+ union { unsigned char c[4]; tzidx tzidx_aligned; } x;
int c;
- if (__builtin_expect (__fread_unlocked (x, 1,
- sizeof (x), f) != sizeof (x),
- 0))
+ if (__builtin_expect (__fread_unlocked (&x, 1, 4, f) != 4, 0))
goto lose;
c = __getc_unlocked (f);
- if (__glibc_unlikely ((unsigned int) c > 1u))
+ if (__glibc_unlikely (! (0 <= c && c <= 1)))
goto lose;
types[i].isdst = c;
c = __getc_unlocked (f);
- if (__glibc_unlikely ((size_t) c > chars))
+ if (__glibc_unlikely (! (0 <= c && c < chars)))
/* Bogus index in data file. */
goto lose;
+ if (max_idx < c)
+ max_idx = c;
types[i].idx = c;
- types[i].offset = decode (x);
+ types[i].offset = decode (&x);
+
+ /* If long int is only 32 bits, reject offsets that cannot be negated.
+ RFC 9636 section 3.2 allows this. */
+ long int negated_offset;
+ if (ckd_sub (&negated_offset, 0, types[i].offset))
+ goto lose;
}
if (__glibc_unlikely (__fread_unlocked (zone_names, 1, chars, f) != chars))
goto lose;
+ if (__strnlen (zone_names + max_idx, chars - max_idx) == chars - max_idx)
+ goto lose;
+ int minimum_leap_gap = 2419199; /* See RFC 9636 section 3.2. */
+ __time64_t prevtransition = -minimum_leap_gap;
+ int prevchange = 0;
for (i = 0; i < num_leaps; ++i)
{
- unsigned char x[8];
- if (__builtin_expect (__fread_unlocked (x, 1, trans_width, f)
- != trans_width, 0))
- goto lose;
- if (trans_width == 4)
- leaps[i].transition = decode (x);
- else
- leaps[i].transition = decode64 (x);
-
- if (__glibc_unlikely (__fread_unlocked (x, 1, 4, f) != 4))
- goto lose;
- leaps[i].change = (long int) decode (x);
+ {
+ union { char c[8]; tzidx tzidx_aligned; int64_t int64_t_aligned; } x;
+ if (__builtin_expect ((__fread_unlocked (&x, 1, trans_width, f)
+ != trans_width),
+ 0))
+ goto lose;
+ leaps[i].transition = trans_width == 4 ? decode (&x) : decode64 (&x);
+ if (leaps[i].transition < 0
+ || leaps[i].transition - minimum_leap_gap < prevtransition)
+ goto lose;
+ prevtransition = leaps[i].transition;
+ }
+
+ {
+ union { unsigned char c[4]; tzidx tzidx_aligned; } x;
+ if (__glibc_unlikely (__fread_unlocked (&x, 1, 4, f) != 4))
+ goto lose;
+ leaps[i].change = decode (&x);
+ int delta_change;
+ if (ckd_sub (&delta_change, leaps[i].change, prevchange)
+ || ! (delta_change == 1 || delta_change == -1))
+ goto lose;
+ prevchange = leaps[i].change;
+ }
}
for (i = 0; i < num_isstd; ++i)
@@ -393,14 +478,16 @@ __tzfile_read (const char *file)
/* Read the POSIX TZ-style information if possible. */
if (tzspec != NULL)
{
- assert (tzspec_len > 0);
- /* Skip over the newline first. */
- if (__getc_unlocked (f) != '\n'
- || (__fread_unlocked (tzspec, 1, tzspec_len - 1, f)
- != tzspec_len - 1))
- tzspec = NULL;
+ char *nl;
+ assert (tzspec_size > 0);
+ /* Skip the leading newline, then grab everything up to the next
+ newline; ignore everything after that. */
+ if (__getc_unlocked (f) == '\n'
+ && __fread_unlocked (tzspec, 1, tzspec_size, f) == tzspec_size
+ && (nl = memchr (tzspec, '\n', tzspec_size)) != NULL)
+ *nl = '\0';
else
- tzspec[tzspec_len - 1] = '\0';
+ tzspec = NULL;
}
/* Don't use an empty TZ string. */
@@ -484,16 +571,16 @@ __tzfile_read (const char *file)
fclose (f);
ret_free_transitions:
free (new);
- free ((void *) transitions);
+ free (transitions);
transitions = NULL;
}
\f
void
__tzfile_compute (__time64_t timer, int use_localtime,
- long int *leap_correct, int *leap_hit,
+ int *leap_correct, bool *leap_hit,
struct tm *tp)
{
- size_t i;
+ tzidx i;
if (use_localtime)
{
@@ -519,7 +606,7 @@ __tzfile_compute (__time64_t timer, int use_localtime,
__tzname[0] = __tzstring (&zone_names[types[i].idx]);
if (__tzname[1] == NULL)
{
- size_t j = i;
+ tzidx j = i;
while (j < num_types)
if (types[j].isdst)
{
@@ -556,15 +643,16 @@ __tzfile_compute (__time64_t timer, int use_localtime,
{
/* Find the first transition after TIMER, and
then pick the type of the transition before it. */
- size_t lo = 0;
- size_t hi = num_transitions - 1;
+ tzidx lo = 0;
+ tzidx hi = num_transitions - 1;
/* Assume that DST is changing twice a year and guess
initial search spot from it. Half of a gregorian year
has on average 365.2425 * 86400 / 2 = 15778476 seconds.
- The value i can be truncated if size_t is smaller than
- __time64_t, but this is harmless because it is just
- a guess. */
- i = (transitions[num_transitions - 1] - timer) / 15778476;
+ Although i's value can be wrong if overflow occurs,
+ this is harmless because it is just a guess. */
+ __time64_t tdiff;
+ ckd_sub (&tdiff, transitions[num_transitions - 1], timer);
+ ckd_add (&i, tdiff / 15778476, 0);
if (i < num_transitions)
{
i = num_transitions - 1 - i;
@@ -581,7 +669,7 @@ __tzfile_compute (__time64_t timer, int use_localtime,
}
else
{
- if (i + 10 >= num_transitions || timer < transitions[i + 10])
+ if (num_transitions - i <= 10 || timer < transitions[i + 10])
{
/* Linear search. */
while (timer >= transitions[i])
@@ -596,7 +684,7 @@ __tzfile_compute (__time64_t timer, int use_localtime,
/* assert (timer >= transitions[lo] && timer < transitions[hi]); */
while (lo + 1 < hi)
{
- i = (lo + hi) / 2;
+ i = (lo >> 1) + (hi >> 1) + (lo & hi & 1);
if (timer < transitions[i])
hi = i;
else
@@ -609,7 +697,7 @@ __tzfile_compute (__time64_t timer, int use_localtime,
&& (i == num_transitions || timer < transitions[i])); */
__tzname[types[type_idxs[i - 1]].isdst]
= __tzstring (&zone_names[types[type_idxs[i - 1]].idx]);
- size_t j = i;
+ tzidx j = i;
while (j < num_transitions)
{
int type = type_idxs[j];
@@ -654,8 +742,8 @@ __tzfile_compute (__time64_t timer, int use_localtime,
}
leap:
- *leap_correct = 0L;
- *leap_hit = 0;
+ *leap_correct = 0;
+ *leap_hit = false;
/* Find the last leap second correction transition time before TIMER. */
i = num_leaps;
@@ -669,16 +757,7 @@ __tzfile_compute (__time64_t timer, int use_localtime,
if (timer == leaps[i].transition /* Exactly at the transition time. */
&& (leaps[i].change > (i == 0 ? 0 : leaps[i - 1].change)))
- {
- *leap_hit = 1;
- while (i > 0
- && leaps[i].transition == leaps[i - 1].transition + 1
- && leaps[i].change == leaps[i - 1].change + 1)
- {
- ++*leap_hit;
- --i;
- }
- }
+ *leap_hit = true;
}
weak_alias (transitions, __libc_tzfile_freemem_ptr)
diff --git a/time/tzset.c b/time/tzset.c
index 9fb3a127f7..68e68f6cb9 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -557,8 +557,8 @@ weak_alias (__tzset, tzset)
struct tm *
__tz_convert (__time64_t timer, int use_localtime, struct tm *tp)
{
- long int leap_correction;
- int leap_extra_secs;
+ int leap_correction;
+ bool leap_extra_sec;
/* Update internal database according to current TZ setting.
POSIX.1 8.3.7.2 says that localtime_r is not required to set tzname.
@@ -567,15 +567,15 @@ __tz_convert (__time64_t timer, int use_localtime, struct tm *tp)
if (__use_tzfile)
__tzfile_compute (timer, use_localtime, &leap_correction,
- &leap_extra_secs, tp);
+ &leap_extra_sec, tp);
else
{
if (! __offtime (timer, 0, tp))
tp = NULL;
else
__tz_compute (timer, tp, use_localtime);
- leap_correction = 0L;
- leap_extra_secs = 0;
+ leap_correction = 0;
+ leap_extra_sec = false;
}
if (tp)
@@ -588,7 +588,16 @@ __tz_convert (__time64_t timer, int use_localtime, struct tm *tp)
}
if (__offtime (timer, tp->tm_gmtoff - leap_correction, tp))
- tp->tm_sec += leap_extra_secs;
+ {
+ /* This assumes leap seconds can occur only when the local
+ time offset from UTC is a multiple of 60 seconds,
+ so the leap second occurs at HH:MM:60 local time.
+ Historically this has always been true, as the last
+ timezone to use some other UTC offset was Africa/Monrovia
+ in January 1972, whereas the first leap second did not
+ occur until almost six months later. */
+ tp->tm_sec += leap_extra_sec;
+ }
else
tp = NULL;
}
diff --git a/time/tzset.h b/time/tzset.h
index fb7984563e..d2d5853f0b 100644
--- a/time/tzset.h
+++ b/time/tzset.h
@@ -3,6 +3,7 @@
#include <time.h>
#include <libc-lock.h>
+#include <stdbool.h>
/* Defined in tzset.c. */
extern char *__tzstring (const char *string) attribute_hidden;
@@ -11,7 +12,7 @@ extern int __use_tzfile attribute_hidden;
extern void __tzfile_read (const char *file) attribute_hidden;
extern void __tzfile_compute (__time64_t timer, int use_localtime,
- long int *leap_correct, int *leap_hit,
+ int *leap_correct, bool *leap_hit,
struct tm *tp) attribute_hidden;
extern void __tzset_parse_tz (const char *tz) attribute_hidden;
extern void __tz_compute (__time64_t timer, struct tm *tm, int use_localtime)
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 13/59] Remove alignment assumption from __tzfile_read
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (12 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 12/59] Check TZif files more carefully Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:56 ` [PATCH 14/59] Treat oddball UTC offset strings as zero Paul Eggert
` (46 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzfile.c (__tzfile_read): Allocate more portably,
removing the need for a couple of _Static_asserts.
---
time/tzfile.c | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index e60c3c0b9c..7125f5a8e3 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -302,14 +302,14 @@ __tzfile_read (const char *file)
The piece-wise allocations from buf below verify that no
overflow/wraparound occurred in these computations.
- The order of the suballocations is important for alignment
- purposes. __time64_t outside a struct may require more alignment
- then inside a struct on some architectures, so it must come
- first. */
- _Static_assert (__alignof (__time64_t) >= __alignof (struct leap),
- "alignment of __time64_t");
- _Static_assert (__alignof (struct leap) >= __alignof (struct ttinfo),
- "alignment of struct leap");
+ The order of the suballocations is important for alignment purposes.
+ The calculation of ALIGNMENT_SLOP assumes arrays are allocated in
+ element order __time_64_t, struct leap, struct ttinfo. */
+ size_t alignment_slop =
+ ((__alignof__ (struct leap) <= __alignof__ (__time64_t)
+ ? 0 : __alignof__ (struct leap) - __alignof__ (__time64_t))
+ + (__alignof__ (struct ttinfo) <= __alignof__ (struct leap)
+ ? 0 : __alignof__ (struct ttinfo) - __alignof__ (struct leap)));
struct alloc_buffer buf;
{
size_t total_size, product;
@@ -322,6 +322,7 @@ __tzfile_read (const char *file)
v |= ckd_add (&total_size, total_size, num_transitions); /* type_idxs */
v |= ckd_add (&total_size, total_size, chars); /* zone_names */
v |= ckd_add (&total_size, total_size, tzspec_size);
+ v |= ckd_add (&total_size, total_size, alignment_slop);
if (v)
goto lose;
transitions = malloc (total_size);
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 14/59] Treat oddball UTC offset strings as zero
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (13 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 13/59] Remove alignment assumption from __tzfile_read Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:56 ` [PATCH 15/59] Simplify __tz_compute Paul Eggert
` (45 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
Also, extend support of offsets and times to the range INT_MIN to
INT_MAX. tzfile already supports offsets in that range because the
TZif format uses 32-bit signed numbers for offsets, so we might as
well be consistent here, if only to allow easier testing via
traditional POSIX TZ strings.
* time/tzset.c: Include stdckdint.h, not stdio.h.
(compute_offset): Remove.
(parse_int, parse_hhmmss): New functions.
(parse_offset): Use parse_hhmmss instead of sscanf to have well-defined
behavior with outlandish input, such as numbers so large they
overflow, or whose textual representations have more than
INT_MAX bytes. Consistently treat overflow as 0 rather than as
some randomish value. Treat values outside the POSIX range
as overflows, as the rest of the code cannot deal with times
greater than week or so or offsets greater than a day or so.
(parse_rule): Use parse_int and parse_hhmmss instead of strtoul and
sscanf, for similar reasons.
---
time/tzset.c | 177 +++++++++++++++++++++++++++++----------------------
1 file changed, 102 insertions(+), 75 deletions(-)
diff --git a/time/tzset.c b/time/tzset.c
index 68e68f6cb9..6783067053 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -17,8 +17,8 @@
#include <ctype.h>
#include <stdbool.h>
+#include <stdckdint.h>
#include <stddef.h>
-#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
@@ -133,18 +133,6 @@ update_vars (void)
}
-static unsigned int
-compute_offset (unsigned int ss, unsigned int mm, unsigned int hh)
-{
- if (ss > 59)
- ss = 59;
- if (mm > 59)
- mm = 59;
- if (hh > 24)
- hh = 24;
- return ss + mm * 60 + hh * 60 * 60;
-}
-
/* Parses the time zone abbreviation at *TZP, and writes a pointer to an
interned string to tz_rules[WHICHRULE].name. On success, advances
*TZP, and returns true. Returns false otherwise. */
@@ -182,44 +170,90 @@ parse_tzname (const char **tzp, int whichrule)
return true;
}
-/* Parses the time zone offset at *TZP, and writes it to
- tz_rules[WHICHRULE].offset. Returns true if the parse was
+/* Parse the prefix of *TZP into an int, and update *TZP.
+ Return the unsigned decimal integer represented by a prefix of S; however,
+ return -1 if the integer is missing or if exceeds INT_MAX. */
+static int
+parse_int (char const **tzp)
+{
+ char const *tz = *tzp;
+ if (!isdigit (*tz))
+ return -1;
+ int r = 0;
+ bool v = false;
+ do
+ {
+ v |= ckd_mul (&r, r, 10);
+ v |= ckd_add (&r, r, *tz - '0');
+ tz++;
+ }
+ while (isdigit (*tz));
+
+ *tzp = tz;
+ return v ? -1 : r;
+}
+
+/* Parse an optional [+-]HH[:MM[:SS]] prefix into an integer number of seconds.
+ It is a prefix of the text string *TZP; advance *TZP past the prefix.
+ HH, MM, and SS can be any positive number of digits.
+ The absolute value of the offset must be less than ABS_TOOLARGE.
+ Ordinarily, store the result into *OFFSET and return 0.
+ If the prefix is missing, store 0 and return a negative number.
+ If the result overflows, store 0 and return a positive number. */
+static int
+parse_hhmmss (char const **tzp, int abs_toolarge, int *offset)
+{
+ const char *tz = *tzp;
+ char tz0 = *tz;
+ tz += tz0 == '-' || tz0 == '+';
+ if (!isdigit (*tz))
+ {
+ *offset = 0;
+ return -1;
+ }
+
+ int hh = parse_int (&tz), mm = 0, ss = 0;
+ if (tz[0] == ':' && isdigit (tz[1]))
+ {
+ tz++, mm = parse_int (&tz);
+ if (tz[0] == ':' && isdigit (tz[1]))
+ tz++, ss = parse_int (&tz);
+ }
+
+ bool v = (hh < 0) | (mm < 0) | (ss < 0);
+ int off, mm60;
+ v |= ckd_mul (&off, hh, 60 * 60);
+ v |= ckd_mul (&mm60, mm, 60);
+ v |= ckd_add (&off, off, mm60);
+ v |= ckd_add (&off, off, ss);
+ v |= abs_toolarge <= off;
+ if (tz0 == '-')
+ ckd_sub (&off, 0, off); /* Ignore any overflow. */
+ *tzp = tz;
+ *offset = v ? 0 : off;
+ return v;
+}
+
+/* Parse the time zone offset at *TZP, and write it to
+ tz_rules[WHICHRULE].offset. Return true if the parse was
successful. */
static bool
parse_offset (const char **tzp, int whichrule)
{
- const char *tz = *tzp;
- bool leading_sign = *tz == '-' || *tz == '+';
- if (whichrule == 0
- && (!leading_sign && !isdigit (*tz)))
- return false;
+ int offset;
+ int status = parse_hhmmss (tzp, (24 + 1) * 60 * 60, &offset);
- int sign;
- if (leading_sign)
- sign = *tz++ == '-' ? 1 : -1;
+ if (status == 0)
+ offset = -offset;
+ else if (status < 0 && whichrule != 0)
+ {
+ /* A missing DST stands for one hour east of standard time. */
+ offset = tz_rules[0].offset + 60 * 60;
+ }
else
- sign = -1;
- *tzp = tz;
+ return false;
- unsigned short int hh;
- unsigned short mm = 0;
- unsigned short ss = 0;
- int consumed = 0;
- if (sscanf (tz, "%hu%n:%hu%n:%hu%n",
- &hh, &consumed, &mm, &consumed, &ss, &consumed) > 0)
- tz_rules[whichrule].offset = sign * compute_offset (ss, mm, hh);
- else
- /* Nothing could be parsed. */
- if (whichrule == 0)
- {
- /* Standard time defaults to offset zero. */
- tz_rules[0].offset = 0;
- return false;
- }
- else
- /* DST defaults to one hour later than standard time. */
- tz_rules[1].offset = tz_rules[0].offset + (60 * 60);
- *tzp = tz + consumed;
+ tz_rules[whichrule].offset = offset;
return true;
}
@@ -237,30 +271,32 @@ parse_rule (const char **tzp, int whichrule)
tz += *tz == ',';
/* Get the date of the change. */
- if (*tz == 'J' || isdigit (*tz))
+ bool is_J = *tz == 'J';
+ if (is_J || isdigit (*tz))
{
- char *end;
- tzr->type = *tz == 'J' ? J1 : J0;
- if (tzr->type == J1 && !isdigit (*++tz))
- return false;
- unsigned long int d = strtoul (tz, &end, 10);
- if (end == tz || d > 365)
- return false;
- if (tzr->type == J1 && d == 0)
+ tzr->type = is_J ? J1 : J0;
+ tz += is_J;
+ int d = parse_int (&tz);
+ if (! (is_J <= d && d <= 365))
return false;
tzr->d = d;
- tz = end;
}
else if (*tz == 'M')
{
tzr->type = M;
- int consumed;
- if (sscanf (tz, "M%hu.%hu.%hu%n",
- &tzr->m, &tzr->n, &tzr->d, &consumed) != 3
- || tzr->m < 1 || tzr->m > 12
- || tzr->n < 1 || tzr->n > 5 || tzr->d > 6)
+ tz++;
+ int i = parse_int (&tz);
+ if (! (1 <= i && i <= 12 && *tz++ == '.'))
+ return false;
+ tzr->m = i;
+ i = parse_int (&tz);
+ if (! (1 <= i && i <= 5 && *tz++ == '.'))
return false;
- tz += consumed;
+ tzr->n = i;
+ i = parse_int (&tz);
+ if (! (0 <= i && i <= 6))
+ return false;
+ tzr->d = i;
}
else if (*tz == '\0')
{
@@ -287,31 +323,22 @@ parse_rule (const char **tzp, int whichrule)
else
return false;
+ int secs;
+
if (*tz != '\0' && *tz != '/' && *tz != ',')
return false;
else if (*tz == '/')
{
/* Get the time of day of the change. */
- int negative;
++tz;
- if (*tz == '\0')
+ if (parse_hhmmss (&tz, (167 + 1) * 60 * 60, &secs) != 0)
return false;
- negative = *tz == '-';
- tz += negative;
- /* Default to 2:00 AM. */
- unsigned short hh = 2;
- unsigned short mm = 0;
- unsigned short ss = 0;
- int consumed = 0;
- sscanf (tz, "%hu%n:%hu%n:%hu%n",
- &hh, &consumed, &mm, &consumed, &ss, &consumed);;
- tz += consumed;
- tzr->secs = (negative ? -1 : 1) * ((hh * 60 * 60) + (mm * 60) + ss);
}
else
/* Default to 2:00 AM. */
- tzr->secs = 2 * 60 * 60;
+ secs = 2 * 60 * 60;
+ tzr->secs = secs;
tzr->computed_for = -1;
*tzp = tz;
return true;
@@ -491,7 +518,7 @@ compute_change (tz_rule *rule, int year)
}
/* T is now the Epoch-relative time of 0:00:00 GMT on the day we want.
- Just add the time of day and local offset from GMT, and we're done. */
+ Just subtract the UT offset and add the time of day, and we're done. */
rule->change = t - rule->offset + rule->secs;
rule->computed_for = year;
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 15/59] Simplify __tz_compute
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (14 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 14/59] Treat oddball UTC offset strings as zero Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:56 ` [PATCH 16/59] Improve performance in 2 BC Paul Eggert
` (44 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzset.c (__tz_compute): Omit last arg USE_LOCALTIME,
since this function should be called only when using localtime.
All callers changed. Clarify isdst computation.
---
time/tzfile.c | 2 +-
time/tzset.c | 35 ++++++++++++-----------------------
time/tzset.h | 2 +-
3 files changed, 14 insertions(+), 25 deletions(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index 7125f5a8e3..5a7514e466 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -636,7 +636,7 @@ __tzfile_compute (__time64_t timer, int use_localtime,
goto use_last;
/* Use the rules from the TZ string to compute the change. */
- __tz_compute (timer, tp, 1);
+ __tz_compute (timer, tp);
goto leap;
}
diff --git a/time/tzset.c b/time/tzset.c
index 6783067053..bd3509307e 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -525,32 +525,21 @@ compute_change (tz_rule *rule, int year)
}
-/* Figure out the correct timezone for TM and set `__tzname',
- `__timezone', and `__daylight' accordingly. */
+/* Figure out the correct local timezone for TM. */
void
-__tz_compute (__time64_t timer, struct tm *tm, int use_localtime)
+__tz_compute (__time64_t timer, struct tm *tm)
{
compute_change (&tz_rules[0], 1900 + tm->tm_year);
compute_change (&tz_rules[1], 1900 + tm->tm_year);
- if (use_localtime)
- {
- int isdst;
-
- /* We have to distinguish between northern and southern
- hemisphere. For the latter the daylight saving time
- ends in the next year. */
- if (__builtin_expect (tz_rules[0].change
- > tz_rules[1].change, 0))
- isdst = (timer < tz_rules[1].change
- || timer >= tz_rules[0].change);
- else
- isdst = (timer >= tz_rules[0].change
- && timer < tz_rules[1].change);
- tm->tm_isdst = isdst;
- tm->tm_zone = __tzname[isdst];
- tm->tm_gmtoff = tz_rules[isdst].offset;
- }
+ /* Distinguish between northern and southern hemisphere.
+ For the latter the daylight saving time ends in the next year. */
+ bool isdst = (__glibc_likely (tz_rules[0].change <= tz_rules[1].change)
+ ? tz_rules[0].change <= timer && timer < tz_rules[1].change
+ : ! (tz_rules[1].change <= timer && timer < tz_rules[0].change));
+ tm->tm_isdst = isdst;
+ tm->tm_zone = __tzname[isdst];
+ tm->tm_gmtoff = tz_rules[isdst].offset;
}
\f
/* Reinterpret the TZ environment variable and set `tzname'. */
@@ -599,8 +588,8 @@ __tz_convert (__time64_t timer, int use_localtime, struct tm *tp)
{
if (! __offtime (timer, 0, tp))
tp = NULL;
- else
- __tz_compute (timer, tp, use_localtime);
+ else if (use_localtime)
+ __tz_compute (timer, tp);
leap_correction = 0;
leap_extra_sec = false;
}
diff --git a/time/tzset.h b/time/tzset.h
index d2d5853f0b..8c87ad6a24 100644
--- a/time/tzset.h
+++ b/time/tzset.h
@@ -15,7 +15,7 @@ extern void __tzfile_compute (__time64_t timer, int use_localtime,
int *leap_correct, bool *leap_hit,
struct tm *tp) attribute_hidden;
extern void __tzset_parse_tz (const char *tz) attribute_hidden;
-extern void __tz_compute (__time64_t timer, struct tm *tm, int use_localtime)
+extern void __tz_compute (__time64_t timer, struct tm *tm)
__THROW attribute_hidden;
__libc_lock_define (extern, __tzset_lock attribute_hidden);
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 16/59] Improve performance in 2 BC
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (15 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 15/59] Simplify __tz_compute Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:56 ` [PATCH 17/59] Don’t mishandle years < 1970 in compute_change Paul Eggert
` (43 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzset.c: Include <limits.h>, for INT_MIN.
(parse_rule): Initialize computed_for to INT_MIN, not -1, as
INT_MIN is otherwise impossible after adding 1900.
(compute_change): Simplify accordingly.
---
time/tzset.c | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/time/tzset.c b/time/tzset.c
index bd3509307e..e869fbf3fd 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -16,6 +16,7 @@
<https://www.gnu.org/licenses/>. */
#include <ctype.h>
+#include <limits.h>
#include <stdbool.h>
#include <stdckdint.h>
#include <stddef.h>
@@ -55,7 +56,8 @@ typedef struct
/* We cache the computed time of change for a
given year so we don't have to recompute it. */
__time64_t change; /* When to change to this zone. */
- int computed_for; /* Year above is computed for. */
+ int computed_for; /* Year that CHANGE is computed for.
+ If INT_MIN, CHANGE is unspecified. */
} tz_rule;
/* tz_rules[0] is standard, tz_rules[1] is daylight. */
@@ -339,7 +341,7 @@ parse_rule (const char **tzp, int whichrule)
secs = 2 * 60 * 60;
tzr->secs = secs;
- tzr->computed_for = -1;
+ tzr->computed_for = INT_MIN;
*tzp = tz;
return true;
}
@@ -444,8 +446,7 @@ compute_change (tz_rule *rule, int year)
{
__time64_t t;
- if (year != -1 && rule->computed_for == year)
- /* Operations on times in 2 BC will be slower. Oh well. */
+ if (rule->computed_for == year)
return;
/* First set T to January 1st, 0:00:00 GMT in YEAR. */
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 17/59] Don’t mishandle years < 1970 in compute_change
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (16 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 16/59] Improve performance in 2 BC Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:56 ` [PATCH 18/59] Match RFC 9636 names Paul Eggert
` (42 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tst-posixtz.c (tests): Test 1966 too.
* time/tzset.c (tz_rule): Change computed_for member from int
to long long int, so that we needn't worry about int overflow
when adding 1900 to a year. All uses changed.
* time/tzset.c (compute_change): Year arg is now long long int,
not int. All uses changed. Do not mess up if the year is
less than 1970.
---
time/tst-posixtz.c | 13 +++++++++--
time/tzset.c | 54 ++++++++++++++++++++++++++--------------------
2 files changed, 42 insertions(+), 25 deletions(-)
diff --git a/time/tst-posixtz.c b/time/tst-posixtz.c
index 936c5b8fc6..482bd4f2bc 100644
--- a/time/tst-posixtz.c
+++ b/time/tst-posixtz.c
@@ -29,8 +29,17 @@ struct
{ 919973892L, "EST+5EDT,M4.1.0/2,M10.5.0/2",
"1999/02/25 15:18:12 dst=0 zone=EST" },
- /* Test Atlantic Standard / Atlantic Daylight transitions in 2024 and 2038,
- with explicit DST rule. */
+ /* Test Atlantic Standard / Atlantic Daylight transitions in 1966,
+ 2024 and 2038, with explicit DST rule. See Bug#32395 for why
+ 1966 is being tested. */
+ { -120074401, "AST4ADT,M3.2.0,M11.1.0",
+ "1966/03/13 01:59:59 dst=0 zone=AST" },
+ { -120074400, "AST4ADT,M3.2.0/2,M11.1.0/2",
+ "1966/03/13 03:00:00 dst=1 zone=ADT" },
+ { -99514801, "AST4ADT,M3.2.0/02:00,M11.1.0/02:00",
+ "1966/11/06 01:59:59 dst=1 zone=ADT" },
+ { -99514800, "AST4ADT,M3.2.0/02:00:00,M11.1.0/02:00:00",
+ "1966/11/06 01:00:00 dst=0 zone=AST" },
{ 1710050399, "AST4ADT,M3.2.0,M11.1.0",
"2024/03/10 01:59:59 dst=0 zone=AST" },
{ 1710050400, "AST4ADT,M3.2.0/2,M11.1.0/2",
diff --git a/time/tzset.c b/time/tzset.c
index e869fbf3fd..7cc675334a 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -56,15 +56,15 @@ typedef struct
/* We cache the computed time of change for a
given year so we don't have to recompute it. */
__time64_t change; /* When to change to this zone. */
- int computed_for; /* Year that CHANGE is computed for.
- If INT_MIN, CHANGE is unspecified. */
+ long long int computed_for; /* Year that CHANGE is computed for.
+ If LLONG_MIN, CHANGE is unspecified. */
} tz_rule;
/* tz_rules[0] is standard, tz_rules[1] is daylight. */
static tz_rule tz_rules[2];
-static void compute_change (tz_rule *rule, int year) __THROW;
+static void compute_change (tz_rule *rule, long long int year) __THROW;
static void tzset_internal (int always);
\f
/* List of buffers containing time zone strings. */
@@ -341,7 +341,7 @@ parse_rule (const char **tzp, int whichrule)
secs = 2 * 60 * 60;
tzr->secs = secs;
- tzr->computed_for = INT_MIN;
+ tzr->computed_for = LLONG_MIN;
*tzp = tz;
return true;
}
@@ -442,7 +442,7 @@ tzset_internal (int always)
when the change described by RULE will occur and
put it in RULE->change, saving YEAR in RULE->computed_for. */
static void
-compute_change (tz_rule *rule, int year)
+compute_change (tz_rule *rule, long long int year)
{
__time64_t t;
@@ -450,17 +450,15 @@ compute_change (tz_rule *rule, int year)
return;
/* First set T to January 1st, 0:00:00 GMT in YEAR. */
- if (year > 1970)
- t = ((year - 1970) * 365
- + /* Compute the number of leapdays between 1970 and YEAR
- (exclusive). There is a leapday every 4th year ... */
- + ((year - 1) / 4 - 1970 / 4)
- /* ... except every 100th year ... */
- - ((year - 1) / 100 - 1970 / 100)
- /* ... but still every 400th year. */
- + ((year - 1) / 400 - 1970 / 400)) * SECSPERDAY;
- else
- t = 0;
+ t = (((year - 1970) * 365
+ /* Compute the number of leap days between 1970 and YEAR (exclusive).
+ There is a leap day every 4th year ... */
+ + (((year - 1) >> 2) - 1970 / 4)
+ /* ... except every 100th year ... */
+ - ((year - 1) / 100 - ((year - 1) % 100 < 0) - 1970 / 100)
+ /* ... but still every 400th year. */
+ + ((year - 1) / 400 - ((year - 1) % 400 < 0) - 1970 / 400))
+ * SECSPERDAY);
switch (rule->type)
{
@@ -484,7 +482,7 @@ compute_change (tz_rule *rule, int year)
/* Mm.n.d - Nth "Dth day" of month M. */
{
unsigned int i;
- int d, m1, yy0, yy1, yy2, dow;
+ int d, m1;
const unsigned short int *myday =
&__mon_yday[__isleap (year)][rule->m];
@@ -493,10 +491,12 @@ compute_change (tz_rule *rule, int year)
/* Use Zeller's Congruence to get day-of-week of first day of month. */
m1 = (rule->m + 9) % 12 + 1;
- yy0 = (rule->m <= 2) ? (year - 1) : year;
- yy1 = yy0 / 100;
- yy2 = yy0 % 100;
- dow = ((26 * m1 - 2) / 10 + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7;
+ long long int yy0 = year - (rule->m <= 2);
+ int yy1 = yy0 / 100 - (yy0 % 100 < 0);
+ int yy2 = yy0 % 100 + (yy0 % 100 < 0 ? 100 : 0);
+ int dow = (((13 * m1 - 1) / 5 + 1 + yy2 + (yy2 >> 2)
+ + (yy1 >> 2) - 2 * yy1)
+ % 7);
if (dow < 0)
dow += 7;
@@ -530,8 +530,11 @@ compute_change (tz_rule *rule, int year)
void
__tz_compute (__time64_t timer, struct tm *tm)
{
- compute_change (&tz_rules[0], 1900 + tm->tm_year);
- compute_change (&tz_rules[1], 1900 + tm->tm_year);
+ _Static_assert (INT_MAX <= LLONG_MAX - 1900,
+ "long long int must be wider than int");
+ long long int year = 1900LL + tm->tm_year;
+ compute_change (&tz_rules[0], year);
+ compute_change (&tz_rules[1], year);
/* Distinguish between northern and southern hemisphere.
For the latter the daylight saving time ends in the next year. */
@@ -574,6 +577,11 @@ weak_alias (__tzset, tzset)
struct tm *
__tz_convert (__time64_t timer, int use_localtime, struct tm *tp)
{
+ /* Check that __time64_t is wider than int,
+ so that __tz_convert cannot fail due to integer overflow. */
+ _Static_assert (sizeof (int) < sizeof (__time64_t),
+ "__time64_t must be wider than int");
+
int leap_correction;
bool leap_extra_sec;
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 18/59] Match RFC 9636 names
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (17 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 17/59] Don’t mishandle years < 1970 in compute_change Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:56 ` [PATCH 19/59] Trivial tzset_internal speedup Paul Eggert
` (41 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
Refactor to change spelling of identifiers to match RFC 9636.
This eases conformance review.
* time/tzfile.c (struct ttinfo, struct leap, __tzfile_read)
(__tzfile_default, __tzfile_compute): Change member names and
locals to match the RFC. All use changed.
(timecnt, typecnt, leapcnt): Rename from num_transitions,
num_types, num_leaps. All uses changed.
---
time/tzfile.c | 217 +++++++++++++++++++++++++-------------------------
1 file changed, 109 insertions(+), 108 deletions(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index 5a7514e466..dcd2bcc96d 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -40,17 +40,17 @@ static __time64_t tzfile_mtime;
struct ttinfo
{
- int offset; /* Seconds east of GMT. */
- unsigned char isdst; /* Used to set tm_isdst. */
+ int utoff; /* Seconds east of UT. */
+ unsigned char dst; /* Used to set tm_isdst. */
unsigned char idx; /* Index into `zone_names'. */
unsigned char isstd; /* Transition times are in standard time. */
- unsigned char isgmt; /* Transition times are in GMT. */
+ unsigned char isutc; /* Transition times are in UT. */
};
struct leap
{
- __time64_t transition; /* Time the transition takes effect. */
- int change; /* Seconds of correction to apply. */
+ __time64_t occur; /* Time the transition takes effect. */
+ int corr; /* Seconds of correction to apply. */
};
/* Internal index derived from a count taken from a TZif file.
@@ -59,14 +59,14 @@ struct leap
code ordinarily should not assume it is unsigned. */
typedef unsigned int tzidx;
-static tzidx num_transitions;
+static tzidx timecnt;
static __time64_t *transitions;
static unsigned char *type_idxs;
-static tzidx num_types;
+static tzidx typecnt;
static struct ttinfo *types;
static char *zone_names;
static long int rule_stdoff;
-static tzidx num_leaps;
+static tzidx leapcnt;
static struct leap *leaps;
static char *tzspec;
@@ -142,10 +142,10 @@ void
__tzfile_read (const char *file)
{
static const char default_tzdir[] = TZDIR;
- tzidx num_isstd, num_isgmt;
+ tzidx isstdcnt, isutcnt;
FILE *f;
union { struct tzhead tzhead; tzidx tzidx_aligned; } u;
- tzidx chars;
+ tzidx charcnt;
tzidx i;
int was_using_tzfile = __use_tzfile;
int trans_width = 4;
@@ -227,15 +227,15 @@ __tzfile_read (const char *file)
goto lose;
f_offset += sizeof u.tzhead;
- num_transitions = tzidx_decode (u.tzhead.tzh_timecnt);
- num_types = tzidx_decode (u.tzhead.tzh_typecnt);
- chars = tzidx_decode (u.tzhead.tzh_charcnt);
- num_leaps = tzidx_decode (u.tzhead.tzh_leapcnt);
- num_isstd = tzidx_decode (u.tzhead.tzh_ttisstdcnt);
- num_isgmt = tzidx_decode (u.tzhead.tzh_ttisutcnt);
+ timecnt = tzidx_decode (u.tzhead.tzh_timecnt);
+ typecnt = tzidx_decode (u.tzhead.tzh_typecnt);
+ charcnt = tzidx_decode (u.tzhead.tzh_charcnt);
+ leapcnt = tzidx_decode (u.tzhead.tzh_leapcnt);
+ isstdcnt = tzidx_decode (u.tzhead.tzh_ttisstdcnt);
+ isutcnt = tzidx_decode (u.tzhead.tzh_ttisutcnt);
- if (__glibc_unlikely (num_isstd > num_types || num_isgmt > num_types
- || !num_types))
+ if (__glibc_unlikely (isstdcnt > typecnt || isutcnt > typecnt
+ || !typecnt))
goto lose;
if (trans_width == 4 && u.tzhead.tzh_version[0] != '\0')
@@ -246,14 +246,14 @@ __tzfile_read (const char *file)
/* Position the stream before the second header. */
off_t to_skip, product;
bool v = false;
- v |= ckd_mul (&to_skip, num_transitions, 4 + 1);
- v |= ckd_mul (&product, num_types, 6);
+ v |= ckd_mul (&to_skip, timecnt, 4 + 1);
+ v |= ckd_mul (&product, typecnt, 6);
v |= ckd_add (&to_skip, to_skip, product);
- v |= ckd_add (&to_skip, to_skip, chars);
- v |= ckd_mul (&product, num_leaps, 8);
+ v |= ckd_add (&to_skip, to_skip, charcnt);
+ v |= ckd_mul (&product, leapcnt, 8);
v |= ckd_add (&to_skip, to_skip, product);
- v |= ckd_add (&to_skip, to_skip, num_isstd);
- v |= ckd_add (&to_skip, to_skip, num_isgmt);
+ v |= ckd_add (&to_skip, to_skip, isstdcnt);
+ v |= ckd_add (&to_skip, to_skip, isutcnt);
if (v)
goto lose;
@@ -271,15 +271,15 @@ __tzfile_read (const char *file)
{
off_t rem = st.st_size - f_offset, product;
bool v = false;
- v |= ckd_mul (&product, num_transitions, 8 + 1);
+ v |= ckd_mul (&product, timecnt, 8 + 1);
v |= ckd_sub (&rem, rem, product);
- v |= ckd_mul (&product, num_types, 6);
+ v |= ckd_mul (&product, typecnt, 6);
v |= ckd_sub (&rem, rem, product);
- v |= ckd_sub (&rem, rem, chars);
- v |= ckd_mul (&product, num_leaps, 12);
+ v |= ckd_sub (&rem, rem, charcnt);
+ v |= ckd_mul (&product, leapcnt, 12);
v |= ckd_sub (&rem, rem, product);
- v |= ckd_sub (&rem, rem, num_isstd);
- v |= ckd_sub (&rem, rem, num_isgmt);
+ v |= ckd_sub (&rem, rem, isstdcnt);
+ v |= ckd_sub (&rem, rem, isutcnt);
v |= rem <= 1;
v |= 3 <= rem && rem < sizeof "XYZ0\n";
if (v)
@@ -292,11 +292,11 @@ __tzfile_read (const char *file)
/* The file is parsed into a single heap allocation, comprising of
the following arrays:
- __time64_t transitions[num_transitions];
- struct leap leaps[num_leaps];
- struct ttinfo types[num_types];
- unsigned char type_idxs[num_types];
- char zone_names[chars];
+ __time64_t transitions[timecnt];
+ struct leap leaps[leapcnt];
+ struct ttinfo types[typecnt];
+ unsigned char type_idxs[typecnt];
+ char zone_names[charcnt];
char tzspec[tzspec_size];
The piece-wise allocations from buf below verify that no
@@ -314,13 +314,13 @@ __tzfile_read (const char *file)
{
size_t total_size, product;
bool v = false;
- v |= ckd_mul (&total_size, num_transitions, sizeof (__time64_t));
- v |= ckd_mul (&product, num_leaps, sizeof (struct leap));
+ v |= ckd_mul (&total_size, timecnt, sizeof (__time64_t));
+ v |= ckd_mul (&product, leapcnt, sizeof (struct leap));
v |= ckd_add (&total_size, total_size, product);
- v |= ckd_mul (&product, num_types, sizeof (struct ttinfo));
+ v |= ckd_mul (&product, typecnt, sizeof (struct ttinfo));
v |= ckd_add (&total_size, total_size, product);
- v |= ckd_add (&total_size, total_size, num_transitions); /* type_idxs */
- v |= ckd_add (&total_size, total_size, chars); /* zone_names */
+ v |= ckd_add (&total_size, total_size, timecnt); /* type_idxs */
+ v |= ckd_add (&total_size, total_size, charcnt); /* zone_names */
v |= ckd_add (&total_size, total_size, tzspec_size);
v |= ckd_add (&total_size, total_size, alignment_slop);
if (v)
@@ -333,11 +333,11 @@ __tzfile_read (const char *file)
/* The address of the first allocation is already stored in the
pointer transitions. */
- (void) alloc_buffer_alloc_array (&buf, __time64_t, num_transitions);
- leaps = alloc_buffer_alloc_array (&buf, struct leap, num_leaps);
- types = alloc_buffer_alloc_array (&buf, struct ttinfo, num_types);
- type_idxs = alloc_buffer_alloc_array (&buf, unsigned char, num_transitions);
- zone_names = alloc_buffer_alloc_array (&buf, char, chars);
+ (void) alloc_buffer_alloc_array (&buf, __time64_t, timecnt);
+ leaps = alloc_buffer_alloc_array (&buf, struct leap, leapcnt);
+ types = alloc_buffer_alloc_array (&buf, struct ttinfo, typecnt);
+ type_idxs = alloc_buffer_alloc_array (&buf, unsigned char, timecnt);
+ zone_names = alloc_buffer_alloc_array (&buf, char, charcnt);
if (trans_width == 8)
tzspec = alloc_buffer_alloc_array (&buf, char, tzspec_size);
else
@@ -347,7 +347,7 @@ __tzfile_read (const char *file)
if (trans_width == 4)
{
- for (i = 0; i < num_transitions; i++)
+ for (i = 0; i < timecnt; i++)
{
union { unsigned char c[4]; tzidx tzidx_aligned; } x;
if (__fread_unlocked (&x, 1, 4, f) != 4)
@@ -359,10 +359,10 @@ __tzfile_read (const char *file)
}
else if (sizeof (int64_t) == 8)
{
- if (__fread_unlocked (transitions, 8, num_transitions, f)
- != num_transitions)
+ if (__fread_unlocked (transitions, 8, timecnt, f)
+ != timecnt)
goto lose;
- for (i = 0; i < num_transitions; i++)
+ for (i = 0; i < timecnt; i++)
{
if (BYTE_ORDER != BIG_ENDIAN)
transitions[i] = decode64 (&transitions[i]);
@@ -372,7 +372,7 @@ __tzfile_read (const char *file)
}
else
{
- for (i = 0; i < num_transitions; i++)
+ for (i = 0; i < timecnt; i++)
{
union { unsigned char c[8]; int64_t int64_t_aligned; } x;
if (__fread_unlocked (&x, 1, 8, f) != 8)
@@ -383,18 +383,18 @@ __tzfile_read (const char *file)
}
}
- if (__glibc_unlikely (__fread_unlocked (type_idxs, 1, num_transitions, f)
- != num_transitions))
+ if (__glibc_unlikely (__fread_unlocked (type_idxs, 1, timecnt, f)
+ != timecnt))
goto lose;
/* Check for bogus indices in the data file, so we can hereafter
safely use type_idxs[T] as indices into `types' and never crash. */
- for (i = 0; i < num_transitions; ++i)
- if (__glibc_unlikely (type_idxs[i] >= num_types))
+ for (i = 0; i < timecnt; ++i)
+ if (__glibc_unlikely (type_idxs[i] >= typecnt))
goto lose;
unsigned char max_idx = 0;
- for (i = 0; i < num_types; ++i)
+ for (i = 0; i < typecnt; ++i)
{
union { unsigned char c[4]; tzidx tzidx_aligned; } x;
int c;
@@ -403,32 +403,33 @@ __tzfile_read (const char *file)
c = __getc_unlocked (f);
if (__glibc_unlikely (! (0 <= c && c <= 1)))
goto lose;
- types[i].isdst = c;
+ types[i].dst = c;
c = __getc_unlocked (f);
- if (__glibc_unlikely (! (0 <= c && c < chars)))
+ if (__glibc_unlikely (! (0 <= c && c < charcnt)))
/* Bogus index in data file. */
goto lose;
if (max_idx < c)
max_idx = c;
types[i].idx = c;
- types[i].offset = decode (&x);
+ types[i].utoff = decode (&x);
/* If long int is only 32 bits, reject offsets that cannot be negated.
RFC 9636 section 3.2 allows this. */
long int negated_offset;
- if (ckd_sub (&negated_offset, 0, types[i].offset))
+ if (ckd_sub (&negated_offset, 0, types[i].utoff))
goto lose;
}
- if (__glibc_unlikely (__fread_unlocked (zone_names, 1, chars, f) != chars))
+ if (__glibc_unlikely (__fread_unlocked (zone_names, 1, charcnt, f)
+ != charcnt))
goto lose;
- if (__strnlen (zone_names + max_idx, chars - max_idx) == chars - max_idx)
+ if (__strnlen (zone_names + max_idx, charcnt - max_idx) == charcnt - max_idx)
goto lose;
int minimum_leap_gap = 2419199; /* See RFC 9636 section 3.2. */
__time64_t prevtransition = -minimum_leap_gap;
int prevchange = 0;
- for (i = 0; i < num_leaps; ++i)
+ for (i = 0; i < leapcnt; ++i)
{
{
union { char c[8]; tzidx tzidx_aligned; int64_t int64_t_aligned; } x;
@@ -436,45 +437,45 @@ __tzfile_read (const char *file)
!= trans_width),
0))
goto lose;
- leaps[i].transition = trans_width == 4 ? decode (&x) : decode64 (&x);
- if (leaps[i].transition < 0
- || leaps[i].transition - minimum_leap_gap < prevtransition)
+ leaps[i].occur = trans_width == 4 ? decode (&x) : decode64 (&x);
+ if (leaps[i].occur < 0
+ || leaps[i].occur - minimum_leap_gap < prevtransition)
goto lose;
- prevtransition = leaps[i].transition;
+ prevtransition = leaps[i].occur;
}
{
union { unsigned char c[4]; tzidx tzidx_aligned; } x;
if (__glibc_unlikely (__fread_unlocked (&x, 1, 4, f) != 4))
goto lose;
- leaps[i].change = decode (&x);
+ leaps[i].corr = decode (&x);
int delta_change;
- if (ckd_sub (&delta_change, leaps[i].change, prevchange)
+ if (ckd_sub (&delta_change, leaps[i].corr, prevchange)
|| ! (delta_change == 1 || delta_change == -1))
goto lose;
- prevchange = leaps[i].change;
+ prevchange = leaps[i].corr;
}
}
- for (i = 0; i < num_isstd; ++i)
+ for (i = 0; i < isstdcnt; i++)
{
int c = __getc_unlocked (f);
if (__glibc_unlikely (c == EOF))
goto lose;
types[i].isstd = c != 0;
}
- while (i < num_types)
+ while (i < typecnt)
types[i++].isstd = 0;
- for (i = 0; i < num_isgmt; ++i)
+ for (i = 0; i < isutcnt; i++)
{
int c = __getc_unlocked (f);
if (__glibc_unlikely (c == EOF))
goto lose;
- types[i].isgmt = c != 0;
+ types[i].isutc = c != 0;
}
- while (i < num_types)
- types[i++].isgmt = 0;
+ while (i < typecnt)
+ types[i++].isutc = 0;
/* Read the POSIX TZ-style information if possible. */
if (tzspec != NULL)
@@ -498,7 +499,7 @@ __tzfile_read (const char *file)
fclose (f);
/* First "register" all time zone abbreviations. */
- for (i = 0; i < num_types; ++i)
+ for (i = 0; i < typecnt; ++i)
if (__tzstring (&zone_names[types[i].idx]) == NULL)
goto ret_free_transitions;
@@ -507,10 +508,10 @@ __tzfile_read (const char *file)
transitioned to earliest in time. */
__tzname[0] = NULL;
__tzname[1] = NULL;
- for (i = num_transitions; i > 0; )
+ for (i = timecnt; i > 0; )
{
int type = type_idxs[--i];
- int dst = types[type].isdst;
+ int dst = types[type].dst;
if (__tzname[dst] == NULL)
{
@@ -533,20 +534,20 @@ __tzfile_read (const char *file)
__tzname[1] = __tzname[0];
daylight_saved = 0;
- if (num_transitions == 0)
+ if (timecnt == 0)
/* Use the first rule (which should also be the only one). */
- rule_stdoff = types[0].offset;
+ rule_stdoff = types[0].utoff;
else
{
rule_stdoff = 0;
/* Search for the last rule with a standard time offset. This
will be used for the global timezone variable. */
- i = num_transitions - 1;
+ i = timecnt - 1;
do
- if (!types[type_idxs[i]].isdst)
+ if (!types[type_idxs[i]].dst)
{
- rule_stdoff = types[type_idxs[i]].offset;
+ rule_stdoff = types[type_idxs[i]].utoff;
break;
}
else
@@ -557,7 +558,7 @@ __tzfile_read (const char *file)
information will be used to set the global daylight
variable. */
while (i-- > 0 && !daylight_saved)
- daylight_saved = types[type_idxs[i]].isdst;
+ daylight_saved = types[type_idxs[i]].dst;
}
__daylight = daylight_saved;
@@ -588,13 +589,13 @@ __tzfile_compute (__time64_t timer, int use_localtime,
__tzname[0] = NULL;
__tzname[1] = NULL;
- if (__glibc_unlikely (num_transitions == 0 || timer < transitions[0]))
+ if (__glibc_unlikely (timecnt == 0 || timer < transitions[0]))
{
/* TIMER is before any transition (or there are no transitions).
Choose the first non-DST type
(or the first if they're all DST types). */
i = 0;
- while (i < num_types && types[i].isdst)
+ while (i < typecnt && types[i].dst)
{
if (__tzname[1] == NULL)
__tzname[1] = __tzstring (&zone_names[types[i].idx]);
@@ -602,14 +603,14 @@ __tzfile_compute (__time64_t timer, int use_localtime,
++i;
}
- if (i == num_types)
+ if (i == typecnt)
i = 0;
__tzname[0] = __tzstring (&zone_names[types[i].idx]);
if (__tzname[1] == NULL)
{
tzidx j = i;
- while (j < num_types)
- if (types[j].isdst)
+ while (j < typecnt)
+ if (types[j].dst)
{
__tzname[1] = __tzstring (&zone_names[types[j].idx]);
break;
@@ -618,12 +619,12 @@ __tzfile_compute (__time64_t timer, int use_localtime,
++j;
}
}
- else if (timer >= transitions[num_transitions - 1])
+ else if (timer >= transitions[timecnt - 1])
{
if (__glibc_unlikely (tzspec == NULL))
{
use_last:
- i = num_transitions;
+ i = timecnt;
goto found;
}
@@ -645,18 +646,18 @@ __tzfile_compute (__time64_t timer, int use_localtime,
/* Find the first transition after TIMER, and
then pick the type of the transition before it. */
tzidx lo = 0;
- tzidx hi = num_transitions - 1;
+ tzidx hi = timecnt - 1;
/* Assume that DST is changing twice a year and guess
initial search spot from it. Half of a gregorian year
has on average 365.2425 * 86400 / 2 = 15778476 seconds.
Although i's value can be wrong if overflow occurs,
this is harmless because it is just a guess. */
__time64_t tdiff;
- ckd_sub (&tdiff, transitions[num_transitions - 1], timer);
+ ckd_sub (&tdiff, transitions[timecnt - 1], timer);
ckd_add (&i, tdiff / 15778476, 0);
- if (i < num_transitions)
+ if (i < timecnt)
{
- i = num_transitions - 1 - i;
+ i = timecnt - 1 - i;
if (timer < transitions[i])
{
if (i < 10 || timer >= transitions[i - 10])
@@ -670,7 +671,7 @@ __tzfile_compute (__time64_t timer, int use_localtime,
}
else
{
- if (num_transitions - i <= 10 || timer < transitions[i + 10])
+ if (timecnt - i <= 10 || timer < transitions[i + 10])
{
/* Linear search. */
while (timer >= transitions[i])
@@ -695,14 +696,14 @@ __tzfile_compute (__time64_t timer, int use_localtime,
found:
/* assert (timer >= transitions[i - 1]
- && (i == num_transitions || timer < transitions[i])); */
- __tzname[types[type_idxs[i - 1]].isdst]
+ && (i == timecnt || timer < transitions[i])); */
+ __tzname[types[type_idxs[i - 1]].dst]
= __tzstring (&zone_names[types[type_idxs[i - 1]].idx]);
tzidx j = i;
- while (j < num_transitions)
+ while (j < timecnt)
{
int type = type_idxs[j];
- int dst = types[type].isdst;
+ int dst = types[type].dst;
int idx = types[type].idx;
if (__tzname[dst] == NULL)
@@ -730,16 +731,16 @@ __tzfile_compute (__time64_t timer, int use_localtime,
{
/* This should only happen if there are no transition rules.
In this case there should be only one single type. */
- assert (num_types == 1);
+ assert (typecnt == 1);
__tzname[0] = __tzstring (zone_names);
}
if (__tzname[1] == NULL)
/* There is no daylight saving time. */
__tzname[1] = __tzname[0];
- tp->tm_isdst = info->isdst;
+ tp->tm_isdst = info->dst;
assert (strcmp (&zone_names[info->idx], __tzname[tp->tm_isdst]) == 0);
tp->tm_zone = __tzname[tp->tm_isdst];
- tp->tm_gmtoff = info->offset;
+ tp->tm_gmtoff = info->utoff;
}
leap:
@@ -747,17 +748,17 @@ __tzfile_compute (__time64_t timer, int use_localtime,
*leap_hit = false;
/* Find the last leap second correction transition time before TIMER. */
- i = num_leaps;
+ i = leapcnt;
do
if (i-- == 0)
return;
- while (timer < leaps[i].transition);
+ while (timer < leaps[i].occur);
/* Apply its correction. */
- *leap_correct = leaps[i].change;
+ *leap_correct = leaps[i].corr;
- if (timer == leaps[i].transition /* Exactly at the transition time. */
- && (leaps[i].change > (i == 0 ? 0 : leaps[i - 1].change)))
+ if (timer == leaps[i].occur /* Exactly at the transition time. */
+ && (leaps[i].corr > (i == 0 ? 0 : leaps[i - 1].corr)))
*leap_hit = true;
}
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 19/59] Trivial tzset_internal speedup
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (18 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 18/59] Match RFC 9636 names Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:56 ` [PATCH 20/59] Fix obscure bug when stdoffset == dstoffset Paul Eggert
` (40 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzset.c (tzset_internal): Omit useless initialization.
All that matters is that the two change values are equal,
which they are because they’ve just been zeroed.
---
time/tzset.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/time/tzset.c b/time/tzset.c
index 7cc675334a..8f7315dabe 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -430,7 +430,6 @@ tzset_internal (int always)
tz_rules[0].name = tz_rules[1].name = "UTC";
if (J0 != 0)
tz_rules[0].type = tz_rules[1].type = J0;
- tz_rules[0].change = tz_rules[1].change = -1;
update_vars ();
return;
}
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 20/59] Fix obscure bug when stdoffset == dstoffset
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (19 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 19/59] Trivial tzset_internal speedup Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:56 ` [PATCH 21/59] Document assumption that NO_DST == 0 Paul Eggert
` (39 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tst-tzname.c (do_test): Test odd (but allowed by POSIX)
zone that has the same UTC offset in both standard time and DST.
* time/tzset.c (enum tz_rule_type): This type now has an enum tag.
(NO_DST): New value for the type.
(update_vars): Set __daylight depending the type, not on whether
offsets differ.
(parse_rule): Do not set rule type unless successful.
(tzset_internal): Default rules to NO_DST, not to J0.
(compute_change): Do not adjust if NO_DST.
---
time/tst-tzname.c | 25 +++++++++++++++++++++++++
time/tzset.c | 19 ++++++++++++-------
2 files changed, 37 insertions(+), 7 deletions(-)
diff --git a/time/tst-tzname.c b/time/tst-tzname.c
index 5009328e85..b9b4aea35e 100644
--- a/time/tst-tzname.c
+++ b/time/tst-tzname.c
@@ -43,6 +43,31 @@ do_test (void)
printf ("FAIL: TZ=%s, tzname[0] = %s\n", TZDEFRULES, tzname[0]);
result = 1;
}
+
+ static char const oddzone[] = "XST12XST12,M9.5.0,M4.1.0";
+ setenv ("TZ", oddzone, 1);
+ tzset ();
+ if (strcmp (tzname[0], "XST") != 0)
+ {
+ printf ("FAIL: TZ=%s, tzname[0] = %s\n", oddzone, tzname[0]);
+ result = 1;
+ }
+ if (strcmp (tzname[1], "XST") != 0)
+ {
+ printf ("FAIL: TZ=%s, tzname[1] = %s\n", oddzone, tzname[1]);
+ result = 1;
+ }
+ if (daylight <= 0)
+ {
+ printf ("FAIL: TZ=%s, daylight = %d\n", oddzone, daylight);
+ result = 1;
+ }
+ if (timezone != 12 * 60 * 60)
+ {
+ printf ("FAIL: TZ=%s, timezone = %ld\n", oddzone, timezone);
+ result = 1;
+ }
+
return result;
}
diff --git a/time/tzset.c b/time/tzset.c
index 8f7315dabe..e001a5898c 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -47,7 +47,7 @@ typedef struct
const char *name;
/* When to change. */
- enum { J0, J1, M } type; /* Interpretation of: */
+ enum tz_rule_type { NO_DST, J0, J1, M } type; /* Interpretation of: */
unsigned short int m, n, d; /* Month, week, day. */
int secs; /* Time of day. */
@@ -128,7 +128,7 @@ static char *old_tz;
static void
update_vars (void)
{
- __daylight = tz_rules[0].offset != tz_rules[1].offset;
+ __daylight = tz_rules[1].type != NO_DST;
__timezone = -tz_rules[0].offset;
__tzname[0] = (char *) tz_rules[0].name;
__tzname[1] = (char *) tz_rules[1].name;
@@ -267,6 +267,7 @@ parse_rule (const char **tzp, int whichrule)
{
const char *tz = *tzp;
tz_rule *tzr = &tz_rules[whichrule];
+ enum tz_rule_type type;
/* Ignore comma to support string following the incorrect
specification in early POSIX.1 printings. */
@@ -276,7 +277,7 @@ parse_rule (const char **tzp, int whichrule)
bool is_J = *tz == 'J';
if (is_J || isdigit (*tz))
{
- tzr->type = is_J ? J1 : J0;
+ type = is_J ? J1 : J0;
tz += is_J;
int d = parse_int (&tz);
if (! (is_J <= d && d <= 365))
@@ -285,7 +286,7 @@ parse_rule (const char **tzp, int whichrule)
}
else if (*tz == 'M')
{
- tzr->type = M;
+ type = M;
tz++;
int i = parse_int (&tz);
if (! (1 <= i && i <= 12 && *tz++ == '.'))
@@ -308,7 +309,7 @@ parse_rule (const char **tzp, int whichrule)
of 2005 [Pub. L. no. 109-58, 119 Stat 594 (2005)].
Below is the equivalent of "M3.2.0,M11.1.0" [/2 not needed
since 2:00AM is the default]. */
- tzr->type = M;
+ type = M;
if (tzr == &tz_rules[0])
{
tzr->m = 3;
@@ -341,6 +342,7 @@ parse_rule (const char **tzp, int whichrule)
secs = 2 * 60 * 60;
tzr->secs = secs;
+ tzr->type = type;
tzr->computed_for = LLONG_MIN;
*tzp = tz;
return true;
@@ -428,8 +430,8 @@ tzset_internal (int always)
{
memset (tz_rules, '\0', sizeof tz_rules);
tz_rules[0].name = tz_rules[1].name = "UTC";
- if (J0 != 0)
- tz_rules[0].type = tz_rules[1].type = J0;
+ if (NO_DST != 0)
+ tz_rules[0].type = tz_rules[1].type = NO_DST;
update_vars ();
return;
}
@@ -461,6 +463,9 @@ compute_change (tz_rule *rule, long long int year)
switch (rule->type)
{
+ case NO_DST:
+ break;
+
case J1:
/* Jn - Julian day, 1 == January 1, 60 == March 1 even in leap years.
In non-leap years, or if the day number is 59 or less, just
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 21/59] Document assumption that NO_DST == 0
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (20 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 20/59] Fix obscure bug when stdoffset == dstoffset Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:56 ` [PATCH 22/59] __use_tzfile is boolean Paul Eggert
` (38 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzset.c (__tzset_parse_tz): Document assumption that
NO_DST is zero here, just as it is already documentted in
tzset_internal. This does not change the generated code
when optimizing.
---
time/tzset.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/time/tzset.c b/time/tzset.c
index e001a5898c..a198b180eb 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -354,6 +354,8 @@ __tzset_parse_tz (const char *tz)
{
/* Clear out old state and reset to unnamed UTC. */
memset (tz_rules, '\0', sizeof tz_rules);
+ if (NO_DST != 0)
+ tz_rules[0].type = tz_rules[1].type = NO_DST;
tz_rules[0].name = tz_rules[1].name = "";
/* Get the standard time zone abbreviations. */
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 22/59] __use_tzfile is boolean
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (21 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 21/59] Document assumption that NO_DST == 0 Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:56 ` [PATCH 23/59] Minor tzfile.c clarity improvements Paul Eggert
` (37 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzfile.c (__use_tzfile): Now bool instead of int.
---
time/tzfile.c | 8 ++++----
time/tzset.h | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index dcd2bcc96d..8d8391acfe 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -33,7 +33,7 @@
#include <timezone/tzfile.h>
-int __use_tzfile;
+bool __use_tzfile;
static dev_t tzfile_dev;
static ino64_t tzfile_ino;
static __time64_t tzfile_mtime;
@@ -147,14 +147,14 @@ __tzfile_read (const char *file)
union { struct tzhead tzhead; tzidx tzidx_aligned; } u;
tzidx charcnt;
tzidx i;
- int was_using_tzfile = __use_tzfile;
+ bool was_using_tzfile = __use_tzfile;
int trans_width = 4;
char *new = NULL;
_Static_assert (sizeof (__time64_t) == 8,
"__time64_t must be eight bytes");
- __use_tzfile = 0;
+ __use_tzfile = false;
if (file == NULL)
/* No user specification; use the site-wide default. */
@@ -565,8 +565,8 @@ __tzfile_read (const char *file)
__timezone = -rule_stdoff;
done:
- __use_tzfile = 1;
free (new);
+ __use_tzfile = true;
return;
lose:
diff --git a/time/tzset.h b/time/tzset.h
index 8c87ad6a24..3687c044e3 100644
--- a/time/tzset.h
+++ b/time/tzset.h
@@ -8,7 +8,7 @@
/* Defined in tzset.c. */
extern char *__tzstring (const char *string) attribute_hidden;
-extern int __use_tzfile attribute_hidden;
+extern bool __use_tzfile attribute_hidden;
extern void __tzfile_read (const char *file) attribute_hidden;
extern void __tzfile_compute (__time64_t timer, int use_localtime,
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 23/59] Minor tzfile.c clarity improvements
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (22 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 22/59] __use_tzfile is boolean Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:56 ` [PATCH 24/59] Stop using zic's -y option Paul Eggert
` (36 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzfile.c (__tzfile_read, __tzfile_compute):
Avoid some unnecesary unsigned char -> int conversions.
Improve clarity of loop controlling.
---
time/tzfile.c | 23 +++++++++--------------
1 file changed, 9 insertions(+), 14 deletions(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index 8d8391acfe..291b3aa0dd 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -508,16 +508,14 @@ __tzfile_read (const char *file)
transitioned to earliest in time. */
__tzname[0] = NULL;
__tzname[1] = NULL;
- for (i = timecnt; i > 0; )
+ for (i = timecnt; i > 0; i--)
{
- int type = type_idxs[--i];
- int dst = types[type].dst;
+ struct ttinfo *ttype = &types[type_idxs[i - 1]];
+ unsigned char dst = ttype->dst;
if (__tzname[dst] == NULL)
{
- int idx = types[type].idx;
-
- __tzname[dst] = __tzstring (&zone_names[idx]);
+ __tzname[dst] = __tzstring (&zone_names[ttype->idx]);
if (__tzname[1 - dst] != NULL)
break;
@@ -699,22 +697,19 @@ __tzfile_compute (__time64_t timer, int use_localtime,
&& (i == timecnt || timer < transitions[i])); */
__tzname[types[type_idxs[i - 1]].dst]
= __tzstring (&zone_names[types[type_idxs[i - 1]].idx]);
- tzidx j = i;
- while (j < timecnt)
+
+ for (tzidx j = i; j < timecnt; j++)
{
- int type = type_idxs[j];
- int dst = types[type].dst;
- int idx = types[type].idx;
+ struct ttinfo *ttype = &types[type_idxs[j]];
+ unsigned char dst = ttype->dst;
if (__tzname[dst] == NULL)
{
- __tzname[dst] = __tzstring (&zone_names[idx]);
+ __tzname[dst] = __tzstring (&zone_names[ttype->idx]);
if (__tzname[1 - dst] != NULL)
break;
}
-
- ++j;
}
if (__glibc_unlikely (__tzname[0] == NULL))
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 24/59] Stop using zic's -y option
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (23 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 23/59] Minor tzfile.c clarity improvements Paul Eggert
@ 2025-01-05 5:56 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 25/59] Fix mishandling of default DST rule Paul Eggert
` (35 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:56 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* timezone/Makefile (build-testdata): Omit -y option,
as current zic merely warns and discards it.
(zic-deps): Omit yearistype.
---
timezone/Makefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/timezone/Makefile b/timezone/Makefile
index 6e28a2e670..0c55b72f3f 100644
--- a/timezone/Makefile
+++ b/timezone/Makefile
@@ -103,7 +103,7 @@ tst-bz28707-ENV = TZDIR=$(testdata)
tst-bz29951-ENV = TZDIR=$(testdata)
# Note this must come second in the deps list for $(built-program-cmd) to work.
-zic-deps = $(objpfx)zic $(leapseconds) yearistype
+zic-deps = $(objpfx)zic $(leapseconds)
$(testdata)/America/New_York: northamerica $(zic-deps)
$(build-testdata)
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 25/59] Fix mishandling of default DST rule
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (24 preceding siblings ...)
2025-01-05 5:56 ` [PATCH 24/59] Stop using zic's -y option Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 26/59] Simplify treatment of missing and empty TZ Paul Eggert
` (34 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
For timezone settings like TZ="AST4ADT" where POSIX says the
default is implementation-defined, time/tzfile.c has long had
vestiges of an old attempt to be upward compatible with UNIX
System V. This code has not worked for decades and evidently is
not being used, so remove it and use the simpler fallback code
already present in time/tzset.c, which uses US DST. Also, add a
few test cases to demonstrate some bugs in the removed code.
* time/tst-posixtz.c: Include stdckdint.h.
(tests): WHEN is now intmax_t, not time_t, to support
skipping tests after 2038 on 32-bit time_t. All uses changed.
Make it const while we're at it. Add several tests for AST4ADT.
* time/tzfile.c (rule_dstoff): Remove. All uses removed.
(__tzfile_read): Remove args EXTRA, EXTRAP. All uses changed.
(__tzfile_default): Remove. All uses removed.
(__tzfile_compute): Remove no-longer-needed code dealing
with __tzfile_default.
* timezone/Makefile (build-testdata):
Add '-p $(posixrules)' option if compiling 'northamerica',
so that we can test non-use of posixrules.
* timezone/tst-timezone.c: Include stdbool.h, stdckdint.h.
(failed): Now bool, not int. All uses changed.
(do_test): Test AST4ADT too.
---
Makeconfig | 16 +++++++---------
NEWS | 11 +++++------
manual/time.texi | 8 +++-----
3 files changed, 15 insertions(+), 20 deletions(-)
diff --git a/Makeconfig b/Makeconfig
index d0108d2caa..a4be5b0e21 100644
--- a/Makeconfig
+++ b/Makeconfig
@@ -285,19 +285,17 @@ ifndef leapseconds
leapseconds = /dev/null
endif
-# What timezone's DST rules should be used when a POSIX-style TZ
-# environment variable doesn't specify any rules. For 1003.1 compliance
-# this timezone must use rules that are as U.S. federal law defines DST.
-# Run `make -C time echo-zonenames' to see a list of available zone names.
-# This setting can be changed with `zic -p TIMEZONE' at any time.
-# If you want POSIX.1 compatibility, use `America/New_York'.
+# What timezone's DST rules to use when testing that glibc does
+# not mishandle TZif files built with zic's obsolescent -p option.
+# This macro no longer affects glibc's code or installation.
ifndef posixrules
posixrules = America/New_York
endif
-# Where to install the "posixrules" timezone file; this is file
-# whose contents $(posixrules) specifies. If this is a relative
-# pathname, it is relative to $(zonedir).
+# Where to put the "posixrules" timezone file used during testing; this
+# is the file whose contents $(posixrules) specifies.
+# If this is a relative pathname, it is relative to $(zonedir).
+# Like posixrules, this macro no longer affects glibc's code or installation.
ifndef posixrules-file
posixrules-file = posixrules
endif
diff --git a/NEWS b/NEWS
index 3e6227a448..a62fb8136d 100644
--- a/NEWS
+++ b/NEWS
@@ -84,12 +84,11 @@ Deprecated and removed features, and other changes affecting compatibility:
explicitly because of the executable bit in GNU_STACK, and the stack is
not already executable. Instead, loading such objects will fail.
-* The default daylight saving rules for old Unix System V style TZ
- strings like TZ="AST4ADT" are now those of current US DST. Although
- the rules were supposed to be those of /usr/share/zoneinfo/posixrules,
- this feature, which has been disabled by default and marked obsolete
- upstream in tzcode, did not work in either glibc or tzcode, and in
- practice posixrules invariably specified current US DST anyway.
+* The US daylight saving rule is now the default for incomplete TZ
+ strings like TZ="AST4ADT" that use the TZ syntax from circa-1980s
+ Unix System V. Formerly the rule was documented to be that of
+ /usr/share/zoneinfo/posixrules but this did not work and in practice
+ posixrules invariably specified the US rule anyway.
Changes to build and runtime requirements:
diff --git a/manual/time.texi b/manual/time.texi
index 28d8912446..cff94cb48b 100644
--- a/manual/time.texi
+++ b/manual/time.texi
@@ -2759,11 +2759,9 @@ and offset for the corresponding daylight saving time zone; if the
The remainder of the proleptic format, which starts with the first comma,
describes when daylight saving time is in effect. This remainder is
-optional and if omitted, @theglibc{} defaults to US daylight saving rules.
-rules that would be used if @env{TZ} had the value @t{"posixrules"}.
-However, other POSIX implementations default to different daylight
-saving rules, so portable @env{TZ} settings should not omit the
-remainder.
+optional and if omitted, @theglibc{} defaults to the US daylight saving rule.
+POSIX does not specify this default, so portable @env{TZ} settings
+should not omit the remainder.
In the remainder, the @var{start} field is when daylight saving time goes into
effect and the @var{end} field is when the change is made back to standard
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 26/59] Simplify treatment of missing and empty TZ
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (25 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 25/59] Fix mishandling of default DST rule Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 27/59] TZ="" always means UTC sans leap seconds Paul Eggert
` (33 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
Refactor to help the compiler.
* time/tzfile.c (__tzfile_read): Assume the arg is a nonempty string.
Caller changed.
* time/tzset.c (tzset_internal): Simplify due to the fact that
TZDEFAULT is not a null pointer (tzfile.c is already assuming this).
Do not pass an empty string to __tzfile_read.
---
time/tzfile.c | 33 ++++++++++++---------------------
time/tzset.c | 24 ++++++++++++++----------
2 files changed, 26 insertions(+), 31 deletions(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index 291b3aa0dd..08cf0686df 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -156,28 +156,19 @@ __tzfile_read (const char *file)
__use_tzfile = false;
- if (file == NULL)
- /* No user specification; use the site-wide default. */
- file = TZDEFAULT;
- else if (*file == '\0')
- /* User specified the empty string; use UTC with no leap seconds. */
+ /* We must not allow to read an arbitrary file in a setuid
+ program. So we fail for any file which is not in the
+ directory hierarchy starting at TZDIR
+ and which is not the system wide default TZDEFAULT. */
+ if (__libc_enable_secure
+ && ((*file == '/'
+ && strcmp (file, TZDEFAULT) != 0
+ && (strncmp (file, default_tzdir, sizeof (default_tzdir) - 1)
+ != 0))
+ || strstr (file, "../") != NULL))
+ /* This test is certainly a bit too restrictive but it should
+ catch all critical cases. */
goto ret_free_transitions;
- else
- {
- /* We must not allow to read an arbitrary file in a setuid
- program. So we fail for any file which is not in the
- directory hierarchy starting at TZDIR
- and which is not the system wide default TZDEFAULT. */
- if (__libc_enable_secure
- && ((*file == '/'
- && strcmp (file, TZDEFAULT) != 0
- && (strncmp (file, default_tzdir, sizeof (default_tzdir) - 1)
- != 0))
- || strstr (file, "../") != NULL))
- /* This test is certainly a bit too restrictive but it should
- catch all critical cases. */
- goto ret_free_transitions;
- }
if (*file != '/')
{
diff --git a/time/tzset.c b/time/tzset.c
index a198b180eb..b014bca8d3 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -394,25 +394,26 @@ tzset_internal (int always)
/* Examine the TZ environment variable. */
tz = getenv ("TZ");
- if (tz && *tz == '\0')
+
+ if (tz == NULL)
+ /* No user specification; use the site-wide default. */
+ tz = TZDEFAULT;
+
+ if (*tz == '\0')
/* User specified the empty string; use UTC explicitly. */
tz = "Universal";
/* A leading colon means "implementation defined syntax".
We ignore the colon and always use the same algorithm:
try a data file, and if none exists parse the 1003.1 syntax. */
- if (tz && *tz == ':')
+ if (*tz == ':')
++tz;
/* Check whether the value changed since the last run. */
- if (old_tz != NULL && tz != NULL && strcmp (tz, old_tz) == 0)
+ if (old_tz != NULL && strcmp (tz, old_tz) == 0)
/* No change, simply return. */
return;
- if (tz == NULL)
- /* No user specification; use the site-wide default. */
- tz = TZDEFAULT;
-
tz_rules[0].name = NULL;
tz_rules[1].name = NULL;
@@ -421,9 +422,12 @@ tzset_internal (int always)
old_tz = __strdup (tz);
/* Try to read a data file. */
- __tzfile_read (tz);
- if (__use_tzfile)
- return;
+ if (*tz != '\0')
+ {
+ __tzfile_read (tz);
+ if (__use_tzfile)
+ return;
+ }
/* No data file found. Default to UTC if nothing specified or if
TZDEFAULT is broken. */
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 27/59] TZ="" always means UTC sans leap seconds
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (26 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 26/59] Simplify treatment of missing and empty TZ Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 28/59] Prefer "UTC" for UTC Paul Eggert
` (32 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
TZ="" now always means UTC without leap seconds.
This agrees better with doc and with tzcode,
where TZ="" is intended to be a performance hack.
* manual/time.texi (TZ Variable): Clarify that TZ=":" is equivalent
to TZ="", and that both mean UTC without leap seconds.
* time/tzfile.c (__tzfile_read): Check for TZ="" here,
so that __use_tzfile is set properly.
* time/tzset.c (tzset_internal): Simplify by relying on
__tzfile_read to check for TZ="".
---
NEWS | 7 +++++++
manual/time.texi | 10 +++++-----
time/tzfile.c | 3 +++
time/tzset.c | 13 +++----------
timezone/test-tz.c | 1 +
5 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/NEWS b/NEWS
index a62fb8136d..a737a0124b 100644
--- a/NEWS
+++ b/NEWS
@@ -90,6 +90,13 @@ Deprecated and removed features, and other changes affecting compatibility:
/usr/share/zoneinfo/posixrules but this did not work and in practice
posixrules invariably specified the US rule anyway.
+* TZ="" now always agrees with TZ=":" and means UTC without leap seconds.
+ Previously TZ="" meant UTC with leap seconds on the rare systems
+ configured to use leap seconds by default. However, this behavior
+ disagreed with both the glibc manual (which said that TZ="" and TZ=":"
+ have the same behavior) and with tzcode upstream, and it did not work
+ in some cases.
+
Changes to build and runtime requirements:
* On recent Linux kernels with vDSO getrandom support, getrandom does not
diff --git a/manual/time.texi b/manual/time.texi
index cff94cb48b..d5619534a5 100644
--- a/manual/time.texi
+++ b/manual/time.texi
@@ -2642,6 +2642,10 @@ EST+5EDT,M3.2.0/2,M11.1.0/2
<-02>+2<-01>,M3.5.0/-1,M10.5.0/0
@end smallexample
+@item
+As an extension to POSIX, when the value of @env{TZ} is the empty string,
+@theglibc{} uses UTC without leap seconds.
+
@item
The @dfn{colon format} begins with @samp{:}. Here is an example.
@@ -2652,11 +2656,7 @@ The @dfn{colon format} begins with @samp{:}. Here is an example.
@noindent
Each operating system can interpret this format differently;
in @theglibc{}, the @samp{:} is ignored and @var{characters}
-are treated as if they specified the geographical or proleptic format.
-
-@item
-As an extension to POSIX, when the value of @env{TZ} is the empty string,
-@theglibc{} uses UTC.
+are treated as if they specified one of the other formats.
@end itemize
@pindex /etc/localtime
diff --git a/time/tzfile.c b/time/tzfile.c
index 08cf0686df..4ca1f3c0c8 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -172,6 +172,9 @@ __tzfile_read (const char *file)
if (*file != '/')
{
+ if (*file == '\0')
+ goto ret_free_transitions;
+
const char *tzdir;
tzdir = getenv ("TZDIR");
diff --git a/time/tzset.c b/time/tzset.c
index b014bca8d3..c2575edad9 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -399,10 +399,6 @@ tzset_internal (int always)
/* No user specification; use the site-wide default. */
tz = TZDEFAULT;
- if (*tz == '\0')
- /* User specified the empty string; use UTC explicitly. */
- tz = "Universal";
-
/* A leading colon means "implementation defined syntax".
We ignore the colon and always use the same algorithm:
try a data file, and if none exists parse the 1003.1 syntax. */
@@ -422,12 +418,9 @@ tzset_internal (int always)
old_tz = __strdup (tz);
/* Try to read a data file. */
- if (*tz != '\0')
- {
- __tzfile_read (tz);
- if (__use_tzfile)
- return;
- }
+ __tzfile_read (tz);
+ if (__use_tzfile)
+ return;
/* No data file found. Default to UTC if nothing specified or if
TZDEFAULT is broken. */
diff --git a/timezone/test-tz.c b/timezone/test-tz.c
index 642b45a0ed..597096a5c0 100644
--- a/timezone/test-tz.c
+++ b/timezone/test-tz.c
@@ -8,6 +8,7 @@ struct {
time_t expected;
} tests[] = {
{"MST", 832935315},
+ {":", 832910115},
{"", 832910115},
{":UTC", 832910115},
{"UTC", 832910115},
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 28/59] Prefer "UTC" for UTC
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (27 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 27/59] TZ="" always means UTC sans leap seconds Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 29/59] Simplify setting tz_rules to UTC Paul Eggert
` (31 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* NEWS: Mention this.
* resolv/ns_date.c, sysdeps/generic/netinet/in_systm.h:
* sysdeps/nptl/pthread.h, time/bits/types/struct_timeb.h:
* time/strftime_l.c, time/sys/time.h, time/tzfile.c, time/tzset.c:
In strings and commentary, prefer "UTC" to "GMT" when referring to
Coordinated Universal Time. POSIX.1-2024 requires this and other
systems do it this way.
---
NEWS | 5 +++++
resolv/ns_date.c | 2 +-
sysdeps/generic/netinet/in_systm.h | 2 +-
sysdeps/nptl/pthread.h | 2 +-
time/bits/types/struct_timeb.h | 2 +-
time/strftime_l.c | 2 +-
time/sys/time.h | 2 +-
time/tzset.c | 10 +++++-----
8 files changed, 16 insertions(+), 11 deletions(-)
diff --git a/NEWS b/NEWS
index a737a0124b..69a730a72c 100644
--- a/NEWS
+++ b/NEWS
@@ -84,6 +84,11 @@ Deprecated and removed features, and other changes affecting compatibility:
explicitly because of the executable bit in GNU_STACK, and the stack is
not already executable. Instead, loading such objects will fail.
+* gmtime and similar functions now use "UTC", not "GMT", to abbreviate
+ Coordinated Universal Time (often called "GMT", though they are not
+ quite the same). This is more compatible with other platforms and is
+ required by POSIX.1-2024.
+
* The US daylight saving rule is now the default for incomplete TZ
strings like TZ="AST4ADT" that use the TZ syntax from circa-1980s
Unix System V. Formerly the rule was documented to be that of
diff --git a/resolv/ns_date.c b/resolv/ns_date.c
index 8e94d1c4e8..d931c70df8 100644
--- a/resolv/ns_date.c
+++ b/resolv/ns_date.c
@@ -35,7 +35,7 @@ static int datepart(const char *, int, int, int, int *);
/*%
* Convert a date in ASCII into the number of seconds since
- * 1 January 1970 (GMT assumed). Format is yyyymmddhhmmss, all
+ * 1 January 1970 (UTC assumed). Format is yyyymmddhhmmss, all
* digits required, no spaces allowed.
*/
diff --git a/sysdeps/generic/netinet/in_systm.h b/sysdeps/generic/netinet/in_systm.h
index cd09598926..1b8b7de710 100644
--- a/sysdeps/generic/netinet/in_systm.h
+++ b/sysdeps/generic/netinet/in_systm.h
@@ -33,7 +33,7 @@ __BEGIN_DECLS
typedef uint16_t n_short; /* short as received from the net */
typedef uint32_t n_long; /* long as received from the net */
-typedef uint32_t n_time; /* ms since 00:00 GMT, byte rev */
+typedef uint32_t n_time; /* ms since 00:00 UTC, byte rev */
__END_DECLS
diff --git a/sysdeps/nptl/pthread.h b/sysdeps/nptl/pthread.h
index b643546dc8..5ebfadc2bd 100644
--- a/sysdeps/nptl/pthread.h
+++ b/sysdeps/nptl/pthread.h
@@ -1137,7 +1137,7 @@ extern int pthread_cond_wait (pthread_cond_t *__restrict __cond,
/* Wait for condition variable COND to be signaled or broadcast until
ABSTIME. MUTEX is assumed to be locked before. ABSTIME is an
absolute time specification; zero is the beginning of the epoch
- (00:00:00 GMT, January 1, 1970).
+ (00:00:00 UTC, January 1, 1970).
This function is a cancellation point and therefore not marked with
__THROW. */
diff --git a/time/bits/types/struct_timeb.h b/time/bits/types/struct_timeb.h
index 1fe60c7131..b4939ba4b3 100644
--- a/time/bits/types/struct_timeb.h
+++ b/time/bits/types/struct_timeb.h
@@ -8,7 +8,7 @@ struct timeb
{
time_t time; /* Seconds since epoch, as from 'time'. */
unsigned short int millitm; /* Additional milliseconds. */
- short int timezone; /* Minutes west of GMT. */
+ short int timezone; /* Minutes west of UTC. */
short int dstflag; /* Nonzero if Daylight Savings Time used. */
};
diff --git a/time/strftime_l.c b/time/strftime_l.c
index 584df83d15..2c79eeddae 100644
--- a/time/strftime_l.c
+++ b/time/strftime_l.c
@@ -543,7 +543,7 @@ __strftime_internal (CHAR_T *s, size_t maxsize, const CHAR_T *format,
if (ut)
{
if (! (zone && *zone))
- zone = "GMT";
+ zone = "UTC";
}
#endif
diff --git a/time/sys/time.h b/time/sys/time.h
index 34ccee0e0e..0eee32bbce 100644
--- a/time/sys/time.h
+++ b/time/sys/time.h
@@ -51,7 +51,7 @@ __BEGIN_DECLS
This is obsolete and should never be used. */
struct timezone
{
- int tz_minuteswest; /* Minutes west of GMT. */
+ int tz_minuteswest; /* Minutes west of UTC. */
int tz_dsttime; /* Nonzero if DST is ever in effect. */
};
#endif
diff --git a/time/tzset.c b/time/tzset.c
index c2575edad9..2bbf8f055b 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -29,7 +29,7 @@
#define SECSPERDAY ((__time64_t) 86400)
-char *__tzname[2] = { (char *) "GMT", (char *) "GMT" };
+char *__tzname[2] = { (char *) "UTC", (char *) "UTC" };
int __daylight = 0;
long int __timezone = 0L;
@@ -51,7 +51,7 @@ typedef struct
unsigned short int m, n, d; /* Month, week, day. */
int secs; /* Time of day. */
- int offset; /* Seconds east of GMT (west if < 0). */
+ int offset; /* Seconds east of UTC (west if < 0). */
/* We cache the computed time of change for a
given year so we don't have to recompute it. */
@@ -449,7 +449,7 @@ compute_change (tz_rule *rule, long long int year)
if (rule->computed_for == year)
return;
- /* First set T to January 1st, 0:00:00 GMT in YEAR. */
+ /* First set T to YEAR-01-01 00:00:00 UT. */
t = (((year - 1970) * 365
/* Compute the number of leap days between 1970 and YEAR (exclusive).
There is a leap day every 4th year ... */
@@ -521,7 +521,7 @@ compute_change (tz_rule *rule, long long int year)
break;
}
- /* T is now the Epoch-relative time of 0:00:00 GMT on the day we want.
+ /* T is now the Epoch-relative time of 00:00:00 UT on the day we want.
Just subtract the UT offset and add the time of day, and we're done. */
rule->change = t - rule->offset + rule->secs;
@@ -611,7 +611,7 @@ __tz_convert (__time64_t timer, int use_localtime, struct tm *tp)
if (! use_localtime)
{
tp->tm_isdst = 0;
- tp->tm_zone = "GMT";
+ tp->tm_zone = "UTC";
tp->tm_gmtoff = 0L;
}
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 29/59] Simplify setting tz_rules to UTC
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (28 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 28/59] Prefer "UTC" for UTC Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 30/59] Update tzname etc. even if TZ is unchanged Paul Eggert
` (30 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzset.c (__tzset_parse_tz): Reset to named UTC, not unnamed.
(tzset_internal): Rely on __tzset_parse_tz to reset state.
---
time/tzset.c | 21 +++++----------------
1 file changed, 5 insertions(+), 16 deletions(-)
diff --git a/time/tzset.c b/time/tzset.c
index 2bbf8f055b..1873ad205b 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -352,11 +352,11 @@ parse_rule (const char **tzp, int whichrule)
void
__tzset_parse_tz (const char *tz)
{
- /* Clear out old state and reset to unnamed UTC. */
+ /* Clear out old state and reset to UTC. */
memset (tz_rules, '\0', sizeof tz_rules);
if (NO_DST != 0)
tz_rules[0].type = tz_rules[1].type = NO_DST;
- tz_rules[0].name = tz_rules[1].name = "";
+ tz_rules[0].name = tz_rules[1].name = "UTC";
/* Get the standard time zone abbreviations. */
if (parse_tzname (&tz, 0) && parse_offset (&tz, 0))
@@ -410,9 +410,6 @@ tzset_internal (int always)
/* No change, simply return. */
return;
- tz_rules[0].name = NULL;
- tz_rules[1].name = NULL;
-
/* Save the value of `tz'. */
free (old_tz);
old_tz = __strdup (tz);
@@ -422,18 +419,10 @@ tzset_internal (int always)
if (__use_tzfile)
return;
- /* No data file found. Default to UTC if nothing specified or if
+ /* No data file found. Default to UTC without leap seconds if
TZDEFAULT is broken. */
-
- if (*tz == '\0' || strcmp (tz, TZDEFAULT) == 0)
- {
- memset (tz_rules, '\0', sizeof tz_rules);
- tz_rules[0].name = tz_rules[1].name = "UTC";
- if (NO_DST != 0)
- tz_rules[0].type = tz_rules[1].type = NO_DST;
- update_vars ();
- return;
- }
+ if (strcmp (tz, TZDEFAULT) == 0)
+ tz = "";
__tzset_parse_tz (tz);
}
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 30/59] Update tzname etc. even if TZ is unchanged
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (29 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 29/59] Simplify setting tz_rules to UTC Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 31/59] "POSIX TZ" -> "proleptic TZ" Paul Eggert
` (29 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
POSIX says that external variables like tzname[0] are updated by
tzset even if TZ has not changed.
* time/tst-tzname.c: Include limits.h.
(do_test): Test this.
* time/tzset.c (tzset_internal):
Update external vars even if TZ has not changed.
When __tzfile_read succeeds, save its results.
(__tzset_unlocked): Do not set __tzname here, as tzset_internal
has already done it.
---
time/tst-tzname.c | 85 ++++++++++++++++++++++++++++++++++++++++++++---
time/tzset.c | 30 +++++++++++------
2 files changed, 100 insertions(+), 15 deletions(-)
diff --git a/time/tst-tzname.c b/time/tst-tzname.c
index b9b4aea35e..a70d5aee6a 100644
--- a/time/tst-tzname.c
+++ b/time/tst-tzname.c
@@ -16,6 +16,7 @@
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
+#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -29,13 +30,87 @@ do_test (void)
setenv ("TZ", TZDEFRULES, 1);
tzset ();
const char *stdtz = strdup (tzname[0]);
- setenv ("TZ", "STD-1DST", 1);
- tzset ();
- if (strcmp (tzname[0], "STD") != 0)
+
+ /* Test that proleptic TZ settings set tzname and other external vars.
+ Do not test geographic or empty settings, as POSIX does not require
+ external vars to be set for those cases. */
+ struct tz_test
+ {
+ char const *tz;
+ int daylight;
+ long int timezone;
+ char const *tzname[2];
+ } const tzs_to_test[] =
{
- printf ("FAIL: TZ=STD-1DST, tzname[0] = %s\n", tzname[0]);
- result = 1;
+ { ":STD-1DST", 1, - 1 * 60 * 60, { "STD", "DST" } },
+ { ":AST4ADT", 1, 4 * 60 * 60, { "AST", "ADT" } },
+ { ":QQQ5RRR5", 1, 5 * 60 * 60, { "QQQ", "RRR" } },
+ { ":NZST-12NZDT,M9.5.0,M4.1.0/3", 1, -12 * 60 * 60, { "NZST", "NZDT" } },
+ { ":<-02>2<-01>,M3.5.0/-1,M10.5.0/0", 1, 2 * 60 * 60, { "-02", "-01" } },
+ { ":UTC0", 0, 0, { "UTC", } },
+ };
+ for (int i = 0; i < sizeof tzs_to_test / sizeof *tzs_to_test; i++)
+ {
+ for (int j = 0; j < 2; j++)
+ {
+ char const *tz = tzs_to_test[i].tz + j;
+ if (setenv ("TZ", tz, 1) < 0)
+ {
+ printf ("FAIL: setenv (\"TZ\", \"%s\", 1)\n", tz);
+ result = 1;
+ continue;
+ }
+
+ struct tm *tm;
+ for (int k = 0; k < 5; k++)
+ {
+ time_t t = 0;
+ daylight = INT_MIN;
+ timezone = LONG_MIN;
+ tzname[0] = tzname[1] = (char *) "XYZ";
+
+ char const *method;
+ switch (k)
+ {
+ case 0:
+ case 1: method = "tzset"; tzset (); break;
+ case 2: method = "localtime"; tm = localtime (&t); break;
+ case 3: method = "mktime", mktime (tm); break;
+ case 4: method = "ctime"; ctime (&t); break;
+ }
+
+ if (daylight < 0
+ || (daylight != 0) != tzs_to_test[i].daylight)
+ {
+ printf ("FAIL: TZ=%s, %s, daylight = %d\n",
+ tz, method, daylight);
+ result = 1;
+ }
+ if (timezone != tzs_to_test[i].timezone)
+ {
+ printf ("FAIL: TZ=%s, %s, timezone = %ld\n",
+ tz, method, timezone);
+ result = 1;
+ }
+ if (tzname[0] == NULL
+ || strcmp (tzname[0], tzs_to_test[i].tzname[0]) != 0)
+ {
+ printf ("FAIL: TZ=%s, %s, tzname[0] = %s\n",
+ tz, method, tzname[0] == NULL ? "(NULL)" : tzname[0]);
+ result = 1;
+ }
+ if (tzs_to_test[i].tzname[1] != NULL
+ && (tzname[1] == NULL
+ || strcmp (tzname[1], tzs_to_test[i].tzname[1]) != 0))
+ {
+ printf ("FAIL: TZ=%s, %s, tzname[1] = %s\n",
+ tz, method, tzname[1] == NULL ? "(NULL)" : tzname[0]);
+ result = 1;
+ }
+ }
+ }
}
+
setenv ("TZ", TZDEFRULES, 1);
tzset ();
if (strcmp (tzname[0], stdtz) != 0)
diff --git a/time/tzset.c b/time/tzset.c
index 1873ad205b..270383e383 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -125,6 +125,7 @@ __tzstring (const char *s)
static char *old_tz;
+/* Update POSIX-required external variables to their saved values. */
static void
update_vars (void)
{
@@ -407,8 +408,11 @@ tzset_internal (int always)
/* Check whether the value changed since the last run. */
if (old_tz != NULL && strcmp (tz, old_tz) == 0)
- /* No change, simply return. */
- return;
+ {
+ /* No change, simply update external vars. */
+ update_vars ();
+ return;
+ }
/* Save the value of `tz'. */
free (old_tz);
@@ -417,7 +421,20 @@ tzset_internal (int always)
/* Try to read a data file. */
__tzfile_read (tz);
if (__use_tzfile)
- return;
+ {
+ /* Save equivalent of 'daylight' for later use by update_vars.
+ Although the other external variables have unspecified values
+ and so need not be saved in the usual case, save them anyway,
+ as POSIX requires this for the rare case of file-backed proleptic
+ TZ strings like "EST5EDT", and it is more likely to match user
+ expectations for geographical TZ strings. */
+ enum tz_rule_type some_DST = J0; /* anything but NO_DST */
+ tz_rules[1].type = NO_DST + __daylight * (some_DST - NO_DST);
+ tz_rules[0].offset = -__timezone;
+ tz_rules[0].name = __tzname[0];
+ tz_rules[1].name = __tzname[1];
+ return;
+ }
/* No data file found. Default to UTC without leap seconds if
TZDEFAULT is broken. */
@@ -545,13 +562,6 @@ void
__tzset_unlocked (void)
{
tzset_internal (1);
-
- if (!__use_tzfile)
- {
- /* Set `tzname'. */
- __tzname[0] = (char *) tz_rules[0].name;
- __tzname[1] = (char *) tz_rules[1].name;
- }
}
void
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 31/59] "POSIX TZ" -> "proleptic TZ"
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (30 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 30/59] Update tzname etc. even if TZ is unchanged Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 32/59] Reject invalid TZ strings in TZif files Paul Eggert
` (28 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
Since POSIX.1-2024 specifies geographical TZ as well as the
traditional proleptic TZ settings, change commentary to say
"proleptic TZ" rather than "POSIX TZ".
---
time/tst-mktime2.c | 4 ++--
time/tzfile.c | 6 +++---
time/tzset.c | 4 ++--
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/time/tst-mktime2.c b/time/tst-mktime2.c
index 09f85134d2..f63e205b4c 100644
--- a/time/tst-mktime2.c
+++ b/time/tst-mktime2.c
@@ -55,9 +55,9 @@ spring_forward_gap (void)
/* glibc (up to about 1998-10-07) failed this test. */
struct tm tm;
- /* Use the portable POSIX.1 specification "TZ=PST8PDT,M4.1.0,M10.5.0"
+ /* Use the proleptic POSIX.1 specification "TZ=PST8PDT,M4.1.0,M10.5.0"
instead of "TZ=America/Vancouver" in order to detect the bug even
- on systems that don't support the Olson extension, or don't have the
+ on systems that don't support POSIX.1-2024, or don't have the
full zoneinfo tables installed. */
set_timezone ("PST8PDT,M4.1.0,M10.5.0");
diff --git a/time/tzfile.c b/time/tzfile.c
index 4ca1f3c0c8..d30faedfa2 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -258,7 +258,7 @@ __tzfile_read (const char *file)
goto read_again;
}
- /* Compute the size of the POSIX time zone specification in the
+ /* Compute the size of the proleptic time zone specification in the
file. This includes the trailing but not the leading newline. */
size_t tzspec_size;
if (trans_width == 8)
@@ -471,7 +471,7 @@ __tzfile_read (const char *file)
while (i < typecnt)
types[i++].isutc = 0;
- /* Read the POSIX TZ-style information if possible. */
+ /* Read the proleptic TZ information if possible. */
if (tzspec != NULL)
{
char *nl;
@@ -620,7 +620,7 @@ __tzfile_compute (__time64_t timer, int use_localtime,
goto found;
}
- /* Parse the POSIX TZ-style string. */
+ /* Parse the proleptic TZ string. */
__tzset_parse_tz (tzspec);
/* Convert to broken down structure. If this fails do not
diff --git a/time/tzset.c b/time/tzset.c
index 270383e383..7fdce9de7b 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -41,7 +41,7 @@ weak_alias (__timezone, timezone)
__libc_lock_define_initialized (, __tzset_lock)
/* This structure contains all the information about a
- timezone given in the POSIX standard TZ envariable. */
+ timezone given in a proleptic TZ string. */
typedef struct
{
const char *name;
@@ -349,7 +349,7 @@ parse_rule (const char **tzp, int whichrule)
return true;
}
-/* Parse the POSIX TZ-style string. */
+/* Parse a proleptic TZ string. */
void
__tzset_parse_tz (const char *tz)
{
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 32/59] Reject invalid TZ strings in TZif files
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (31 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 31/59] "POSIX TZ" -> "proleptic TZ" Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 33/59] Reject TZif files containing '\0' in TZ string Paul Eggert
` (27 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
Previously, the code could have had quirky behavior if a TZif
file's TZ string was corrupted. Now, the code reliably ignores
such a string.
* time/tzfile.c (__tzfile_compute): If the proleptic TZ string
is invalid, do not use it.
* time/tzset.c (__tzset_parse_tz): Return a success indication.
Do not try to parse DST rule if there is no DST.
---
time/tzfile.c | 5 +++--
time/tzset.c | 15 ++++++---------
time/tzset.h | 2 +-
3 files changed, 10 insertions(+), 12 deletions(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index d30faedfa2..dae6b2b613 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -620,8 +620,9 @@ __tzfile_compute (__time64_t timer, int use_localtime,
goto found;
}
- /* Parse the proleptic TZ string. */
- __tzset_parse_tz (tzspec);
+ /* Parse the proleptic TZ string. If this fails do not use it. */
+ if (__glibc_unlikely (! __tzset_parse_tz (tzspec)))
+ goto use_last;
/* Convert to broken down structure. If this fails do not
use the string. */
diff --git a/time/tzset.c b/time/tzset.c
index 7fdce9de7b..e4fbf729b8 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -350,7 +350,7 @@ parse_rule (const char **tzp, int whichrule)
}
/* Parse a proleptic TZ string. */
-void
+bool
__tzset_parse_tz (const char *tz)
{
/* Clear out old state and reset to UTC. */
@@ -360,17 +360,13 @@ __tzset_parse_tz (const char *tz)
tz_rules[0].name = tz_rules[1].name = "UTC";
/* Get the standard time zone abbreviations. */
- if (parse_tzname (&tz, 0) && parse_offset (&tz, 0))
+ bool ok = parse_tzname (&tz, 0) && parse_offset (&tz, 0);
+ if (ok)
{
/* Get the DST time zone abbreviation (if any). */
if (*tz != '\0')
- {
- if (parse_tzname (&tz, 1))
- parse_offset (&tz, 1);
- /* Figure out the standard <-> DST rules. */
- if (parse_rule (&tz, 0))
- parse_rule (&tz, 1);
- }
+ ok = (parse_tzname (&tz, 1) && parse_offset (&tz, 1)
+ && parse_rule (&tz, 0) && parse_rule (&tz, 1));
else
{
/* There is no DST. */
@@ -380,6 +376,7 @@ __tzset_parse_tz (const char *tz)
}
update_vars ();
+ return ok;
}
/* Interpret the TZ envariable. */
diff --git a/time/tzset.h b/time/tzset.h
index 3687c044e3..61c85fba75 100644
--- a/time/tzset.h
+++ b/time/tzset.h
@@ -14,7 +14,7 @@ extern void __tzfile_read (const char *file) attribute_hidden;
extern void __tzfile_compute (__time64_t timer, int use_localtime,
int *leap_correct, bool *leap_hit,
struct tm *tp) attribute_hidden;
-extern void __tzset_parse_tz (const char *tz) attribute_hidden;
+extern bool __tzset_parse_tz (const char *tz) attribute_hidden;
extern void __tz_compute (__time64_t timer, struct tm *tm)
__THROW attribute_hidden;
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 33/59] Reject TZif files containing '\0' in TZ string
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (32 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 32/59] Reject invalid TZ strings in TZif files Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 34/59] mktime should not consult 'daylight' Paul Eggert
` (26 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzfile.c (__tzfile_read): Don't allow TZif strings
that contain '\0'. If a TZif file is longer than needed,
perhaps due to future TZif format, don't bother reading
the excess data.
---
time/tzfile.c | 39 ++++++++++++++++++++++++---------------
1 file changed, 24 insertions(+), 15 deletions(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index dae6b2b613..744c7690ab 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -258,7 +258,7 @@ __tzfile_read (const char *file)
goto read_again;
}
- /* Compute the size of the proleptic time zone specification in the
+ /* Compute a bound on the size of the proleptic time zone specification in the
file. This includes the trailing but not the leading newline. */
size_t tzspec_size;
if (trans_width == 8)
@@ -472,24 +472,33 @@ __tzfile_read (const char *file)
types[i++].isutc = 0;
/* Read the proleptic TZ information if possible. */
- if (tzspec != NULL)
+ if (tzspec_size != 0)
{
- char *nl;
- assert (tzspec_size > 0);
- /* Skip the leading newline, then grab everything up to the next
- newline; ignore everything after that. */
- if (__getc_unlocked (f) == '\n'
- && __fread_unlocked (tzspec, 1, tzspec_size, f) == tzspec_size
- && (nl = memchr (tzspec, '\n', tzspec_size)) != NULL)
- *nl = '\0';
- else
+ /* Don't use a TZ string not preceded by newline. */
+ if (__getc_unlocked (f) != '\n')
+ goto lose;
+ size_t len = 0;
+ /* Grab everything up to the next newline. Ignore the remaining
+ part of the file as it may be a later TZif version. */
+ for (int ch; (ch = __getc_unlocked (f)) != '\n'; )
+ {
+ /* Don't use a truncated TZ string, or one containing '\0'. */
+ if (ch <= 0)
+ goto lose;
+
+ tzspec[len++] = ch;
+
+ /* Don't use a TZ string that lacks a trailing newline. */
+ if (len == tzspec_size)
+ goto lose;
+ }
+ tzspec[len] = '\0';
+
+ /* Don't use an empty TZ string. */
+ if (len == 0)
tzspec = NULL;
}
- /* Don't use an empty TZ string. */
- if (tzspec != NULL && tzspec[0] == '\0')
- tzspec = NULL;
-
fclose (f);
/* First "register" all time zone abbreviations. */
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 34/59] mktime should not consult 'daylight'
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (33 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 33/59] Reject TZif files containing '\0' in TZ string Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 35/59] Remove __use_tzfile Paul Eggert
` (25 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* lib/mktime.c (__mktime_internal): Do not consult __daylight as it
has unreliable contents when a geographical timezone is used, and the
user can set it. Instead, use tm_isdst for mktime, and ignore
tm_isdst for timegm. This patch is cherry-picked from Gnulib commit
72abb08f4495bf232736f4d13a24bced72a9c327 dated Thu Nov 14 10:40:24
2024 -0700.
---
time/mktime.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/time/mktime.c b/time/mktime.c
index 3a951b2c8c..0981cd1d1d 100644
--- a/time/mktime.c
+++ b/time/mktime.c
@@ -356,8 +356,8 @@ __mktime_internal (struct tm *tp, bool local, mktime_offset_t *offset)
int mon = tp->tm_mon;
int year_requested = tp->tm_year;
- /* If the timezone never observes DST, ignore any tm_isdst request. */
- int isdst = local && __daylight ? tp->tm_isdst : 0;
+ /* Ignore any tm_isdst request for timegm. */
+ int isdst = local ? tp->tm_isdst : 0;
/* 1 if the previous probe was DST. */
int dst2 = 0;
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 35/59] Remove __use_tzfile
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (34 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 34/59] mktime should not consult 'daylight' Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 36/59] Remove arbitrary limit on TZ, TZDIR lengths Paul Eggert
` (24 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
This refactoring removes a hidden extern and shrinks static state a bit.
* time/tzfile.c (__use_tzfile): Remove static var. All uses removed.
(__tzfile_read): Use (tzfile_mtime != 0) instead of __use_tzfile.
Return bool. Caller changed.
* time/tzset.c (tzset_internal): Accept and return bool.
All callers changed. Use old_tz instead of is_initialized to test
whether we are initialized, removing the need for the is_initialized
static. New static use_tzfile instead, which means what the old
__use_tzfile meant.
---
time/tzfile.c | 23 +++++-----
time/tzset.c | 117 +++++++++++++++++++++++++-------------------------
time/tzset.h | 4 +-
3 files changed, 69 insertions(+), 75 deletions(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index 744c7690ab..bd6df16b00 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -33,7 +33,6 @@
#include <timezone/tzfile.h>
-bool __use_tzfile;
static dev_t tzfile_dev;
static ino64_t tzfile_ino;
static __time64_t tzfile_mtime;
@@ -138,7 +137,7 @@ decode64 (const void *ptr)
}
-void
+bool
__tzfile_read (const char *file)
{
static const char default_tzdir[] = TZDIR;
@@ -147,15 +146,12 @@ __tzfile_read (const char *file)
union { struct tzhead tzhead; tzidx tzidx_aligned; } u;
tzidx charcnt;
tzidx i;
- bool was_using_tzfile = __use_tzfile;
int trans_width = 4;
char *new = NULL;
_Static_assert (sizeof (__time64_t) == 8,
"__time64_t must be eight bytes");
- __use_tzfile = false;
-
/* We must not allow to read an arbitrary file in a setuid
program. So we fail for any file which is not in the
directory hierarchy starting at TZDIR
@@ -187,7 +183,7 @@ __tzfile_read (const char *file)
/* If we were already using tzfile, check whether the file changed. */
struct __stat64_t64 st;
- if (was_using_tzfile
+ if (tzfile_mtime != 0
&& __stat64_time64 (file, &st) == 0
&& tzfile_ino == st.st_ino && tzfile_dev == st.st_dev
&& tzfile_mtime == st.st_mtime)
@@ -207,11 +203,6 @@ __tzfile_read (const char *file)
free (transitions);
transitions = NULL;
- /* Remember the inode and device number and modification time. */
- tzfile_dev = st.st_dev;
- tzfile_ino = st.st_ino;
- tzfile_mtime = st.st_mtime;
-
/* No threads reading this stream. */
__fsetlocking (f, FSETLOCKING_BYCALLER);
@@ -565,10 +556,14 @@ __tzfile_read (const char *file)
__daylight = daylight_saved;
__timezone = -rule_stdoff;
+ /* Remember the inode and device number and modification time. */
+ tzfile_dev = st.st_dev;
+ tzfile_ino = st.st_ino;
+ tzfile_mtime = st.st_mtime;
+
done:
free (new);
- __use_tzfile = true;
- return;
+ return true;
lose:
fclose (f);
@@ -576,6 +571,8 @@ __tzfile_read (const char *file)
free (new);
free (transitions);
transitions = NULL;
+ tzfile_mtime = 0;
+ return false;
}
\f
void
diff --git a/time/tzset.c b/time/tzset.c
index e4fbf729b8..af397d97dd 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -65,7 +65,6 @@ static tz_rule tz_rules[2];
static void compute_change (tz_rule *rule, long long int year) __THROW;
-static void tzset_internal (int always);
\f
/* List of buffers containing time zone strings. */
struct tzstring_l
@@ -379,66 +378,67 @@ __tzset_parse_tz (const char *tz)
return ok;
}
-/* Interpret the TZ envariable. */
-static void
-tzset_internal (int always)
+/* Interpret the TZ envariable. If ALWAYS, always do so; otherwise do
+ so only if TZ has changed. Return true if TZ stands for a TZif file. */
+static bool
+tzset_internal (bool always)
{
- static int is_initialized;
- const char *tz;
-
- if (is_initialized && !always)
- return;
- is_initialized = 1;
-
- /* Examine the TZ environment variable. */
- tz = getenv ("TZ");
-
- if (tz == NULL)
- /* No user specification; use the site-wide default. */
- tz = TZDEFAULT;
-
- /* A leading colon means "implementation defined syntax".
- We ignore the colon and always use the same algorithm:
- try a data file, and if none exists parse the 1003.1 syntax. */
- if (*tz == ':')
- ++tz;
-
- /* Check whether the value changed since the last run. */
- if (old_tz != NULL && strcmp (tz, old_tz) == 0)
- {
- /* No change, simply update external vars. */
- update_vars ();
- return;
- }
-
- /* Save the value of `tz'. */
- free (old_tz);
- old_tz = __strdup (tz);
+ static bool use_tzfile;
- /* Try to read a data file. */
- __tzfile_read (tz);
- if (__use_tzfile)
+ if (old_tz == NULL || always)
{
- /* Save equivalent of 'daylight' for later use by update_vars.
- Although the other external variables have unspecified values
- and so need not be saved in the usual case, save them anyway,
- as POSIX requires this for the rare case of file-backed proleptic
- TZ strings like "EST5EDT", and it is more likely to match user
- expectations for geographical TZ strings. */
- enum tz_rule_type some_DST = J0; /* anything but NO_DST */
- tz_rules[1].type = NO_DST + __daylight * (some_DST - NO_DST);
- tz_rules[0].offset = -__timezone;
- tz_rules[0].name = __tzname[0];
- tz_rules[1].name = __tzname[1];
- return;
+ /* Examine the TZ environment variable. */
+ char const *tz = getenv ("TZ");
+
+ if (tz == NULL)
+ /* No user specification; use the site-wide default. */
+ tz = TZDEFAULT;
+
+ /* A leading colon means "implementation defined syntax".
+ We ignore the colon and always use the same algorithm:
+ try a data file, and if none exists parse the 1003.1 syntax. */
+ if (*tz == ':')
+ ++tz;
+
+ /* If the value changed has not since the last run,
+ simply update internal vars. */
+ if (old_tz != NULL && strcmp (tz, old_tz) == 0)
+ update_vars ();
+ else
+ {
+ /* Save the value of 'tz'. */
+ free (old_tz);
+ old_tz = __strdup (tz);
+
+ /* Try to read a data file. */
+ use_tzfile = __tzfile_read (tz);
+ if (use_tzfile)
+ {
+ /* Save equivalent of 'daylight' for later use by update_vars.
+ Although the other external variables have unspecified values
+ and so need not be saved in the usual case, save them anyway,
+ as POSIX requires this for rare file-backed proleptic
+ TZ strings like "EST5EDT", and it is more likely to match user
+ expectations for geographical TZ strings. */
+ enum tz_rule_type some_DST = J0; /* anything but NO_DST */
+ tz_rules[1].type = NO_DST + __daylight * (some_DST - NO_DST);
+ tz_rules[0].offset = -__timezone;
+ tz_rules[0].name = __tzname[0];
+ tz_rules[1].name = __tzname[1];
+ }
+ else
+ {
+ /* No data file found. Default to UTC without leap seconds if
+ TZDEFAULT is broken. */
+ if (strcmp (tz, TZDEFAULT) == 0)
+ tz = "";
+
+ __tzset_parse_tz (tz);
+ }
+ }
}
- /* No data file found. Default to UTC without leap seconds if
- TZDEFAULT is broken. */
- if (strcmp (tz, TZDEFAULT) == 0)
- tz = "";
-
- __tzset_parse_tz (tz);
+ return use_tzfile;
}
\f
/* Figure out the exact time (as a __time64_t) in YEAR
@@ -558,7 +558,7 @@ __tz_compute (__time64_t timer, struct tm *tm)
void
__tzset_unlocked (void)
{
- tzset_internal (1);
+ tzset_internal (true);
}
void
@@ -587,9 +587,8 @@ __tz_convert (__time64_t timer, int use_localtime, struct tm *tp)
/* Update internal database according to current TZ setting.
POSIX.1 8.3.7.2 says that localtime_r is not required to set tzname.
This is a good idea since this allows at least a bit more parallelism. */
- tzset_internal (tp == &_tmbuf && use_localtime);
- if (__use_tzfile)
+ if (tzset_internal (tp == &_tmbuf && use_localtime))
__tzfile_compute (timer, use_localtime, &leap_correction,
&leap_extra_sec, tp);
else
diff --git a/time/tzset.h b/time/tzset.h
index 61c85fba75..959f38c7ab 100644
--- a/time/tzset.h
+++ b/time/tzset.h
@@ -8,9 +8,7 @@
/* Defined in tzset.c. */
extern char *__tzstring (const char *string) attribute_hidden;
-extern bool __use_tzfile attribute_hidden;
-
-extern void __tzfile_read (const char *file) attribute_hidden;
+extern bool __tzfile_read (const char *file) attribute_hidden;
extern void __tzfile_compute (__time64_t timer, int use_localtime,
int *leap_correct, bool *leap_hit,
struct tm *tp) attribute_hidden;
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 36/59] Remove arbitrary limit on TZ, TZDIR lengths
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (35 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 35/59] Remove __use_tzfile Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 37/59] Tighten setuid TZif file name check Paul Eggert
` (23 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzfile.c (__tzfile_read): Don't use __asprintf, as it
mishandles strings longer than INT_MAX.
---
time/tzfile.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index bd6df16b00..678ecdd58b 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -176,8 +176,15 @@ __tzfile_read (const char *file)
tzdir = getenv ("TZDIR");
if (tzdir == NULL || *tzdir == '\0')
tzdir = default_tzdir;
- if (__asprintf (&new, "%s/%s", tzdir, file) == -1)
+
+ size_t tzdirlen = strlen (tzdir);
+ size_t filelen = strlen (file);
+ new = malloc (tzdirlen + 1 + filelen + 1);
+ if (new == NULL)
goto ret_free_transitions;
+ char *newp = __mempcpy (new, tzdir, tzdirlen);
+ *newp++ = '/';
+ __mempcpy (newp, file, filelen + 1);
file = new;
}
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 37/59] Tighten setuid TZif file name check
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (36 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 36/59] Remove arbitrary limit on TZ, TZDIR lengths Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 38/59] Avoid a malloc in __tzfile_read Paul Eggert
` (22 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzfile.c (__tzfile_read): In a setuid program, reject TZ
settings like "/usr/share/zoneinfobad/Foo" which have a prefix
"/usr/share/zoneinfo" but do not refer to /usr/share/zoneinfo/*.
---
time/tzfile.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index 678ecdd58b..8efe5b2ec9 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -140,7 +140,7 @@ decode64 (const void *ptr)
bool
__tzfile_read (const char *file)
{
- static const char default_tzdir[] = TZDIR;
+ static const char default_tzdir[] = TZDIR "/";
tzidx isstdcnt, isutcnt;
FILE *f;
union { struct tzhead tzhead; tzidx tzidx_aligned; } u;
@@ -178,6 +178,7 @@ __tzfile_read (const char *file)
tzdir = default_tzdir;
size_t tzdirlen = strlen (tzdir);
+ tzdirlen -= tzdir[tzdirlen - 1] == '/';
size_t filelen = strlen (file);
new = malloc (tzdirlen + 1 + filelen + 1);
if (new == NULL)
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 38/59] Avoid a malloc in __tzfile_read
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (37 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 37/59] Tighten setuid TZif file name check Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 39/59] Free earlier " Paul Eggert
` (21 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzfile.c (__tzfile_read): Avoid a malloc in the usual case,
by reusing a stack buffer.
---
time/tzfile.c | 23 +++++++++++++++--------
1 file changed, 15 insertions(+), 8 deletions(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index 8efe5b2ec9..09ebc7d8ea 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -143,7 +143,7 @@ __tzfile_read (const char *file)
static const char default_tzdir[] = TZDIR "/";
tzidx isstdcnt, isutcnt;
FILE *f;
- union { struct tzhead tzhead; tzidx tzidx_aligned; } u;
+ union { struct tzhead tzhead; tzidx tzidx_aligned; char tzfilename[64]; } u;
tzidx charcnt;
tzidx i;
int trans_width = 4;
@@ -180,13 +180,20 @@ __tzfile_read (const char *file)
size_t tzdirlen = strlen (tzdir);
tzdirlen -= tzdir[tzdirlen - 1] == '/';
size_t filelen = strlen (file);
- new = malloc (tzdirlen + 1 + filelen + 1);
- if (new == NULL)
- goto ret_free_transitions;
- char *newp = __mempcpy (new, tzdir, tzdirlen);
- *newp++ = '/';
- __mempcpy (newp, file, filelen + 1);
- file = new;
+ size_t absfile_size = tzdirlen + 1 + filelen + 1;
+ char *absfile;
+ if (absfile_size <= sizeof u.tzfilename)
+ absfile = u.tzfilename;
+ else
+ {
+ absfile = new = malloc (absfile_size);
+ if (absfile == NULL)
+ goto ret_free_transitions;
+ }
+ char *p = __mempcpy (absfile, tzdir, tzdirlen);
+ *p++ = '/';
+ __mempcpy (p, file, filelen + 1);
+ file = absfile;
}
/* If we were already using tzfile, check whether the file changed. */
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 39/59] Free earlier in __tzfile_read
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (38 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 38/59] Avoid a malloc in __tzfile_read Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 40/59] Improve __tzfile_read TZ="" comment Paul Eggert
` (20 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzfile.c (__tzfile_read): Free NEW earlier, to help the
heap manager and remove the need for a goto.
---
time/tzfile.c | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index 09ebc7d8ea..d600b88e89 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -202,11 +202,15 @@ __tzfile_read (const char *file)
&& __stat64_time64 (file, &st) == 0
&& tzfile_ino == st.st_ino && tzfile_dev == st.st_dev
&& tzfile_mtime == st.st_mtime)
- goto done; /* Nothing to do. */
+ {
+ free (new);
+ return true;
+ }
/* Note the file is opened with cancellation in the I/O functions
disabled and if available FD_CLOEXEC set. */
f = fopen (file, "rce");
+ free (new);
if (f == NULL)
goto ret_free_transitions;
off_t f_offset = 0;
@@ -576,14 +580,11 @@ __tzfile_read (const char *file)
tzfile_ino = st.st_ino;
tzfile_mtime = st.st_mtime;
- done:
- free (new);
return true;
lose:
fclose (f);
ret_free_transitions:
- free (new);
free (transitions);
transitions = NULL;
tzfile_mtime = 0;
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 40/59] Improve __tzfile_read TZ="" comment
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (39 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 39/59] Free earlier " Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 41/59] Refactor enum tz_rule_type Paul Eggert
` (19 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzfile.c (__tzfile_read): Improve comment about empty TZ strings.
---
time/tzfile.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index d600b88e89..3ca08d16d4 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -504,7 +504,7 @@ __tzfile_read (const char *file)
}
tzspec[len] = '\0';
- /* Don't use an empty TZ string. */
+ /* An empty TZ string means to use the last transition's type. */
if (len == 0)
tzspec = NULL;
}
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 41/59] Refactor enum tz_rule_type
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (40 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 40/59] Improve __tzfile_read TZ="" comment Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 42/59] __offtime now returns struct tm * Paul Eggert
` (18 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzset.c (enum tz_rule_type): Break out into a separate
declaration and add comments.
---
time/tzset.c | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/time/tzset.c b/time/tzset.c
index af397d97dd..90ecdf4210 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -40,6 +40,23 @@ weak_alias (__timezone, timezone)
/* This locks all the state variables in tzfile.c and this file. */
__libc_lock_define_initialized (, __tzset_lock)
+/* Type of a DST rule date given in a proleptic TZ string. */
+enum tz_rule_type
+ {
+ /* No DST. */
+ NO_DST,
+
+ /* Zero-based Julian day counting any Feb. 29, e.g., ",59"
+ for Feb. 29 in leap years, Mar. 1 in nonleap years. */
+ J0,
+
+ /* One-based Julian day ignoring any Feb. 29, e.g., ",J59" for Mar. 1. */
+ J1,
+
+ /* Month m, week n, weekday d, e.g., ",M10.5.0" for Oct.'s last Sunday. */
+ M
+ };
+
/* This structure contains all the information about a
timezone given in a proleptic TZ string. */
typedef struct
@@ -47,7 +64,7 @@ typedef struct
const char *name;
/* When to change. */
- enum tz_rule_type { NO_DST, J0, J1, M } type; /* Interpretation of: */
+ enum tz_rule_type type; /* Interpretation of: */
unsigned short int m, n, d; /* Month, week, day. */
int secs; /* Time of day. */
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 42/59] __offtime now returns struct tm *
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (41 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 41/59] Refactor enum tz_rule_type Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 43/59] Fix overflow checking in offtime Paul Eggert
` (17 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/offtime.c (__offtime): Refactor by returning struct tm *
instead of int. All callers changed.
---
include/time.h | 8 ++++----
time/offtime.c | 8 ++++----
time/tzfile.c | 2 +-
time/tzset.c | 7 +++----
4 files changed, 12 insertions(+), 13 deletions(-)
diff --git a/include/time.h b/include/time.h
index e9c083646a..186ea657cb 100644
--- a/include/time.h
+++ b/include/time.h
@@ -250,10 +250,10 @@ libc_hidden_proto (__gettimeofday64)
/* Compute the `struct tm' representation of T,
offset OFFSET seconds east of UTC,
and store year, yday, mon, mday, wday, hour, min, sec into *TP.
- Return nonzero if successful. */
-extern int __offtime (__time64_t __timer,
- long int __offset,
- struct tm *__tp) attribute_hidden;
+ Return TP if successful, a null pointer otherwise. */
+extern struct tm *__offtime (__time64_t __timer,
+ long int __offset,
+ struct tm *__tp) attribute_hidden;
extern char *__asctime_r (const struct tm *__tp, char *__buf)
attribute_hidden;
diff --git a/time/offtime.c b/time/offtime.c
index c94573e931..89cdcc4b15 100644
--- a/time/offtime.c
+++ b/time/offtime.c
@@ -24,8 +24,8 @@
/* Compute the `struct tm' representation of T,
offset OFFSET seconds east of UTC,
and store year, yday, mon, mday, wday, hour, min, sec into *TP.
- Return nonzero if successful. */
-int
+ Return TP if successful, a null pointer otherwise. */
+struct tm *
__offtime (__time64_t t, long int offset, struct tm *tp)
{
__time64_t days, rem, y;
@@ -73,7 +73,7 @@ __offtime (__time64_t t, long int offset, struct tm *tp)
{
/* The year cannot be represented due to overflow. */
__set_errno (EOVERFLOW);
- return 0;
+ return NULL;
}
tp->tm_yday = days;
ip = __mon_yday[__isleap(y)];
@@ -82,5 +82,5 @@ __offtime (__time64_t t, long int offset, struct tm *tp)
days -= ip[y];
tp->tm_mon = y;
tp->tm_mday = days + 1;
- return 1;
+ return tp;
}
diff --git a/time/tzfile.c b/time/tzfile.c
index 3ca08d16d4..9c05811e22 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -648,7 +648,7 @@ __tzfile_compute (__time64_t timer, int use_localtime,
/* Convert to broken down structure. If this fails do not
use the string. */
- if (__glibc_unlikely (! __offtime (timer, 0, tp)))
+ if (__glibc_unlikely (__offtime (timer, 0, tp) == NULL))
goto use_last;
/* Use the rules from the TZ string to compute the change. */
diff --git a/time/tzset.c b/time/tzset.c
index 90ecdf4210..671394f02d 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -610,9 +610,8 @@ __tz_convert (__time64_t timer, int use_localtime, struct tm *tp)
&leap_extra_sec, tp);
else
{
- if (! __offtime (timer, 0, tp))
- tp = NULL;
- else if (use_localtime)
+ tp = __offtime (timer, 0, tp);
+ if (tp && use_localtime)
__tz_compute (timer, tp);
leap_correction = 0;
leap_extra_sec = false;
@@ -627,7 +626,7 @@ __tz_convert (__time64_t timer, int use_localtime, struct tm *tp)
tp->tm_gmtoff = 0L;
}
- if (__offtime (timer, tp->tm_gmtoff - leap_correction, tp))
+ if (__offtime (timer, tp->tm_gmtoff - leap_correction, tp) != NULL)
{
/* This assumes leap seconds can occur only when the local
time offset from UTC is a multiple of 60 seconds,
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 43/59] Fix overflow checking in offtime
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (42 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 42/59] __offtime now returns struct tm * Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 44/59] Handle TZif time type 0 correctly Paul Eggert
` (16 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/offtime.c: Include <stdckdint.h>.
(__offtime): New arg CORR, so that overflow cannot happen when
the caller subtracts it from OFFSET. All callers changed.
Replace loops with straight-line code.
Do not set *TP unless successful.
Do not rely on undefined behavior when checking for year overflow.
Use narrower integers when computing mon and mday.
---
include/time.h | 10 ++++----
time/offtime.c | 64 +++++++++++++++++++++++---------------------------
time/tzfile.c | 2 +-
time/tzset.c | 4 ++--
4 files changed, 37 insertions(+), 43 deletions(-)
diff --git a/include/time.h b/include/time.h
index 186ea657cb..e460037ee2 100644
--- a/include/time.h
+++ b/include/time.h
@@ -247,12 +247,12 @@ extern int __gettimeofday64 (struct __timeval64 *restrict tv,
libc_hidden_proto (__gettimeofday64)
#endif
-/* Compute the `struct tm' representation of T,
- offset OFFSET seconds east of UTC,
- and store year, yday, mon, mday, wday, hour, min, sec into *TP.
- Return TP if successful, a null pointer otherwise. */
+/* Compute the 'struct tm' representation of T, offset OFFSET seconds
+ east of UTC and minus a leap second correction CORR.
+ If successful, store year, yday, mon, mday, wday, hour, min, sec
+ into *TP and return TP; otherwise return a null pointer. */
extern struct tm *__offtime (__time64_t __timer,
- long int __offset,
+ long int __offset, int __corr,
struct tm *__tp) attribute_hidden;
extern char *__asctime_r (const struct tm *__tp, char *__buf)
diff --git a/time/offtime.c b/time/offtime.c
index 89cdcc4b15..9fce47c01d 100644
--- a/time/offtime.c
+++ b/time/offtime.c
@@ -16,43 +16,28 @@
<https://www.gnu.org/licenses/>. */
#include <errno.h>
+#include <stdckdint.h>
#include <time.h>
#define SECS_PER_HOUR (60 * 60)
#define SECS_PER_DAY (SECS_PER_HOUR * 24)
-/* Compute the `struct tm' representation of T,
- offset OFFSET seconds east of UTC,
- and store year, yday, mon, mday, wday, hour, min, sec into *TP.
- Return TP if successful, a null pointer otherwise. */
+/* Compute the 'struct tm' representation of T, offset OFFSET seconds
+ east of UTC and minus a leap second correction CORR.
+ If successful, store year, yday, mon, mday, wday, hour, min, sec
+ into *TP and return TP; otherwise return a null pointer. */
struct tm *
-__offtime (__time64_t t, long int offset, struct tm *tp)
+__offtime (__time64_t t, long int offset, int corr, struct tm *tp)
{
- __time64_t days, rem, y;
- const unsigned short int *ip;
+ int rem = (t % SECS_PER_DAY + offset % SECS_PER_DAY - corr % SECS_PER_DAY
+ + 3 * SECS_PER_DAY);
+ __time64_t days = (t / SECS_PER_DAY + offset / SECS_PER_DAY
+ - corr / SECS_PER_DAY + rem / SECS_PER_DAY - 3);
- days = t / SECS_PER_DAY;
- rem = t % SECS_PER_DAY;
- rem += offset;
- while (rem < 0)
- {
- rem += SECS_PER_DAY;
- --days;
- }
- while (rem >= SECS_PER_DAY)
- {
- rem -= SECS_PER_DAY;
- ++days;
- }
- tp->tm_hour = rem / SECS_PER_HOUR;
- rem %= SECS_PER_HOUR;
- tp->tm_min = rem / 60;
- tp->tm_sec = rem % 60;
/* January 1, 1970 was a Thursday. */
- tp->tm_wday = (4 + days) % 7;
- if (tp->tm_wday < 0)
- tp->tm_wday += 7;
- y = 1970;
+ int wday_rem = (4 + days) % 7;
+
+ __time64_t y = 1970;
#define DIV(a, b) ((a) / (b) - ((a) % (b) < 0))
#define LEAPS_THRU_END_OF(y) (DIV (y, 4) - DIV (y, 100) + DIV (y, 400))
@@ -68,19 +53,28 @@ __offtime (__time64_t t, long int offset, struct tm *tp)
- LEAPS_THRU_END_OF (y - 1));
y = yg;
}
- tp->tm_year = y - 1900;
- if (tp->tm_year != y - 1900)
+ int year;
+ if (ckd_sub (&year, y, 1900))
{
/* The year cannot be represented due to overflow. */
__set_errno (EOVERFLOW);
return NULL;
}
+
+ int day_rem = rem % SECS_PER_DAY;
+ tp->tm_hour = day_rem / SECS_PER_HOUR;
+ int hour_rem = day_rem % SECS_PER_HOUR;
+ tp->tm_sec = hour_rem % 60;
+ tp->tm_min = hour_rem / 60;
+ tp->tm_year = year;
+ tp->tm_wday = wday_rem + (wday_rem < 0 ? 7 : 0);
tp->tm_yday = days;
- ip = __mon_yday[__isleap(y)];
- for (y = 11; days < (long int) ip[y]; --y)
+ unsigned short int yday = days;
+ unsigned short int const *ip = __mon_yday[__isleap (y)];
+ int mon;
+ for (mon = 11; yday < ip[mon]; mon--)
continue;
- days -= ip[y];
- tp->tm_mon = y;
- tp->tm_mday = days + 1;
+ tp->tm_mon = mon;
+ tp->tm_mday = yday - ip[mon] + 1;
return tp;
}
diff --git a/time/tzfile.c b/time/tzfile.c
index 9c05811e22..01821cc368 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -648,7 +648,7 @@ __tzfile_compute (__time64_t timer, int use_localtime,
/* Convert to broken down structure. If this fails do not
use the string. */
- if (__glibc_unlikely (__offtime (timer, 0, tp) == NULL))
+ if (__glibc_unlikely (__offtime (timer, 0, 0, tp) == NULL))
goto use_last;
/* Use the rules from the TZ string to compute the change. */
diff --git a/time/tzset.c b/time/tzset.c
index 671394f02d..add0073e0d 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -610,7 +610,7 @@ __tz_convert (__time64_t timer, int use_localtime, struct tm *tp)
&leap_extra_sec, tp);
else
{
- tp = __offtime (timer, 0, tp);
+ tp = __offtime (timer, 0, 0, tp);
if (tp && use_localtime)
__tz_compute (timer, tp);
leap_correction = 0;
@@ -626,7 +626,7 @@ __tz_convert (__time64_t timer, int use_localtime, struct tm *tp)
tp->tm_gmtoff = 0L;
}
- if (__offtime (timer, tp->tm_gmtoff - leap_correction, tp) != NULL)
+ if (__offtime (timer, tp->tm_gmtoff, leap_correction, tp) != NULL)
{
/* This assumes leap seconds can occur only when the local
time offset from UTC is a multiple of 60 seconds,
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 44/59] Handle TZif time type 0 correctly
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (43 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 43/59] Fix overflow checking in offtime Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 45/59] Remove invalid tzset tests from tst-bz29951.c Paul Eggert
` (15 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzfile.c (__tzfile_compute): For time stamps before the
first transition, or for all transitions if no TZ string is
specified, use time type 0 as specified by Internet RFC 9636
section 3.2. Omit an unnecessary loop to set tzname, daylight,
and timezone as that behavior is undocumented, POSIX does not
require it, and it does not make sense in general. Instead, just
set tzname[isdst] as that is all that makes sense (particularly in
light of the time type 0 fix) and that might conceivably help
old-fashioned apps.
---
time/tzfile.c | 88 ++++++++++-----------------------------------------
1 file changed, 17 insertions(+), 71 deletions(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index 01821cc368..dd5973b76d 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -600,40 +600,16 @@ __tzfile_compute (__time64_t timer, int use_localtime,
if (use_localtime)
{
- __tzname[0] = NULL;
- __tzname[1] = NULL;
+ unsigned char ti;
- if (__glibc_unlikely (timecnt == 0 || timer < transitions[0]))
+ if (__glibc_unlikely (timecnt == 0 ? tzspec == NULL
+ : timer < transitions[0]))
{
- /* TIMER is before any transition (or there are no transitions).
- Choose the first non-DST type
- (or the first if they're all DST types). */
- i = 0;
- while (i < typecnt && types[i].dst)
- {
- if (__tzname[1] == NULL)
- __tzname[1] = __tzstring (&zone_names[types[i].idx]);
-
- ++i;
- }
-
- if (i == typecnt)
- i = 0;
- __tzname[0] = __tzstring (&zone_names[types[i].idx]);
- if (__tzname[1] == NULL)
- {
- tzidx j = i;
- while (j < typecnt)
- if (types[j].dst)
- {
- __tzname[1] = __tzstring (&zone_names[types[j].idx]);
- break;
- }
- else
- ++j;
- }
+ /* TIMER is before the first transition; or there are no
+ transitions and no TZ string. Use the first type. */
+ ti = 0;
}
- else if (timer >= transitions[timecnt - 1])
+ else if (timecnt == 0 || timer >= transitions[timecnt - 1])
{
if (__glibc_unlikely (tzspec == NULL))
{
@@ -710,49 +686,19 @@ __tzfile_compute (__time64_t timer, int use_localtime,
i = hi;
found:
- /* assert (timer >= transitions[i - 1]
- && (i == timecnt || timer < transitions[i])); */
- __tzname[types[type_idxs[i - 1]].dst]
- = __tzstring (&zone_names[types[type_idxs[i - 1]].idx]);
-
- for (tzidx j = i; j < timecnt; j++)
- {
- struct ttinfo *ttype = &types[type_idxs[j]];
- unsigned char dst = ttype->dst;
-
- if (__tzname[dst] == NULL)
- {
- __tzname[dst] = __tzstring (&zone_names[ttype->idx]);
-
- if (__tzname[1 - dst] != NULL)
- break;
- }
- }
-
- if (__glibc_unlikely (__tzname[0] == NULL))
- __tzname[0] = __tzname[1];
-
- i = type_idxs[i - 1];
+ ti = type_idxs[i - 1];
}
- struct ttinfo *info = &types[i];
- __daylight = daylight_saved;
- __timezone = -rule_stdoff;
-
- if (__tzname[0] == NULL)
- {
- /* This should only happen if there are no transition rules.
- In this case there should be only one single type. */
- assert (typecnt == 1);
- __tzname[0] = __tzstring (zone_names);
- }
- if (__tzname[1] == NULL)
- /* There is no daylight saving time. */
- __tzname[1] = __tzname[0];
- tp->tm_isdst = info->dst;
- assert (strcmp (&zone_names[info->idx], __tzname[tp->tm_isdst]) == 0);
- tp->tm_zone = __tzname[tp->tm_isdst];
+ struct ttinfo *info = &types[ti];
+ unsigned char dst = info->dst;
+ char *tm_zone = __tzstring (&zone_names[info->idx]);
+ tp->tm_isdst = dst;
tp->tm_gmtoff = info->utoff;
+ tp->tm_zone = tm_zone;
+
+ /* Set tzname[isdst] too; although not required by POSIX or documented,
+ this is closer to what proleptic TZ does. */
+ __tzname[dst] = tm_zone;
}
leap:
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 45/59] Remove invalid tzset tests from tst-bz29951.c
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (44 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 44/59] Handle TZif time type 0 correctly Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 46/59] tzset, not localtime, now parses TZif TZ spec Paul Eggert
` (14 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* timezone/tst-bz29951.c (set_timezone):
Do not test errno after tzset, as its value is unspecified then.
(do_test): Do not test daylight or timezone after tzset, as their
values are unspecified with geographical TZ. See Austin Group
Defect 1816 <https://www.austingroupbugs.net/view.php?id=1816>.
---
timezone/tst-bz29951.c | 8 --------
1 file changed, 8 deletions(-)
diff --git a/timezone/tst-bz29951.c b/timezone/tst-bz29951.c
index ff0eb6ff1e..ee68c5c143 100644
--- a/timezone/tst-bz29951.c
+++ b/timezone/tst-bz29951.c
@@ -26,9 +26,7 @@ static void
set_timezone (const char *name)
{
TEST_VERIFY (setenv ("TZ", name, 1) == 0);
- errno = 0;
tzset ();
- TEST_COMPARE (errno, 0);
}
static int
@@ -44,15 +42,9 @@ do_test (void)
= Fri Oct 25 02:00:00 2013 EET isdst=0 gmtoff=7200
*/
set_timezone ("XT6");
- TEST_VERIFY (daylight != 0);
- TEST_COMPARE (timezone, -7200);
- /* Check that localtime re-initializes the two variables. */
- daylight = timezone = 17;
time_t t = 844034401;
struct tm *tm = localtime (&t);
- TEST_VERIFY (daylight != 0);
- TEST_COMPARE (timezone, -7200);
TEST_COMPARE (tm->tm_year, 96);
TEST_COMPARE (tm->tm_mon, 8);
TEST_COMPARE (tm->tm_mday, 29);
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 46/59] tzset, not localtime, now parses TZif TZ spec
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (45 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 45/59] Remove invalid tzset tests from tst-bz29951.c Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 47/59] Minor compute_change widening tweak Paul Eggert
` (13 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
It's typically faster to parse a TZif file's TZ spec once after TZ
changes instead of parsing it on every call to localtime, as most
slim TZif files' last transitions precede the current time.
* time/tzfile.c (rule_stdoff, daylight_saved):
Remove static vars. All uses removed.
(__tzfile_read): Parse the tzspec here. Simplify the guesswork
for tzname etc.: if a tzspec exists, use its proleptic values as
this is more likely to be what obsolescent code wants; otherwise,
simplify three loops into one.
(__tzfile_compute): Do not parse tzspec, as tzset now does that.
* time/tzset.c (tzset_internal): When TZ hasn't changed,
update external vars only if TZ is proleptic.
(tzset_internal): Do not attempt to migrate __tzfile_read's
settings of tzname etc. into tz_rules as that would mess
up later use by __tzfile_compute of a parsed tzspec.
---
time/tzfile.c | 99 ++++++++++++++++++++-------------------------------
time/tzset.c | 27 +++++---------
2 files changed, 47 insertions(+), 79 deletions(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index dd5973b76d..f86df0c8d5 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -64,15 +64,10 @@ static unsigned char *type_idxs;
static tzidx typecnt;
static struct ttinfo *types;
static char *zone_names;
-static long int rule_stdoff;
static tzidx leapcnt;
static struct leap *leaps;
static char *tzspec;
-/* Used to restore the daylight variable during time conversion, as if
- tzset had been called. */
-static int daylight_saved;
-
#include <endian.h>
#include <byteswap.h>
@@ -516,65 +511,53 @@ __tzfile_read (const char *file)
if (__tzstring (&zone_names[types[i].idx]) == NULL)
goto ret_free_transitions;
- /* Find the standard and daylight time offsets used by the rule file.
- We choose the offsets in the types of each flavor that are
- transitioned to earliest in time. */
- __tzname[0] = NULL;
- __tzname[1] = NULL;
- for (i = timecnt; i > 0; i--)
- {
- struct ttinfo *ttype = &types[type_idxs[i - 1]];
- unsigned char dst = ttype->dst;
-
- if (__tzname[dst] == NULL)
- {
- __tzname[dst] = __tzstring (&zone_names[ttype->idx]);
+ /* POSIX does not say what daylight, timezone and tzname should be;
+ try to set them to something reasonable. */
- if (__tzname[1 - dst] != NULL)
- break;
- }
- }
- if (__tzname[0] == NULL)
+ if (tzspec != NULL)
{
- /* This should only happen if there are no transition rules.
- In this case there's usually only one single type, unless
- e.g. the data file has a truncated time-range. */
- __tzname[0] = __tzstring (zone_names);
+ /* The usual case with tzspec; just use its values. */
+ if (__glibc_unlikely (! __tzset_parse_tz (tzspec)))
+ goto ret_free_transitions;
}
- if (__tzname[1] == NULL)
- __tzname[1] = __tzname[0];
-
- daylight_saved = 0;
- if (timecnt == 0)
- /* Use the first rule (which should also be the only one). */
- rule_stdoff = types[0].utoff;
else
{
- rule_stdoff = 0;
-
- /* Search for the last rule with a standard time offset. This
- will be used for the global timezone variable. */
- i = timecnt - 1;
- do
- if (!types[type_idxs[i]].dst)
- {
- rule_stdoff = types[type_idxs[i]].utoff;
+ /* The unusual case without tzspec; infer from transitions.
+ Default to the first type's offset and isdst. */
+ __timezone = -types[0].utoff;
+ __daylight = types[0].dst;
+
+ /* Find the abbr and offset for the last transition to standard time,
+ and the abbr for the last transition to DST. */
+ __tzname[0] = NULL;
+ __tzname[1] = NULL;
+ for (i = timecnt; i > 0; i--)
+ {
+ struct ttinfo *ttype = &types[type_idxs[i - 1]];
+ unsigned char dst = ttype->dst;
+
+ if (__tzname[dst] == NULL)
+ {
+ __tzname[dst] = __tzstring (&zone_names[ttype->idx]);
+ if (!dst)
+ __timezone = -ttype->utoff;
+ }
+
+ if (__tzname[1 - dst] != NULL)
break;
- }
- else
- daylight_saved = 1;
- while (i-- > 0);
-
- /* Keep searching to see if there is a DST rule. This
- information will be used to set the global daylight
- variable. */
- while (i-- > 0 && !daylight_saved)
- daylight_saved = types[type_idxs[i]].dst;
+ }
+ /* With no appropriate transitions, default standard time's
+ abbreviation to that of the first type, and default DST's
+ abbreviation to that of standard time. Also, set 'daylight'
+ if a DST transition was found. */
+ if (__tzname[0] == NULL)
+ __tzname[0] = __tzstring (&zone_names[types[0].idx]);
+ if (__tzname[1] == NULL)
+ __tzname[1] = __tzname[0];
+ else
+ __daylight = 1;
}
- __daylight = daylight_saved;
- __timezone = -rule_stdoff;
-
/* Remember the inode and device number and modification time. */
tzfile_dev = st.st_dev;
tzfile_ino = st.st_ino;
@@ -618,10 +601,6 @@ __tzfile_compute (__time64_t timer, int use_localtime,
goto found;
}
- /* Parse the proleptic TZ string. If this fails do not use it. */
- if (__glibc_unlikely (! __tzset_parse_tz (tzspec)))
- goto use_last;
-
/* Convert to broken down structure. If this fails do not
use the string. */
if (__glibc_unlikely (__offtime (timer, 0, 0, tp) == NULL))
diff --git a/time/tzset.c b/time/tzset.c
index add0073e0d..b5bbd05573 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -412,15 +412,18 @@ tzset_internal (bool always)
tz = TZDEFAULT;
/* A leading colon means "implementation defined syntax".
- We ignore the colon and always use the same algorithm:
+ Ignore the colon and always use the same algorithm:
try a data file, and if none exists parse the 1003.1 syntax. */
if (*tz == ':')
++tz;
- /* If the value changed has not since the last run,
- simply update internal vars. */
+ /* If the value has not changed since the last run,
+ simply update external vars if POSIX requires it. */
if (old_tz != NULL && strcmp (tz, old_tz) == 0)
- update_vars ();
+ {
+ if (!use_tzfile)
+ update_vars ();
+ }
else
{
/* Save the value of 'tz'. */
@@ -429,21 +432,7 @@ tzset_internal (bool always)
/* Try to read a data file. */
use_tzfile = __tzfile_read (tz);
- if (use_tzfile)
- {
- /* Save equivalent of 'daylight' for later use by update_vars.
- Although the other external variables have unspecified values
- and so need not be saved in the usual case, save them anyway,
- as POSIX requires this for rare file-backed proleptic
- TZ strings like "EST5EDT", and it is more likely to match user
- expectations for geographical TZ strings. */
- enum tz_rule_type some_DST = J0; /* anything but NO_DST */
- tz_rules[1].type = NO_DST + __daylight * (some_DST - NO_DST);
- tz_rules[0].offset = -__timezone;
- tz_rules[0].name = __tzname[0];
- tz_rules[1].name = __tzname[1];
- }
- else
+ if (!use_tzfile)
{
/* No data file found. Default to UTC without leap seconds if
TZDEFAULT is broken. */
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 47/59] Minor compute_change widening tweak
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (46 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 46/59] tzset, not localtime, now parses TZif TZ spec Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 48/59] Minor SECSPERDAY " Paul Eggert
` (12 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzset.c (compute_change): Subtract before widening.
This can't overflow.
---
time/tzset.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/time/tzset.c b/time/tzset.c
index b5bbd05573..f3af960620 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -533,7 +533,7 @@ compute_change (tz_rule *rule, long long int year)
/* T is now the Epoch-relative time of 00:00:00 UT on the day we want.
Just subtract the UT offset and add the time of day, and we're done. */
- rule->change = t - rule->offset + rule->secs;
+ rule->change = t - (rule->offset - rule->secs);
rule->computed_for = year;
}
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 48/59] Minor SECSPERDAY widening tweak
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (47 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 47/59] Minor compute_change widening tweak Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 49/59] Remove leap-second goto from __tzfile_compute Paul Eggert
` (11 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzset.c (SECSPERDAY): Now an enum, not a macro.
There's no need to widen it to __time64_t, and keeping it
an enum can help the compiler do cheaper multiplication.
---
time/tzset.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/time/tzset.c b/time/tzset.c
index f3af960620..cc64516e37 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -27,7 +27,7 @@
#include <timezone/tzfile.h>
-#define SECSPERDAY ((__time64_t) 86400)
+enum { SECSPERDAY = 24 * 60 * 60 };
char *__tzname[2] = { (char *) "UTC", (char *) "UTC" };
int __daylight = 0;
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 49/59] Remove leap-second goto from __tzfile_compute
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (48 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 48/59] Minor SECSPERDAY " Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 50/59] Remove use_last label " Paul Eggert
` (10 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzfile.c (__tzfile_compute): Do leap second computations
first, to avoid the need for a goto and a label.
---
time/tzfile.c | 40 +++++++++++++++++++++-------------------
1 file changed, 21 insertions(+), 19 deletions(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index f86df0c8d5..e491df7d9f 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -581,6 +581,26 @@ __tzfile_compute (__time64_t timer, int use_localtime,
{
tzidx i;
+ /* Find the last leap second correction transition time before TIMER. */
+ int corr = 0;
+ bool hit = false;
+ for (i = leapcnt; 0 < i; )
+ {
+ i--;
+ if (leaps[i].occur <= timer)
+ {
+ /* Apply its correction. */
+ corr = leaps[i].corr;
+
+ /* A hit if exactly at a positive leap second. */
+ hit = (timer == leaps[i].occur
+ && (i == 0 ? 0 : leaps[i - 1].corr) < leaps[i].corr);
+ break;
+ }
+ }
+ *leap_correct = corr;
+ *leap_hit = hit;
+
if (use_localtime)
{
unsigned char ti;
@@ -609,7 +629,7 @@ __tzfile_compute (__time64_t timer, int use_localtime,
/* Use the rules from the TZ string to compute the change. */
__tz_compute (timer, tp);
- goto leap;
+ return;
}
else
{
@@ -679,24 +699,6 @@ __tzfile_compute (__time64_t timer, int use_localtime,
this is closer to what proleptic TZ does. */
__tzname[dst] = tm_zone;
}
-
- leap:
- *leap_correct = 0;
- *leap_hit = false;
-
- /* Find the last leap second correction transition time before TIMER. */
- i = leapcnt;
- do
- if (i-- == 0)
- return;
- while (timer < leaps[i].occur);
-
- /* Apply its correction. */
- *leap_correct = leaps[i].corr;
-
- if (timer == leaps[i].occur /* Exactly at the transition time. */
- && (leaps[i].corr > (i == 0 ? 0 : leaps[i - 1].corr)))
- *leap_hit = true;
}
weak_alias (transitions, __libc_tzfile_freemem_ptr)
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 50/59] Remove use_last label from __tzfile_compute
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (49 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 49/59] Remove leap-second goto from __tzfile_compute Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 51/59] Refactor ‘if’ in __tzfile_compute Paul Eggert
` (9 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzfile.c (__tzfile_compute): Redo last-transition check
so that there is no need for the use_last label or goto.
---
time/tzfile.c | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index e491df7d9f..8b5faad424 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -614,18 +614,16 @@ __tzfile_compute (__time64_t timer, int use_localtime,
}
else if (timecnt == 0 || timer >= transitions[timecnt - 1])
{
- if (__glibc_unlikely (tzspec == NULL))
+ /* TIMER is after the last transition. Do not use the TZ
+ string if it is absent or if we cannot convert to the
+ broken down structure. */
+ if (__glibc_unlikely (tzspec == NULL)
+ || __glibc_unlikely (__offtime (timer, 0, 0, tp) == NULL))
{
- use_last:
i = timecnt;
goto found;
}
- /* Convert to broken down structure. If this fails do not
- use the string. */
- if (__glibc_unlikely (__offtime (timer, 0, 0, tp) == NULL))
- goto use_last;
-
/* Use the rules from the TZ string to compute the change. */
__tz_compute (timer, tp);
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 51/59] Refactor ‘if’ in __tzfile_compute
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (50 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 50/59] Remove use_last label " Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 52/59] Refactor localtime " Paul Eggert
` (8 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzfile.c (__tzfile_compute): Negate the sense of an ‘if’ to
simplify future changes.
---
time/tzfile.c | 20 +++++++++-----------
1 file changed, 9 insertions(+), 11 deletions(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index 8b5faad424..37b194df16 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -614,20 +614,18 @@ __tzfile_compute (__time64_t timer, int use_localtime,
}
else if (timecnt == 0 || timer >= transitions[timecnt - 1])
{
- /* TIMER is after the last transition. Do not use the TZ
- string if it is absent or if we cannot convert to the
- broken down structure. */
- if (__glibc_unlikely (tzspec == NULL)
- || __glibc_unlikely (__offtime (timer, 0, 0, tp) == NULL))
+ /* TIMER is after the last transition. Use the TZ string if
+ it is present and we can convert to the broken down structure. */
+ if (__glibc_likely (tzspec != NULL)
+ && __glibc_likely (__offtime (timer, 0, 0, tp) != NULL))
{
- i = timecnt;
- goto found;
+ /* Use the rules from the TZ string to compute the change. */
+ __tz_compute (timer, tp);
+ return;
}
- /* Use the rules from the TZ string to compute the change. */
- __tz_compute (timer, tp);
-
- return;
+ i = timecnt;
+ goto found;
}
else
{
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 52/59] Refactor localtime ‘if’ in __tzfile_compute
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (51 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 51/59] Refactor ‘if’ in __tzfile_compute Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 53/59] Refactor post-transition-table ‘if’ Paul Eggert
` (7 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzfile.c (__tzfile_compute): Negate the sense of another
‘if’ to simplify future changes. Almost all of this is
indenting changes.
---
time/tzfile.c | 158 +++++++++++++++++++++++++-------------------------
1 file changed, 79 insertions(+), 79 deletions(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index 37b194df16..783518cd6d 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -601,100 +601,100 @@ __tzfile_compute (__time64_t timer, int use_localtime,
*leap_correct = corr;
*leap_hit = hit;
- if (use_localtime)
- {
- unsigned char ti;
+ if (!use_localtime)
+ return;
+
+ unsigned char ti;
- if (__glibc_unlikely (timecnt == 0 ? tzspec == NULL
- : timer < transitions[0]))
+ if (__glibc_unlikely (timecnt == 0 ? tzspec == NULL
+ : timer < transitions[0]))
+ {
+ /* TIMER is before the first transition; or there are no
+ transitions and no TZ string. Use the first type. */
+ ti = 0;
+ }
+ else if (timecnt == 0 || timer >= transitions[timecnt - 1])
+ {
+ /* TIMER is after the last transition. Use the TZ string if
+ it is present and we can convert to the broken down structure. */
+ if (__glibc_likely (tzspec != NULL)
+ && __glibc_likely (__offtime (timer, 0, 0, tp) != NULL))
{
- /* TIMER is before the first transition; or there are no
- transitions and no TZ string. Use the first type. */
- ti = 0;
+ /* Use the rules from the TZ string to compute the change. */
+ __tz_compute (timer, tp);
+ return;
}
- else if (timecnt == 0 || timer >= transitions[timecnt - 1])
- {
- /* TIMER is after the last transition. Use the TZ string if
- it is present and we can convert to the broken down structure. */
- if (__glibc_likely (tzspec != NULL)
- && __glibc_likely (__offtime (timer, 0, 0, tp) != NULL))
- {
- /* Use the rules from the TZ string to compute the change. */
- __tz_compute (timer, tp);
- return;
- }
- i = timecnt;
- goto found;
- }
- else
+ i = timecnt;
+ goto found;
+ }
+ else
+ {
+ /* Find the first transition after TIMER, and
+ then pick the type of the transition before it. */
+ tzidx lo = 0;
+ tzidx hi = timecnt - 1;
+ /* Assume that DST is changing twice a year and guess
+ initial search spot from it. Half of a gregorian year
+ has on average 365.2425 * 86400 / 2 = 15778476 seconds.
+ Although i's value can be wrong if overflow occurs,
+ this is harmless because it is just a guess. */
+ __time64_t tdiff;
+ ckd_sub (&tdiff, transitions[timecnt - 1], timer);
+ ckd_add (&i, tdiff / 15778476, 0);
+ if (i < timecnt)
{
- /* Find the first transition after TIMER, and
- then pick the type of the transition before it. */
- tzidx lo = 0;
- tzidx hi = timecnt - 1;
- /* Assume that DST is changing twice a year and guess
- initial search spot from it. Half of a gregorian year
- has on average 365.2425 * 86400 / 2 = 15778476 seconds.
- Although i's value can be wrong if overflow occurs,
- this is harmless because it is just a guess. */
- __time64_t tdiff;
- ckd_sub (&tdiff, transitions[timecnt - 1], timer);
- ckd_add (&i, tdiff / 15778476, 0);
- if (i < timecnt)
+ i = timecnt - 1 - i;
+ if (timer < transitions[i])
{
- i = timecnt - 1 - i;
- if (timer < transitions[i])
- {
- if (i < 10 || timer >= transitions[i - 10])
- {
- /* Linear search. */
- while (timer < transitions[i - 1])
- --i;
- goto found;
- }
- hi = i - 10;
- }
- else
+ if (i < 10 || timer >= transitions[i - 10])
{
- if (timecnt - i <= 10 || timer < transitions[i + 10])
- {
- /* Linear search. */
- while (timer >= transitions[i])
- ++i;
- goto found;
- }
- lo = i + 10;
+ /* Linear search. */
+ while (timer < transitions[i - 1])
+ --i;
+ goto found;
}
+ hi = i - 10;
}
-
- /* Binary search. */
- /* assert (timer >= transitions[lo] && timer < transitions[hi]); */
- while (lo + 1 < hi)
+ else
{
- i = (lo >> 1) + (hi >> 1) + (lo & hi & 1);
- if (timer < transitions[i])
- hi = i;
- else
- lo = i;
+ if (timecnt - i <= 10 || timer < transitions[i + 10])
+ {
+ /* Linear search. */
+ while (timer >= transitions[i])
+ ++i;
+ goto found;
+ }
+ lo = i + 10;
}
- i = hi;
-
- found:
- ti = type_idxs[i - 1];
}
- struct ttinfo *info = &types[ti];
- unsigned char dst = info->dst;
- char *tm_zone = __tzstring (&zone_names[info->idx]);
- tp->tm_isdst = dst;
- tp->tm_gmtoff = info->utoff;
- tp->tm_zone = tm_zone;
+ /* Binary search. */
+ /* assert (timer >= transitions[lo] && timer < transitions[hi]); */
+ while (lo + 1 < hi)
+ {
+ i = (lo >> 1) + (hi >> 1) + (lo & hi & 1);
+ if (timer < transitions[i])
+ hi = i;
+ else
+ lo = i;
+ }
+ i = hi;
- /* Set tzname[isdst] too; although not required by POSIX or documented,
- this is closer to what proleptic TZ does. */
- __tzname[dst] = tm_zone;
+ found:
+ ti = type_idxs[i - 1];
}
+
+ struct ttinfo *info = &types[ti];
+ unsigned char dst = info->dst;
+ char *tm_zone = __tzstring (&zone_names[info->idx]);
+ tp->tm_isdst = dst;
+ tp->tm_gmtoff = info->utoff;
+ tp->tm_zone = tm_zone;
+
+ /* Set tzname[isdst] too; although not required by POSIX or documented,
+ this is closer to what proleptic TZ does. */
+ __tzname[dst] = tm_zone;
}
weak_alias (transitions, __libc_tzfile_freemem_ptr)
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 53/59] Refactor post-transition-table ‘if’
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (52 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 52/59] Refactor localtime " Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 54/59] Refactor __tzfile_compute to avoid two gotos Paul Eggert
` (6 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzfile.c (__tzfile_compute): Reorder code and ‘if’
to remove a goto, for clarity. Again, mostly indenting changes.
---
time/tzfile.c | 112 +++++++++++++++++++++++++-------------------------
1 file changed, 57 insertions(+), 55 deletions(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index 783518cd6d..cde8d74b6f 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -613,73 +613,75 @@ __tzfile_compute (__time64_t timer, int use_localtime,
transitions and no TZ string. Use the first type. */
ti = 0;
}
- else if (timecnt == 0 || timer >= transitions[timecnt - 1])
+ else
{
- /* TIMER is after the last transition. Use the TZ string if
- it is present and we can convert to the broken down structure. */
- if (__glibc_likely (tzspec != NULL)
- && __glibc_likely (__offtime (timer, 0, 0, tp) != NULL))
+ if (timecnt == 0 || timer >= transitions[timecnt - 1])
{
- /* Use the rules from the TZ string to compute the change. */
- __tz_compute (timer, tp);
- return;
- }
+ /* TIMER is after the last transition. Use the TZ string if
+ it is present and we can convert to the broken down structure. */
+ if (__glibc_likely (tzspec != NULL)
+ && __glibc_likely (__offtime (timer, 0, 0, tp) != NULL))
+ {
+ /* Use the rules from the TZ string to compute the change. */
+ __tz_compute (timer, tp);
+ return;
+ }
- i = timecnt;
- goto found;
- }
- else
- {
- /* Find the first transition after TIMER, and
- then pick the type of the transition before it. */
- tzidx lo = 0;
- tzidx hi = timecnt - 1;
- /* Assume that DST is changing twice a year and guess
- initial search spot from it. Half of a gregorian year
- has on average 365.2425 * 86400 / 2 = 15778476 seconds.
- Although i's value can be wrong if overflow occurs,
- this is harmless because it is just a guess. */
- __time64_t tdiff;
- ckd_sub (&tdiff, transitions[timecnt - 1], timer);
- ckd_add (&i, tdiff / 15778476, 0);
- if (i < timecnt)
+ i = timecnt;
+ }
+ else
{
- i = timecnt - 1 - i;
- if (timer < transitions[i])
+ /* Find the first transition after TIMER, and
+ then pick the type of the transition before it. */
+ tzidx lo = 0;
+ tzidx hi = timecnt - 1;
+ /* Assume that DST is changing twice a year and guess
+ initial search spot from it. Half of a gregorian year
+ has on average 365.2425 * 86400 / 2 = 15778476 seconds.
+ Although i's value can be wrong if overflow occurs,
+ this is harmless because it is just a guess. */
+ __time64_t tdiff;
+ ckd_sub (&tdiff, transitions[timecnt - 1], timer);
+ ckd_add (&i, tdiff / 15778476, 0);
+ if (i < timecnt)
{
- if (i < 10 || timer >= transitions[i - 10])
+ i = timecnt - 1 - i;
+ if (timer < transitions[i])
{
- /* Linear search. */
- while (timer < transitions[i - 1])
- --i;
- goto found;
+ if (i < 10 || timer >= transitions[i - 10])
+ {
+ /* Linear search. */
+ while (timer < transitions[i - 1])
+ --i;
+ goto found;
+ }
+ hi = i - 10;
}
- hi = i - 10;
- }
- else
- {
- if (timecnt - i <= 10 || timer < transitions[i + 10])
+ else
{
- /* Linear search. */
- while (timer >= transitions[i])
- ++i;
- goto found;
+ if (timecnt - i <= 10 || timer < transitions[i + 10])
+ {
+ /* Linear search. */
+ while (timer >= transitions[i])
+ ++i;
+ goto found;
+ }
+ lo = i + 10;
}
- lo = i + 10;
}
- }
- /* Binary search. */
- /* assert (timer >= transitions[lo] && timer < transitions[hi]); */
- while (lo + 1 < hi)
- {
- i = (lo >> 1) + (hi >> 1) + (lo & hi & 1);
- if (timer < transitions[i])
- hi = i;
- else
- lo = i;
+ /* Binary search. */
+ /* assert (timer >= transitions[lo] && timer < transitions[hi]); */
+ while (lo + 1 < hi)
+ {
+ i = (lo >> 1) + (hi >> 1) + (lo & hi & 1);
+ if (timer < transitions[i])
+ hi = i;
+ else
+ lo = i;
+ }
+ i = hi;
}
- i = hi;
found:
ti = type_idxs[i - 1];
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 54/59] Refactor __tzfile_compute to avoid two gotos
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (53 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 53/59] Refactor post-transition-table ‘if’ Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 55/59] Refactor __tzfile_compute index local Paul Eggert
` (5 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzfile.c (first_transition_after): New function.
(__tzfile_compute): Use it to avoid the need for two gotos.
---
time/tzfile.c | 129 +++++++++++++++++++++++++++-----------------------
1 file changed, 69 insertions(+), 60 deletions(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index cde8d74b6f..0ee70fd356 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -574,6 +574,70 @@ __tzfile_read (const char *file)
return false;
}
\f
+/* Find index of first transition after TIMER.
+ If there are no transitions after TIMER, return the number of transitions.
+ TIMER must not be less than the first transition (if any). */
+
+static tzidx
+first_transition_after (__time64_t timer)
+{
+ if (timecnt == 0 || timer >= transitions[timecnt - 1])
+ return timecnt;
+
+ tzidx lo = 0;
+ tzidx hi = timecnt - 1;
+
+ /* Find the last transition on or before TIMER.
+ Assume that DST is changing twice a year and guess
+ initial search spot from it. Half of a gregorian year
+ has on average 365.2425 * 86400 / 2 = 15778476 seconds.
+ Although i's value can be wrong if overflow occurs,
+ this is harmless because it is just a guess. */
+
+ __time64_t tdiff;
+ tzidx i;
+ ckd_sub (&tdiff, transitions[timecnt - 1], timer);
+ ckd_add (&i, tdiff / 15778476, 0);
+ if (i < timecnt)
+ {
+ i = timecnt - 1 - i;
+ if (timer < transitions[i])
+ {
+ if (i < 10 || timer >= transitions[i - 10])
+ {
+ /* Linear search. */
+ while (timer < transitions[i - 1])
+ --i;
+ return i;
+ }
+ hi = i - 10;
+ }
+ else
+ {
+ if (timecnt - i <= 10 || timer < transitions[i + 10])
+ {
+ /* Linear search. */
+ while (timer >= transitions[i])
+ ++i;
+ return i;
+ }
+ lo = i + 10;
+ }
+ }
+
+ /* Binary search. */
+ /* assert (timer >= transitions[lo] && timer < transitions[hi]); */
+ while (lo + 1 < hi)
+ {
+ i = (lo >> 1) + (hi >> 1) + (lo & hi & 1);
+ if (timer < transitions[i])
+ hi = i;
+ else
+ lo = i;
+ }
+ return hi;
+}
+
void
__tzfile_compute (__time64_t timer, int use_localtime,
int *leap_correct, bool *leap_hit,
@@ -615,10 +679,12 @@ __tzfile_compute (__time64_t timer, int use_localtime,
}
else
{
- if (timecnt == 0 || timer >= transitions[timecnt - 1])
+ i = first_transition_after (timer);
+ if (i == timecnt)
{
- /* TIMER is after the last transition. Use the TZ string if
- it is present and we can convert to the broken down structure. */
+ /* TIMER is after the last transition, or there are no transitions
+ and a TZ string is present. Use the TZ string if it is present
+ and if we can convert to the broken down structure. */
if (__glibc_likely (tzspec != NULL)
&& __glibc_likely (__offtime (timer, 0, 0, tp) != NULL))
{
@@ -626,64 +692,7 @@ __tzfile_compute (__time64_t timer, int use_localtime,
__tz_compute (timer, tp);
return;
}
-
- i = timecnt;
- }
- else
- {
- /* Find the first transition after TIMER, and
- then pick the type of the transition before it. */
- tzidx lo = 0;
- tzidx hi = timecnt - 1;
- /* Assume that DST is changing twice a year and guess
- initial search spot from it. Half of a gregorian year
- has on average 365.2425 * 86400 / 2 = 15778476 seconds.
- Although i's value can be wrong if overflow occurs,
- this is harmless because it is just a guess. */
- __time64_t tdiff;
- ckd_sub (&tdiff, transitions[timecnt - 1], timer);
- ckd_add (&i, tdiff / 15778476, 0);
- if (i < timecnt)
- {
- i = timecnt - 1 - i;
- if (timer < transitions[i])
- {
- if (i < 10 || timer >= transitions[i - 10])
- {
- /* Linear search. */
- while (timer < transitions[i - 1])
- --i;
- goto found;
- }
- hi = i - 10;
- }
- else
- {
- if (timecnt - i <= 10 || timer < transitions[i + 10])
- {
- /* Linear search. */
- while (timer >= transitions[i])
- ++i;
- goto found;
- }
- lo = i + 10;
- }
- }
-
- /* Binary search. */
- /* assert (timer >= transitions[lo] && timer < transitions[hi]); */
- while (lo + 1 < hi)
- {
- i = (lo >> 1) + (hi >> 1) + (lo & hi & 1);
- if (timer < transitions[i])
- hi = i;
- else
- lo = i;
- }
- i = hi;
}
-
- found:
ti = type_idxs[i - 1];
}
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 55/59] Refactor __tzfile_compute index local
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (54 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 54/59] Refactor __tzfile_compute to avoid two gotos Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 56/59] mktime: prefer bool to int Paul Eggert
` (4 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* time/tzfile.c (__tzfile_compute): Avoid portmanteau index local.
---
time/tzfile.c | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/time/tzfile.c b/time/tzfile.c
index 0ee70fd356..bed62d877f 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -643,12 +643,10 @@ __tzfile_compute (__time64_t timer, int use_localtime,
int *leap_correct, bool *leap_hit,
struct tm *tp)
{
- tzidx i;
-
/* Find the last leap second correction transition time before TIMER. */
int corr = 0;
bool hit = false;
- for (i = leapcnt; 0 < i; )
+ for (tzidx i = leapcnt; 0 < i; )
{
i--;
if (leaps[i].occur <= timer)
@@ -679,7 +677,7 @@ __tzfile_compute (__time64_t timer, int use_localtime,
}
else
{
- i = first_transition_after (timer);
+ tzidx i = first_transition_after (timer);
if (i == timecnt)
{
/* TIMER is after the last transition, or there are no transitions
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 56/59] mktime: prefer bool to int
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (55 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 55/59] Refactor __tzfile_compute index local Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 57/59] mktime: port __mktime64 locking to Gnulib Paul Eggert
` (3 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* lib/mktime.c (__mktime_internal): Use bool for a boolean local.
---
time/mktime.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/time/mktime.c b/time/mktime.c
index 0981cd1d1d..fca0b62d76 100644
--- a/time/mktime.c
+++ b/time/mktime.c
@@ -359,8 +359,8 @@ __mktime_internal (struct tm *tp, bool local, mktime_offset_t *offset)
/* Ignore any tm_isdst request for timegm. */
int isdst = local ? tp->tm_isdst : 0;
- /* 1 if the previous probe was DST. */
- int dst2 = 0;
+ /* True if the previous probe was DST. */
+ bool dst2 = false;
/* Ensure that mon is in range, and set year accordingly. */
int mon_remainder = mon % 12;
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 57/59] mktime: port __mktime64 locking to Gnulib
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (56 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 56/59] mktime: prefer bool to int Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 58/59] mktime: improve tm_isdst heuristic Paul Eggert
` (2 subsequent siblings)
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* lib/mktime.c (__mktime64) [!_LIBC && NEED_MKTIME_WORKING]:
Don't assume glibc. This affects only Gnulib.
---
time/mktime.c | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/time/mktime.c b/time/mktime.c
index fca0b62d76..c95805c37c 100644
--- a/time/mktime.c
+++ b/time/mktime.c
@@ -101,8 +101,8 @@ my_tzset (void)
tzset ();
# endif
}
-# undef __tzset
-# define __tzset() my_tzset ()
+# undef tzset
+# define tzset() my_tzset ()
#endif
#if defined _LIBC || NEED_MKTIME_WORKING || NEED_MKTIME_INTERNAL
@@ -554,18 +554,19 @@ __mktime_internal (struct tm *tp, bool local, mktime_offset_t *offset)
__time64_t
__mktime64 (struct tm *tp)
{
-# if defined _LIBC || NEED_MKTIME_WORKING
__libc_lock_lock (__tzset_lock);
__tzset_unlocked ();
+
+# if defined _LIBC || NEED_MKTIME_WORKING
static mktime_offset_t localtime_offset;
__time64_t result = __mktime_internal (tp, true, &localtime_offset);
- __libc_lock_unlock (__tzset_lock);
- return result;
# else
- __tzset ();
# undef mktime
- return mktime (tp);
+ __time64_t result = mktime (tp);
# endif
+
+ __libc_lock_unlock (__tzset_lock);
+ return result;
}
#endif /* _LIBC || NEED_MKTIME_WORKING || NEED_MKTIME_WINDOWS */
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 58/59] mktime: improve tm_isdst heuristic
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (57 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 57/59] mktime: port __mktime64 locking to Gnulib Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-05 5:57 ` [PATCH 59/59] time: test DST adjustment for DST-less zones Paul Eggert
2025-01-07 8:59 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Florian Weimer
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Paul Eggert
* lib/mktime.c (__mktime_internal): When tm_isdst disagrees with
what was requested, search at most a year (+ stride) from the
requested time for a matching tm_isdst, and ignore the request if
the search fails. This is more likely to match user expectations
in timezones like Asia/Kolkata.
Problem reported by Florian Weimer in:
https://sourceware.org/pipermail/libc-alpha/2025-January/163342.html
---
time/mktime.c | 38 ++++++++++++--------------------------
1 file changed, 12 insertions(+), 26 deletions(-)
diff --git a/time/mktime.c b/time/mktime.c
index c95805c37c..4218fca69b 100644
--- a/time/mktime.c
+++ b/time/mktime.c
@@ -355,9 +355,7 @@ __mktime_internal (struct tm *tp, bool local, mktime_offset_t *offset)
int mday = tp->tm_mday;
int mon = tp->tm_mon;
int year_requested = tp->tm_year;
-
- /* Ignore any tm_isdst request for timegm. */
- int isdst = local ? tp->tm_isdst : 0;
+ int isdst = tp->tm_isdst;
/* True if the previous probe was DST. */
bool dst2 = false;
@@ -449,13 +447,10 @@ __mktime_internal (struct tm *tp, bool local, mktime_offset_t *offset)
Heuristic: probe the adjacent timestamps in both directions,
looking for the desired isdst. If none is found within a
- reasonable duration bound, assume a one-hour DST difference.
+ reasonable duration bound, ignore the disagreement.
This should work for all real time zone histories in the tz
database. */
- /* +1 if we wanted standard time but got DST, -1 if the reverse. */
- int dst_difference = (isdst == 0) - (tm.tm_isdst == 0);
-
/* Distance between probes when looking for a DST boundary. In
tzdata2003a, the shortest period of DST is 601200 seconds
(e.g., America/Recife starting 2000-10-08 01:00), and the
@@ -465,21 +460,17 @@ __mktime_internal (struct tm *tp, bool local, mktime_offset_t *offset)
periods when probing. */
int stride = 601200;
- /* In TZDB 2021e, the longest period of DST (or of non-DST), in
- which the DST (or adjacent DST) difference is not one hour,
- is 457243209 seconds: e.g., America/Cambridge_Bay with leap
- seconds, starting 1965-10-31 00:00 in a switch from
- double-daylight time (-05) to standard time (-07), and
- continuing to 1980-04-27 02:00 in a switch from standard time
- (-07) to daylight time (-06). */
- int duration_max = 457243209;
-
- /* Search in both directions, so the maximum distance is half
- the duration; add the stride to avoid off-by-1 problems. */
- int delta_bound = duration_max / 2 + stride;
+ /* Do not probe too far away from the requested time,
+ by striding until at least a year has passed, but then giving up.
+ This helps avoid unexpected results in (for example) Asia/Kolkata,
+ for which today's users expect to see no DST even though it
+ did observe DST long ago. */
+ int year_seconds_bound = 366 * 24 * 60 * 60 + 1;
+ int delta_bound = year_seconds_bound + stride;
int delta, direction;
+ /* Search in both directions, closest first. */
for (delta = stride; delta < delta_bound; delta += stride)
for (direction = -1; direction <= 1; direction += 2)
{
@@ -509,13 +500,8 @@ __mktime_internal (struct tm *tp, bool local, mktime_offset_t *offset)
}
}
- /* No unusual DST offset was found nearby. Assume one-hour DST. */
- t += 60 * 60 * dst_difference;
- if (mktime_min <= t && t <= mktime_max && __tz_convert (t, local, &tm))
- goto offset_found;
-
- __set_errno (EOVERFLOW);
- return -1;
+ /* No probe with the requested tm_isdst was found nearby.
+ Ignore the requested tm_isdst. */
}
offset_found:
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* [PATCH 59/59] time: test DST adjustment for DST-less zones
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (58 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 58/59] mktime: improve tm_isdst heuristic Paul Eggert
@ 2025-01-05 5:57 ` Paul Eggert
2025-01-07 8:59 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Florian Weimer
60 siblings, 0 replies; 72+ messages in thread
From: Paul Eggert @ 2025-01-05 5:57 UTC (permalink / raw)
To: libc-alpha; +Cc: Florian Weimer
From: Florian Weimer <fweimer@redhat.com>
For zones such as UTC, it does not really make sense to perform
the forced adjustment.
The timezone/testdata/IST file is the compiled Asia/Kolkata zone
from tz 2024a.
---
time/Makefile | 1 +
time/tst-mktime-dst-adjust.c | 156 +++++++++++++++++++++++++++++++++++
timezone/testdata/IST | Bin 0 -> 285 bytes
3 files changed, 157 insertions(+)
create mode 100644 time/tst-mktime-dst-adjust.c
create mode 100644 timezone/testdata/IST
diff --git a/time/Makefile b/time/Makefile
index 3e010d2c15..029322ebd7 100644
--- a/time/Makefile
+++ b/time/Makefile
@@ -66,6 +66,7 @@ tests := \
tst-gmtime \
tst-itimer \
tst-mktime \
+ tst-mktime-dst-adjust \
tst-mktime2 \
tst-mktime3 \
tst-mktime4 \
diff --git a/time/tst-mktime-dst-adjust.c b/time/tst-mktime-dst-adjust.c
new file mode 100644
index 0000000000..416ef2abcd
--- /dev/null
+++ b/time/tst-mktime-dst-adjust.c
@@ -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>
diff --git a/timezone/testdata/IST b/timezone/testdata/IST
new file mode 100644
index 0000000000000000000000000000000000000000..0014046d29a38e9b8006f746fea794d7f71eb479
GIT binary patch
literal 285
zcmWHE%1kq2zzf)bvMfL>)Bq&f=kD2c>UNLD8P-CHGgFOLTq+To!N|l6gbWNpH-HKl
zyxl;meIpn+7#N~67<qhrLl}I4*fThULEFI0*nlB~u$}BcgIIxB>^~5w?UK*{(az6b
z8-Qq#8$dM39UvOy7BCHT4~T}kiG`Vk8Rn><3m``Uod$Fi&}lqirwM?Z=7HfnE}%<w
JO|6U#xBzzAN{avh
literal 0
HcmV?d00001
--
2.45.2
^ permalink raw reply [flat|nested] 72+ messages in thread
* Re: [PATCH v2 0/4] mktime tm_isdst compatibility improvements
2025-01-05 2:58 ` [PATCH v2 0/4] mktime tm_isdst compatibility improvements Paul Eggert
` (59 preceding siblings ...)
2025-01-05 5:57 ` [PATCH 59/59] time: test DST adjustment for DST-less zones Paul Eggert
@ 2025-01-07 8:59 ` Florian Weimer
60 siblings, 0 replies; 72+ messages in thread
From: Florian Weimer @ 2025-01-07 8:59 UTC (permalink / raw)
To: Paul Eggert; +Cc: libc-alpha
* Paul Eggert:
> On 2025-01-02 09:42, Florian Weimer wrote:
>
>> I'm still not sure what to do about the forced DST adjustment logic.
>> Should we really remove all that code? Should we change the default of
>> the tunable and disable the code by default?
>
> After some thought I have a better idea: let's fix mktime so that it
> matches user expectations better, without needing a new glibc
> tunable. (The tunable was iffy anyway; why should this be a
> system-wide thing rather than per-process or per-program?)
>
> From the test case, it appears that a user was disappointed because
> when mktime was incorrectly given a contemporaneous timestamp marked
> with daylight saving time (tm_isdst=1) in an Indian environment
> (TZ="Asia/Kolkata") that lacks DST today, mktime obediently thought
> "well, India had daylight saving time in 1945 so I guess this
> timestamp must have come from a World War II era timestamp along with
> user arithmetic to add 80 years" and therefore subtracted an hour from
> the requested time. This disappointed the user, who wanted mktime to
> ignore the incoming tm_isdst instead.
No, this part is synthetic. The reported case was the UTC part of the
test. I added the Asia/Kolkata tests just to have some coverage for the
daylight variable check that I added.
Despite this, I think the change in behavior you are proposing is
desirable.
Thanks,
Florian
^ permalink raw reply [flat|nested] 72+ messages in thread