[glib/wip/date-time-fixes] GDateTime: new approach to timezones



commit 1e3a1eecf87abc76c9210dc1e1dde79805dd70d7
Author: Ryan Lortie <desrt desrt ca>
Date:   Thu Sep 16 04:44:59 2010 -0400

    GDateTime: new approach to timezones

 glib/Makefile.am       |    2 +
 glib/gdatetime.c       |  942 +++++++++++-------------------------------------
 glib/gdatetime.h       |   24 +--
 glib/glib.h            |    1 +
 glib/glib.symbols      |   20 +-
 glib/gtimezone.c       |  514 ++++++++++++++++++++++++++
 glib/gtimezone.h       |   62 ++++
 glib/tests/gdatetime.c |   73 ++---
 8 files changed, 835 insertions(+), 803 deletions(-)
---
diff --git a/glib/Makefile.am b/glib/Makefile.am
index b8ce522..6f3d526 100644
--- a/glib/Makefile.am
+++ b/glib/Makefile.am
@@ -177,6 +177,7 @@ libglib_2_0_la_SOURCES = 	\
 	gthreadprivate.h	\
 	gthreadpool.c   	\
 	gtimer.c		\
+	gtimezone.c	 	\
 	gtree.c			\
 	guniprop.c		\
 	gutf8.c			\
@@ -276,6 +277,7 @@ glibsubinclude_HEADERS =   \
 	gthread.h	\
 	gthreadpool.h	\
 	gtimer.h	\
+	gtimezone.h	\
 	gtree.h		\
 	gtypes.h	\
 	gunicode.h	\
diff --git a/glib/gdatetime.c b/glib/gdatetime.c
index b014f11..fe67e99 100644
--- a/glib/gdatetime.c
+++ b/glib/gdatetime.c
@@ -3,6 +3,7 @@
  * Copyright (C) 2009-2010 Christian Hergert <chris dronelabs com>
  * Copyright (C) 2010 Thiago Santos <thiago sousa santos collabora co uk>
  * Copyright (C) 2010 Emmanuele Bassi <ebassi linux intel com>
+ * Copyright © 2010 Codethink Limited
  *
  * This is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -17,6 +18,11 @@
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors: Christian Hergert <chris dronelabs com>
+ *          Thiago Santos <thiago sousa santos collabora co uk>
+ *          Emmanuele Bassi <ebassi linux intel com>
+ *          Ryan Lortie <desrt desrt ca>
  */
 
 /* Algorithms within this file are based on the Calendar FAQ by
@@ -36,6 +42,8 @@
  *   to its correctness.
  */
 
+/* Prologue {{{1 */
+
 #include "config.h"
 
 #include <stdlib.h>
@@ -60,6 +68,7 @@
 #include "gstrfuncs.h"
 #include "gtestutils.h"
 #include "gthread.h"
+#include "gtimezone.h"
 
 #include "glibintl.h"
 
@@ -68,25 +77,35 @@
  * @title: GDateTime
  * @short_description: A structure representing Date and Time
  *
- * #GDateTime is a structure that combines a date and time into a single
- * structure. It provides many conversion and methods to manipulate dates
- * and times. Time precision is provided down to microseconds.
+ * #GDateTime is a structure that combines a Gregorian date and time
+ * into a single structure.  It provides many conversion and methods to
+ * manipulate dates and times.  Time precision is provided down to
+ * microseconds and the time can range (proleptically) from 0001-01-01
+ * 00:00:00 to 9999-12-31 23:59:59.999999.  #GDateTime follows POSIX
+ * time in the sense that it is oblivious to leap seconds.
  *
- * #GDateTime is an immutable object: once it has been created it cannot be
- * modified further. All modifiers will create a new #GDateTime.
+ * #GDateTime is an immutable object; once it has been created it cannot
+ * be modified further.  All modifiers will create a new #GDateTime.
+ * Nearly all such functions can fail due to the date or time going out
+ * of range, in which case %NULL will be returned.
  *
  * #GDateTime is reference counted: the reference count is increased by calling
  * g_date_time_ref() and decreased by calling g_date_time_unref(). When the
  * reference count drops to 0, the resources allocated by the #GDateTime
  * structure are released.
  *
- * Internally, #GDateTime uses the Proleptic Gregorian Calendar, the first
- * representable date is 0001-01-01. However, the public API uses the
- * internationally accepted Gregorian Calendar.
+ * Many parts of the API may produce non-obvious results.  As an
+ * example, adding two months to January 31st will yield March 31st
+ * whereas adding one month and then one month again will yield either
+ * March 28th or March 29th.  Also note that adding 24 hours is not
+ * always the same as adding one day (since days containing daylight
+ * savings time transitions are either 23 or 25 hours in length).
  *
  * #GDateTime is available since GLib 2.26.
  */
 
+/* Time conversion {{{1 */
+
 #define UNIX_EPOCH_START     719163
 
 #define DAYS_IN_4YEARS    1461    /* days in 4 years */
@@ -132,47 +151,21 @@
 #define SECS_PER_YEAR   (365 * SECS_PER_DAY)
 #define SECS_PER_JULIAN (DAYS_PER_PERIOD * SECS_PER_DAY)
 
-typedef struct _GTimeZoneFile   GTimeZoneFile;
-
 struct _GDateTime
 {
   /* 1 is 0001-01-01 in Proleptic Gregorian */
-  guint32 days;
+  gint32 days;
 
   /* Microsecond timekeeping within Day */
   guint64 usec;
 
-  /* TimeZone information, NULL is UTC */
+  /* TimeZone information */
   GTimeZone *tz;
+  gint interval;
 
   volatile gint ref_count;
 };
 
