[glib] datetime: Rework time zone support in constructors



commit 67f1e52ce2a1372e4f8ffc5fdf292b33120264ab
Author: Emmanuele Bassi <ebassi linux intel com>
Date:   Tue Sep 14 08:00:40 2010 +0100

    datetime: Rework time zone support in constructors
    
    Timezone handling is complicated. Really complicated.
    
    In order to simplify it a little bit, we need to expose the GTimeZone
    structure.
    
    First of all, we allow creating time zone information directly from the
    offset and the DST state, and then pass it to the g_date_time_new_full()
    constructor. We also need to clean up the mess that is UTC-vs.-localtime
    for the other constructors.
    
    We also allow creating a GTimeZone from the Olson zoneinfo database
    names; a time zone created like this will be "floating": it will just
    reference the zoneinfo file - which are mmap()'ed, kept in a cache and
    refcounted. Once the GTimeZone has been associated with a GDateTime, it
    will be "anchored" to it: the offset will be resolved, as well as the
    DST state.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=50076

 docs/reference/glib/glib-sections.txt |    1 -
 glib/gdatetime.c                      |  812 +++++++++++++++++++++++----------
 glib/gdatetime.h                      |   39 ++-
 glib/glib.symbols                     |   11 +-
 glib/tests/gdatetime.c                |   51 ++-
 5 files changed, 630 insertions(+), 284 deletions(-)
---
diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt
index dd9aa35..1568c48 100644
--- a/docs/reference/glib/glib-sections.txt
+++ b/docs/reference/glib/glib-sections.txt
@@ -1424,7 +1424,6 @@ g_date_time_new_from_timeval
 g_date_time_new_now
 g_date_time_new_utc_now
 g_date_time_new_today
-g_date_time_copy
 g_date_time_ref
 g_date_time_unref
 <SUBSECTION>
diff --git a/glib/gdatetime.c b/glib/gdatetime.c
index fdf3733..a51f28f 100644
--- a/glib/gdatetime.c
+++ b/glib/gdatetime.c
@@ -54,9 +54,13 @@
 
 #include "gatomic.h"
 #include "gfileutils.h"
+#include "ghash.h"
 #include "gmain.h"
+#include "gmappedfile.h"
 #include "gstrfuncs.h"
 #include "gtestutils.h"
+#include "gthread.h"
+
 #include "glibintl.h"
 
 /**
@@ -122,12 +126,19 @@
 /* Translators: this is the preferred format for expressing the time */
 #define GET_PREFERRED_TIME(d) (g_date_time_printf ((d), C_("GDateTime", "%H:%M:%S")))
 
-typedef struct _GTimeZone       GTimeZone;
+#define SECS_PER_MINUTE (60)
+#define SECS_PER_HOUR   (60 * SECS_PER_MINUTE)
+#define SECS_PER_DAY    (24 * SECS_PER_HOUR)
+#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;
+
   /* Microsecond timekeeping within Day */
   guint64 usec;
 
