From a707253f0c3c8cfd7b66fc5ca46cfb0a01e41dee Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Sat, 13 Jan 2024 14:30:35 -0800
Subject: [PROPOSED] For strftime %z, use tm_gmtoff if available
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Problem and draft patch reported by Dag-Erling Smørgrav in:
https://mm.icann.org/pipermail/tz/2024-January/033488.html
* NEWS: Mention this.
* localtime.c (EXTERN_TIMEOFF): Default to static.
(timeoff): Use EXTERN_TIMEOFF to declare; this is easier to read.
* private.h (EXTERN_TIMEOFF): New macro.
(timeoff): Define to tz_private_timeoff if it’s not public but
strftime needs it anyway.  Declare if we define it as extern.
* strftime.c (_fmt): If TM_GMTOFF is available, use timeoff
rather than mktime to determine %z.  Do not worry about UNINIT_TRAP
since we are now using a more-generous reading of POSIX.
---
 NEWS        |  3 +++
 localtime.c |  9 ++++++---
 private.h   | 15 ++++++++++++++-
 strftime.c  |  7 ++++---
 4 files changed, 27 insertions(+), 7 deletions(-)

diff --git a/NEWS b/NEWS
index f0ccfa87..cab6c84c 100644
--- a/NEWS
+++ b/NEWS
@@ -34,6 +34,9 @@ Unreleased, experimental changes
     for some timestamps in November 2422, November 2822, etc. in
     America/Ciudad_Juarez.  (Problem reported by Gilmore Davidson.)
 
+    strftime %z now uses tm_gmtoff if available.  (Problem and draft
+    patch reported by Dag-Erling Smørgrav.)
+
   Changes to build procedure
 
     The leap-seconds.list file is now copied from the IERS instead of
diff --git a/localtime.c b/localtime.c
index 8837c345..7613769f 100644
--- a/localtime.c
+++ b/localtime.c
@@ -2292,15 +2292,18 @@ timelocal(struct tm *tmp)
 		tmp->tm_isdst = -1;	/* in case it wasn't initialized */
 	return mktime(tmp);
 }
-#else
+#endif
+
+#ifndef EXTERN_TIMEOFF
 # ifndef timeoff
 #  define timeoff my_timeoff /* Don't collide with OpenBSD 7.4 <time.h>.  */
 # endif
-static
+# define EXTERN_TIMEOFF static
 #endif
+
 /* This function is obsolescent and may disapper in future releases.
    Callers can instead use mktime_z with a fixed-offset zone.  */
-time_t
+EXTERN_TIMEOFF time_t
 timeoff(struct tm *tmp, long offset)
 {
   if (tmp)
diff --git a/private.h b/private.h
index 8eff90b7..b3d563d5 100644
--- a/private.h
+++ b/private.h
@@ -756,7 +756,7 @@ struct tm *offtime(time_t const *, long);
 time_t timelocal(struct tm *);
 # endif
 # if TZ_TIME_T || !defined timeoff
-time_t timeoff(struct tm *, long);
+#  define EXTERN_TIMEOFF
 # endif
 # if TZ_TIME_T || !defined time2posix
 time_t time2posix(time_t);
@@ -899,6 +899,19 @@ static_assert(! TYPE_SIGNED(time_t) || ! SIGNED_PADDING_CHECK_NEEDED
 # define UNINIT_TRAP 0
 #endif
 
+/* localtime.c sometimes needs access to timeoff if it is not already public.
+   tz_private_timeoff should be used only by localtime.c.  */
+#if (!defined EXTERN_TIMEOFF \
+     && defined TM_GMTOFF && (200809 < _POSIX_VERSION || ! UNINIT_TRAP))
+# ifndef timeoff
+#  define timeoff tz_private_timeoff
+# endif
+# define EXTERN_TIMEOFF
+#endif
+#ifdef EXTERN_TIMEOFF
+time_t timeoff(struct tm *, long);
+#endif
+
 #ifdef DEBUG
 # undef unreachable
 # define unreachable() abort()
diff --git a/strftime.c b/strftime.c
index df169830..755d341f 100644
--- a/strftime.c
+++ b/strftime.c
@@ -327,11 +327,12 @@ label:
 					tm.tm_mday = t->tm_mday;
 					tm.tm_mon = t->tm_mon;
 					tm.tm_year = t->tm_year;
+#ifdef TM_GMTOFF
+					mkt = timeoff(&tm, t->TM_GMTOFF);
+#else
 					tm.tm_isdst = t->tm_isdst;
-#if defined TM_GMTOFF && ! UNINIT_TRAP
-					tm.TM_GMTOFF = t->TM_GMTOFF;
-#endif
 					mkt = mktime(&tm);
+#endif
 					/* If mktime fails, %s expands to the
 					   value of (time_t) -1 as a failure
 					   marker; this is better in practice
-- 
2.40.1

