On 2024-06-17 07:51, Bruno Haible wrote:
After the Solaris crash fix, the nstrftime tests still fail, due to this
output:

<-00>0: expected "1970-01-01 00:00:00 -0000 (-00)", got "1970-01-01 00:00:00 +0000 
(-00)"
<-00>0: expected "1985-11-05 00:53:21 -0000 (-00)", got "1985-11-05 00:53:21 +0000 
(-00)"
<-00>0: expected "2001-09-09 01:46:42 -0000 (-00)", got "2001-09-09 01:46:42 +0000 
(-00)"

Can you please look into that? I cannot judge whether -0000 and +0000
should be treated as equivalent or not.

"-0000" signals "I don't know what the time zone is, so I'm using UTC". I installed the attached to try to fix that issue. Thanks for reporting it.
From 55bd47e8ef7d6282c4405652c37a52d1c999b464 Mon Sep 17 00:00:00 2001
From: Paul Eggert <egg...@cs.ucla.edu>
Date: Mon, 17 Jun 2024 20:47:09 -0700
Subject: [PATCH] nstrftime: fix bug with -0000 on Solaris 11
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Problem reported by Bruno Haible in:
https://lists.gnu.org/r/bug-gnulib/2024-06/msg00201.html
* lib/strftime.c (strftime) [!_LIBC]: Move #undef up.
(underlying_strftime) [!_LIBC]: New function, containing most of
what __strftime_internal used to do at the underlying_strftime label.
(get_tm_zone): New function, containing much of what
__strftime_internal used to do for %Z.
Be consistent about using "UTC" rather than "GMT";
this changes behavior only on platforms lacking tm_zone.
(__strftime_internal): Use them to compute time zone abbreviation
when needed.  Make ‘zone’ more local, and compute it only if
needed.  Simplify by removing set_and_revert_tz local var and its
uses; no longer needed now that we have get_tm_zone.  Do not
assume %EZ and %OZ are equivalent to %Z.
---
 ChangeLog      |  18 +++
 lib/strftime.c | 315 +++++++++++++++++++++++++++++++++----------------
 2 files changed, 233 insertions(+), 100 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 9dc5edff7e..553f1aa896 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,21 @@
+2024-06-17  Paul Eggert  <egg...@cs.ucla.edu>
+
+	nstrftime: fix bug with -0000 on Solaris 11
+	Problem reported by Bruno Haible in:
+	https://lists.gnu.org/r/bug-gnulib/2024-06/msg00201.html
+	* lib/strftime.c (strftime) [!_LIBC]: Move #undef up.
+	(underlying_strftime) [!_LIBC]: New function, containing most of
+	what __strftime_internal used to do at the underlying_strftime label.
+	(get_tm_zone): New function, containing much of what
+	__strftime_internal used to do for %Z.
+	Be consistent about using "UTC" rather than "GMT";
+	this changes behavior only on platforms lacking tm_zone.
+	(__strftime_internal): Use them to compute time zone abbreviation
+	when needed.  Make ‘zone’ more local, and compute it only if
+	needed.  Simplify by removing set_and_revert_tz local var and its
+	uses; no longer needed now that we have get_tm_zone.  Do not
+	assume %EZ and %OZ are equivalent to %Z.
+
 2024-06-17  Bruno Haible  <br...@clisp.org>
 
 	nstrftime: Correct indentation.
diff --git a/lib/strftime.c b/lib/strftime.c
index db59d65d05..ff52fc8119 100644
--- a/lib/strftime.c
+++ b/lib/strftime.c
@@ -837,6 +837,190 @@ static size_t __strftime_internal (STREAM_OR_CHAR_T *, STRFTIME_ARG (size_t)
                                    bool, enum pad_style, int, bool *
                                    extra_args_spec LOCALE_PARAM);
 