@@ -139,14 +150,29 @@ struct _GDateTime
 
 struct _GTimeZone
 {
-  /* TZ abbreviation (e.g. PST) */
-  gchar  *name;
+  GTimeZoneFile *tz_file;
+
+  gchar *name;
 
   gint64 offset;
 
-  guint is_dst : 1;
+  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 },
@@ -399,7 +425,6 @@ g_date_time_add_ymd (GDateTime *datetime,
 #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
@@ -408,52 +433,31 @@ g_date_time_add_ymd (GDateTime *datetime,
 static gchar *
 get_tzdata_path (const gchar *tz_name)
 {
-  gchar *retval;
+  gchar *retval = NULL;
+  const gchar *tz_dir = g_getenv ("TZDIR");
 
-  if (tz_name != NULL)
+  if (tz_dir != NULL)
+    retval = g_build_filename (tz_dir, tz_name, NULL);
+  else
     {
-      const gchar *tz_dir = g_getenv ("TZDIR");
-
-      if (tz_dir != NULL)
-        retval = g_build_filename (tz_dir, tz_name, NULL);
+      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);
     }
-  else
-    {
-      /* an empty tz_name means "the current timezone file". tzset(3) defines
-       * it to be /usr/share/zoneinfo/localtime, and it also allows an
-       * /etc/localtime as a symlink to the localtime file under
-       * /usr/share/zoneinfo or to the correct timezone file. Fedora does not
-       * have /usr/share/zoneinfo/localtime, but it does have a real
-       * /etc/localtime.
-       *
-       * in any case, this path should resolve correctly.
-       */
-      retval = g_build_filename ("/", "etc", "localtime", NULL);
-    }
 
   return retval;
 }
 
-/*
- * Parses tzdata database times to get timezone info.
- *
- * @tz_name: Olson database name for the timezone
- * @start: Time offset from epoch we want to know the timezone
- * @_is_dst: Returns if this time in the timezone is in DST
- * @_offset: Returns the offset from UTC for this timezone
- * @_name: Returns the abreviated name for thie timezone
- */
 static gboolean
-parse_tzdata (const gchar  *tz_name,
-              gint64        start,
-              gboolean      is_utc,
-	      gboolean     *_is_dst,
-              gint64       *_offset,
-              gchar       **_name)
-{
-  gchar *filename, *contents;
+parse_tzdata (GTimeZoneFile  *tz_file,
+              gint64          start,
+              gboolean        is_utc,
+              gint64         *_offset,
+              gboolean       *_is_dst,
+              gchar         **_name)
+{
+  gchar *contents;
   gsize length;
   guint32 timecnt, typecnt;
   gint transitions_size, ttinfo_map_size;
@@ -464,26 +468,13 @@ parse_tzdata (const gchar  *tz_name,
   guint8 isdst;
   guint8 name_offset;
   gint i;
-  GError *error;
 
-  filename = get_tzdata_path (tz_name);
-
-  /* XXX: should we be caching this in memory for faster access?
-   * and if so, how do we expire the cache?
-   */
-  error = NULL;
-  if (!g_file_get_contents (filename, &contents, &length, &error))
-    {
-      g_free (filename);
-      return FALSE;
-    }
-
-  g_free (filename);
+  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))
     {
-      g_free (contents);
       return FALSE;
     }
 
@@ -543,8 +534,6 @@ parse_tzdata (const gchar  *tz_name,
   if (_name != NULL)
     *_name = g_strdup ((gchar*) (ttinfos + TZ_TTINFO_SIZE * typecnt + name_offset));
 
-  g_free (contents);
-
   if (_offset)
     *_offset = offset;
 
@@ -554,95 +543,411 @@ parse_tzdata (const gchar  *tz_name,
   return TRUE;
 }
 
+static GTimeZoneFile *
+g_time_zone_file_new (const gchar *filename)
+{
+  GMappedFile *tz_file;
+  GTimeZoneFile *retval;
+
+  tz_file = g_mapped_file_new (filename, FALSE, NULL);
+  if (tz_file == NULL)
+    return NULL;
+
+  retval = g_slice_new (GTimeZoneFile);
+  retval->filename = g_strdup (filename);
+  retval->file = tz_file;
+  retval->ref_count = 1;
+
+  return retval;
+}
+
+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 >
- * g_time_zone_new_from_epoc:
- * @tz_name: The Olson's database timezone name
- * @epoch: The epoch offset
- * @is_utc: If the @epoch is in UTC or already in the @tz_name timezone
+ * 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.
  *
- * Creates a new timezone
+ * 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 GTimeZone *
-g_time_zone_new_from_epoch (const gchar *tz_name,
-                            gint64       epoch,
-                            gboolean     is_utc)
+static GTimeZoneFile *
+g_time_zone_file_get_for_path (const gchar *path)
 {
-  GTimeZone *tz = NULL;
-  gint64 offset;
-  gboolean is_dst;
-  gchar *name = NULL;
+  GTimeZoneFile *retval;
 
-  if (parse_tzdata (tz_name, epoch, is_utc, &is_dst, &offset, &name))
+  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)
     {
-      tz = g_slice_new (GTimeZone);
-      tz->is_dst = is_dst;
-      tz->offset = offset;
-      tz->name = name;
+      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;
 }
 
-#define SECS_PER_MINUTE (60)
-#define SECS_PER_HOUR   (60 * SECS_PER_MINUTE)
-#define SECS_PER_DAY    (24 * SECS_PER_HOUR)
-#define SECS_PER_YEAR   (365 * SECS_PER_DAY)
-#define SECS_PER_JULIAN (DAYS_PER_PERIOD * SECS_PER_DAY)
+/**
+ * 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;
+}
 
-static gint64
-g_date_time_secs_offset (const GDateTime *dt)
+/**
+ * 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)
 {
-  gint64 secs;
-  gint d, y, h, m, s;
-  gint i, leaps;
+  return g_time_zone_new (0, FALSE);
+}
 
-  y = g_date_time_get_year (dt);
-  d = g_date_time_get_day_of_year (dt);
-  h = g_date_time_get_hour (dt);
-  m = g_date_time_get_minute (dt);
-  s = g_date_time_get_second (dt);
+/**
+ * g_time_zone_new_local:
+ *
+ * Creates a new #GTimeZone for the local timezone.
+ *
+ * The returned #GTimeZone is floating.
+ *
+ * Return value: (transfer full): a newly created, floating #GTimeZone.
+ *   Use g_time_zone_free() when done.
+ *
+ * Since: 2.26
+ */
+GTimeZone *
+g_time_zone_new_local (void)
+{
+  return g_time_zone_new_for_name ("localtime");
+}
 
-  leaps = GREGORIAN_LEAP (y) ? 1 : 0;
-  for (i = 1970; i < y; i++)
-    {
-      if (GREGORIAN_LEAP (i))
-        leaps += 1;
-    }
+/**
+ * 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)
+{
+  GTimeZone *retval;
 
-  secs = 0;
-  secs += (y - 1970) * SECS_PER_YEAR;
-  secs += d * SECS_PER_DAY;
-  secs += (leaps - 1) * SECS_PER_DAY;
-  secs += h * SECS_PER_HOUR;
-  secs += m * SECS_PER_MINUTE;
-  secs += s;
+  g_return_val_if_fail (time_zone != NULL, NULL);
 
-  if (dt->tz != NULL)
-    secs -= dt->tz->offset;
+  retval = g_slice_dup (GTimeZone, time_zone);
+
+  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;
+}
+
+/**
+ * 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);
+
+  if (time_zone->tz_file != NULL)
+    g_time_zone_file_unref (time_zone->tz_file);
+
+  g_free (time_zone->name);
+  g_slice_free (GTimeZone, time_zone);
+}
+
+/**
+ * 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);
 
-  return secs;
+  if (!time_zone->is_floating)
+    return time_zone->name;
+
+  return NULL;
+}
+
+/**
+ * g_time_zone_get_offset:
+ * @time_zone: a #GTimeZone
+ *
+ * Retrieves the offset of the @time_zone, in seconds from UTC, or 0
+ * if the #GTimeZone is floating.
+ *
+ * Return value: the offset from UTC, in seconds
+ *
+ * Since: 2.26
+ */
+gint
+g_time_zone_get_offset (const GTimeZone *time_zone)
+{
+  g_return_val_if_fail (time_zone != NULL, 0);
+
+  if (!time_zone->is_floating)
+    return time_zone->offset;
+
+  return 0;
+}
+
+/**
+ * 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);
+
+  if (!time_zone->is_floating)
+    return time_zone->is_dst;
+
+  return FALSE;
+}
+
+/**
+ * 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);
+
+  return time_zone->is_floating;
 }
 
 /*< internal >
- * g_date_time_create_time_zone:
- * @dt: a #GDateTime
- * @tz_name: the name of the timezone
+ * g_time_zone_sink:
+ * @time_zone: a #GTimeZone
+ * @datetime: a #GDateTime
  *
- * Creates a timezone from a #GDateTime (disregarding its own timezone).
- * This function transforms the #GDateTime into seconds since the epoch
- * and creates a timezone for it in the @tz_name zone.
+ * Sinks the floating state of @time_zone by associating it with the
+ * given #GDateTime.
  *
- * Return value: a newly created #GTimeZone
+ * If @time_zone is not floating, this function does not do anything
  */
-static GTimeZone *
-g_date_time_create_time_zone (GDateTime   *dt,
-                              const gchar *tz_name)
+static void
+g_time_zone_sink (GTimeZone *time_zone,
+                  GDateTime *datetime)
 {
-  gint64 secs;
+  gint64 offset, epoch;
+  gboolean is_dst, is_utc;
+  gchar *abbrev;
 
-  secs = g_date_time_secs_offset (dt);
+  if (!time_zone->is_floating)
+    return;
 
-  return g_time_zone_new_from_epoch (tz_name, secs, FALSE);
+  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 GDateTime *
@@ -656,30 +961,30 @@ g_date_time_new (void)
   return datetime;
 }
 
-static GTimeZone *
-g_time_zone_copy (const GTimeZone *time_zone)
+/*< internal >
+ * g_date_time_copy:
+ * @datetime: a #GDateTime
+ *
+ * Creates a copy of @datetime.
+ *
+ * Return value: the newly created #GDateTime which should be freed with
+ *   g_date_time_unref().
+ */
+static GDateTime *
+g_date_time_copy (const GDateTime *datetime)
 {
-  GTimeZone *tz;
-
-  if (G_UNLIKELY (time_zone == NULL))
-    return NULL;
+  GDateTime *copied;
 
-  tz = g_slice_new (GTimeZone);
-  memcpy (tz, time_zone, sizeof (GTimeZone));
+  g_return_val_if_fail (datetime != NULL, NULL);
 
-  tz->name = g_strdup (time_zone->name);
+  copied = g_date_time_new ();
+  copied->days = datetime->days;
+  copied->usec = datetime->usec;
 
-  return tz;
-}
+  if (datetime->tz != NULL)
+    copied->tz = g_time_zone_copy (datetime->tz);
 
-static void
-g_time_zone_free (GTimeZone *time_zone)
-{
-  if (G_LIKELY (time_zone != NULL))
-    {
-      g_free (time_zone->name);
-      g_slice_free (GTimeZone, time_zone);
-    }
+  return copied;
 }
 
 static void
@@ -688,12 +993,54 @@ g_date_time_free (GDateTime *datetime)
   if (G_UNLIKELY (datetime == NULL))
     return;
 
-  if (datetime->tz)
+  if (datetime->tz != NULL)
     g_time_zone_free (datetime->tz);
 
   g_slice_free (GDateTime, datetime);
 }
 
+/**
+ * g_date_time_ref:
+ * @datetime: a #GDateTime
+ *
+ * Atomically increments the reference count of @datetime by one.
+ *
+ * Return value: the #GDateTime with the reference count increased
+ *
+ * Since: 2.26
+ */
+GDateTime *
+g_date_time_ref (GDateTime *datetime)
+{
+  g_return_val_if_fail (datetime != NULL, NULL);
+  g_return_val_if_fail (datetime->ref_count > 0, NULL);
+
+  g_atomic_int_inc (&datetime->ref_count);
+
+  return datetime;
+}
+
+/**
+ * g_date_time_unref:
+ * @datetime: a #GDateTime
+ *
+ * Atomically decrements the reference count of @datetime by one.
+ *
+ * When the reference count reaches zero, the resources allocated by
+ * @datetime are freed
+ *
+ * Since: 2.26
+ */
+void
+g_date_time_unref (GDateTime *datetime)
+{
+  g_return_if_fail (datetime != NULL);
+  g_return_if_fail (datetime->ref_count > 0);
+
+  if (g_atomic_int_dec_and_test (&datetime->ref_count))
+    g_date_time_free (datetime);
+}
+
 static void
 g_date_time_get_week_number (const GDateTime *datetime,
                              gint            *week_number,
@@ -1060,32 +1407,6 @@ g_date_time_compare (gconstpointer dt1,
 }
 
 /**
- * g_date_time_copy:
- * @datetime: a #GDateTime
- *
- * Creates a copy of @datetime.
- *
- * Return value: the newly created #GDateTime which should be freed with
- *   g_date_time_unref().
- *
- * Since: 2.26
- */
-GDateTime*
-g_date_time_copy (const GDateTime *datetime)
-{
-  GDateTime *copied;
-
-  g_return_val_if_fail (datetime != NULL, NULL);
-
-  copied = g_date_time_new ();
-  copied->days = datetime->days;
-  copied->usec = datetime->usec;
-  copied->tz = g_time_zone_copy (datetime->tz);
-
-  return copied;
-}
-
-/**
  * g_date_time_day:
  * @datetime: a #GDateTime
  *
@@ -1550,7 +1871,7 @@ g_date_time_get_utc_offset (const GDateTime *datetime)
   g_return_val_if_fail (datetime != NULL, 0);
 
   if (datetime->tz != NULL)
-    offset = datetime->tz->offset;
+    offset = g_time_zone_get_offset (datetime->tz);
 
   return (gint64) offset * USEC_PER_SECOND;
 }
@@ -1559,8 +1880,7 @@ g_date_time_get_utc_offset (const GDateTime *datetime)
  * g_date_time_get_timezone_name:
  * @datetime: a #GDateTime
  *
- * Retrieves the Olson's database timezone name of the timezone specified
- * by @datetime.
+ * Retrieves the name of the timezone specified by @datetime, if any.
  *
  * Return value: (transfer none): the name of the timezone. The returned
  *   string is owned by the #GDateTime and it should not be modified or
@@ -1574,7 +1894,7 @@ g_date_time_get_timezone_name (const GDateTime *datetime)
   g_return_val_if_fail (datetime != NULL, NULL);
 
   if (datetime->tz != NULL)
-    return datetime->tz->name;
+    return g_time_zone_get_name (datetime->tz);
 
   return "UTC";
 }
@@ -1656,7 +1976,7 @@ g_date_time_is_daylight_savings (const GDateTime *datetime)
 {
   g_return_val_if_fail (datetime != NULL, FALSE);
 
-  if (!datetime->tz)
+  if (datetime->tz == NULL)
     return FALSE;
 
   return datetime->tz->is_dst;
@@ -1690,7 +2010,8 @@ g_date_time_new_from_date (gint year,
   dt = g_date_time_new ();
 
   dt->days = date_to_proleptic_gregorian (year, month, day);
-  dt->tz = g_date_time_create_time_zone (dt, NULL);
+  dt->tz = g_time_zone_new_local ();
+  g_time_zone_sink (dt->tz, dt);
 
   return dt;
 }
@@ -1701,13 +2022,17 @@ 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.
+ *
  * Return value: the newly created #GDateTime
  *
  * Since: 2.26
  */
 GDateTime*
-g_date_time_new_from_epoch (gint64 t) /* IN */
+g_date_time_new_from_epoch (gint64 t)
 {
+  GDateTime *datetime;
+  GTimeZone *tz;
   struct tm tm;
   time_t tt;
 
@@ -1739,13 +2064,17 @@ g_date_time_new_from_epoch (gint64 t) /* IN */
   }
 #endif /* HAVE_LOCALTIME_R */
 
-  return 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,
-                               NULL);
+  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);
+
+  return datetime;
 }
 
 /**
@@ -1754,6 +2083,8 @@ g_date_time_new_from_epoch (gint64 t) /* IN */
  *
  * Creates a new #GDateTime using the date and time specified by #GTimeVal.
  *
