[46/59] tzset, not localtime, now parses TZif TZ spec

Message ID 20250105055750.1668721-47-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
  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(-)
  

Patch

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.  */