diff --git a/glib/gtimezone.c b/glib/gtimezone.c index 80f388236..fcf105703 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: