Replace zoneinfo pointers with structs

The approach of sucking a zoneinfo file into a GBytes and working with
pointers into it might be fast, but it's obtuse and not compatible with
Microsoft Windows.
This commit is contained in:
John Ralls 2012-10-15 14:32:58 -07:00
parent 489e031f22
commit 59f2da1749

View File

@ -115,24 +115,28 @@ struct ttinfo
guint8 tt_abbrind; guint8 tt_abbrind;
}; };
typedef struct
{
gint32 gmt_offset;
gboolean is_dst;
gboolean is_standard;
gboolean is_gmt;
gchar *abbrev;
} TransitionInfo;
typedef struct
{
gint64 time;
gint info_index;
} Transition;
/* GTimeZone structure and lifecycle {{{1 */ /* GTimeZone structure and lifecycle {{{1 */
struct _GTimeZone struct _GTimeZone
{ {
gchar *name; gchar *name;
gchar version; GArray *t_info;
GBytes *zoneinfo; GArray *transitions;
const struct tzhead *header;
const struct ttinfo *infos;
union
{
const gint32_be *one;
const gint64_be *two;
} trans;
const guint8 *indices;
const gchar *abbrs;
gint timecnt;
gint ref_count; gint ref_count;
}; };
@ -174,9 +178,9 @@ again:
G_UNLOCK(time_zones); G_UNLOCK(time_zones);
} }
if (tz->zoneinfo) g_array_free (tz->t_info, TRUE);
g_bytes_unref (tz->zoneinfo); if (tz->transitions != NULL)
g_array_free (tz->transitions, TRUE);
g_free (tz->name); g_free (tz->name);
g_slice_free (GTimeZone, tz); g_slice_free (GTimeZone, tz);
@ -254,6 +258,11 @@ static gboolean
parse_constant_offset (const gchar *name, parse_constant_offset (const gchar *name,
gint32 *offset) gint32 *offset)
{ {
if (g_strcmp0 (name, "UTC") == 0)
{
*offset = 0;
return TRUE;
}
switch (*name++) switch (*name++)
{ {
case 'Z': case 'Z':
@ -275,36 +284,30 @@ parse_constant_offset (const gchar *name,
} }
} }
static GBytes * static void
zone_for_constant_offset (const gchar *name) zone_for_constant_offset (GTimeZone *gtz, const gchar *name)
{ {
const gchar fake_zoneinfo_headers[] =
"TZif" "2..." "...." "...." "...."
"\0\0\0\0" "\0\0\0\0" "\0\0\0\0" "\0\0\0\0" "\0\0\0\0" "\0\0\0\0"
"TZif" "2..." "...." "...." "...."
"\0\0\0\0" "\0\0\0\0" "\0\0\0\0" "\0\0\0\0" "\0\0\0\1" "\0\0\0\7";
struct {
struct tzhead headers[2];
struct ttinfo info;
gchar abbr[8];
} *fake;
gint32 offset; gint32 offset;
TransitionInfo info;
if (name == NULL || !parse_constant_offset (name, &offset)) if (name == NULL || !parse_constant_offset (name, &offset))
return NULL; return;
offset = GINT32_TO_BE (offset); info.gmt_offset = offset;
info.is_dst = FALSE;
info.is_standard = TRUE;
info.is_gmt = TRUE;
info.abbrev = g_strdup (name);
fake = g_malloc (sizeof *fake);
memcpy (fake, fake_zoneinfo_headers, sizeof fake_zoneinfo_headers);
memcpy (&fake->info.tt_gmtoff, &offset, sizeof offset);
fake->info.tt_isdst = FALSE;
fake->info.tt_abbrind = 0;
strcpy (fake->abbr, name);
return g_bytes_new_take (fake, sizeof *fake); gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo), 1);
g_array_append_val (gtz->t_info, info);
/* Constant offset, no transitions */
gtz->transitions = NULL;
} }
#ifdef G_OS_UNIX
static GBytes* static GBytes*
zone_info_unix (const gchar *identifier) zone_info_unix (const gchar *identifier)
{ {
@ -349,23 +352,25 @@ zone_info_unix (const gchar *identifier)
} }
static void static void
init_zone_from_iana_info (GTimeZone *tz) init_zone_from_iana_info (GTimeZone *gtz, GBytes *zoneinfo)
{ {
gsize size; gsize size;
const struct tzhead *header = g_bytes_get_data (tz->zoneinfo, &size); guint index;
guint32 time_count, type_count, leap_count, isgmt_count;
guint32 isstd_count, char_count ;
gpointer tz_transitions, tz_type_index, tz_ttinfo;
gpointer tz_leaps, tz_isgmt, tz_isstd;
gchar* tz_abbrs;
guint timesize = sizeof (gint32), countsize = sizeof (gint32);
const struct tzhead *header = g_bytes_get_data (zoneinfo, &size);
if (size < sizeof (struct tzhead) || memcmp (header, "TZif", 4)) g_return_if_fail (size >= sizeof (struct tzhead) &&
{ memcmp (header, "TZif", 4) == 0);
g_bytes_unref (tz->zoneinfo);
tz->zoneinfo = NULL; if (header->tzh_version == '2')
} {
else /* Skip ahead to the newer 64-bit data if it's available. */
{ header = (const struct tzhead *)
gint typecnt;
tz->version = header->tzh_version;
/* we trust the file completely. */
if (tz->version == '2')
tz->header = (const struct tzhead *)
(((const gchar *) (header + 1)) + (((const gchar *) (header + 1)) +
guint32_from_be(header->tzh_ttisgmtcnt) + guint32_from_be(header->tzh_ttisgmtcnt) +
guint32_from_be(header->tzh_ttisstdcnt) + guint32_from_be(header->tzh_ttisstdcnt) +
@ -373,26 +378,59 @@ init_zone_from_iana_info (GTimeZone *tz)
5 * guint32_from_be(header->tzh_timecnt) + 5 * guint32_from_be(header->tzh_timecnt) +
6 * guint32_from_be(header->tzh_typecnt) + 6 * guint32_from_be(header->tzh_typecnt) +
guint32_from_be(header->tzh_charcnt)); guint32_from_be(header->tzh_charcnt));
else timesize = sizeof (gint64);
tz->header = header; }
time_count = guint32_from_be(header->tzh_timecnt);
type_count = guint32_from_be(header->tzh_typecnt);
leap_count = guint32_from_be(header->tzh_leapcnt);
isgmt_count = guint32_from_be(header->tzh_ttisgmtcnt);
isstd_count = guint32_from_be(header->tzh_ttisstdcnt);
char_count = guint32_from_be(header->tzh_charcnt);
typecnt = guint32_from_be (tz->header->tzh_typecnt); g_assert (type_count == isgmt_count);
tz->timecnt = guint32_from_be (tz->header->tzh_timecnt); g_assert (type_count == isstd_count);
if (tz->version == '2')
{ tz_transitions = (gpointer)(header + 1);
tz->trans.two = (gconstpointer) (tz->header + 1); tz_type_index = tz_transitions + timesize * time_count;
tz->indices = (gconstpointer) (tz->trans.two + tz->timecnt); tz_ttinfo = tz_type_index + time_count;
} tz_abbrs = tz_ttinfo + sizeof (struct ttinfo) * type_count;
else tz_leaps = tz_abbrs + char_count;
{ tz_isstd = tz_leaps + (timesize + countsize) * leap_count;
tz->trans.one = (gconstpointer) (tz->header + 1); tz_isgmt = tz_isstd + isstd_count;
tz->indices = (gconstpointer) (tz->trans.one + tz->timecnt);
} gtz->t_info = g_array_sized_new (FALSE, TRUE, sizeof (TransitionInfo),
tz->infos = (gconstpointer) (tz->indices + tz->timecnt); type_count);
tz->abbrs = (gconstpointer) (tz->infos + typecnt); gtz->transitions = g_array_sized_new (FALSE, TRUE, sizeof (Transition),
time_count);
for (index = 0; index < type_count; index++)
{
TransitionInfo t_info;
struct ttinfo info = ((struct ttinfo*)tz_ttinfo)[index];
t_info.gmt_offset = gint32_from_be (info.tt_gmtoff);
t_info.is_dst = info.tt_isdst ? TRUE : FALSE;
t_info.is_standard = ((guint8*)tz_isstd)[index] ? TRUE : FALSE;
t_info.is_gmt = ((guint8*)tz_isgmt)[index] ? TRUE : FALSE;
t_info.abbrev = g_strdup (&tz_abbrs[info.tt_abbrind]);
g_array_append_val (gtz->t_info, t_info);
} }
for (index = 0; index < time_count; index++)
{
Transition trans;
if (header->tzh_version == '2')
trans.time = gint64_from_be (((gint64_be*)tz_transitions)[index]);
else
trans.time = gint32_from_be (((gint32_be*)tz_transitions)[index]);
trans.info_index = ((guint8*)tz_type_index)[index];
g_assert (trans.info_index >= 0);
g_assert (trans.info_index < gtz->t_info->len);
g_array_append_val (gtz->transitions, trans);
}
g_bytes_unref (zoneinfo);
} }
#endif
/* Construction {{{1 */ /* Construction {{{1 */
/** /**
@ -439,31 +477,46 @@ init_zone_from_iana_info (GTimeZone *tz)
GTimeZone * GTimeZone *
g_time_zone_new (const gchar *identifier) g_time_zone_new (const gchar *identifier)
{ {
GTimeZone *tz; GTimeZone *tz = NULL;
GMappedFile *file;
G_LOCK (time_zones); G_LOCK (time_zones);
if (time_zones == NULL) if (time_zones == NULL)
time_zones = g_hash_table_new (g_str_hash, g_str_equal); time_zones = g_hash_table_new (g_str_hash, g_str_equal);
if (identifier) if (identifier)
tz = g_hash_table_lookup (time_zones, identifier);
else
tz = NULL;
if (tz == NULL)
{ {
tz = g_slice_new0 (GTimeZone); tz = g_hash_table_lookup (time_zones, identifier);
tz->name = g_strdup (identifier); if (tz)
tz->ref_count = 0; {
g_atomic_int_inc (&tz->ref_count);
G_UNLOCK (time_zones);
return tz;
}
}
tz->zoneinfo = zone_for_constant_offset (identifier); tz = g_slice_new0 (GTimeZone);
tz->name = g_strdup (identifier);
tz->ref_count = 0;
if (tz->zoneinfo == NULL) zone_for_constant_offset (tz, identifier);
tz->zoneinfo = zone_info_unix (identifier);
if (tz->zoneinfo != NULL) if (tz->t_info == NULL)
init_zone_from_iana_info (tz); {
#ifdef G_OS_UNIX
GBytes *zoneinfo = zone_info_unix (identifier);
if (!zoneinfo)
zone_for_constant_offset (tz, "UTC");
else
{
init_zone_from_iana_info (tz, zoneinfo);
g_bytes_unref (zoneinfo);
}
#elif defined G_OS_WIN32
#endif
}
if (tz->t_info != NULL)
{
if (identifier) if (identifier)
g_hash_table_insert (time_zones, tz->name, tz); g_hash_table_insert (time_zones, tz->name, tz);
} }
@ -518,69 +571,89 @@ g_time_zone_new_local (void)
return g_time_zone_new (getenv ("TZ")); return g_time_zone_new (getenv ("TZ"));
} }
/* Internal helpers {{{1 */ #define TRANSITION(n) g_array_index (tz->transitions, Transition, n)
inline static const struct ttinfo * #define TRANSITION_INFO(n) g_array_index (tz->t_info, TransitionInfo, n)
interval_info (GTimeZone *tz,
gint interval)
{
if (interval)
return tz->infos + tz->indices[interval - 1];
return tz->infos; /* Internal helpers {{{1 */
/* Note that interval 0 is *before* the first transition time, so
* interval 1 gets transitions[0].
*/
inline static const TransitionInfo*
interval_info (GTimeZone *tz,
guint interval)
{
guint index;
g_return_val_if_fail (tz->t_info != NULL, NULL);
if (interval && tz->transitions && interval <= tz->transitions->len)
index = (TRANSITION(interval - 1)).info_index;
else
index = 0;
return &(TRANSITION_INFO(index));
} }
inline static gint64 inline static gint64
interval_start (GTimeZone *tz, interval_start (GTimeZone *tz,
gint interval) guint interval)
{ {
if (interval) if (!interval || tz->transitions == NULL || tz->transitions->len == 0)
{ return G_MININT64;
if (tz->version == '2') if (interval > tz->transitions->len)
return gint64_from_be (tz->trans.two[interval - 1]); interval = tz->transitions->len;
else return (TRANSITION(interval - 1)).time;
return gint32_from_be (tz->trans.one[interval - 1]);
}
return G_MININT64;
} }
inline static gint64 inline static gint64
interval_end (GTimeZone *tz, interval_end (GTimeZone *tz,
gint interval) guint interval)
{ {
if (interval < tz->timecnt) if (tz->transitions && interval < tz->transitions->len)
{ return (TRANSITION(interval)).time - 1;
if (tz->version == '2')
return gint64_from_be (tz->trans.two[interval]) - 1;
else
return gint32_from_be (tz->trans.one[interval]) - 1;
}
return G_MAXINT64; return G_MAXINT64;
} }
inline static gint32 inline static gint32
interval_offset (GTimeZone *tz, interval_offset (GTimeZone *tz,
gint interval) guint interval)
{ {
return gint32_from_be (interval_info (tz, interval)->tt_gmtoff); g_return_val_if_fail (tz->t_info != NULL, 0);
return interval_info (tz, interval)->gmt_offset;
} }
inline static gboolean inline static gboolean
interval_isdst (GTimeZone *tz, interval_isdst (GTimeZone *tz,
gint interval) guint interval)
{ {
return interval_info (tz, interval)->tt_isdst; g_return_val_if_fail (tz->t_info != NULL, 0);
return interval_info (tz, interval)->is_dst;
} }
inline static guint8
interval_abbrind (GTimeZone *tz, inline static gboolean
gint interval) interval_isgmt (GTimeZone *tz,
guint interval)
{ {
return interval_info (tz, interval)->tt_abbrind; g_return_val_if_fail (tz->t_info != NULL, 0);
return interval_info (tz, interval)->is_gmt;
}
inline static gboolean
interval_isstandard (GTimeZone *tz,
guint interval)
{
return interval_info (tz, interval)->is_standard;
}
inline static gchar*
interval_abbrev (GTimeZone *tz,
guint interval)
{
g_return_val_if_fail (tz->t_info != NULL, 0);
return interval_info (tz, interval)->abbrev;
} }
inline static gint64 inline static gint64
interval_local_start (GTimeZone *tz, interval_local_start (GTimeZone *tz,
gint interval) guint interval)
{ {
if (interval) if (interval)
return interval_start (tz, interval) + interval_offset (tz, interval); return interval_start (tz, interval) + interval_offset (tz, interval);
@ -590,9 +663,9 @@ interval_local_start (GTimeZone *tz,
inline static gint64 inline static gint64
interval_local_end (GTimeZone *tz, interval_local_end (GTimeZone *tz,
gint interval) guint interval)
{ {
if (interval < tz->timecnt) if (tz->transitions && interval < tz->transitions->len)
return interval_end (tz, interval) + interval_offset (tz, interval); return interval_end (tz, interval) + interval_offset (tz, interval);
return G_MAXINT64; return G_MAXINT64;
@ -600,9 +673,11 @@ interval_local_end (GTimeZone *tz,
static gboolean static gboolean
interval_valid (GTimeZone *tz, interval_valid (GTimeZone *tz,
gint interval) guint interval)
{ {
return interval <= tz->timecnt; if ( tz->transitions == NULL)
return interval == 0;
return interval <= tz->transitions->len;
} }
/* g_time_zone_find_interval() {{{1 */ /* g_time_zone_find_interval() {{{1 */
@ -640,13 +715,16 @@ g_time_zone_adjust_time (GTimeZone *tz,
gint64 *time_) gint64 *time_)
{ {
gint i; gint i;
guint intervals;
if (tz->zoneinfo == NULL) if (tz->transitions == NULL)
return 0; return 0;
intervals = tz->transitions->len;
/* find the interval containing *time UTC /* find the interval containing *time UTC
* TODO: this could be binary searched (or better) */ * TODO: this could be binary searched (or better) */
for (i = 0; i < tz->timecnt; i++) for (i = 0; i <= intervals; i++)
if (*time_ <= interval_end (tz, i)) if (*time_ <= interval_end (tz, i))
break; break;
@ -686,7 +764,7 @@ g_time_zone_adjust_time (GTimeZone *tz,
if (i && *time_ <= interval_local_end (tz, i - 1)) if (i && *time_ <= interval_local_end (tz, i - 1))
i--; i--;
else if (i < tz->timecnt && else if (i < intervals &&
*time_ >= interval_local_start (tz, i + 1)) *time_ >= interval_local_start (tz, i + 1))
i++; i++;
} }
@ -730,11 +808,12 @@ g_time_zone_find_interval (GTimeZone *tz,
gint64 time_) gint64 time_)
{ {
gint i; gint i;
guint intervals;
if (tz->zoneinfo == NULL) if (tz->transitions == NULL)
return 0; return 0;
intervals = tz->transitions->len;
for (i = 0; i < tz->timecnt; i++) for (i = 0; i <= intervals; i++)
if (time_ <= interval_end (tz, i)) if (time_ <= interval_end (tz, i))
break; break;
@ -758,7 +837,7 @@ g_time_zone_find_interval (GTimeZone *tz,
if (i && time_ <= interval_local_end (tz, i - 1)) if (i && time_ <= interval_local_end (tz, i - 1))
i--; i--;
else if (i < tz->timecnt && time_ >= interval_local_start (tz, i + 1)) else if (i < intervals && time_ >= interval_local_start (tz, i + 1))
i++; i++;
} }
@ -787,12 +866,9 @@ const gchar *
g_time_zone_get_abbreviation (GTimeZone *tz, g_time_zone_get_abbreviation (GTimeZone *tz,
gint interval) gint interval)
{ {
g_return_val_if_fail (interval_valid (tz, interval), NULL); g_return_val_if_fail (interval_valid (tz, (guint)interval), NULL);
if (tz->header == NULL) return interval_abbrev (tz, (guint)interval);
return "UTC";
return tz->abbrs + interval_abbrind (tz, interval);
} }
/** /**
@ -816,12 +892,9 @@ gint32
g_time_zone_get_offset (GTimeZone *tz, g_time_zone_get_offset (GTimeZone *tz,
gint interval) gint interval)
{ {
g_return_val_if_fail (interval_valid (tz, interval), 0); g_return_val_if_fail (interval_valid (tz, (guint)interval), 0);
if (tz->header == NULL) return interval_offset (tz, (guint)interval);
return 0;
return interval_offset (tz, interval);
} }
/** /**
@ -842,10 +915,10 @@ g_time_zone_is_dst (GTimeZone *tz,
{ {
g_return_val_if_fail (interval_valid (tz, interval), FALSE); g_return_val_if_fail (interval_valid (tz, interval), FALSE);
if (tz->header == NULL) if (tz->transitions == NULL)
return FALSE; return FALSE;
return interval_isdst (tz, interval); return interval_isdst (tz, (guint)interval);
} }
/* Epilogue {{{1 */ /* Epilogue {{{1 */