+ * The timezone of the returned #GDateTime is UTC.
+ *
  * Return value: the newly created #GDateTime
  *
  * Since: 2.26
@@ -1767,7 +2098,8 @@ 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_date_time_create_time_zone (datetime, NULL);
+  datetime->tz = g_time_zone_new_local ();
+  g_time_zone_sink (datetime->tz, datetime);
 
   return datetime;
 }
@@ -1780,23 +2112,26 @@ g_date_time_new_from_timeval (GTimeVal *tv)
  * @hour: the hour of the day
  * @minute: the minute of the hour
  * @second: the second of the minute
- * @time_zone: (allow-none): the Olson's database timezone name, or %NULL
- *   for local (e.g. America/New_York)
+ * @time_zone: (allow-none): a #GTimeZone, or %NULL for UTC
+ *
+ * Creates a new #GDateTime using the date and times in the Gregorian
+ * calendar.
  *
- * Creates a new #GDateTime using the date and times in the Gregorian calendar.
+ * If @time_zone is not %NULL, the #GDateTime will copy the #GTimeZone
+ * and sink it, if floating.
  *
  * Return value: the newly created #GDateTime
  *
  * Since: 2.26
  */
 GDateTime *
-g_date_time_new_full (gint         year,
-                      gint         month,
-                      gint         day,
-                      gint         hour,
-                      gint         minute,
-                      gint         second,
-                      const gchar *time_zone)
+g_date_time_new_full (gint             year,
+                      gint             month,
+                      gint             day,
+                      gint             hour,
+                      gint             minute,
+                      gint             second,
+                      const GTimeZone *time_zone)
 {
   GDateTime *dt;
 
@@ -1804,20 +2139,19 @@ g_date_time_new_full (gint         year,
   g_return_val_if_fail (minute >= 0 && minute < 60, NULL);
   g_return_val_if_fail (second >= 0 && second <= 60, NULL);
 
-  if ((dt = g_date_time_new_from_date (year, month, day)) == NULL)
-    return NULL;
-
+  dt = g_date_time_new ();
+  dt->days = date_to_proleptic_gregorian (year, month, day);
   dt->usec = (hour   * USEC_PER_HOUR)
            + (minute * USEC_PER_MINUTE)
            + (second * USEC_PER_SECOND);
 
-  dt->tz = g_date_time_create_time_zone (dt, time_zone);
-  if (time_zone != NULL && dt->tz == NULL)
+  if (time_zone != NULL)
     {
-      /* timezone creation failed */
-      g_date_time_unref (dt);
-      dt = NULL;
+      dt->tz = g_time_zone_copy (time_zone);
+      g_time_zone_sink (dt->tz, dt);
     }
+  else
+    dt->tz = NULL;
 
   return dt;
 }
@@ -1888,7 +2222,8 @@ g_date_time_new_now (void)
  *      the date.
  * %%y  The year as a decimal number without the century.
  * %%Y  The year as a decimal number including the century.
- * %%Z  Alphabetic time zone abbreviation (e.g. EDT).
+ * %%z  The time-zone as hour offset from UTC
+ * %%Z  The timezone or name or abbreviation
  * %%%  A literal %% character.
  *
  * Return value: a newly allocated string formatted to the requested format or
@@ -2048,11 +2383,25 @@ g_date_time_printf (const GDateTime *datetime,
                 case 'Y':
                   g_string_append_printf (outstr, "%d", g_date_time_get_year (datetime));
                   break;
+                case 'z':
+                  if (datetime->tz != NULL)
+                    {
+                      gint64 offset = g_date_time_get_utc_offset (datetime)
+                                    / USEC_PER_SECOND;
+
+                      g_string_append_printf (outstr, "%c%02d%02d",
+                                              offset >= 0 ? '+' : '-',
+                                              (int) offset / 3600,
+                                              (int) offset / 60 % 60);
+                    }
+                  else
+                    g_string_append (outstr, "+0000");
+                  break;
                 case 'Z':
                   if (datetime->tz != NULL)
-                    g_string_append_printf (outstr, "%s", datetime->tz->name);
+                    g_string_append (outstr, g_date_time_get_timezone_name (datetime));
                   else
-                    g_string_append_printf (outstr, "UTC");
+                    g_string_append (outstr, "UTC");
                   break;
                 case '%':
                   g_string_append_c (outstr, '%');
@@ -2078,48 +2427,6 @@ bad_format:
 }
 
 /**
- * g_date_time_ref:
- * @datetime: a #GDateTime
- *
- * Atomically increments the reference count of @datetime by one.
- *
- * Return value: the #GDateTime with the reference count increased
- *
- * Since: 2.26
- */
-GDateTime *
-g_date_time_ref (GDateTime *datetime)
-{
-  g_return_val_if_fail (datetime != NULL, NULL);
-  g_return_val_if_fail (datetime->ref_count > 0, NULL);
-
-  g_atomic_int_inc (&datetime->ref_count);
-
-  return datetime;
-}
-
-/**
- * g_date_time_unref:
- * @datetime: a #GDateTime
- *
- * Atomically decrements the reference count of @datetime by one.
- *
- * When the reference count reaches zero, the resources allocated by
- * @datetime are freed
- *
- * Since: 2.26
- */
-void
-g_date_time_unref (GDateTime *datetime)
-{
-  g_return_if_fail (datetime != NULL);
-  g_return_if_fail (datetime->ref_count > 0);
-
-  if (g_atomic_int_dec_and_test (&datetime->ref_count))
-    g_date_time_free (datetime);
-}
-
-/**
  * g_date_time_to_local:
  * @datetime: a #GDateTime
  *
@@ -2139,10 +2446,11 @@ g_date_time_to_local (const GDateTime *datetime)
   dt = g_date_time_copy (datetime);
   if (dt->tz == NULL)
     {
-      dt->tz = g_date_time_create_time_zone (dt, NULL);
+      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);
     }
 
@@ -2172,7 +2480,7 @@ g_date_time_to_epoch (const GDateTime *datetime)
 
   result = (datetime->days - UNIX_EPOCH_START) * SEC_PER_DAY
          + datetime->usec / USEC_PER_SECOND
-         - g_date_time_get_utc_offset (datetime) / 1000000;
+         - g_date_time_get_utc_offset (datetime) / USEC_PER_SECOND;
 
   return result;
 }
@@ -2191,9 +2499,7 @@ g_date_time_to_timeval (const GDateTime *datetime,
                         GTimeVal        *tv)
 {
   g_return_if_fail (datetime != NULL);
-
-  tv->tv_sec = 0;
-  tv->tv_usec = 0;
+  g_return_if_fail (tv != NULL);
 
   tv->tv_sec = g_date_time_to_epoch (datetime);
   tv->tv_usec = datetime->usec % USEC_PER_SECOND;
@@ -2215,13 +2521,17 @@ GDateTime *
 g_date_time_to_utc (const GDateTime *datetime)
 {
   GDateTime *dt;
-  GTimeSpan  ts;
+  GTimeSpan ts;
 
   g_return_val_if_fail (datetime != NULL, NULL);
 
   ts = g_date_time_get_utc_offset (datetime) * -1;
   dt = g_date_time_add (datetime, ts);
-  dt->tz = NULL;
+  if (dt->tz != NULL)
+    {
+      g_time_zone_free (dt->tz);
+      dt->tz = NULL;
+    }
 
   return dt;
 }
diff --git a/glib/gdatetime.h b/glib/gdatetime.h
index 4fdbb61..4a7bf8f 100644
--- a/glib/gdatetime.h
+++ b/glib/gdatetime.h
@@ -75,23 +75,33 @@ G_BEGIN_DECLS
 #define G_TIME_SPAN_MILLISECOND         (G_GINT64_CONSTANT (1000))
 
 /**
- * GDateTime:
+ * GTimeSpan:
  *
- * <structname>GDateTime</structname> is an opaque structure whose members
+ * A value representing an interval of time, in microseconds.
+ *
+ * Since: 2.26
+ */
+typedef gint64 GTimeSpan;
+
+/**
+ * GTimeZone:
+ *
+ * <structname>GTimeZone</structname> is an opaque structure whose members
  * cannot be accessed directly.
  *
  * Since: 2.26
  */
-typedef struct _GDateTime GDateTime;
+typedef struct _GTimeZone GTimeZone;
 
 /**
- * GTimeSpan:
+ * GDateTime:
  *
- * A value representing an interval of time, in microseconds.
+ * <structname>GDateTime</structname> is an opaque structure whose members
+ * cannot be accessed directly.
  *
  * Since: 2.26
  */
-typedef gint64 GTimeSpan;
+typedef struct _GDateTime GDateTime;
 
 GDateTime *           g_date_time_new_now                (void);
 GDateTime *           g_date_time_new_today              (void);
@@ -107,9 +117,8 @@ GDateTime *           g_date_time_new_full               (gint             year,
                                                           gint             hour,
                                                           gint             minute,
                                                           gint             second,
-                                                          const gchar     *time_zone);
+                                                          const GTimeZone *time_zone);
 
-GDateTime *           g_date_time_copy                   (const GDateTime *datetime);
 GDateTime *           g_date_time_ref                    (GDateTime       *datetime);
 void                  g_date_time_unref                  (GDateTime       *datetime);
 
@@ -174,9 +183,9 @@ void                  g_date_time_get_dmy                (const GDateTime *datet
 
 GTimeSpan             g_date_time_get_utc_offset         (const GDateTime *datetime);
 G_CONST_RETURN gchar *g_date_time_get_timezone_name      (const GDateTime *datetime);
+gboolean              g_date_time_is_daylight_savings    (const GDateTime *datetime);
 
 gboolean              g_date_time_is_leap_year           (const GDateTime *datetime);
-gboolean              g_date_time_is_daylight_savings    (const GDateTime *datetime);
 
 GDateTime *           g_date_time_to_local               (const GDateTime *datetime);
 gint64                g_date_time_to_epoch               (const GDateTime *datetime);
@@ -186,6 +195,18 @@ GDateTime *           g_date_time_to_utc                 (const GDateTime *datet
 gchar *               g_date_time_printf                 (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.symbols b/glib/glib.symbols
index 09f5e23..4f34949 100644
--- a/glib/glib.symbols
+++ b/glib/glib.symbols
@@ -336,7 +336,6 @@ g_date_time_add_seconds
 g_date_time_add_weeks
 g_date_time_add_years
 g_date_time_compare
-g_date_time_copy
 g_date_time_day
 g_date_time_difference
 g_date_time_equal
@@ -372,6 +371,16 @@ 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
+g_time_zone_new
+g_time_zone_new_for_name
+g_time_zone_new_local
+g_time_zone_new_utc
 #endif
 #endif
 
diff --git a/glib/tests/gdatetime.c b/glib/tests/gdatetime.c
index 1f4cb3c..1c04459 100644
--- a/glib/tests/gdatetime.c
+++ b/glib/tests/gdatetime.c
@@ -193,20 +193,6 @@ test_GDateTime_compare (void)
 }
 
 static void
-test_GDateTime_copy (void)
-{
-  GDateTime *dt1, *dt2;
-
-  dt1 = g_date_time_new_now ();
-  dt2 = g_date_time_copy (dt1);
-  g_assert (dt1 != NULL);
-  g_assert (dt2 != NULL);
-  g_assert_cmpint (0, ==, g_date_time_compare (dt1, dt2));
-  g_date_time_unref (dt1);
-  g_date_time_unref (dt2);
-}
-
-static void
 test_GDateTime_date (void)
 {
   GDateTime *dt1, *dt2;
@@ -229,6 +215,7 @@ 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);
@@ -242,12 +229,26 @@ test_GDateTime_equal (void)
   g_date_time_unref (dt1);
   g_date_time_unref (dt2);
 
-  /* America/Recife is GMT-3 and is not in DST for this time */
-  dt1 = g_date_time_new_full (2010, 5, 24, 8, 0, 0, "America/Recife");
-  dt2 = g_date_time_new_full (2010, 5, 24, 11, 0, 0, "UTC");
+  /* 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);
+  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);
+  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);
+  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
@@ -388,7 +389,7 @@ test_GDateTime_get_millisecond (void)
 
   g_get_current_time (&tv);
   dt = g_date_time_new_from_timeval (&tv);
-  g_assert_cmpint ((tv.tv_usec/1000), ==, g_date_time_get_millisecond (dt));
+  g_assert_cmpint ((tv.tv_usec / 1000), ==, g_date_time_get_millisecond (dt));
   g_date_time_unref (dt);
 }
 
@@ -437,10 +438,14 @@ test_GDateTime_new_from_timeval (void)
   dt = g_date_time_new_from_timeval (&tv);
 
   if (g_test_verbose ())
-    g_print ("\nDT%d/%d/%d\n",
+    g_print ("\nDT%04d-%02d-%02dT%02d:%02d:%02d%s\n",
              g_date_time_get_year (dt),
              g_date_time_get_month (dt),
-             g_date_time_get_day_of_month (dt));
+             g_date_time_get_day_of_month (dt),
+             g_date_time_get_hour (dt),
+             g_date_time_get_minute (dt),
+             g_date_time_get_second (dt),
+             g_date_time_get_timezone_name (dt));
 
   g_date_time_to_timeval (dt, &tv2);
   g_assert_cmpint (tv.tv_sec, ==, tv2.tv_sec);
@@ -723,6 +728,7 @@ 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);
   g_assert_cmpint (2009, ==, g_date_time_get_year (dt));
@@ -733,7 +739,8 @@ test_GDateTime_new_full (void)
   g_assert_cmpint (10, ==, g_date_time_get_second (dt));
   g_date_time_unref (dt);
 
-  dt = g_date_time_new_full (2010, 5, 24, 8, 4, 0, "America/Recife");
+  time_zone = g_time_zone_new_for_name ("America/Recife");
+  dt = g_date_time_new_full (2010, 5, 24, 8, 4, 0, time_zone);
   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));
@@ -743,6 +750,7 @@ 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
@@ -987,7 +995,6 @@ main (gint   argc,
   g_test_add_func ("/GDateTime/add_weeks", test_GDateTime_add_weeks);
   g_test_add_func ("/GDateTime/add_years", test_GDateTime_add_years);
   g_test_add_func ("/GDateTime/compare", test_GDateTime_compare);
-  g_test_add_func ("/GDateTime/copy", test_GDateTime_copy);
   g_test_add_func ("/GDateTime/date", test_GDateTime_date);
   g_test_add_func ("/GDateTime/diff", test_GDateTime_diff);
   g_test_add_func ("/GDateTime/equal", test_GDateTime_equal);



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