[30/59] Update tzname etc. even if TZ is unchanged

Message ID 20250105055750.1668721-31-eggert@cs.ucla.edu (mailing list archive)
State New
Headers
Series time: sync mktime from Gnulib |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent

Commit Message

Paul Eggert Jan. 5, 2025, 5:57 a.m. UTC
  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(-)
  

Patch

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