[glib: 5/8] gtimezone: support footers in TZif files



commit 25d950b61f92f25cc9ab20d683aa4d6969f93098
Author: Paul Eggert <eggert cs ucla edu>
Date:   Thu Jul 16 12:41:49 2020 -0700

    gtimezone: support footers in TZif files
    
    Since tzcode95f (1995), TZif files have had a trailing
    TZ string, used for timestamps after the last transition.
    This string is specified in Internet RFC 8536 section 3.3.
    init_zone_from_iana_info has ignored this string, causing it
    to mishandle timestamps past the year 2038.  With zic's new -b
    slim flag, init_zone_from_iana_info would even mishandle current
    timestamps.  Fix this by parsing the trailing TZ string and adding
    its transitions.
    
    Closes #2129

 glib/gtimezone.c | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 93 insertions(+), 3 deletions(-)
---
diff --git a/glib/gtimezone.c b/glib/gtimezone.c
index 80f388236e..fcf105703a 100644
--- a/glib/gtimezone.c
+++ b/glib/gtimezone.c
@@ -203,6 +203,10 @@ static GTimeZone *tz_local = NULL;
                            there's no point in getting carried
                            away. */
 
+#ifdef G_OS_UNIX
+static GTimeZone *parse_footertz (const gchar *, size_t);
+#endif
+
 /**
  * g_time_zone_unref:
  * @tz: a #GTimeZone
@@ -555,7 +559,12 @@ init_zone_from_iana_info (GTimeZone *gtz,
   guint8 *tz_transitions, *tz_type_index, *tz_ttinfo;
   guint8 *tz_abbrs;
   gsize timesize = sizeof (gint32);
-  const struct tzhead *header = g_bytes_get_data (zoneinfo, &size);
+  gconstpointer header_data = g_bytes_get_data (zoneinfo, &size);
+  const gchar *data = header_data;
+  const struct tzhead *header = header_data;
+  GTimeZone *footertz = NULL;
+  guint extra_time_count = 0, extra_type_count = 0;
+  gint64 last_explicit_transition_time;
 
   g_return_if_fail (size >= sizeof (struct tzhead) &&
                     memcmp (header, "TZif", 4) == 0);
@@ -576,6 +585,30 @@ init_zone_from_iana_info (GTimeZone *gtz,
   time_count = guint32_from_be(header->tzh_timecnt);
   type_count = guint32_from_be(header->tzh_typecnt);
 
+  if (header->tzh_version >= '2')
+    {
+      const gchar *footer = (((const gchar *) (header + 1))
+                             + guint32_from_be(header->tzh_ttisgmtcnt)
+                             + guint32_from_be(header->tzh_ttisstdcnt)
+                             + 12 * guint32_from_be(header->tzh_leapcnt)
+                             + 9 * time_count
+                             + 6 * type_count
+                             + guint32_from_be(header->tzh_charcnt));
+      const gchar *footerlast;
+      size_t footerlen;
+      g_return_if_fail (footer <= data + size - 2 && footer[0] == '\n');
+      footerlast = memchr (footer + 1, '\n', data + size - (footer + 1));
+      g_return_if_fail (footerlast);
+      footerlen = footerlast + 1 - footer;
+      if (footerlen != 2)
+        {
+          footertz = parse_footertz (footer, footerlen);
+          g_return_if_fail (footertz);
+          extra_type_count = footertz->t_info->len;
+          extra_time_count = footertz->transitions->len;
+        }
+    }
+
   tz_transitions = ((guint8 *) (header) + sizeof (*header));
   tz_type_index = tz_transitions + timesize * time_count;
   tz_ttinfo = tz_type_index + time_count;
@@ -583,9 +616,9 @@ init_zone_from_iana_info (GTimeZone *gtz,
 
   gtz->name = g_steal_pointer (&identifier);
   gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo),
-                                   type_count);
+                                   type_count + extra_type_count);
   gtz->transitions = g_array_sized_new (FALSE, TRUE, sizeof (Transition),
-                                        time_count);
+                                        time_count + extra_time_count);
 
   for (index = 0; index < type_count; index++)
     {
@@ -604,11 +637,46 @@ init_zone_from_iana_info (GTimeZone *gtz,
         trans.time = gint64_from_be (((gint64_be*)tz_transitions)[index]);
       else
         trans.time = gint32_from_be (((gint32_be*)tz_transitions)[index]);
+      last_explicit_transition_time = trans.time;
       trans.info_index = tz_type_index[index];
       g_assert (trans.info_index >= 0);
       g_assert ((guint) trans.info_index < gtz->t_info->len);
       g_array_append_val (gtz->transitions, trans);
     }
+
+  if (footertz)
+    {
+      /* Append footer time types.  Don't bother to coalesce
+         duplicates with existing time types.  */
+      for (index = 0; index < extra_type_count; index++)
+        {
+          TransitionInfo t_info;
+          TransitionInfo *footer_t_info
+            = &g_array_index (footertz->t_info, TransitionInfo, index);
+          t_info.gmt_offset = footer_t_info->gmt_offset;
+          t_info.is_dst = footer_t_info->is_dst;
+          t_info.abbrev = g_steal_pointer (&footer_t_info->abbrev);
+          g_array_append_val (gtz->t_info, t_info);
+        }
+
+      /* Append footer transitions that follow the last explicit
+         transition.  */
+      for (index = 0; index < extra_time_count; index++)
+        {
+          Transition *footer_transition
+            = &g_array_index (footertz->transitions, Transition, index);
+          if (time_count <= 0
+              || last_explicit_transition_time < footer_transition->time)
+            {
+              Transition trans;
+              trans.time = footer_transition->time;
+              trans.info_index = type_count + footer_transition->info_index;
+              g_array_append_val (gtz->transitions, trans);
+            }
+        }
+
+      g_time_zone_unref (footertz);
+    }
 }
 
 #elif defined (G_OS_WIN32)
@@ -1504,6 +1572,28 @@ rules_from_identifier (const gchar   *identifier,
   return create_ruleset_from_rule (rules, &tzr);
 }
 
+#ifdef G_OS_UNIX
+static GTimeZone *
+parse_footertz (const gchar *footer, size_t footerlen)
+{
+  gchar *tzstring = g_strndup (footer + 1, footerlen - 2);
+  GTimeZone *footertz = NULL;
+  gchar *ident;
+  TimeZoneRule *rules;
+  guint rules_num = rules_from_identifier (tzstring, &ident, &rules);
+  g_free (ident);
+  g_free (tzstring);
+  if (rules_num > 1)
+    {
+      footertz = g_slice_new0 (GTimeZone);
+      init_zone_from_rules (footertz, rules, rules_num, NULL);
+      footertz->ref_count++;
+    }
+  g_free (rules);
+  return footertz;
+}
+#endif
+
 /* Construction {{{1 */
 /**
  * g_time_zone_new:


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]