-struct _GTimeZone
-{
-  GTimeZoneFile *tz_file;
-
-  gchar *name;
-
-  gint64 offset;
-
-  guint is_dst      : 1;
-  guint is_utc      : 1;
-  guint is_floating : 1;
-};
-
-struct _GTimeZoneFile
-{
-  GMappedFile *file;
-
-  gchar *filename;
-
-  volatile gint ref_count;
-};
-
-G_LOCK_DEFINE_STATIC (time_zone_files);
-static GHashTable *time_zone_files = NULL;
-
 static const guint16 days_in_months[2][13] =
 {
   { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
@@ -313,641 +306,179 @@ get_weekday_name_abbr (gint day)
   return NULL;
 }
 
-#define ZONEINFO_DIR            "zoneinfo"
-#define TZ_MAGIC                "TZif"
-#define TZ_MAGIC_LEN            (strlen (TZ_MAGIC))
-#define TZ_HEADER_SIZE          44
-#define TZ_TIMECNT_OFFSET       32
-#define TZ_TYPECNT_OFFSET       36
-#define TZ_TRANSITIONS_OFFSET   44
-#define TZ_TTINFO_SIZE          6
-#define TZ_TTINFO_GMTOFF_OFFSET 0
-#define TZ_TTINFO_ISDST_OFFSET  4
-#define TZ_TTINFO_NAME_OFFSET   5
-
-static gchar *
-get_tzdata_path (const gchar *tz_name)
-{
-  gchar *retval = NULL;
-  const gchar *tz_dir = g_getenv ("TZDIR");
-
-  if (tz_dir != NULL)
-    retval = g_build_filename (tz_dir, tz_name, NULL);
-  else
-    {
-      if (strcmp (tz_name, "localtime") == 0)
-        retval = g_build_filename ("/", "etc", "localtime", NULL);
-      else
-        retval = g_build_filename ("/", "usr", "share", ZONEINFO_DIR, tz_name, NULL);
-    }
-
-  return retval;
-}
-
-static gboolean
-parse_tzdata (GTimeZoneFile  *tz_file,
-              gint64          start,
-              gboolean        is_utc,
-              gint64         *_offset,
-              gboolean       *_is_dst,
-              gchar         **_name)
+static inline gint
+date_to_proleptic_gregorian (gint year,
+                             gint month,
+                             gint day)
 {
-  gchar *contents;
-  gsize length;
-  guint32 timecnt, typecnt;
-  gint transitions_size, ttinfo_map_size;
-  guint8 *ttinfo_map, *ttinfos;
-  gint start_transition = -1;
-  guint32 *transitions;
-  gint32 offset;
-  guint8 isdst;
-  guint8 name_offset;
-  gint i;
-
-  contents = g_mapped_file_get_contents (tz_file->file);
-  length = g_mapped_file_get_length (tz_file->file);
-
-  if (length < TZ_HEADER_SIZE ||
-      (strncmp (contents, TZ_MAGIC, TZ_MAGIC_LEN) != 0))
-    {
-      return FALSE;
-    }
-
-  timecnt = GUINT32_FROM_BE (*(guint32 *)(contents + TZ_TIMECNT_OFFSET));
-  typecnt = GUINT32_FROM_BE (*(guint32 *)(contents + TZ_TYPECNT_OFFSET));
-
-  transitions = (guint32 *)(contents + TZ_TRANSITIONS_OFFSET);
-  transitions_size = timecnt * sizeof (*transitions);
-  ttinfo_map = (void *)(contents + TZ_TRANSITIONS_OFFSET + transitions_size);
-  ttinfo_map_size = timecnt;
-  ttinfos = (void *)(ttinfo_map + ttinfo_map_size);
-
-  /*
-   * Find the first transition that contains the 'start' time
-   */
-  for (i = 1; i < timecnt && start_transition == -1; i++)
-    {
-      gint32 transition_time = GINT32_FROM_BE (transitions[i]);
-
-      /* if is_utc is not set, we need to add this time offset to compare with
-       * start, because it is already on the timezone time */
-      if (!is_utc)
-        {
-          gint32 off;
-
-          off = *(gint32 *)(ttinfos + ttinfo_map[i] * TZ_TTINFO_SIZE +
-                TZ_TTINFO_GMTOFF_OFFSET);
-          off = GINT32_FROM_BE (off);
-
-          transition_time += off;
-        }
-
-      if (transition_time > start)
-        {
-          start_transition = ttinfo_map[i - 1];
-          break;
-        }
-    }
-
-  if (start_transition == -1)
-    {
-      if (timecnt)
-        start_transition = ttinfo_map[timecnt - 1];
-      else
-        start_transition = 0;
-    }
-
-  /* Copy the data out of the corresponding ttinfo structs */
-  offset = *(gint32 *)(ttinfos + start_transition * TZ_TTINFO_SIZE +
-           TZ_TTINFO_GMTOFF_OFFSET);
-  offset = GINT32_FROM_BE (offset);
-  isdst = *(ttinfos + start_transition * TZ_TTINFO_SIZE +
-          TZ_TTINFO_ISDST_OFFSET);
-  name_offset = *(ttinfos + start_transition * TZ_TTINFO_SIZE +
-                TZ_TTINFO_NAME_OFFSET);
-
-  if (_name != NULL)
-    *_name = g_strdup ((gchar*) (ttinfos + TZ_TTINFO_SIZE * typecnt + name_offset));
-
-  if (_offset)
-    *_offset = offset;
-
-  if (_is_dst)
-    *_is_dst = isdst;
-
-  return TRUE;
-}
+  gint64 days;
 
-static GTimeZoneFile *
-g_time_zone_file_new (const gchar *filename)
-{
-  GMappedFile *tz_file;
-  GTimeZoneFile *retval;
+  days = (year - 1) * 365 + ((year - 1) / 4) - ((year - 1) / 100)
+      + ((year - 1) / 400);
 
-  tz_file = g_mapped_file_new (filename, FALSE, NULL);
-  if (tz_file == NULL)
-    return NULL;
+  days += days_in_year[0][month - 1];
+  if (GREGORIAN_LEAP (year) && month > 2)
+    day++;
 
-  retval = g_slice_new (GTimeZoneFile);
-  retval->filename = g_strdup (filename);
-  retval->file = tz_file;
-  retval->ref_count = 1;
+  days += day;
 
-  return retval;
+  return days;
 }
 
-static GTimeZoneFile *
-g_time_zone_file_ref (GTimeZoneFile *tz_file)
-{
-  if (tz_file == NULL)
-    return NULL;
-
-  g_atomic_int_inc (&tz_file->ref_count);
-
-  return tz_file;
-}
-
-static void
-g_time_zone_file_unref (GTimeZoneFile *tz_file)
-{
-  if (tz_file == NULL)
-    return;
-
-  if (g_atomic_int_dec_and_test (&tz_file->ref_count))
-    {
-      /* remove the TimeZoneFile from the hash table of known files */
-      G_LOCK (time_zone_files);
-      g_hash_table_remove (time_zone_files, tz_file->filename);
-      G_UNLOCK (time_zone_files);
-
-      g_mapped_file_unref (tz_file->file);
-      g_free (tz_file->filename);
-      g_slice_free (GTimeZoneFile, tz_file);
-    }
-}
+/* Internal state transformers {{{1 */
 
 /*< internal >
- * g_time_zone_file_get_for_path:
- * @path: the full path for a time zone file
- *
- * Retrieves a #GTimeZoneFile for the given path.
- *
- * If a time zone file for the same path has been already requested,
- * this function will return the same #GTimeZoneFile with its reference
- * count increased by one; otherwise, a new #GTimeZoneFile will be created
- * and added to the known time zone files.
- *
- * The time zone files are removed from the list of known files when their
- * reference count reaches 0.
- *
- * This function holds the lock on the time_zone_files hash table.
- *
- * Return value: a #GTimeZoneFile or %NULL
- */
-static GTimeZoneFile *
-g_time_zone_file_get_for_path (const gchar *path)
-{
-  GTimeZoneFile *retval;
-
-  G_LOCK (time_zone_files);
-
-  if (G_LIKELY (time_zone_files != NULL))
-    {
-      retval = g_hash_table_lookup (time_zone_files, path);
-      if (retval != NULL)
-        {
-          retval = g_time_zone_file_ref (retval);
-          goto out;
-        }
-    }
-
-  retval = g_time_zone_file_new (path);
-  if (retval != NULL)
-    {
-      if (G_UNLIKELY (time_zone_files == NULL))
-        time_zone_files = g_hash_table_new (g_str_hash, g_str_equal);
-
-      g_hash_table_insert (time_zone_files, retval->filename, retval);
-    }
-
-out:
-  G_UNLOCK (time_zone_files);
-
-  return retval;
-}
-
-/**
- * g_time_zone_new:
- * @offset: the timezone offset from UTC, in seconds
- * @is_dst: whether the timezone is in Daylight Saving Time or not
- *
- * Creates a new #GTimeZone for the given @offset, to be used when
- * creating a #GDateTime.
- *
- * The #GTimeZone created will not be floating.
- *
- * Return value: (transfer full): the newly allocated #GTimeZone
- *
- * Since: 2.26
- */
-GTimeZone *
-g_time_zone_new (gint     offset,
-                 gboolean is_dst)
-{
-  GTimeZone *tz;
-
-  g_return_val_if_fail (offset >= -12 * SECS_PER_HOUR && offset <= 12 * SECS_PER_HOUR, NULL);
-
-  tz = g_slice_new (GTimeZone);
-  tz->offset = offset;
-  tz->is_dst = is_dst;
-  tz->tz_file = NULL;
-  tz->is_floating = FALSE;
-
-  if (tz->offset == 0)
-    tz->name = g_strdup_printf ("UTC");
-  else
-    tz->name = g_strdup_printf ("UTC%c%02d:%02d",
-                                tz->offset > 0 ? '+' : '-',
-                                (int) tz->offset / 3600,
-                                (int) tz->offset / 60 % 60);
-
-  return tz;
-}
-
-/**
- * g_time_zone_new_for_name:
- * @name: an ASCII string containing the Olson's database name of the
- *   timezone, e.g. "America/New_York" or "Europe/London"
- *
- * Creates a new #GTimeZone for the given @name.
- *
- * The returned timezone is "floating": if queried, it will return invalid
- * values. A floating #GTimeZone is "sunk" when it is bound to a #GDateTime
- * object.
- *
- * For instance:
- *
- * |[
- * /&ast; the time zone is "floating" &ast;/
- * GTimeZone *tz = g_time_zone_new_for_name ("Europe/London");
- *
- * /&ast; this will print: "UTC offset: 0 seconds" &ast;/
- * g_print ("UTC offset: %d seconds\n", g_time_zone_get_offset (tz));
- *
- * /&ast; the GDateTime object copies the time zone &ast;/
- * GDateTime *dt = g_date_time_new_full (2010, 9, 15, 12, 0, 0, tz);
- *
- * /&ast; this will print: "UTC offset: 3600 seconds", because Europe/London
- *  &ast; on that date is in daylight saving time
- *  &ast;/
- * g_print ("UTC offset: %d seconds\n",
- *          g_date_time_get_utc_offset (dt) / G_USEC_PER_SEC);
- * ]|
- *
- * Return value: (transfer full): the newly created, floating #GTimeZone
- *   object for the given name, or %NULL if none was found
- *
- * Since: 2.26
- */
-GTimeZone *
-g_time_zone_new_for_name (const gchar *name)
-{
-  GTimeZone *tz;
-  gchar *filename;
-  GTimeZoneFile *tz_file;
-
-  g_return_val_if_fail (name != NULL, NULL);
-
-  filename = get_tzdata_path (name);
-  if (filename == NULL)
-    return NULL;
-
-  tz_file = g_time_zone_file_get_for_path (filename);
-  g_free (filename);
-
-  if (tz_file == NULL)
-    return NULL;
-
-  tz = g_slice_new (GTimeZone);
-  tz->tz_file = tz_file;
-  tz->name = NULL;
-  tz->offset = 0;
-  tz->is_dst = FALSE;
-  tz->is_utc = (strcmp (name, "UTC") == 0);
-  tz->is_floating = TRUE;
-
-  return tz;
-}
-
-/**
- * g_time_zone_new_utc:
- *
- * Creates a new #GTimeZone for UTC.
- *
- * Return value: (transfer full): a newly created #GTimeZone for UTC.
- *   Use g_time_zone_free() when done.
- *
- * Since: 2.26
- */
-GTimeZone *
-g_time_zone_new_utc (void)
-{
-  return g_time_zone_new (0, FALSE);
-}
-
-/**
- * g_time_zone_new_local:
+ * g_date_time_deal_with_date_change:
+ * @datetime: a #GDateTime
  *
- * Creates a new #GTimeZone for the local timezone.
+ * This function should be called whenever the date changes by adding
+ * days, months or years.  It does three things.
  *
- * The returned #GTimeZone is floating.
+ * First, we ensure that the date falls between 0001-01-01 and
+ * 9999-12-31 and return %FALSE if it does not.
  *
- * Return value: (transfer full): a newly created, floating #GTimeZone.
- *   Use g_time_zone_free() when done.
+ * Next we update the ->interval field.
  *
- * Since: 2.26
+ * Finally, we ensure that the resulting date and time pair exists (by
+ * ensuring that our timezone has an interval containing it) and
+ * adjusting as required.  For example, if we have the time 02:30:00 on
+ * March 13 2010 in Toronto and we add 1 day to it, we would end up with
+ * 2:30am on March 14th, which doesn't exist.  In that case, we bump the
+ * time up to 3:00am.
  */
-GTimeZone *
-g_time_zone_new_local (void)
-{
-  return g_time_zone_new_for_name ("localtime");
-}
-
-/**
- * g_time_zone_copy:
- * @time_zone: a #GTimeZone
- *
- * Copies a #GTimeZone. If @time_zone is floating, the returned copy
- * will be floating as well.
- *
- * Return value: (transfer full): the newly created #GTimeZone
- *
- * Since: 2.26
- */
-GTimeZone *
-g_time_zone_copy (const GTimeZone *time_zone)
+static gboolean
+g_date_time_deal_with_date_change (GDateTime *datetime)
 {
-  GTimeZone *retval;
-
-  g_return_val_if_fail (time_zone != NULL, NULL);
-
-  retval = g_slice_dup (GTimeZone, time_zone);
+  GTimeType was_dst;
+  gint64 full_time;
+  gint64 usec;
 
-  if (time_zone->tz_file != NULL)
-    retval->tz_file = g_time_zone_file_ref (time_zone->tz_file);
-
-  if (time_zone->name != NULL)
-    retval->name = g_strdup (time_zone->name);
-
-  return retval;
-}
+  if (datetime->days < 1 || datetime->days > 3652059)
+    return FALSE;
 
-/**
- * g_time_zone_free:
- * @time_zone: a #GTimeZone
- *
- * Frees the resources associated with a #GTimeZone
- *
- * Since: 2.26
- */
-void
-g_time_zone_free (GTimeZone *time_zone)
-{
-  g_return_if_fail (time_zone != NULL);
+  was_dst = g_time_zone_is_dst (datetime->tz, datetime->interval);
 
-  if (time_zone->tz_file != NULL)
-    g_time_zone_file_unref (time_zone->tz_file);
+  full_time = datetime->days * USEC_PER_DAY + datetime->usec;
 
-  g_free (time_zone->name);
-  g_slice_free (GTimeZone, time_zone);
-}
+  usec = full_time % USEC_PER_SECOND;
+  full_time /= USEC_PER_SECOND;
+  full_time -= UNIX_EPOCH_START * SEC_PER_DAY;
 
-/**
- * g_time_zone_get_name:
- * @time_zone: a #GTimeZone
- *
- * Retrieves the name of the @time_zone, or %NULL if the #GTimeZone is
- * floating.
- *
- * Return value: (transfer none): the name of the #GTimeZone. The returned
- *   string is owned by the #GTimeZone and it should never be modified or
- *   freed
- *
- * Since: 2.26
- */
-G_CONST_RETURN gchar *
-g_time_zone_get_name (const GTimeZone *time_zone)
-{
-  g_return_val_if_fail (time_zone != NULL, NULL);
+  datetime->interval = g_time_zone_find_interval (datetime->tz,
+                                                  &was_dst,
+                                                  &full_time);
+  full_time += UNIX_EPOCH_START * SEC_PER_DAY;
+  full_time *= USEC_PER_SECOND;
+  full_time += usec;
 
-  if (!time_zone->is_floating)
-    return time_zone->name;
+  datetime->days = full_time / USEC_PER_DAY;
+  datetime->usec = full_time % USEC_PER_DAY;
 
-  return NULL;
+  /* maybe daylight time caused us to shift to a different day,
+   * but it definitely didn't push us into a different year */
+  return TRUE;
 }
 
-/**
- * g_time_zone_get_offset:
- * @time_zone: a #GTimeZone
+/*< internal >
+ * g_date_time_add_usec:
+ * @datetime: a #GDateTime
+ * @usec: number of microseconds to add
  *
- * Retrieves the offset of the @time_zone, in seconds from UTC, or 0
- * if the #GTimeZone is floating.
+ * This function is the only function that should be used to adjust a
+ * #GDateTime by adding a particular timespan (as measured in hours,
+ * minutes, seconds, microseconds).
  *
- * Return value: the offset from UTC, in seconds
+ * We avoid incontinuities in the local time zone by first converting to
+ * UTC, adding the proper amount of time and converting back.
  *
- * Since: 2.26
+ * Finally, we ensure that the resulting date falls between 0001-01-01
+ * and 9999-12-31, returning %FALSE if it does not.
  */
-gint
-g_time_zone_get_offset (const GTimeZone *time_zone)
+static gboolean
+g_date_time_add_usec (GDateTime *datetime,
+                      gint64     usec)
 {
-  g_return_val_if_fail (time_zone != NULL, 0);
+  GTimeType time_type = G_TIME_TYPE_UNIVERSAL;
+  gint64 full_time;
 
-  if (!time_zone->is_floating)
-    return time_zone->offset;
+  /* This is enough to drive any valid time out of the valid region.
+   * We check for it here to avoid wrapping the full_time below.
+   */
+  if (ABS (usec) > 1000000000000000000)
+   return FALSE;
 
-  return 0;
-}
+  full_time = datetime->days * USEC_PER_DAY + datetime->usec;
+  full_time += usec;
 
-/**
- * g_time_zone_get_is_dst:
- * @time_zone: a #GTimeZone
- *
- * Checks whether the @time_zone is in Daylight Saving Time.
- * If the #GTimeZone is floating, %FALSE is always returned.
- *
- * Return value: %TRUE if the #GTimeZone is in DST, or %FALSE.
- *
- * Since: 2.26
- */
-gboolean
-g_time_zone_get_is_dst (const GTimeZone *time_zone)
-{
-  g_return_val_if_fail (time_zone != NULL, FALSE);
+  usec = full_time % USEC_PER_SECOND;
+  full_time /= USEC_PER_SECOND;
+  full_time -= UNIX_EPOCH_START * SEC_PER_DAY;
 
-  if (!time_zone->is_floating)
-    return time_zone->is_dst;
+  full_time -= g_time_zone_get_offset (datetime->tz, datetime->interval);
+  datetime->interval = g_time_zone_find_interval (datetime->tz,
+                                                  &time_type,
+                                                  &full_time);
+  full_time += g_time_zone_get_offset (datetime->tz, datetime->interval);
 
-  return FALSE;
-}
+  full_time += UNIX_EPOCH_START * SEC_PER_DAY;
+  full_time *= USEC_PER_SECOND;
+  full_time += usec;
 
-/**
- * g_time_zone_is_floating:
- * @time_zone: a #GTimeZone
- *
- * Checks whether @time_zone is floating
- *
- * Return value: %TRUE if the #GTimeZone is floating, and %FALSE otherwise
- *
- * Since: 2.26
- */
-gboolean
-g_time_zone_is_floating (const GTimeZone *time_zone)
-{
-  g_return_val_if_fail (time_zone != NULL, FALSE);
+  datetime->days = full_time / USEC_PER_DAY;
+  datetime->usec = full_time % USEC_PER_DAY;
 
-  return time_zone->is_floating;
+  return 1 <= datetime->days || datetime->days <= 3652059;
 }
 
 /*< internal >
- * g_time_zone_sink:
- * @time_zone: a #GTimeZone
+ * g_date_time_change_zone:
  * @datetime: a #GDateTime
+ * @new_tz: the new #GTimeZone
  *
- * Sinks the floating state of @time_zone by associating it with the
- * given #GDateTime.
+ * This function is the only function that should be used to change the
+ * timezone of a #GDateTime.
  *
- * If @time_zone is not floating, this function does not do anything
+ * First, we convert the time to UTC using the old time zone.  Then we
+ * install the new timezone and convert ourselves into it.
+ *
+ * Finally, we ensure that the resulting date falls between 0001-01-01
+ * and 9999-12-31, returning %FALSE if it does not.
  */
-static void
-g_time_zone_sink (GTimeZone *time_zone,
-                  GDateTime *datetime)
-{
-  gint64 offset, epoch;
-  gboolean is_dst, is_utc;
-  gchar *abbrev;
-
-  if (!time_zone->is_floating)
-    return;
-
-  epoch = g_date_time_to_epoch (datetime);
-  is_utc = time_zone->is_utc;
-  offset = 0;
-  is_dst = FALSE;
-  abbrev = NULL;
-  if (parse_tzdata (time_zone->tz_file, epoch, is_utc, &offset, &is_dst, &abbrev))
-    {
-      time_zone->offset = offset;
-      time_zone->is_dst = is_dst;
-      time_zone->name = abbrev;
-      time_zone->is_utc = (offset == 0);
-      time_zone->is_floating = FALSE;
-    }
-}
-
-static inline gint
-date_to_proleptic_gregorian (gint year,
-                             gint month,
-                             gint day)
-{
-  gint64 days;
-
-  days = (year - 1) * 365 + ((year - 1) / 4) - ((year - 1) / 100)
-      + ((year - 1) / 400);
-
-  days += days_in_year[0][month - 1];
-  if (GREGORIAN_LEAP (year) && month > 2)
-    day++;
-
-  days += day;
-
-  return days;
-}
-
-static inline void g_date_time_add_usec (GDateTime *datetime,
-                                         gint64     usecs);
-
-static inline void
-g_date_time_add_days_internal (GDateTime *datetime,
-                               gint64     days)
+static gboolean
+g_date_time_change_zone (GDateTime *datetime,
+                         GTimeZone *new_tz)
 {
-  gboolean was_dst = FALSE;
-  gint64 old_offset = 0;
-
-  if (datetime->tz != NULL && datetime->tz->tz_file != NULL)
+  if (datetime->tz != new_tz)
     {
-      was_dst = g_time_zone_get_is_dst (datetime->tz);
-      old_offset = g_time_zone_get_offset (datetime->tz);
-
-      datetime->tz->is_floating = TRUE;
-    }
-
-  datetime->days += days;
+      GTimeType time_type = G_TIME_TYPE_UNIVERSAL;
+      gint64 full_time;
+      gint64 usec;
 
-  if (datetime->tz != NULL && datetime->tz->tz_file != NULL)
-    {
-      gint64 offset;
+      full_time = datetime->days * USEC_PER_DAY + datetime->usec;
 
-      g_time_zone_sink (datetime->tz, datetime);
+      usec = full_time % USEC_PER_SECOND;
+      full_time /= USEC_PER_SECOND;
+      full_time -= UNIX_EPOCH_START * SEC_PER_DAY;
 
-      if (was_dst == g_time_zone_get_is_dst (datetime->tz))
-        return;
+      full_time -= g_time_zone_get_offset (datetime->tz, datetime->interval);
+      g_time_zone_unref (datetime->tz);
+      datetime->tz = g_time_zone_ref (new_tz);
+      datetime->interval = g_time_zone_find_interval (datetime->tz,
+                                                      &time_type,
+                                                      &full_time);
+      full_time += g_time_zone_get_offset (datetime->tz, datetime->interval);
 
-      offset = old_offset - g_time_zone_get_offset (datetime->tz);
-      g_date_time_add_usec (datetime, offset * USEC_PER_SECOND * -1);
-    }
-}
+      full_time += UNIX_EPOCH_START * SEC_PER_DAY;
+      full_time *= USEC_PER_SECOND;
+      full_time += usec;
 
-static inline void
-g_date_time_add_usec (GDateTime *datetime,
-                      gint64     usecs)
-{
-  gint64 u = datetime->usec + usecs;
-  gint d = u / USEC_PER_DAY;
-  gboolean was_dst = FALSE;
-  gint64 old_offset = 0;
-
-  /* if we are using a time zone from a zoneinfo we want to
-   * check for changes in the DST and update the DateTime
-   * accordingly in case we change for standard time to DST
-   * and vice versa
-   */
-  if (datetime->tz != NULL && datetime->tz->tz_file != NULL)
-    {
-      was_dst = g_time_zone_get_is_dst (datetime->tz);
-      old_offset = g_time_zone_get_offset (datetime->tz);
+      datetime->days = full_time / USEC_PER_DAY;
+      datetime->usec = full_time % USEC_PER_DAY;
 
-      /* force the floating state */
-      datetime->tz->is_floating = TRUE;
+      return 1 <= datetime->days || datetime->days <= 3652059;
     }
 
-  if (u < 0)
-    d -= 1;
-
-  if (d != 0)
-    g_date_time_add_days_internal (datetime, d);
-
-  if (u < 0)
-    datetime->usec = USEC_PER_DAY + (u % USEC_PER_DAY);
-  else
-    datetime->usec = u % USEC_PER_DAY;
-
-  if (datetime->tz != NULL && datetime->tz->tz_file != NULL)
-    {
-      gint64 offset;
-
-      /* sink the timezone; if there were no changes in the
-       * DST state then bail out; otherwise, apply the change
-       * in the offset to the DateTime
-       */
-      g_time_zone_sink (datetime->tz, datetime);
-
-      if (was_dst == g_time_zone_get_is_dst (datetime->tz))
-        return;
-
-      offset = old_offset - g_time_zone_get_offset (datetime->tz);
-      g_date_time_add_usec (datetime, offset * USEC_PER_SECOND * -1);
-    }
+  return TRUE;
 }
 
 /*< internal >
@@ -962,7 +493,7 @@ g_date_time_add_usec (GDateTime *datetime,
  * This function modifies the passed #GDateTime so public accessors
  * should make always pass a copy
  */
-static inline void
+static gboolean
 g_date_time_add_ymd (GDateTime *datetime,
                      gint       years,
                      gint       months,
@@ -1006,10 +537,13 @@ g_date_time_add_ymd (GDateTime *datetime,
   if (max_days[m] < d)
     d = max_days[m];
 
-  datetime->days = date_to_proleptic_gregorian (y, m, d);
-  g_date_time_add_days_internal (datetime, days);
+  datetime->days = date_to_proleptic_gregorian (y, m, d) + days;
+
+  return g_date_time_deal_with_date_change (datetime);
 }
 
+/* Lifecycle {{{1 */
+
 static GDateTime *
 g_date_time_new (void)
 {
@@ -1040,25 +574,12 @@ g_date_time_copy (const GDateTime *datetime)
   copied = g_date_time_new ();
   copied->days = datetime->days;
   copied->usec = datetime->usec;
-
-  if (datetime->tz != NULL)
-    copied->tz = g_time_zone_copy (datetime->tz);
+  copied->tz = g_time_zone_ref (datetime->tz);
+  copied->interval = datetime->interval;
 
   return copied;
 }
 
-static void
-g_date_time_free (GDateTime *datetime)
-{
-  if (G_UNLIKELY (datetime == NULL))
-    return;
-
-  if (datetime->tz != NULL)
-    g_time_zone_free (datetime->tz);
-
-  g_slice_free (GDateTime, datetime);
-}
-
 /**
  * g_date_time_ref:
  * @datetime: a #GDateTime
@@ -1098,9 +619,14 @@ g_date_time_unref (GDateTime *datetime)
   g_return_if_fail (datetime->ref_count > 0);
 
   if (g_atomic_int_dec_and_test (&datetime->ref_count))
-    g_date_time_free (datetime);
+    {
+      g_time_zone_unref (datetime->tz);
+      g_slice_free (GDateTime, datetime);
+    }
 }
 
+/* Public API {{{1 */
+
 static void
 g_date_time_get_week_number (const GDateTime *datetime,
                              gint            *week_number,
@@ -1889,12 +1415,11 @@ g_date_time_get_second (const GDateTime *datetime)
 GTimeSpan
 g_date_time_get_utc_offset (const GDateTime *datetime)
 {
-  gint offset = 0;
+  gint offset;
 
   g_return_val_if_fail (datetime != NULL, 0);
 
-  if (datetime->tz != NULL)
-    offset = g_time_zone_get_offset (datetime->tz);
+  offset = g_time_zone_get_offset (datetime->tz, datetime->interval);
 
   return (gint64) offset * USEC_PER_SECOND;
 }
@@ -1916,10 +1441,7 @@ g_date_time_get_timezone_name (const GDateTime *datetime)
 {
   g_return_val_if_fail (datetime != NULL, NULL);
 
-  if (datetime->tz != NULL)
-    return g_time_zone_get_name (datetime->tz);
-
-  return "UTC";
+  return g_time_zone_get_name (datetime->tz, datetime->interval);
 }
 
 /**
@@ -1986,22 +1508,15 @@ g_date_time_equal (gconstpointer dt1,
                    gconstpointer dt2)
 {
   const GDateTime *a, *b;
-  GDateTime *a_utc, *b_utc;
-  gint64 a_epoch, b_epoch;
+  gint64 at, bt;
 
   a = dt1;
   b = dt2;
 
-  a_utc = g_date_time_to_utc (a);
-  b_utc = g_date_time_to_utc (b);
-
-  a_epoch = g_date_time_to_epoch (a_utc);
-  b_epoch = g_date_time_to_epoch (b_utc);
+  at = a->days * USEC_PER_DAY + a->usec - g_date_time_get_utc_offset (a);
+  bt = b->days * USEC_PER_DAY + b->usec - g_date_time_get_utc_offset (b);
 
-  g_date_time_unref (a_utc);
-  g_date_time_unref (b_utc);
-
-  return a_epoch == b_epoch;
+  return at == bt;
 }
 
 /**
@@ -2043,10 +1558,7 @@ g_date_time_is_daylight_savings (const GDateTime *datetime)
 {
   g_return_val_if_fail (datetime != NULL, FALSE);
 
-  if (datetime->tz == NULL)
-    return FALSE;
-
-  return datetime->tz->is_dst;
+  return g_time_zone_is_dst (datetime->tz, datetime->interval);
 }
 
 /**
@@ -2078,7 +1590,8 @@ g_date_time_new_from_date (gint year,
 
   dt->days = date_to_proleptic_gregorian (year, month, day);
   dt->tz = g_time_zone_new_local ();
-  g_time_zone_sink (dt->tz, dt);
+
+  g_date_time_deal_with_date_change (dt);
 
   return dt;
 }
@@ -2089,7 +1602,7 @@ g_date_time_new_from_date (gint year,
  *
  * Creates a new #GDateTime using the time since Jan 1, 1970 specified by @t.
  *
- * The timezone of the returned #GDateTime is the local time.
+ * The timezone of the returned #GDateTime is local time.
  *
  * Return value: the newly created #GDateTime
  *
@@ -2099,47 +1612,27 @@ GDateTime*
 g_date_time_new_from_epoch (gint64 t)
 {
   GDateTime *datetime;
-  GTimeZone *tz;
-  struct tm tm;
-  time_t tt;
-
-  tt = (time_t) t;
-  memset (&tm, 0, sizeof (tm));
-
-  /* XXX: GLib should probably have a wrapper for this */
-#ifdef HAVE_LOCALTIME_R
-  localtime_r (&tt, &tm);
-#else
-  {
-    struct tm *ptm = localtime (&tt);
-
-    if (ptm == NULL)
-      {
-        /* Happens at least in Microsoft's C library if you pass a
-         * negative time_t. Use 2000-01-01 as default date.
-         */
-#ifndef G_DISABLE_CHECKS
-        g_return_if_fail_warning (G_LOG_DOMAIN, G_STRFUNC, "ptm != NULL");
-#endif
+  gint64 days;
+  gint64 usec;
 
-        tm.tm_mon = 0;
-        tm.tm_mday = 1;
-        tm.tm_year = 100;
-      }
-    else
-      memcpy ((void *) &tm, (void *) ptm, sizeof (struct tm));
-  }
-#endif /* HAVE_LOCALTIME_R */
-
-  tz = g_time_zone_new_local ();
-  datetime = g_date_time_new_full (tm.tm_year + 1900,
-                                   tm.tm_mon  + 1,
-                                   tm.tm_mday,
-                                   tm.tm_hour,
-                                   tm.tm_min,
-                                   tm.tm_sec,
-                                   tz);
-  g_time_zone_free (tz);
+  t *= 1000000;
+  t += UNIX_EPOCH_START * USEC_PER_DAY;
+
+  if (t < 0)
+    return NULL;
+
+  days = t / USEC_PER_DAY;
+  usec = t % USEC_PER_DAY;
+
+  if (days < 1 || days > 3652059)
+    return NULL;
+
+  datetime = g_date_time_new ();
+  datetime->tz = g_time_zone_new_utc ();
+  datetime->days = days;
+  datetime->usec = usec;
+
+  g_date_time_change_zone (datetime, g_time_zone_new_local ());
 
   return datetime;
 }
@@ -2150,7 +1643,7 @@ g_date_time_new_from_epoch (gint64 t)
  *
  * Creates a new #GDateTime using the date and time specified by #GTimeVal.
  *
- * The timezone of the returned #GDateTime is UTC.
+ * The timezone of the returned #GDateTime is local time.
  *
  * Return value: the newly created #GDateTime
  *
@@ -2165,8 +1658,6 @@ g_date_time_new_from_timeval (GTimeVal *tv)
 
   datetime = g_date_time_new_from_epoch (tv->tv_sec);
   datetime->usec += tv->tv_usec;
-  datetime->tz = g_time_zone_new_local ();
-  g_time_zone_sink (datetime->tz, datetime);
 
   return datetime;
 }
@@ -2198,7 +1689,7 @@ g_date_time_new_full (gint             year,
                       gint             hour,
                       gint             minute,
                       gdouble          second,
-                      const GTimeZone *time_zone)
+                      const gchar     *time_zone)
 {
   GDateTime *dt;
 
@@ -2212,13 +1703,8 @@ g_date_time_new_full (gint             year,
            + (minute * USEC_PER_MINUTE)
            + (second * USEC_PER_SECOND);
 
-  if (time_zone != NULL)
-    {
-      dt->tz = g_time_zone_copy (time_zone);
-      g_time_zone_sink (dt->tz, dt);
-    }
-  else
-    dt->tz = NULL;
+  dt->tz = g_time_zone_new (time_zone);
+  g_date_time_deal_with_date_change (dt);
 
   return dt;
 }
@@ -2507,19 +1993,17 @@ GDateTime *
 g_date_time_to_local (const GDateTime *datetime)
 {
   GDateTime *dt;
+  GTimeZone *local;
 
-  g_return_val_if_fail (datetime != NULL, NULL);
+  local = g_time_zone_new_local ();
 
   dt = g_date_time_copy (datetime);
-  if (dt->tz == NULL)
+  if (!g_date_time_change_zone (dt, local))
     {
-      dt->tz = g_time_zone_new_local ();
-      if (dt->tz == NULL)
-        return dt;
-
-      g_time_zone_sink (dt->tz, dt);
-      g_date_time_add_usec (dt, dt->tz->offset * USEC_PER_SECOND);
+      g_date_time_unref (dt);
+      dt = NULL;
     }
+  g_time_zone_unref (local);
 
   return dt;
 }
@@ -2588,17 +2072,18 @@ GDateTime *
 g_date_time_to_utc (const GDateTime *datetime)
 {
   GDateTime *dt;
-  GTimeSpan ts;
+  GTimeZone *utc;
 
   g_return_val_if_fail (datetime != NULL, NULL);
 
-  ts = g_date_time_get_utc_offset (datetime) * -1;
-  dt = g_date_time_add (datetime, ts);
-  if (dt->tz != NULL)
+  utc = g_time_zone_new_utc ();
+  dt = g_date_time_copy (datetime);
+  if (!g_date_time_change_zone (dt, utc))
     {
-      g_time_zone_free (dt->tz);
-      dt->tz = NULL;
+      g_date_time_unref (dt);
+      dt = NULL;
     }
+  g_time_zone_unref (utc);
 
   return dt;
 }
@@ -2667,3 +2152,6 @@ g_date_time_get_week_of_year (const GDateTime *datetime)
 
   return weeknum;
 }