+#ifndef _LIBC
+
+/* Make sure we're calling the actual underlying strftime.
+   In some cases, time.h contains something like
+   "#define strftime rpl_strftime".  */
+# ifdef strftime
+#  undef strftime
+# endif
+
+/* Assuming the time zone is TZ, store into UBUF, of size UBUFSIZE, a
+   ' ' followed by the result of calling strftime with the format
+   "%MF" where M is MODIFIER (or is omitted if !MODIFIER) and F is
+   FORMAT_CHAR, along with the time information specified by *TP.
+   Return the number of bytes stored if successful, zero otherwise.  */
+static size_t
+underlying_strftime (timezone_t tz, char *ubuf, size_t ubufsize,
+                     char modifier, char format_char, struct tm const *tp)
+{
+  /* The relevant information is available only via the
+     underlying strftime implementation, so use that.  */
+  char ufmt[5];
+  char *u = ufmt;
+
+  /* The space helps distinguish strftime failure from empty
+     output.  */
+  *u++ = ' ';
+  *u++ = '%';
+  *u = modifier;
+  u += !!modifier;
+  *u++ = format_char;
+  *u = '\0';
+
+#if !HAVE_NATIVE_TIME_Z
+  if (tz && tz != local_tz)
+    {
+      tz = set_tz (tz);
+      if (!tz)
+        return 0;
+    }
+#endif
+
+  size_t len;
+# if USE_C_LOCALE && (HAVE_STRFTIME_L || HAVE_STRFTIME_LZ)
+  locale_t locale = c_locale ();
+  if (!locale)
+    return 0; /* errno is set here */
+#  if HAVE_STRFTIME_LZ
+  len = strftime_lz (tz, ubuf, ubufsize, ufmt, tp, locale);
+#  else
+  len = strftime_l (ubuf, ubufsize, ufmt, tp, locale);
+#  endif
+# else
+#  if HAVE_STRFTIME_Z
+  len = strftime_z (tz, ubuf, ubufsize, ufmt, tp);
+#  else
+  len = strftime (ubuf, ubufsize, ufmt, tp);
+#  endif
+# endif
+
+# if !HAVE_NATIVE_TIME_Z
+  if (tz && !revert_tz (tz))
+    return 0;
+# endif
+
+  if (len != 0)
+    {
+# if ((__GLIBC__ == 2 && __GLIBC_MINOR__ < 31) \
+      || defined __NetBSD__ || defined __sun)
+      /* glibc < 2.31, NetBSD, Solaris */
+      if (format_char == 'c')
+        {
+          /* The output of the strftime %c directive consists of the
+             date, the time, and the time zone.  But the time zone is
+             wrong, since neither TZ nor ZONE was passed as argument.
+             Therefore, remove the the last space-delimited word.
+             In order not to accidentally remove a date or a year
+             (that contains no letter) or an AM/PM indicator (that has
+             length 2), remove that last word only if it contains a
+             letter and has length >= 3.  */
+          char *space;
+          for (space = ubuf + len - 1; *space != ' '; space--)
+            continue;
+          if (space > ubuf)
+            {
+              /* Found a space.  */
+              if (strlen (space + 1) >= 3)
+                {
+                  /* The last word has length >= 3.  */
+                  bool found_letter = false;
+                  const char *p;
+                  for (p = space + 1; *p != '\0'; p++)
+                    if ((*p >= 'A' && *p <= 'Z')
+                        || (*p >= 'a' && *p <= 'z'))
+                      {
+                        found_letter = true;
+                        break;
+                      }
+                  if (found_letter)
+                    {
+                      /* The last word contains a letter.  */
+                      *space = '\0';
+                      len = space - ubuf;
+                    }
+                }
+            }
+        }
+#  if (defined __NetBSD__ || defined __sun) && REQUIRE_GNUISH_STRFTIME_AM_PM
+      /* The output of the strftime %p and %r directives contains
+         an AM/PM indicator even for locales where it is not
+         suitable, such as French.  Remove this indicator.  */
+      if (format_char == 'p')
+        {
+          bool found_ampm = (len > 1);
+          if (found_ampm && should_remove_ampm ())
+            {
+              ubuf[1] = '\0';
+              len = 1;
+            }
+        }
+      else if (format_char == 'r')
+        {
+          char last_char = ubuf[len - 1];
+          bool found_ampm = !(last_char >= '0' && last_char <= '9');
+          if (found_ampm && should_remove_ampm ())
+            {
+              char *space;
+              for (space = ubuf + len - 1; *space != ' '; space--)
+                continue;
+              if (space > ubuf)
+                {
+                  *space = '\0';
+                  len = space - ubuf;
+                }
+            }
+        }
+#  endif
+# endif
+    }
+  return len;
+}
+#endif
+
+/* Return a time zone abbreviation for TZ.  Use BUF, of size BUFSIZE,
+   to store it if needed.  If MODIFIER use the strftime format
+   "%mZ" to format it, where m is the MODIFIER; otherwise
+   use plain "%Z".  Format an abbreviation appropriate for
+   TP and EXTRA_ARGS_SPEC.  Return the empty string on failure.  */
+static char const *
+get_tm_zone (timezone_t tz, char *ubuf, int ubufsize, int modifier,
+             struct tm const *tp)
+{
+#if HAVE_STRUCT_TM_TM_ZONE
+  /* The POSIX test suite assumes that setting
+     the environment variable TZ to a new value before calling strftime()
+     will influence the result (the %Z format) even if the information in
+     *TP is computed with a totally different time zone.
+     This is bogus: though POSIX allows bad behavior like this,
+     POSIX does not require it.  Do the right thing instead.  */
+  return tp->tm_zone;
+#else
+  if (!tz)
+    return "UTC";
+
+# if !HAVE_NATIVE_TIME_Z
+  timezone_t old_tz = tz;
+  if (tz != local_tz)
+    {
+      old_tz = set_tz (tz);
+      if (!old_tz)
+        return "";
+    }
+# endif
+
+  int zsize = underlying_strftime (tz, ubuf, ubufsize, 0, 'Z', tp);
+
+# if !HAVE_NATIVE_TIME_Z
+  if (!revert_tz (old_tz))
+    return "";
+# endif
+
+  return zsize ? ubuf + 1 : "";
+#endif
+}
+
 /* Write information from TP into S according to the format
    string FORMAT, writing no more that MAXSIZE characters
    (including the terminating '\0') and returning number of
@@ -927,7 +1111,6 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
 # define ampm (L_("AMPM") + 2 * (tp->tm_hour > 11))
 # define ap_len 2
 #endif
-  const char *zone;
   size_t i = 0;
   STREAM_OR_CHAR_T *p = s;
   const CHAR_T *f;
@@ -935,25 +1118,6 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
   const char *format_end = NULL;
 #endif
 
-  zone = NULL;
-#if HAVE_STRUCT_TM_TM_ZONE
-  /* The POSIX test suite assumes that setting
-     the environment variable TZ to a new value before calling strftime()
-     will influence the result (the %Z format) even if the information in
-     TP is computed with a totally different time zone.
-     This is bogus: though POSIX allows bad behavior like this,
-     POSIX does not require it.  Do the right thing instead.  */
-  zone = (const char *) tp->tm_zone;
-#else
-  if (!tz)
-    {
-      if (! (zone && *zone))
-        zone = "GMT";
-    }
-#endif
-  if (! zone)
-    zone = "";
-
   if (hour12 > 12)
     hour12 -= 12;
   else