+
+/* Epilogue {{{1 */
+/* vim:set foldmethod=marker: */
diff --git a/glib/gdatetime.h b/glib/gdatetime.h
index 727a855..471414c 100644
--- a/glib/gdatetime.h
+++ b/glib/gdatetime.h
@@ -84,16 +84,6 @@ G_BEGIN_DECLS
 typedef gint64 GTimeSpan;
 
 /**
- * GTimeZone:
- *
- * <structname>GTimeZone</structname> is an opaque structure whose members
- * cannot be accessed directly.
- *
- * Since: 2.26
- */
-typedef struct _GTimeZone GTimeZone;
-
-/**
  * GDateTime:
  *
  * <structname>GDateTime</structname> is an opaque structure whose members
@@ -117,7 +107,7 @@ GDateTime *           g_date_time_new_full               (gint             year,
                                                           gint             hour,
                                                           gint             minute,
                                                           gdouble          second,
-                                                          const GTimeZone *time_zone);
+                                                          const gchar     *time_zone);
 
 GDateTime *           g_date_time_ref                    (GDateTime       *datetime);
 void                  g_date_time_unref                  (GDateTime       *datetime);
@@ -195,18 +185,6 @@ GDateTime *           g_date_time_to_utc                 (const GDateTime *datet
 gchar *               g_date_time_format                 (const GDateTime *datetime,
                                                           const gchar     *format) G_GNUC_MALLOC;
 
-GTimeZone *           g_time_zone_new                    (gint             offset,
-                                                          gboolean         is_dst);
-GTimeZone *           g_time_zone_new_for_name           (const gchar     *name);
-GTimeZone *           g_time_zone_new_utc                (void);
-GTimeZone *           g_time_zone_new_local              (void);
-GTimeZone *           g_time_zone_copy                   (const GTimeZone *time_zone);
-void                  g_time_zone_free                   (GTimeZone       *time_zone);
-G_CONST_RETURN gchar *g_time_zone_get_name               (const GTimeZone *time_zone);
-gint                  g_time_zone_get_offset             (const GTimeZone *time_zone);
-gboolean              g_time_zone_get_is_dst             (const GTimeZone *time_zone);
-gboolean              g_time_zone_is_floating            (const GTimeZone *time_zone);
-
 G_END_DECLS
 
 #endif /* __G_DATE_TIME_H__ */
diff --git a/glib/glib.h b/glib/glib.h
index cec8f32..06d0190 100644
--- a/glib/glib.h
+++ b/glib/glib.h
@@ -82,6 +82,7 @@
 #include <glib/gthread.h>
 #include <glib/gthreadpool.h>
 #include <glib/gtimer.h>
+#include <glib/gtimezone.h>
 #include <glib/gtree.h>
 #include <glib/gtypes.h>
 #include <glib/gunicode.h>
diff --git a/glib/glib.symbols b/glib/glib.symbols
index 4f34949..3a30916 100644
--- a/glib/glib.symbols
+++ b/glib/glib.symbols
@@ -364,23 +364,27 @@ g_date_time_new_full
 g_date_time_new_now
 g_date_time_new_today
 g_date_time_new_utc_now
-g_date_time_printf G_GNUC_MALLOC
+g_date_time_format G_GNUC_MALLOC
 g_date_time_ref
 g_date_time_to_local
 g_date_time_to_epoch
 g_date_time_to_timeval
 g_date_time_to_utc
 g_date_time_unref
-g_time_zone_copy
-g_time_zone_free
-g_time_zone_get_is_dst
-g_time_zone_get_name
-g_time_zone_get_offset
-g_time_zone_is_floating
+#endif
+#endif
+
+#if IN_HEADER(__G_TIME_ZONE_H__)
+#if IN_FILE(__G_TIME_ZONE_C__)
 g_time_zone_new