@@ -983,9 +1147,6 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
       bool change_case = false;
       int format_char;
       int subwidth;
-#ifndef _LIBC
-      bool set_and_revert_tz = false;
-#endif
 
 #if DO_MULTIBYTE && !defined COMPILE_WIDE
       switch (*f)
@@ -1311,63 +1472,8 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
           {
             char ubuf[1024]; /* enough for any single format in practice */
             size_t len;
-            if (set_and_revert_tz && !tz)
-              {
-                memcpy (ubuf, " UTC", 4);
-                len = 4;
-              }
-            else
-              {
-                /* The relevant information is available only via the
-                   underlying strftime implementation, so use that.  */
-                char ufmt[5];
-                char *u = ufmt;
-                /* Make sure we're calling the actual underlying strftime.
-                   In some cases, time.h contains something like
-                   "#define strftime rpl_strftime".  */
-# ifdef strftime
-#  undef strftime
-# endif
-
-                /* The space helps distinguish strftime failure from empty
-                   output.  */
-                *u++ = ' ';
-                *u++ = '%';
-                if (modifier != 0)
-                  *u++ = modifier;
-                *u++ = format_char;
-                *u = '\0';
-
-# if !HAVE_NATIVE_TIME_Z
-                timezone_t old_tz = NULL;
-                if (set_and_revert_tz)
-                  {
-                    old_tz = set_tz (tz);
-                    if (!old_tz)
-                      return 0;
-                  }
-# endif
-# if USE_C_LOCALE && (HAVE_STRFTIME_L || HAVE_STRFTIME_LZ)
-                locale_t locale = c_locale ();
-                if (!locale)
-                  return 0; /* errno is set here */
-#  if HAVE_STRFTIME_LZ
-                len = strftime_lz (tz, ubuf, sizeof ubuf, ufmt, tp, locale);
-#  else
-                len = strftime_l (ubuf, sizeof ubuf, ufmt, tp, locale);
-#  endif
-# else
-#  if HAVE_STRFTIME_Z
-                len = strftime_z (tz, ubuf, sizeof ubuf, ufmt, tp);
-#  else
-                len = strftime (ubuf, sizeof ubuf, ufmt, tp);
-#  endif
-# endif
-# if !HAVE_NATIVE_TIME_Z
-                if (old_tz && !revert_tz (old_tz))
-                  return 0;
-# endif
-              }
+            len = underlying_strftime (tz, ubuf, sizeof ubuf,
+                                       modifier, format_char, tp);
             if (len != 0)
               {
 # if (__GLIBC__ == 2 && __GLIBC_MINOR__ < 31) || defined __NetBSD__ || defined __sun /* glibc < 2.31, NetBSD, Solaris */
@@ -1948,28 +2054,30 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
               to_lowcase = true;
             }
 
-          /* The tzset() call might have changed the value.  */
-          if (!(zone && *zone) && tp->tm_isdst >= 0)
-            {
+         {
+            char const *zone;
 #ifdef _LIBC
-              /* POSIX.1 requires that local time zone information be used as
-                 though strftime called tzset.  */
-              if (!*tzset_called)
-                {
-                  tzset ();
-                  *tzset_called = true;
-                }
-              zone = tp->tm_isdst <= 1 ? tzname[tp->tm_isdst] : "?";
+            zone = tp->tm_zone;
+            /* The tzset() call might have changed the value.  */
+            if (!(zone && *zone) && tp->tm_isdst >= 0)
+              {
+                /* POSIX.1 requires that local time zone information be used as
+                   though strftime called tzset.  */
+                if (!*tzset_called)
+                  {
+                    tzset ();
+                    *tzset_called = true;
+                  }
+                zone = tp->tm_isdst <= 1 ? tzname[tp->tm_isdst] : "?";
+              }
+            if (! zone)
+              zone = "";
 #else
-              set_and_revert_tz = tz != local_tz;
-              goto underlying_strftime;
+            char zonebuf[128]; /* Enough for any time zone abbreviation.  */
+            zone = get_tm_zone (tz, zonebuf, sizeof zonebuf, modifier, tp);
 #endif
-            }
-          if (! zone)
-            zone = "";
 
 #ifdef COMPILE_WIDE
-          {
             /* The zone string is always given in multibyte form.  We have
                to convert it to wide character.  */
             size_t w = pad == NO_PAD || width < 0 ? 0 : width;
@@ -1997,10 +2105,10 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
                 p += incr;
               }
             i += incr;
-          }
 #else
-          cpy (strlen (zone), zone);
+            cpy (strlen (zone), zone);
 #endif
+          }
           break;
 
         case L_(':'):
@@ -2045,7 +2153,14 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
               }
 #endif
 
-            negative_number = diff < 0 || (diff == 0 && *zone == '-');
+            negative_number = diff < 0;
+            if (diff == 0)
+              {
+                char zonebuf[128]; /* Enough for any time zone abbreviation.  */
+                negative_number = (*get_tm_zone (tz, zonebuf, sizeof zonebuf,
+                                                 0, tp)
+                                   == '-');
+              }
             hour_diff = diff / 60 / 60;
             min_diff = diff / 60 % 60;
             sec_diff = diff % 60;
-- 
2.43.0

Reply via email to