-g_time_zone_new_for_name
 g_time_zone_new_local
 g_time_zone_new_utc
+g_time_zone_ref
+g_time_zone_unref
+g_time_zone_find_interval
+g_time_zone_get_name
+g_time_zone_get_offset
+g_time_zone_is_dst
 #endif
 #endif
 
diff --git a/glib/gtimezone.c b/glib/gtimezone.c
new file mode 100644
index 0000000..761bf08
--- /dev/null
+++ b/glib/gtimezone.c
@@ -0,0 +1,514 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+/* Prologue {{{1 */
+
+#include "gtimezone.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <signal.h>
+
+#include "gmappedfile.h"
+#include "gtestutils.h"
+#include "gfileutils.h"
+#include "gstrfuncs.h"
+#include "ghash.h"
+#include "gthread.h"
+#include "gbuffer.h"
+
+/* zoneinfo file format {{{1 */
+
+/* unaligned */
+typedef struct { gchar bytes[8]; } gint64_be;
+typedef struct { gchar bytes[4]; } gint32_be;
+typedef struct { gchar bytes[4]; } guint32_be;
+
+static inline gint64 gint64_from_be (const gint64_be be) {
+  gint64 tmp; memcpy (&tmp, &be, sizeof tmp); return GINT64_FROM_BE (tmp);
+}
+
+static inline gint32 gint32_from_be (const gint32_be be) {
+  gint32 tmp; memcpy (&tmp, &be, sizeof tmp); return GINT32_FROM_BE (tmp);
+}
+
+static inline guint32 guint32_from_be (const guint32_be be) {
+  guint32 tmp; memcpy (&tmp, &be, sizeof tmp); return GUINT32_FROM_BE (tmp);
+}
+
+struct tzhead
+{
+  gchar      tzh_magic[4];
+  gchar      tzh_version;
+  guchar     tzh_reserved[15];
+
+  guint32_be tzh_ttisgmtcnt;
+  guint32_be tzh_ttisstdcnt;
+  guint32_be tzh_leapcnt;
+  guint32_be tzh_timecnt;
+  guint32_be tzh_typecnt;
+  guint32_be tzh_charcnt;
+};
+
+struct ttinfo
+{
+  gint32_be tt_gmtoff;
+  guint8    tt_isdst;
+  guint8    tt_abbrind;
+};
+
+/* GTimeZone structure and lifecycle {{{1 */
+struct _GTimeZone
+{
+  gchar   *name;
+
+  GBuffer *zoneinfo;
+
+  const struct tzhead *header;
+  const struct ttinfo *infos;
+  const gint64_be     *trans;
+  const guint8        *indices;
+  const gchar         *abbrs;
+  gint                 timecnt;
+
+  gint     ref_count;
+};
+
+G_LOCK_DEFINE_STATIC (time_zones);
+static GHashTable/*<string?, GTimeZone>*/ *time_zones;
+
+static guint
+g_str_hash0 (gconstpointer data)
+{
+  return data ? g_str_hash (data) : 0;
+}
+
+static gboolean
+g_str_equal0 (gconstpointer a,
+              gconstpointer b)
+{
+  if (a == b)
+    return TRUE;
+
+  if (!a || !b)
+    return FALSE;
+
+  return g_str_equal (a, b);
+}
+
+void
+g_time_zone_unref (GTimeZone *tz)
+{
+  g_assert (tz->ref_count > 0);
+
+  if (g_atomic_int_dec_and_test (&tz->ref_count))
+    {
+      G_LOCK(time_zones);
+      g_hash_table_remove (time_zones, tz->name);
+      G_UNLOCK(time_zones);
+
+      if (tz->zoneinfo)
+        g_buffer_unref (tz->zoneinfo);
+
+      g_free (tz->name);
+
+      g_slice_free (GTimeZone, tz);
+    }
+}
+
+GTimeZone *
+g_time_zone_ref (GTimeZone *tz)
+{
+  g_assert (tz->ref_count > 0);
+
+  g_atomic_int_inc (&tz->ref_count);
+
+  return tz;
+}
+
+/* fake zoneinfo creation (for RFC3339/ISO 8601 timezones) {{{1 */
+/*
+ * parses strings of the form 'hh' 'hhmm' or 'hh:mm' where:
+ *  - hh is 00 to 23
+ *  - mm is 00 to 59
+ */
+gboolean
+parse_time (const gchar *time,
+            gint32      *offset)
+{
+  if (*time < '0' || '2' < *time)
+    return FALSE;
+
+  *offset = 10 * 60 * 60 * (*time++ - '0');
+
+  if (*time < '0' || '9' < *time)
+    return FALSE;
+
+  *offset += 60 * 60 * (*time++ - '0');
+
+  if (*offset > 23 * 60 * 60)
+    return FALSE;
+
+  if (*time == '\0')
+    return TRUE;
+
+  if (*time == ':')
+    time++;
+
+  if (*time < '0' || '5' < *time)
+    return FALSE;
+
+  *offset += 10 * 60 * (*time++ - '0');
+
+  if (*time < '0' || '9' < *time)
+    return FALSE;
+
+  *offset += 60 * (*time++ - '0');
+
+  return *time == '\0';
+}
+
+gboolean
+parse_constant_offset (const gchar *name,
+                       gint32      *offset)
+{
+  switch (*name++)
+    {
+    case 'Z':
+      *offset = 0;
+      return !*name;
+
+    case '+':
+      return parse_time (name, offset);
+
+    case '-':
+      if (parse_time (name, offset))
+        {
+          *offset = -*offset;
+          return TRUE;
+        }
+
+    default:
+      return FALSE;
+    }
+}
+
+static GBuffer *
+zone_for_constant_offset (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;
+
+  if (name == NULL || !parse_constant_offset (name, &offset))
+    return NULL;
+
+  offset = GINT32_TO_BE (offset);
+
+  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_buffer_new_take_data (fake, sizeof *fake);
+}
+
+/* Construction {{{1 */
+GTimeZone *
+g_time_zone_new (const gchar *name)
+{
+  GTimeZone *tz;
+
+  G_LOCK (time_zones);
+  if (time_zones == NULL)
+    time_zones = g_hash_table_new (g_str_hash0,
+                                   g_str_equal0);
+
+  tz = g_hash_table_lookup (time_zones, name);
+  if (tz == NULL)
+    {
+      tz = g_slice_new0 (GTimeZone);
+      tz->name = g_strdup (name);
+      tz->ref_count = 0;
+
+      tz->zoneinfo = zone_for_constant_offset (name);
+
+      if (tz->zoneinfo == NULL)
+        {
+          gchar *filename;
+
+          if (name != NULL)
+            {
+              const gchar *tzdir;
+
+              tzdir = getenv ("TZDIR");
+              if (tzdir == NULL)
+                tzdir = "/usr/share/zoneinfo";
+
+              filename = g_build_filename (tzdir, name, NULL);
+            }
+          else
+            filename = g_strdup ("/etc/localtime");
+
+          tz->zoneinfo = (GBuffer *) g_mapped_file_new (filename, FALSE, NULL);
+          g_free (filename);
+        }
+
+      if (tz->zoneinfo != NULL)
+        {
+          const struct tzhead *header = tz->zoneinfo->data;
+          gsize size = tz->zoneinfo->size;
+
+          /* we only bother to support version 2 */
+          if (size < sizeof (struct tzhead) || memcmp (header, "TZif2", 5))
+            {
+              g_buffer_unref (tz->zoneinfo);
+              tz->zoneinfo = NULL;
+            }
+          else
+            {
+              gint typecnt;
+
+              /* we trust the file completely. */
+              tz->header = (const struct tzhead *)
+                (((const gchar *) (header + 1)) +
+                  guint32_from_be(header->tzh_ttisgmtcnt) +
+                  guint32_from_be(header->tzh_ttisstdcnt) +
+                  8 * guint32_from_be(header->tzh_leapcnt) +
+                  5 * guint32_from_be(header->tzh_timecnt) +
+                  6 * guint32_from_be(header->tzh_typecnt) +
+                  guint32_from_be(header->tzh_charcnt));
+
+              typecnt     = guint32_from_be (tz->header->tzh_typecnt);
+              tz->timecnt = guint32_from_be (tz->header->tzh_timecnt);
+              tz->trans   = (gconstpointer) (tz->header + 1);
+              tz->indices = (gconstpointer) (tz->trans + tz->timecnt);
+              tz->infos   = (gconstpointer) (tz->indices + tz->timecnt);
+              tz->abbrs   = (gconstpointer) (tz->infos + typecnt);
+            }
+        }
+
+      g_hash_table_insert (time_zones, tz->name, tz);
+    }
+  g_atomic_int_inc (&tz->ref_count);
+  G_UNLOCK (time_zones);
+
+  return tz;
+}
+
+GTimeZone *
+g_time_zone_new_utc (void)
+{
+  return g_time_zone_new ("");
+}
+
+GTimeZone *
+g_time_zone_new_local (void)
+{
+  return g_time_zone_new (getenv ("TZ"));
+}
+
+/* Internal helpers {{{1 */
+inline static const struct ttinfo *
+interval_info (GTimeZone *tz,
+               gint       interval)
+{
+  if (interval)
+    return tz->infos + tz->indices[interval - 1];
+
+  return tz->infos;
+}
+
+inline static gint64
+interval_start (GTimeZone *tz,
+                gint       interval)
+{
+  if (interval)
+    return gint64_from_be (tz->trans[interval - 1]);
+
+  return G_MININT64;
+}
+
+inline static gint64
+interval_end (GTimeZone *tz,
+              gint       interval)
+{
+  if (interval < tz->timecnt)
+    return gint64_from_be (tz->trans[interval]);
+
+  return G_MAXINT64;
+}
+
+inline static gint32
+interval_offset (GTimeZone *tz,
+                 gint       interval)
+{
+  return gint32_from_be (interval_info (tz, interval)->tt_gmtoff);
+}
+
+inline static gboolean
+interval_isdst (GTimeZone *tz,
+                gint       interval)
+{
+  return interval_info (tz, interval)->tt_isdst;
+}
+
+inline static guint8
+interval_abbrind (GTimeZone *tz,
+                  gint       interval)
+{
+  return interval_info (tz, interval)->tt_abbrind;
+}
+
+inline static gint64
+interval_local_start (GTimeZone *tz,
+                      gint       interval)
+{
+  return interval_start (tz, interval) + interval_offset (tz, interval);
+}
+
+inline static gint64
+interval_local_end (GTimeZone *tz,
+                    gint       interval)
+{
+  return interval_end (tz, interval) + interval_offset (tz, interval) - 1;
+}
+
+static gboolean
+interval_valid (GTimeZone *tz,
+                gint       interval)
+{
+  return interval <= tz->timecnt;
+}
+
+/* g_time_zone_find_interval() {{{1 */
+gint
+g_time_zone_find_interval (GTimeZone *tz,
+                           GTimeType *type,
+                           gint64    *time)
+{
+  gint i;
+
+  if (tz->zoneinfo == NULL)
+    return 0;
+
+  /* find the interval containing *time UTC
+   * TODO: this could be binary searched (or better) */
+  for (i = 0; i < tz->timecnt; i++)
+    if (*time <= interval_end (tz, i))
+      break;
+
+  g_assert (interval_start (tz, i) <= *time && *time <= interval_end (tz, i));
+
+  if (*type != G_TIME_TYPE_UNIVERSAL)
+    {
+      if (*time < interval_local_start (tz, i))
+        /* if time came before the start of this interval... */
+        {
+          i--;
+
+          /* if it's not in the previous interval... */
+          if (*time > interval_local_end (tz, i))
+            {
+              /* it doesn't exist.  fast-forward it. */
+              i++;
+              *time = interval_local_start (tz, i);
+            }
+        }
+
+      else if (*time > interval_local_end (tz, i))
+        /* if time came after the end of this interval... */
+        {
+          i++;
+
+          /* if it's not in the next interval... */
+          if (*time < interval_local_start (tz, i))
+            /* it doesn't exist.  fast-forward it. */
+            *time = interval_local_start (tz, i);
+        }
+
+      else if (interval_isdst (tz, i) != *type)
+        /* it's in this interval, but dst flag doesn't match.
+         * check neighbours for a better fit. */
+        {
+          if (i && *time <= interval_local_end (tz, i - 1))
+            i--;
+
+          else if (i < tz->timecnt &&
+                   *time >= interval_local_start (tz, i + 1))
+            i++;
+        }
+
+      *type = interval_isdst (tz, i);
+    }
+
+  return i;
+}
+
+/* Public API accessors {{{1 */
+const gchar *
+g_time_zone_get_name (GTimeZone *tz,
+                      gint       interval)
+{
+  g_return_val_if_fail (interval_valid (tz, interval), NULL);
+
+  if (tz->header == NULL)
+    return "UTC";
+
+  return tz->abbrs + interval_abbrind (tz, interval);
+}
+
+gint32
+g_time_zone_get_offset (GTimeZone *tz,
+                        gint       interval)
+{
+  g_return_val_if_fail (interval_valid (tz, interval), 0);
+
+  if (tz->header == NULL)
+    return 0;
+
+  return interval_offset (tz, interval);
+}
+
+gboolean
+g_time_zone_is_dst (GTimeZone *tz,
+                    gint       interval)
+{
+  g_return_val_if_fail (interval_valid (tz, interval), FALSE);
+
+  if (tz->header == NULL)
+    return FALSE;
+
+  return interval_isdst (tz, interval);
+}
+
+/* Epilogue {{{1 */
+/* vim:set foldmethod=marker: */
diff --git a/glib/gtimezone.h b/glib/gtimezone.h
new file mode 100644
index 0000000..702ad11
--- /dev/null
+++ b/glib/gtimezone.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the licence, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#if defined(G_DISABLE_SINGLE_INCLUDES) && !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION)
+#error "Only <glib.h> can be included directly."
+#endif
+
+#ifndef __G_TIME_ZONE_H__
+#define __G_TIME_ZONE_H__
+
+#include <glib/gtypes.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GTimeZone GTimeZone;
+
+typedef enum
+{
+  G_TIME_TYPE_STANDARD,
+  G_TIME_TYPE_DAYLIGHT,
+  G_TIME_TYPE_UNIVERSAL
+} GTimeType;
+
+GTimeZone *             g_time_zone_new                                 (const gchar *name);
+GTimeZone *             g_time_zone_new_utc                             (void);
+GTimeZone *             g_time_zone_new_local                           (void);
+
+GTimeZone *             g_time_zone_ref                                 (GTimeZone   *tz);
+void                    g_time_zone_unref                               (GTimeZone   *tz);
+
+gint                    g_time_zone_find_interval                       (GTimeZone   *tz,
+                                                                         GTimeType   *type,
+                                                                         gint64      *time);
+
+const gchar *           g_time_zone_get_name                            (GTimeZone   *tz,
+                                                                         gint         interval);
+gint32                  g_time_zone_get_offset                          (GTimeZone   *tz,
+                                                                         gint         interval);
+gboolean                g_time_zone_is_dst                              (GTimeZone   *tz,
+                                                                         gint         interval);
+
+G_END_DECLS
+
+#endif /* __G_TIME_ZONE_H__ */
diff --git a/glib/tests/gdatetime.c b/glib/tests/gdatetime.c
index a51afb0..c6f3286 100644
--- a/glib/tests/gdatetime.c
+++ b/glib/tests/gdatetime.c
@@ -177,15 +177,15 @@ test_GDateTime_compare (void)
       g_date_time_unref (dt2);
     }
 
-  dt2 = g_date_time_new_full (1999, 12, 31, 23, 59, 59, NULL);
+  dt2 = g_date_time_new_full (1999, 12, 31, 23, 59, 59, "Z");
   g_assert_cmpint (1, ==, g_date_time_compare (dt1, dt2));
   g_date_time_unref (dt2);
 
-  dt2 = g_date_time_new_full (2000, 1, 1, 0, 0, 1, NULL);
+  dt2 = g_date_time_new_full (2000, 1, 1, 0, 0, 1, "Z");
   g_assert_cmpint (-1, ==, g_date_time_compare (dt1, dt2));
   g_date_time_unref (dt2);
 
-  dt2 = g_date_time_new_full (2000, 1, 1, 0, 0, 0, NULL);
+  dt2 = g_date_time_new_full (2000, 1, 1, 0, 0, 0, "Z");
   g_assert_cmpint (0, ==, g_date_time_compare (dt1, dt2));
   g_date_time_unref (dt2);
   g_date_time_unref (dt1);
@@ -196,7 +196,7 @@ test_GDateTime_date (void)
 {
   GDateTime *dt1, *dt2;
 
-  dt1 = g_date_time_new_full (2009, 10, 19, 13, 0, 55, NULL);
+  dt1 = g_date_time_new_full (2009, 10, 19, 13, 0, 55, "Z");
   dt2 = g_date_time_day (dt1);
   g_assert (dt1 != NULL);
   g_assert (dt2 != NULL);
@@ -214,7 +214,6 @@ static void
 test_GDateTime_equal (void)
 {
   GDateTime *dt1, *dt2;
-  GTimeZone *time_zone;
 
   dt1 = g_date_time_new_from_date (2009, 10, 19);
   dt2 = g_date_time_new_from_date (2009, 10, 19);
@@ -229,25 +228,21 @@ test_GDateTime_equal (void)
   g_date_time_unref (dt2);
 
   /* UTC-0300 and not in DST */
-  time_zone = g_time_zone_new (-3 * 3600, FALSE);
-  dt1 = g_date_time_new_full (2010, 5, 24,  8, 0, 0, time_zone);
+  dt1 = g_date_time_new_full (2010, 5, 24,  8, 0, 0, "-03:00");
   g_assert_cmpint (g_date_time_get_utc_offset (dt1) / G_USEC_PER_SEC, ==, (-3 * 3600));
   /* UTC */
-  dt2 = g_date_time_new_full (2010, 5, 24, 11, 0, 0, NULL);
+  dt2 = g_date_time_new_full (2010, 5, 24, 11, 0, 0, "Z");
   g_assert_cmpint (g_date_time_get_utc_offset (dt2), ==, 0);
 
   g_assert (g_date_time_equal (dt1, dt2));
   g_date_time_unref (dt1);
-  g_time_zone_free (time_zone);
 
   /* America/Recife is in UTC-0300 */
-  time_zone = g_time_zone_new_for_name ("America/Recife");
-  dt1 = g_date_time_new_full (2010, 5, 24,  8, 0, 0, time_zone);
+  dt1 = g_date_time_new_full (2010, 5, 24,  8, 0, 0, "America/Recife");
   g_assert_cmpint (g_date_time_get_utc_offset (dt1) / G_USEC_PER_SEC, ==, (-3 * 3600));
   g_assert (g_date_time_equal (dt1, dt2));
   g_date_time_unref (dt1);
   g_date_time_unref (dt2);
-  g_time_zone_free (time_zone);
 }
 
 static void
@@ -335,19 +330,19 @@ test_GDateTime_get_hour (void)
 {
   GDateTime *dt;
 
-  dt = g_date_time_new_full (2009, 10, 19, 15, 13, 11, NULL);
+  dt = g_date_time_new_full (2009, 10, 19, 15, 13, 11, "Z");
   g_assert_cmpint (15, ==, g_date_time_get_hour (dt));
   g_date_time_unref (dt);
 
-  dt = g_date_time_new_full (100, 10, 19, 1, 0, 0, NULL);
+  dt = g_date_time_new_full (100, 10, 19, 1, 0, 0, "Z");
   g_assert_cmpint (1, ==, g_date_time_get_hour (dt));
   g_date_time_unref (dt);
 
-  dt = g_date_time_new_full (100, 10, 19, 0, 0, 0, NULL);
+  dt = g_date_time_new_full (100, 10, 19, 0, 0, 0, "Z");
   g_assert_cmpint (0, ==, g_date_time_get_hour (dt));
   g_date_time_unref (dt);
 
-  dt = g_date_time_new_full (100, 10, 1, 23, 59, 59, NULL);
+  dt = g_date_time_new_full (100, 10, 1, 23, 59, 59, "Z");
   g_assert_cmpint (23, ==, g_date_time_get_hour (dt));
   g_date_time_unref (dt);
 }
@@ -391,7 +386,7 @@ test_GDateTime_get_millisecond (void)
   g_assert_cmpint ((tv.tv_usec / 1000), ==, g_date_time_get_millisecond (dt));
   g_date_time_unref (dt);
 
-  dt = g_date_time_new_full (2010, 9, 15, 12, 0, 0.1234, NULL);
+  dt = g_date_time_new_full (2010, 9, 15, 12, 0, 0.1234, "Z");
   g_assert_cmpint (123, ==, g_date_time_get_millisecond (dt));
   g_assert_cmpint (123400, ==, g_date_time_get_microsecond (dt));
   g_date_time_unref (dt);
@@ -484,11 +479,9 @@ test_GDateTime_add_years (void)
 static void
 test_GDateTime_add_months (void)
 {
-  GTimeZone *utc_tz = g_time_zone_new_utc ();
-
 #define TEST_ADD_MONTHS(y,m,d,a,ny,nm,nd) G_STMT_START { \
   GDateTime *dt, *dt2; \
-  dt = g_date_time_new_full (y, m, d, 0, 0, 0, utc_tz); \
+  dt = g_date_time_new_full (y, m, d, 0, 0, 0, "Z"); \
   dt2 = g_date_time_add_months (dt, a); \
   ASSERT_DATE (dt2, ny, nm, nd); \
   g_date_time_unref (dt); \
@@ -506,8 +499,6 @@ test_GDateTime_add_months (void)
   TEST_ADD_MONTHS (2000,  8, 16,  -12, 1999, 8, 16);
   TEST_ADD_MONTHS (2011,  2,  1,  -13, 2010, 1,  1);
   TEST_ADD_MONTHS (1776,  7,  4, 1200, 1876, 7,  4);
-
-  g_time_zone_free (utc_tz);
 }
 
 static void
@@ -558,7 +549,7 @@ test_GDateTime_add_hours (void)
 {
 #define TEST_ADD_HOURS(y,m,d,h,mi,s,a,ny,nm,nd,nh,nmi,ns) G_STMT_START { \
   GDateTime *dt, *dt2; \
-  dt = g_date_time_new_full (y, m, d, h, mi, s, NULL); \
+  dt = g_date_time_new_full (y, m, d, h, mi, s, "Z"); \
   dt2 = g_date_time_add_hours (dt, a); \
   g_assert_cmpint (ny, ==, g_date_time_get_year (dt2)); \
   g_assert_cmpint (nm, ==, g_date_time_get_month (dt2)); \
@@ -579,7 +570,7 @@ test_GDateTime_add_full (void)
 {
 #define TEST_ADD_FULL(y,m,d,h,mi,s,ay,am,ad,ah,ami,as,ny,nm,nd,nh,nmi,ns) G_STMT_START { \
   GDateTime *dt, *dt2; \
-  dt = g_date_time_new_full (y, m, d, h, mi, s, NULL); \
+  dt = g_date_time_new_full (y, m, d, h, mi, s, "Z"); \
   dt2 = g_date_time_add_full (dt, ay, am, ad, ah, ami, as); \
   g_assert_cmpint (ny, ==, g_date_time_get_year (dt2)); \
   g_assert_cmpint (nm, ==, g_date_time_get_month (dt2)); \
@@ -641,8 +632,8 @@ test_GDateTime_add_minutes (void)
   TEST_ADD_MINUTES (60, 0);
   TEST_ADD_MINUTES (100, 40);
   TEST_ADD_MINUTES (5, 5);
-  TEST_ADD_MINUTES (86401, 1);
-  TEST_ADD_MINUTES (-86401, 59);
+  TEST_ADD_MINUTES (1441, 1);
+  TEST_ADD_MINUTES (-1441, 59);
 }
 
 static void
@@ -695,7 +686,7 @@ test_GDateTime_get_minute (void)
 {
   GDateTime *dt;
 
-  dt = g_date_time_new_full (2009, 12, 1, 1, 31, 0, NULL);
+  dt = g_date_time_new_full (2009, 12, 1, 1, 31, 0, "Z");
   g_assert_cmpint (31, ==, g_date_time_get_minute (dt));
   g_date_time_unref (dt);
 }
@@ -705,7 +696,7 @@ test_GDateTime_get_month (void)
 {
   GDateTime *dt;
 
-  dt = g_date_time_new_full (2009, 12, 1, 1, 31, 0, NULL);
+  dt = g_date_time_new_full (2009, 12, 1, 1, 31, 0, "Z");
   g_assert_cmpint (12, ==, g_date_time_get_month (dt));
   g_date_time_unref (dt);
 }
@@ -715,7 +706,7 @@ test_GDateTime_get_second (void)
 {
   GDateTime *dt;
 
-  dt = g_date_time_new_full (2009, 12, 1, 1, 31, 44, NULL);
+  dt = g_date_time_new_full (2009, 12, 1, 1, 31, 44, "Z");
   g_assert_cmpint (44, ==, g_date_time_get_second (dt));
   g_date_time_unref (dt);
 }
@@ -736,9 +727,8 @@ static void
 test_GDateTime_new_full (void)
 {
   GDateTime *dt;
-  GTimeZone *time_zone;
 
-  dt = g_date_time_new_full (2009, 12, 11, 12, 11, 10, NULL);
+  dt = g_date_time_new_full (2009, 12, 11, 12, 11, 10, "Z");
   g_assert_cmpint (2009, ==, g_date_time_get_year (dt));
   g_assert_cmpint (12, ==, g_date_time_get_month (dt));
   g_assert_cmpint (11, ==, g_date_time_get_day_of_month (dt));
@@ -747,8 +737,7 @@ test_GDateTime_new_full (void)
   g_assert_cmpint (10, ==, g_date_time_get_second (dt));
   g_date_time_unref (dt);
 
-  time_zone = g_time_zone_new_for_name ("America/Recife");
-  dt = g_date_time_new_full (2010, 5, 24, 8, 4, 0, time_zone);
+  dt = g_date_time_new_full (2010, 5, 24, 8, 4, 0, "America/Recife");
   g_assert_cmpint (2010, ==, g_date_time_get_year (dt));
   g_assert_cmpint (5, ==, g_date_time_get_month (dt));
   g_assert_cmpint (24, ==, g_date_time_get_day_of_month (dt));
@@ -758,7 +747,6 @@ test_GDateTime_new_full (void)
   g_assert_cmpstr ("BRT", ==, g_date_time_get_timezone_name (dt));
   g_assert (!g_date_time_is_daylight_savings (dt));
   g_date_time_unref (dt);
-  g_time_zone_free (time_zone);
 }
 
 static void
@@ -912,7 +900,7 @@ GDateTime *__dt = g_date_time_new_from_date (2009, 10, 24);     \
   g_free (p);                                   } G_STMT_END
 
 #define TEST_PRINTF_TIME(h,m,s,f,o)             G_STMT_START { \
-  GDateTime *dt = g_date_time_new_full (2009, 10, 24, (h), (m), (s), NULL); \
+  GDateTime *dt = g_date_time_new_full (2009, 10, 24, (h), (m), (s), "Z"); \
   gchar *p = g_date_time_format (dt, (f));                      \
   g_assert_cmpstr (p, ==, (o));                                 \
   g_date_time_unref (dt);                                       \
@@ -989,38 +977,33 @@ static void
 test_GDateTime_dst (void)
 {
   GDateTime *dt1, *dt2;
-  GTimeZone *tz;
-
-  tz = g_time_zone_new_for_name ("Europe/London");
 
   /* this date has the DST state set for Europe/London */
-  dt1 = g_date_time_new_full (2009, 8, 15, 3, 0, 1, tz);
+  dt1 = g_date_time_new_full (2009, 8, 15, 3, 0, 1, "Europe/London");
   g_assert (g_date_time_is_daylight_savings (dt1));
   g_assert_cmpint (g_date_time_get_utc_offset (dt1) / G_USEC_PER_SEC, ==, 3600);
   g_assert_cmpint (g_date_time_get_hour (dt1), ==, 3);
 
-  /* add 6 months to clear the DST flag and go back one hour */
+  /* add 6 months to clear the DST flag but keep the same time */
   dt2 = g_date_time_add_months (dt1, 6);
   g_assert (!g_date_time_is_daylight_savings (dt2));
   g_assert_cmpint (g_date_time_get_utc_offset (dt2) / G_USEC_PER_SEC, ==, 0);
-  g_assert_cmpint (g_date_time_get_hour (dt2), ==, 2);
+  g_assert_cmpint (g_date_time_get_hour (dt2), ==, 3);
 
   g_date_time_unref (dt2);
   g_date_time_unref (dt1);
 
   /* now do the reverse: start with a non-DST state and move to DST */
-  dt1 = g_date_time_new_full (2009, 2, 15, 2, 0, 1, tz);
+  dt1 = g_date_time_new_full (2009, 2, 15, 2, 0, 1, "Europe/London");
   g_assert (!g_date_time_is_daylight_savings (dt1));
   g_assert_cmpint (g_date_time_get_hour (dt1), ==, 2);
 
   dt2 = g_date_time_add_months (dt1, 6);
   g_assert (g_date_time_is_daylight_savings (dt2));
-  g_assert_cmpint (g_date_time_get_hour (dt2), ==, 3);
+  g_assert_cmpint (g_date_time_get_hour (dt2), ==, 2);
 
   g_date_time_unref (dt2);
   g_date_time_unref (dt1);
-
-  g_time_zone_free (tz);
 }
 
 gint



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