[kdbus] KDBUS_ITEM_PAYLOAD_OFF items are (once again) relative to msg header
[platform/upstream/glib.git] / glib / gdatetime.c
index f76fc48..cbbf620 100644 (file)
@@ -3,20 +3,27 @@
  * 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
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
+ * 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.1 of the
+ * licence, or (at your option) any later version.
  *
- * This 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.
+ * This 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ * 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
  *   to its correctness.
  */
 
-#include "config.h"
+/* Prologue {{{1 */
 
-#include "glib.h"
+#include "config.h"
 
 #include <stdlib.h>
 #include <string.h>
 
-#ifdef HAVE_UNISTD_H
-#include <unistd.h>
+#ifdef HAVE_LANGINFO_TIME
+#include <langinfo.h>
 #endif
 
+#include "gdatetime.h"
+
+#include "gslice.h"
+#include "gatomic.h"
+#include "gcharset.h"
+#include "gconvert.h"
+#include "gfileutils.h"
+#include "ghash.h"
+#include "gmain.h"
+#include "gmappedfile.h"
+#include "gstrfuncs.h"
+#include "gtestutils.h"
+#include "gthread.h"
+#include "gtimezone.h"
+
+#include "glibintl.h"
+
 #ifndef G_OS_WIN32
 #include <sys/time.h>
 #include <time.h>
 #endif /* !G_OS_WIN32 */
 
-#include "glibintl.h"
-
-#include "gdatetime.h"
-
 /**
  * SECTION:date-time
  * @title: GDateTime
- * @short_description: A Date and Time structure
+ * @short_description: a structure representing Date and Time
+ * @see_also: #GTimeZone
  *
- * #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 Julian Day Number since the
- * initial Julian Period (-4712 BC). 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.
  */
 
+struct _GDateTime
+{
+  /* Microsecond timekeeping within Day */
+  guint64 usec;
+
+  /* TimeZone information */
+  GTimeZone *tz;
+  gint interval;
+
+  /* 1 is 0001-01-01 in Proleptic Gregorian */
+  gint32 days;
+
+  volatile gint ref_count;
+};
+
+/* Time conversion {{{1 */
+
+#define UNIX_EPOCH_START     719163
+#define INSTANT_TO_UNIX(instant) \
+  ((instant)/USEC_PER_SECOND - UNIX_EPOCH_START * SEC_PER_DAY)
+#define UNIX_TO_INSTANT(unix) \
+  (((unix) + UNIX_EPOCH_START * SEC_PER_DAY) * USEC_PER_SECOND)
+
+#define DAYS_IN_4YEARS    1461    /* days in 4 years */
+#define DAYS_IN_100YEARS  36524   /* days in 100 years */
+#define DAYS_IN_400YEARS  146097  /* days in 400 years  */
+
 #define USEC_PER_SECOND      (G_GINT64_CONSTANT (1000000))
 #define USEC_PER_MINUTE      (G_GINT64_CONSTANT (60000000))
 #define USEC_PER_HOUR        (G_GINT64_CONSTANT (3600000000))
 #define USEC_PER_MILLISECOND (G_GINT64_CONSTANT (1000))
 #define USEC_PER_DAY         (G_GINT64_CONSTANT (86400000000))
+#define SEC_PER_DAY          (G_GINT64_CONSTANT (86400))
+
+#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)
 
-#define GREGORIAN_LEAP(y)    (((y % 4) == 0) && (!(((y % 100) == 0) && ((y % 400) != 0))))
+#define GREGORIAN_LEAP(y)    ((((y) % 4) == 0) && (!((((y) % 100) == 0) && (((y) % 400) != 0))))
 #define JULIAN_YEAR(d)       ((d)->julian / 365.25)
 #define DAYS_PER_PERIOD      (G_GINT64_CONSTANT (2914695))
 
-#define GET_AMPM(d,l)         ((g_date_time_get_hour (d) < 12)  \
-                                ? (l ? C_("GDateTime", "am") : C_("GDateTime", "AM"))       \
-                                : (l ? C_("GDateTime", "pm") : C_("GDateTime", "PM")))
-
-#define WEEKDAY_ABBR(d)       (get_weekday_name_abbr (g_date_time_get_day_of_week (datetime)))
-#define WEEKDAY_FULL(d)       (get_weekday_name (g_date_time_get_day_of_week (datetime)))
+static const guint16 days_in_months[2][13] =
+{
+  { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
+  { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
+};
 
-#define MONTH_ABBR(d)         (get_month_name_abbr (g_date_time_get_month (datetime)))
-#define MONTH_FULL(d)         (get_month_name (g_date_time_get_month (datetime)))
+static const guint16 days_in_year[2][13] =
+{
+  {  0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
+  {  0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
+};
 
-/* Translators: this is the preferred format for expressing the date */
-#define GET_PREFERRED_DATE(d) (g_date_time_printf ((d), C_("GDateTime", "%m/%d/%y")))
+#ifdef HAVE_LANGINFO_TIME
 
-/* 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")))
+#define GET_AMPM(d) ((g_date_time_get_hour (d) < 12) ? \
+                     nl_langinfo (AM_STR) : \
+                     nl_langinfo (PM_STR))
 
-typedef struct _GTimeZone       GTimeZone;
+#define PREFERRED_DATE_TIME_FMT nl_langinfo (D_T_FMT)
+#define PREFERRED_DATE_FMT nl_langinfo (D_FMT)
+#define PREFERRED_TIME_FMT nl_langinfo (T_FMT)
+#define PREFERRED_TIME_FMT nl_langinfo (T_FMT)
+#define PREFERRED_12HR_TIME_FMT nl_langinfo (T_FMT_AMPM)
 
-struct _GDateTime
+static const gint weekday_item[2][7] =
 {
-  /* Julian Period, 0 is Initial Epoch */
-  gint period  :  3;
-
-  /* Day within Julian Period */
-  guint julian : 22;
+  { ABDAY_2, ABDAY_3, ABDAY_4, ABDAY_5, ABDAY_6, ABDAY_7, ABDAY_1 },
+  { DAY_2, DAY_3, DAY_4, DAY_5, DAY_6, DAY_7, DAY_1 }
+};
 
-  /* Microsecond timekeeping within Day */
-  guint64 usec : 37;
+static const gint month_item[2][12] =
+{
+  { ABMON_1, ABMON_2, ABMON_3, ABMON_4, ABMON_5, ABMON_6, ABMON_7, ABMON_8, ABMON_9, ABMON_10, ABMON_11, ABMON_12 },
+  { MON_1, MON_2, MON_3, MON_4, MON_5, MON_6, MON_7, MON_8, MON_9, MON_10, MON_11, MON_12 },
+};
 
-  gint reserved : 2;
+#define WEEKDAY_ABBR(d) nl_langinfo (weekday_item[0][g_date_time_get_day_of_week (d) - 1])
+#define WEEKDAY_FULL(d) nl_langinfo (weekday_item[1][g_date_time_get_day_of_week (d) - 1])
+#define MONTH_ABBR(d) nl_langinfo (month_item[0][g_date_time_get_month (d) - 1])
+#define MONTH_FULL(d) nl_langinfo (month_item[1][g_date_time_get_month (d) - 1])
 
-  /* TimeZone information, NULL is UTC */
-  GTimeZone *tz;
+#else
 
-  volatile gint ref_count;
-};
+#define GET_AMPM(d)          ((g_date_time_get_hour (d) < 12)  \
+                                       /* Translators: 'before midday' indicator */ \
+                                ? C_("GDateTime", "AM") \
+                                  /* Translators: 'after midday' indicator */ \
+                                : C_("GDateTime", "PM"))
 
-struct _GTimeZone
-{
-  /* TZ abbreviation (e.g. PST) */
-  gchar  *name;
+/* Translators: this is the preferred format for expressing the date and the time */
+#define PREFERRED_DATE_TIME_FMT C_("GDateTime", "%a %b %e %H:%M:%S %Y")
 
-  gint64 offset;
+/* Translators: this is the preferred format for expressing the date */
+#define PREFERRED_DATE_FMT C_("GDateTime", "%m/%d/%y")
 
-  guint is_dst : 1;
-};
+/* Translators: this is the preferred format for expressing the time */
+#define PREFERRED_TIME_FMT C_("GDateTime", "%H:%M:%S")
 
-static const guint16 days_in_months[2][13] =
-{
-  { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
-  { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
-};
+/* Translators: this is the preferred format for expressing 12 hour time */
+#define PREFERRED_12HR_TIME_FMT C_("GDateTime", "%I:%M:%S %p")
 
-static const guint16 days_in_year[2][13] =
-{
-  {  0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
-  {  0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
-};
+#define WEEKDAY_ABBR(d)       (get_weekday_name_abbr (g_date_time_get_day_of_week (d)))
+#define WEEKDAY_FULL(d)       (get_weekday_name (g_date_time_get_day_of_week (d)))
+#define MONTH_ABBR(d)         (get_month_name_abbr (g_date_time_get_month (d)))
+#define MONTH_FULL(d)         (get_month_name (g_date_time_get_month (d)))
 
 static const gchar *
 get_month_name (gint month)
@@ -155,29 +225,29 @@ get_month_name (gint month)
   switch (month)
     {
     case 1:
-      return C_("GDateTime", "January");
+      return C_("full month name", "January");
     case 2:
-      return C_("GDateTime", "February");
+      return C_("full month name", "February");
     case 3:
-      return C_("GDateTime", "March");
+      return C_("full month name", "March");
     case 4:
-      return C_("GDateTime", "April");
+      return C_("full month name", "April");
     case 5:
-      return C_("GDateTime", "May");
+      return C_("full month name", "May");
     case 6:
-      return C_("GDateTime", "June");
+      return C_("full month name", "June");
     case 7:
-      return C_("GDateTime", "July");
+      return C_("full month name", "July");
     case 8:
-      return C_("GDateTime", "August");
+      return C_("full month name", "August");
     case 9:
-      return C_("GDateTime", "September");
+      return C_("full month name", "September");
     case 10:
-      return C_("GDateTime", "October");
+      return C_("full month name", "October");
     case 11:
-      return C_("GDateTime", "November");
+      return C_("full month name", "November");
     case 12:
-      return C_("GDateTime", "December");
+      return C_("full month name", "December");
 
     default:
       g_warning ("Invalid month number %d", month);
@@ -192,29 +262,29 @@ get_month_name_abbr (gint month)
   switch (month)
     {
     case 1:
-      return C_("GDateTime", "Jan");
+      return C_("abbreviated month name", "Jan");
     case 2:
-      return C_("GDateTime", "Feb");
+      return C_("abbreviated month name", "Feb");
     case 3:
-      return C_("GDateTime", "Mar");
+      return C_("abbreviated month name", "Mar");
     case 4:
-      return C_("GDateTime", "Apr");
+      return C_("abbreviated month name", "Apr");
     case 5:
-      return C_("GDateTime", "May");
+      return C_("abbreviated month name", "May");
     case 6:
-      return C_("GDateTime", "Jun");
+      return C_("abbreviated month name", "Jun");
     case 7:
-      return C_("GDateTime", "Jul");
+      return C_("abbreviated month name", "Jul");
     case 8:
-      return C_("GDateTime", "Aug");
+      return C_("abbreviated month name", "Aug");
     case 9:
-      return C_("GDateTime", "Sep");
+      return C_("abbreviated month name", "Sep");
     case 10:
-      return C_("GDateTime", "Oct");
+      return C_("abbreviated month name", "Oct");
     case 11:
-      return C_("GDateTime", "Nov");
+      return C_("abbreviated month name", "Nov");
     case 12:
-      return C_("GDateTime", "Dec");
+      return C_("abbreviated month name", "Dec");
 
     default:
       g_warning ("Invalid month number %d", month);
@@ -229,19 +299,19 @@ get_weekday_name (gint day)
   switch (day)
     {
     case 1:
-      return C_("GDateTime", "Monday");
+      return C_("full weekday name", "Monday");
     case 2:
-      return C_("GDateTime", "Tuesday");
+      return C_("full weekday name", "Tuesday");
     case 3:
-      return C_("GDateTime", "Wednesday");
+      return C_("full weekday name", "Wednesday");
     case 4:
-      return C_("GDateTime", "Thursday");
+      return C_("full weekday name", "Thursday");
     case 5:
-      return C_("GDateTime", "Friday");
+      return C_("full weekday name", "Friday");
     case 6:
-      return C_("GDateTime", "Saturday");
+      return C_("full weekday name", "Saturday");
     case 7:
-      return C_("GDateTime", "Sunday");
+      return C_("full weekday name", "Sunday");
 
     default:
       g_warning ("Invalid week day number %d", day);
@@ -256,19 +326,19 @@ get_weekday_name_abbr (gint day)
   switch (day)
     {
     case 1:
-      return C_("GDateTime", "Mon");
+      return C_("abbreviated weekday name", "Mon");
     case 2:
-      return C_("GDateTime", "Tue");
+      return C_("abbreviated weekday name", "Tue");
     case 3:
-      return C_("GDateTime", "Wed");
+      return C_("abbreviated weekday name", "Wed");
     case 4:
-      return C_("GDateTime", "Thu");
+      return C_("abbreviated weekday name", "Thu");
     case 5:
-      return C_("GDateTime", "Fri");
+      return C_("abbreviated weekday name", "Fri");
     case 6:
-      return C_("GDateTime", "Sat");
+      return C_("abbreviated weekday name", "Sat");
     case 7:
-      return C_("GDateTime", "Sun");
+      return C_("abbreviated weekday name", "Sun");
 
     default:
       g_warning ("Invalid week day number %d", day);
@@ -277,348 +347,36 @@ get_weekday_name_abbr (gint day)
   return NULL;
 }
 
-static inline void
-g_date_time_add_days_internal (GDateTime *datetime,
-                               gint64     days)
-{
-  gint __day = datetime->julian + days;
-  if (__day < 1)
-    {
-      datetime->period += -1 + (__day / DAYS_PER_PERIOD);
-      datetime->period += DAYS_PER_PERIOD + (__day % DAYS_PER_PERIOD);
-    }
-  else if (__day > DAYS_PER_PERIOD)
-    {
-      datetime->period += (datetime->julian + days) / DAYS_PER_PERIOD;
-      datetime->julian = (datetime->julian + days) % DAYS_PER_PERIOD;
-    }
-  else
-    datetime->julian += days;
-}
-
-static inline void
-g_date_time_add_usec (GDateTime *datetime,
-                      gint64     usecs)
-{
-  gint64 __usec = datetime->usec + usecs;
-  gint __days = __usec / USEC_PER_DAY;
-
-  if (__usec < 0)
-    __days -= 1;
-
-  if (__days != 0)
-    g_date_time_add_days_internal (datetime, __days);
-
-  if (__usec < 0)
-    datetime->usec = USEC_PER_DAY + (__usec % USEC_PER_DAY);
-  else
-    datetime->usec = __usec % USEC_PER_DAY;
-}
-
-#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;
-
-  if (tz_name != NULL)
-    {
-      const gchar *tz_dir = g_getenv ("TZDIR");
-
-      if (tz_dir != NULL)
-        retval = g_build_filename (tz_dir, tz_name, 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.
- *
- * @tzname: 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  *tzname,
-              gint64        start,
-              gboolean      is_utc,
-             gboolean     *_is_dst,
-              gint64       *_offset,
-              gchar       **_name)
-{
-  gchar *filename, *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;
-  GError *error;
-
-  filename = get_tzdata_path (tzname);
-
-  /* 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);
-
-  if (length < TZ_HEADER_SIZE ||
-      (strncmp (contents, TZ_MAGIC, TZ_MAGIC_LEN) != 0))
-    {
-      g_free (contents);
-      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));
-
-  g_free (contents);
-
-  if (_offset)
-    *_offset = offset;
-
-  if (_is_dst)
-    *_is_dst = isdst;
-
-  return TRUE;
-}
-
-/*< internal >
- * g_time_zone_new_from_epoc:
- * @tzname: The Olson's database timezone name
- * @epoch: The epoch offset
- * @is_utc: If the @epoch is in UTC or already in the @tzname timezone
- *
- * Creates a new timezone
- */
-static GTimeZone *
-g_time_zone_new_from_epoch (const gchar *tzname,
-                            gint64       epoch,
-                            gboolean     is_utc)
-{
-  GTimeZone *tz = NULL;
-  gint64 offset;
-  gboolean is_dst;
-  gchar *name = NULL;
-
-  if (parse_tzdata (tzname, epoch, is_utc, &is_dst, &offset, &name))
-    {
-      tz = g_slice_new (GTimeZone);
-      tz->is_dst = is_dst;
-      tz->offset = offset;
-      tz->name = name;
-    }
-
-  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)
-
-static gint64
-g_date_time_secs_offset (GDateTime * dt)
-{
-  gint64 secs;
-  gint d, y, h, m, s;
-  gint leaps;
-
-  y = g_date_time_get_year (dt) - 1970;
-  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);
-
-  /* FIXME this is an approximation */
-  leaps = y / 4;
-
-  secs = 0;
-  secs += y * SECS_PER_YEAR;
-  secs += d * SECS_PER_DAY;
-  secs += leaps * SECS_PER_DAY;
-  secs += h * SECS_PER_HOUR;
-  secs += m * SECS_PER_MINUTE;
-  secs += s;
-
-  return secs;
-}
-
-/*< internal >
- * g_date_time_create_time_zone:
- * @dt: a #GDateTime
- * @tzname: the name of the timezone
- *
- * 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 @tzname zone.
- *
- * Return value: a newly created #GTimeZone
- */
-static GTimeZone *
-g_date_time_create_time_zone (GDateTime   *dt,
-                              const gchar *tzname)
-{
-  gint64 secs;
-
-  secs = g_date_time_secs_offset (dt);
-
-  return g_time_zone_new_from_epoch (tzname, secs, FALSE);
-}
-
-static GDateTime *
-g_date_time_new (void)
-{
-  GDateTime *datetime;
-
-  datetime = g_slice_new0 (GDateTime);
-  datetime->ref_count = 1;
-
-  return datetime;
-}
+#endif  /* HAVE_LANGINFO_TIME */
 
-static GTimeZone *
-g_time_zone_copy (const GTimeZone *timezone)
+static inline gint
+ymd_to_days (gint year,
+             gint month,
+             gint day)
 {
-  GTimeZone *tz;
-
-  if (G_UNLIKELY (timezone == NULL))
-    return NULL;
-
-  tz = g_slice_new (GTimeZone);
-  memcpy (tz, timezone, sizeof (GTimeZone));
-
-  tz->name = g_strdup (timezone->name);
-
-  return tz;
-}
+  gint64 days;
 
-static void
-g_time_zone_free (GTimeZone *timezone)
-{
-  if (G_LIKELY (timezone != NULL))
-    {
-      g_free (timezone->name);
-      g_slice_free (GTimeZone, timezone);
-    }
-}
+  days = (year - 1) * 365 + ((year - 1) / 4) - ((year - 1) / 100)
+      + ((year - 1) / 400);
 
-static void
-g_date_time_free (GDateTime *datetime)
-{
-  if (G_UNLIKELY (datetime == NULL))
-    return;
+  days += days_in_year[0][month - 1];
+  if (GREGORIAN_LEAP (year) && month > 2)
+    day++;
 
-  if (datetime->tz)
-    g_time_zone_free (datetime->tz);
+  days += day;
 
-  g_slice_free (GDateTime, datetime);
+  return days;
 }
 
 static void
-g_date_time_get_week_number (const GDateTime *datetime,
-                             gint            *week_number,
-                             gint            *day_of_week,
-                             gint            *day_of_year)
+g_date_time_get_week_number (GDateTime *datetime,
+                             gint      *week_number,
+                             gint      *day_of_week,
+                             gint      *day_of_year)
 {
   gint a, b, c, d, e, f, g, n, s, month, day, year;
 
-  g_date_time_get_dmy (datetime, &day, &month, &year);
+  g_date_time_get_ymd (datetime, &year, &month, &day);
 
   if (month <= 2)
     {
@@ -660,254 +418,805 @@ g_date_time_get_week_number (const GDateTime *datetime,
     *day_of_year = f + 1;
 }
 
+/* Lifecycle {{{1 */
+
+static GDateTime *
+g_date_time_alloc (GTimeZone *tz)
+{
+  GDateTime *datetime;
+
+  datetime = g_slice_new0 (GDateTime);
+  datetime->tz = g_time_zone_ref (tz);
+  datetime->ref_count = 1;
+
+  return datetime;
+}
+
 /**
- * g_date_time_add:
+ * g_date_time_ref:
  * @datetime: a #GDateTime
- * @timespan: a #GTimeSpan
  *
- * Creates a copy of @datetime and adds the specified timespan to the copy.
+ * Atomically increments the reference count of @datetime by one.
  *
- * Return value: the newly created #GDateTime which should be freed with
- *   g_date_time_unref().
+ * Returns: the #GDateTime with the reference count increased
  *
  * Since: 2.26
  */
-GDateTime*
-g_date_time_add (const GDateTime *datetime,
-                 GTimeSpan        timespan)
+GDateTime *
+g_date_time_ref (GDateTime *datetime)
 {
-  GDateTime *dt;
-
   g_return_val_if_fail (datetime != NULL, NULL);
+  g_return_val_if_fail (datetime->ref_count > 0, NULL);
 
-  dt = g_date_time_copy (datetime);
-  g_date_time_add_usec (dt, timespan);
+  g_atomic_int_inc (&datetime->ref_count);
 
-  return dt;
+  return datetime;
 }
 
 /**
- * g_date_time_add_years:
+ * g_date_time_unref:
  * @datetime: a #GDateTime
- * @years: the number of years
  *
- * Creates a copy of @datetime and adds the specified number of years to the
- * copy.
+ * Atomically decrements the reference count of @datetime by one.
  *
- * Return value: the newly created #GDateTime which should be freed with
- *   g_date_time_unref().
+ * When the reference count reaches zero, the resources allocated by
+ * @datetime are freed
  *
  * Since: 2.26
  */
-GDateTime*
-g_date_time_add_years (const GDateTime *datetime,
-                       gint             years)
+void
+g_date_time_unref (GDateTime *datetime)
 {
-  GDateTime *dt;
-  gint       day;
-
-  g_return_val_if_fail (datetime != NULL, NULL);
+  g_return_if_fail (datetime != NULL);
+  g_return_if_fail (datetime->ref_count > 0);
 
-  day = g_date_time_get_day_of_month (datetime);
-  if (g_date_time_is_leap_year (datetime) &&
-      g_date_time_get_month (datetime) == 2)
+  if (g_atomic_int_dec_and_test (&datetime->ref_count))
     {
-      if (day == 29)
-        day--;
+      g_time_zone_unref (datetime->tz);
+      g_slice_free (GDateTime, datetime);
     }
-
-  dt = g_date_time_new_from_date (g_date_time_get_year (datetime) + years,
-                                  g_date_time_get_month (datetime),
-                                  day);
-  dt->usec = datetime->usec;
-
-  return dt;
 }
 
-/**
- * g_date_time_add_months:
+/* Internal state transformers {{{1 */
+/*< internal >
+ * g_date_time_to_instant:
  * @datetime: a #GDateTime
- * @months: the number of months
  *
- * Creates a copy of @datetime and adds the specified number of months to the
- * copy.
+ * Convert a @datetime into an instant.
  *
- * Return value: the newly created #GDateTime which should be freed with
- *   g_date_time_unref().
+ * An instant is a number that uniquely describes a particular
+ * microsecond in time, taking time zone considerations into account.
+ * (ie: "03:00 -0400" is the same instant as "02:00 -0500").
  *
- * Since: 2.26
+ * An instant is always positive but we use a signed return value to
+ * avoid troubles with C.
  */
-GDateTime*
-g_date_time_add_months (const GDateTime *datetime,
-                        gint             months)
-{
-  GDateTime     *dt;
-  gint           year,
-                 month,
-                 day,
-                 i,
-                 a;
-  const guint16 *days;
+static gint64
+g_date_time_to_instant (GDateTime *datetime)
+{
+  gint64 offset;
 
-  g_return_val_if_fail (datetime != NULL, NULL);
-  g_return_val_if_fail (months != 0, NULL);
+  offset = g_time_zone_get_offset (datetime->tz, datetime->interval);
+  offset *= USEC_PER_SECOND;
 
-  month = g_date_time_get_month (datetime);
-  year = g_date_time_get_year (datetime);
-  a = months > 0 ? 1 : -1;
+  return datetime->days * USEC_PER_DAY + datetime->usec - offset;
+}
 
-  for (i = 0; i < ABS (months); i++)
-    {
-      month += a;
-      if (month < 1)
-        {
-          year--;
-          month = 12;
-        }
-      else if (month > 12)
-        {
-          year++;
-          month = 1;
-        }
+/*< internal >
+ * g_date_time_from_instant:
+ * @tz: a #GTimeZone
+ * @instant: a instant in time
+ *
+ * Creates a #GDateTime from a time zone and an instant.
+ *
+ * This might fail if the time ends up being out of range.
+ */
+static GDateTime *
+g_date_time_from_instant (GTimeZone *tz,
+                          gint64     instant)
+{
+  GDateTime *datetime;
+  gint64 offset;
+
+  if (instant < 0 || instant > G_GINT64_CONSTANT (1000000000000000000))
+    return NULL;
+
+  datetime = g_date_time_alloc (tz);
+  datetime->interval = g_time_zone_find_interval (tz,
+                                                  G_TIME_TYPE_UNIVERSAL,
+                                                  INSTANT_TO_UNIX (instant));
+  offset = g_time_zone_get_offset (datetime->tz, datetime->interval);
+  offset *= USEC_PER_SECOND;
+
+  instant += offset;
+
+  datetime->days = instant / USEC_PER_DAY;
+  datetime->usec = instant % USEC_PER_DAY;
+
+  if (datetime->days < 1 || 3652059 < datetime->days)
+    {
+      g_date_time_unref (datetime);
+      datetime = NULL;
     }
 
-  day = g_date_time_get_day_of_month (datetime);
-  days = days_in_months [GREGORIAN_LEAP (year) ? 1 : 0];
+  return datetime;
+}
+
+
+/*< internal >
+ * g_date_time_deal_with_date_change:
+ * @datetime: a #GDateTime
+ *
+ * This function should be called whenever the date changes by adding
+ * days, months or years.  It does three things.
+ *
+ * First, we ensure that the date falls between 0001-01-01 and
+ * 9999-12-31 and return %FALSE if it does not.
+ *
+ * Next we update the ->interval field.
+ *
+ * Finally, we ensure that the resulting date and time pair exists (by
+ * ensuring that our time zone 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.
+ */
+static gboolean
+g_date_time_deal_with_date_change (GDateTime *datetime)
+{
+  GTimeType was_dst;
+  gint64 full_time;
+  gint64 usec;
+
+  if (datetime->days < 1 || datetime->days > 3652059)
+    return FALSE;
+
+  was_dst = g_time_zone_is_dst (datetime->tz, datetime->interval);
+
+  full_time = datetime->days * USEC_PER_DAY + datetime->usec;
+
+
+  usec = full_time % USEC_PER_SECOND;
+  full_time /= USEC_PER_SECOND;
+  full_time -= UNIX_EPOCH_START * SEC_PER_DAY;
 
-  if (days[month] < day)
-    day = days[month];
+  datetime->interval = g_time_zone_adjust_time (datetime->tz,
+                                                was_dst,
+                                                &full_time);
+  full_time += UNIX_EPOCH_START * SEC_PER_DAY;
+  full_time *= USEC_PER_SECOND;
+  full_time += usec;
 
-  dt = g_date_time_new_from_date (year, month, day);
-  dt->usec = datetime->usec;
+  datetime->days = full_time / USEC_PER_DAY;
+  datetime->usec = full_time % USEC_PER_DAY;
 
-  return dt;
+  /* maybe daylight time caused us to shift to a different day,
+   * but it definitely didn't push us into a different year */
+  return TRUE;
+}
+
+static GDateTime *
+g_date_time_replace_days (GDateTime *datetime,
+                          gint       days)
+{
+  GDateTime *new;
+
+  new = g_date_time_alloc (datetime->tz);
+  new->interval = datetime->interval;
+  new->usec = datetime->usec;
+  new->days = days;
+
+  if (!g_date_time_deal_with_date_change (new))
+    {
+      g_date_time_unref (new);
+      new = NULL;
+    }
+
+  return new;
+}
+
+/* now/unix/timeval Constructors {{{1 */
+
+/*< internal >
+ * g_date_time_new_from_timeval:
+ * @tz: a #GTimeZone
+ * @tv: a #GTimeVal
+ *
+ * Creates a #GDateTime corresponding to the given #GTimeVal @tv in the
+ * given time zone @tz.
+ *
+ * The time contained in a #GTimeVal is always stored in the form of
+ * seconds elapsed since 1970-01-01 00:00:00 UTC, regardless of the
+ * given time zone.
+ *
+ * This call can fail (returning %NULL) if @tv represents a time outside
+ * of the supported range of #GDateTime.
+ *
+ * You should release the return value by calling g_date_time_unref()
+ * when you are done with it.
+ *
+ * Returns: a new #GDateTime, or %NULL
+ *
+ * Since: 2.26
+ **/
+static GDateTime *
+g_date_time_new_from_timeval (GTimeZone      *tz,
+                              const GTimeVal *tv)
+{
+  return g_date_time_from_instant (tz, tv->tv_usec +
+                                   UNIX_TO_INSTANT (tv->tv_sec));
+}
+
+/*< internal >
+ * g_date_time_new_from_unix:
+ * @tz: a #GTimeZone
+ * @t: the Unix time
+ *
+ * Creates a #GDateTime corresponding to the given Unix time @t in the
+ * given time zone @tz.
+ *
+ * Unix time is the number of seconds that have elapsed since 1970-01-01
+ * 00:00:00 UTC, regardless of the time zone given.
+ *
+ * This call can fail (returning %NULL) if @t represents a time outside
+ * of the supported range of #GDateTime.
+ *
+ * You should release the return value by calling g_date_time_unref()
+ * when you are done with it.
+ *
+ * Returns: a new #GDateTime, or %NULL
+ *
+ * Since: 2.26
+ **/
+static GDateTime *
+g_date_time_new_from_unix (GTimeZone *tz,
+                           gint64     secs)
+{
+  return g_date_time_from_instant (tz, UNIX_TO_INSTANT (secs));
 }
 
 /**
- * g_date_time_add_weeks:
+ * g_date_time_new_now:
+ * @tz: a #GTimeZone
+ *
+ * Creates a #GDateTime corresponding to this exact instant in the given
+ * time zone @tz.  The time is as accurate as the system allows, to a
+ * maximum accuracy of 1 microsecond.
+ *
+ * This function will always succeed unless the system clock is set to
+ * truly insane values (or unless GLib is still being used after the
+ * year 9999).
+ *
+ * You should release the return value by calling g_date_time_unref()
+ * when you are done with it.
+ *
+ * Returns: a new #GDateTime, or %NULL
+ *
+ * Since: 2.26
+ **/
+GDateTime *
+g_date_time_new_now (GTimeZone *tz)
+{
+  GTimeVal tv;
+
+  g_get_current_time (&tv);
+
+  return g_date_time_new_from_timeval (tz, &tv);
+}
+
+/**
+ * g_date_time_new_now_local:
+ *
+ * Creates a #GDateTime corresponding to this exact instant in the local
+ * time zone.
+ *
+ * This is equivalent to calling g_date_time_new_now() with the time
+ * zone returned by g_time_zone_new_local().
+ *
+ * Returns: a new #GDateTime, or %NULL
+ *
+ * Since: 2.26
+ **/
+GDateTime *
+g_date_time_new_now_local (void)
+{
+  GDateTime *datetime;
+  GTimeZone *local;
+
+  local = g_time_zone_new_local ();
+  datetime = g_date_time_new_now (local);
+  g_time_zone_unref (local);
+
+  return datetime;
+}
+
+/**
+ * g_date_time_new_now_utc:
+ *
+ * Creates a #GDateTime corresponding to this exact instant in UTC.
+ *
+ * This is equivalent to calling g_date_time_new_now() with the time
+ * zone returned by g_time_zone_new_utc().
+ *
+ * Returns: a new #GDateTime, or %NULL
+ *
+ * Since: 2.26
+ **/
+GDateTime *
+g_date_time_new_now_utc (void)
+{
+  GDateTime *datetime;
+  GTimeZone *utc;
+
+  utc = g_time_zone_new_utc ();
+  datetime = g_date_time_new_now (utc);
+  g_time_zone_unref (utc);
+
+  return datetime;
+}
+
+/**
+ * g_date_time_new_from_unix_local:
+ * @t: the Unix time
+ *
+ * Creates a #GDateTime corresponding to the given Unix time @t in the
+ * local time zone.
+ *
+ * Unix time is the number of seconds that have elapsed since 1970-01-01
+ * 00:00:00 UTC, regardless of the local time offset.
+ *
+ * This call can fail (returning %NULL) if @t represents a time outside
+ * of the supported range of #GDateTime.
+ *
+ * You should release the return value by calling g_date_time_unref()
+ * when you are done with it.
+ *
+ * Returns: a new #GDateTime, or %NULL
+ *
+ * Since: 2.26
+ **/
+GDateTime *
+g_date_time_new_from_unix_local (gint64 t)
+{
+  GDateTime *datetime;
+  GTimeZone *local;
+
+  local = g_time_zone_new_local ();
+  datetime = g_date_time_new_from_unix (local, t);
+  g_time_zone_unref (local);
+
+  return datetime;
+}
+
+/**
+ * g_date_time_new_from_unix_utc:
+ * @t: the Unix time
+ *
+ * Creates a #GDateTime corresponding to the given Unix time @t in UTC.
+ *
+ * Unix time is the number of seconds that have elapsed since 1970-01-01
+ * 00:00:00 UTC.
+ *
+ * This call can fail (returning %NULL) if @t represents a time outside
+ * of the supported range of #GDateTime.
+ *
+ * You should release the return value by calling g_date_time_unref()
+ * when you are done with it.
+ *
+ * Returns: a new #GDateTime, or %NULL
+ *
+ * Since: 2.26
+ **/
+GDateTime *
+g_date_time_new_from_unix_utc (gint64 t)
+{
+  GDateTime *datetime;
+  GTimeZone *utc;
+
+  utc = g_time_zone_new_utc ();
+  datetime = g_date_time_new_from_unix (utc, t);
+  g_time_zone_unref (utc);
+
+  return datetime;
+}
+
+/**
+ * g_date_time_new_from_timeval_local:
+ * @tv: a #GTimeVal
+ *
+ * Creates a #GDateTime corresponding to the given #GTimeVal @tv in the
+ * local time zone.
+ *
+ * The time contained in a #GTimeVal is always stored in the form of
+ * seconds elapsed since 1970-01-01 00:00:00 UTC, regardless of the
+ * local time offset.
+ *
+ * This call can fail (returning %NULL) if @tv represents a time outside
+ * of the supported range of #GDateTime.
+ *
+ * You should release the return value by calling g_date_time_unref()
+ * when you are done with it.
+ *
+ * Returns: a new #GDateTime, or %NULL
+ *
+ * Since: 2.26
+ **/
+GDateTime *
+g_date_time_new_from_timeval_local (const GTimeVal *tv)
+{
+  GDateTime *datetime;
+  GTimeZone *local;
+
+  local = g_time_zone_new_local ();
+  datetime = g_date_time_new_from_timeval (local, tv);
+  g_time_zone_unref (local);
+
+  return datetime;
+}
+
+/**
+ * g_date_time_new_from_timeval_utc:
+ * @tv: a #GTimeVal
+ *
+ * Creates a #GDateTime corresponding to the given #GTimeVal @tv in UTC.
+ *
+ * The time contained in a #GTimeVal is always stored in the form of
+ * seconds elapsed since 1970-01-01 00:00:00 UTC.
+ *
+ * This call can fail (returning %NULL) if @tv represents a time outside
+ * of the supported range of #GDateTime.
+ *
+ * You should release the return value by calling g_date_time_unref()
+ * when you are done with it.
+ *
+ * Returns: a new #GDateTime, or %NULL
+ *
+ * Since: 2.26
+ **/
+GDateTime *
+g_date_time_new_from_timeval_utc (const GTimeVal *tv)
+{
+  GDateTime *datetime;
+  GTimeZone *utc;
+
+  utc = g_time_zone_new_utc ();
+  datetime = g_date_time_new_from_timeval (utc, tv);
+  g_time_zone_unref (utc);
+
+  return datetime;
+}
+
+/* full new functions {{{1 */
+
+/**
+ * g_date_time_new:
+ * @tz: a #GTimeZone
+ * @year: the year component of the date
+ * @month: the month component of the date
+ * @day: the day component of the date
+ * @hour: the hour component of the date
+ * @minute: the minute component of the date
+ * @seconds: the number of seconds past the minute
+ *
+ * Creates a new #GDateTime corresponding to the given date and time in
+ * the time zone @tz.
+ *
+ * The @year must be between 1 and 9999, @month between 1 and 12 and @day
+ * between 1 and 28, 29, 30 or 31 depending on the month and the year.
+ *
+ * @hour must be between 0 and 23 and @minute must be between 0 and 59.
+ *
+ * @seconds must be at least 0.0 and must be strictly less than 60.0.
+ * It will be rounded down to the nearest microsecond.
+ *
+ * If the given time is not representable in the given time zone (for
+ * example, 02:30 on March 14th 2010 in Toronto, due to daylight savings
+ * time) then the time will be rounded up to the nearest existing time
+ * (in this case, 03:00).  If this matters to you then you should verify
+ * the return value for containing the same as the numbers you gave.
+ *
+ * In the case that the given time is ambiguous in the given time zone
+ * (for example, 01:30 on November 7th 2010 in Toronto, due to daylight
+ * savings time) then the time falling within standard (ie:
+ * non-daylight) time is taken.
+ *
+ * It not considered a programmer error for the values to this function
+ * to be out of range, but in the case that they are, the function will
+ * return %NULL.
+ *
+ * You should release the return value by calling g_date_time_unref()
+ * when you are done with it.
+ *
+ * Returns: a new #GDateTime, or %NULL
+ *
+ * Since: 2.26
+ **/
+GDateTime *
+g_date_time_new (GTimeZone *tz,
+                 gint       year,
+                 gint       month,
+                 gint       day,
+                 gint       hour,
+                 gint       minute,
+                 gdouble    seconds)
+{
+  GDateTime *datetime;
+  gint64 full_time;
+
+  g_return_val_if_fail (tz != NULL, NULL);
+
+  if (year < 1 || year > 9999 ||
+      month < 1 || month > 12 ||
+      day < 1 || day > 31 ||
+      hour < 0 || hour > 23 ||
+      minute < 0 || minute > 59 ||
+      seconds < 0.0 || seconds >= 60.0)
+    return NULL;
+
+  datetime = g_date_time_alloc (tz);
+  datetime->days = ymd_to_days (year, month, day);
+  datetime->usec = (hour   * USEC_PER_HOUR)
+                 + (minute * USEC_PER_MINUTE)
+                 + (gint64) (seconds * USEC_PER_SECOND);
+
+  full_time = SEC_PER_DAY *
+                (ymd_to_days (year, month, day) - UNIX_EPOCH_START) +
+              SECS_PER_HOUR * hour +
+              SECS_PER_MINUTE * minute +
+              (int) seconds;
+
+  datetime->interval = g_time_zone_adjust_time (datetime->tz,
+                                                G_TIME_TYPE_STANDARD,
+                                                &full_time);
+
+  full_time += UNIX_EPOCH_START * SEC_PER_DAY;
+  datetime->days = full_time / SEC_PER_DAY;
+  datetime->usec = (full_time % SEC_PER_DAY) * USEC_PER_SECOND;
+  datetime->usec += ((int) (seconds * USEC_PER_SECOND)) % USEC_PER_SECOND;
+
+  return datetime;
+}
+
+/**
+ * g_date_time_new_local:
+ * @year: the year component of the date
+ * @month: the month component of the date
+ * @day: the day component of the date
+ * @hour: the hour component of the date
+ * @minute: the minute component of the date
+ * @seconds: the number of seconds past the minute
+ *
+ * Creates a new #GDateTime corresponding to the given date and time in
+ * the local time zone.
+ *
+ * This call is equivalent to calling g_date_time_new() with the time
+ * zone returned by g_time_zone_new_local().
+ *
+ * Returns: a #GDateTime, or %NULL
+ *
+ * Since: 2.26
+ **/
+GDateTime *
+g_date_time_new_local (gint    year,
+                       gint    month,
+                       gint    day,
+                       gint    hour,
+                       gint    minute,
+                       gdouble seconds)
+{
+  GDateTime *datetime;
+  GTimeZone *local;
+
+  local = g_time_zone_new_local ();
+  datetime = g_date_time_new (local, year, month, day, hour, minute, seconds);
+  g_time_zone_unref (local);
+
+  return datetime;
+}
+
+/**
+ * g_date_time_new_utc:
+ * @year: the year component of the date
+ * @month: the month component of the date
+ * @day: the day component of the date
+ * @hour: the hour component of the date
+ * @minute: the minute component of the date
+ * @seconds: the number of seconds past the minute
+ *
+ * Creates a new #GDateTime corresponding to the given date and time in
+ * UTC.
+ *
+ * This call is equivalent to calling g_date_time_new() with the time
+ * zone returned by g_time_zone_new_utc().
+ *
+ * Returns: a #GDateTime, or %NULL
+ *
+ * Since: 2.26
+ **/
+GDateTime *
+g_date_time_new_utc (gint    year,
+                     gint    month,
+                     gint    day,
+                     gint    hour,
+                     gint    minute,
+                     gdouble seconds)
+{
+  GDateTime *datetime;
+  GTimeZone *utc;
+
+  utc = g_time_zone_new_utc ();
+  datetime = g_date_time_new (utc, year, month, day, hour, minute, seconds);
+  g_time_zone_unref (utc);
+
+  return datetime;
+}
+
+/* Adders {{{1 */
+
+/**
+ * g_date_time_add:
  * @datetime: a #GDateTime
- * @weeks: the number of weeks
+ * @timespan: a #GTimeSpan
  *
- * Creates a copy of @datetime and adds the specified number of weeks to the
- * copy.
+ * Creates a copy of @datetime and adds the specified timespan to the copy.
  *
- * Return value: the newly created #GDateTime which should be freed with
+ * Returns: the newly created #GDateTime which should be freed with
  *   g_date_time_unref().
  *
  * Since: 2.26
  */
 GDateTime*
-g_date_time_add_weeks (const GDateTime *datetime,
-                       gint             weeks)
+g_date_time_add (GDateTime *datetime,
+                 GTimeSpan  timespan)
 {
-  g_return_val_if_fail (datetime != NULL, NULL);
-
-  return g_date_time_add_days (datetime, weeks * 7);
+  return g_date_time_from_instant (datetime->tz, timespan +
+                                   g_date_time_to_instant (datetime));
 }
 
 /**
- * g_date_time_add_days:
+ * g_date_time_add_years:
  * @datetime: a #GDateTime
- * @days: the number of days
+ * @years: the number of years
  *
- * Creates a copy of @datetime and adds the specified number of days to the
- * copy.
+ * Creates a copy of @datetime and adds the specified number of years to the
+ * copy. Add negative values to subtract years.
  *
- * Return value: the newly created #GDateTime which should be freed with
+ * Returns: the newly created #GDateTime which should be freed with
  *   g_date_time_unref().
  *
  * Since: 2.26
  */
-GDateTime*
-g_date_time_add_days (const GDateTime *datetime,
-                      gint             days)
+GDateTime *
+g_date_time_add_years (GDateTime *datetime,
+                       gint       years)
 {
-  GDateTime *dt;
+  gint year, month, day;
 
   g_return_val_if_fail (datetime != NULL, NULL);
 
-  dt = g_date_time_copy (datetime);
-  g_date_time_add_days_internal (dt, days);
+  if (years < -10000 || years > 10000)
+    return NULL;
 
-  return dt;
+  g_date_time_get_ymd (datetime, &year, &month, &day);
+  year += years;
+
+  /* only possible issue is if we've entered a year with no February 29
+   */
+  if (month == 2 && day == 29 && !GREGORIAN_LEAP (year))
+    day = 28;
+
+  return g_date_time_replace_days (datetime, ymd_to_days (year, month, day));
 }
 
 /**
- * g_date_time_add_hours:
+ * g_date_time_add_months:
  * @datetime: a #GDateTime
- * @hours: the number of hours to add
+ * @months: the number of months
  *
- * Creates a copy of @datetime and adds the specified number of hours
+ * Creates a copy of @datetime and adds the specified number of months to the
+ * copy. Add negative values to subtract months.
  *
- * Return value: the newly created #GDateTime which should be freed with
+ * Returns: the newly created #GDateTime which should be freed with
  *   g_date_time_unref().
  *
  * Since: 2.26
  */
 GDateTime*
-g_date_time_add_hours (const GDateTime *datetime,
-                       gint             hours)
+g_date_time_add_months (GDateTime *datetime,
+                        gint       months)
 {
-  GDateTime *dt;
+  gint year, month, day;
 
   g_return_val_if_fail (datetime != NULL, NULL);
+  g_date_time_get_ymd (datetime, &year, &month, &day);
 
-  dt = g_date_time_copy (datetime);
-  g_date_time_add_usec (dt, (gint64) hours * USEC_PER_HOUR);
+  if (months < -120000 || months > 120000)
+    return NULL;
 
-  return dt;
+  year += months / 12;
+  month += months % 12;
+  if (month < 1)
+    {
+      month += 12;
+      year--;
+    }
+  else if (month > 12)
+    {
+      month -= 12;
+      year++;
+    }
+
+  day = MIN (day, days_in_months[GREGORIAN_LEAP (year)][month]);
+
+  return g_date_time_replace_days (datetime, ymd_to_days (year, month, day));
 }
 
 /**
- * g_date_time_add_seconds:
+ * g_date_time_add_weeks:
  * @datetime: a #GDateTime
- * @seconds: the number of seconds to add
+ * @weeks: the number of weeks
  *
- * Creates a copy of @datetime and adds the specified number of seconds.
+ * Creates a copy of @datetime and adds the specified number of weeks to the
+ * copy. Add negative values to subtract weeks.
  *
- * Return value: the newly created #GDateTime which should be freed with
+ * Returns: the newly created #GDateTime which should be freed with
  *   g_date_time_unref().
  *
  * Since: 2.26
  */
 GDateTime*
-g_date_time_add_seconds (const GDateTime *datetime,
-                         gint             seconds)
+g_date_time_add_weeks (GDateTime *datetime,
+                       gint             weeks)
 {
-  GDateTime *dt;
-
   g_return_val_if_fail (datetime != NULL, NULL);
 
-  dt = g_date_time_copy (datetime);
-  g_date_time_add_usec (dt, (gint64) seconds * USEC_PER_SECOND);
-
-  return dt;
+  return g_date_time_add_days (datetime, weeks * 7);
 }
 
 /**
- * g_date_time_add_milliseconds:
+ * g_date_time_add_days:
  * @datetime: a #GDateTime
- * @milliseconds: the number of milliseconds to add
+ * @days: the number of days
  *
- * Creates a copy of @datetime adding the specified number of milliseconds.
+ * Creates a copy of @datetime and adds the specified number of days to the
+ * copy. Add negative values to subtract days.
  *
- * Return value: the newly created #GDateTime which should be freed with
+ * Returns: the newly created #GDateTime which should be freed with
  *   g_date_time_unref().
  *
  * Since: 2.26
  */
 GDateTime*
-g_date_time_add_milliseconds (const GDateTime *datetime,
-                              gint             milliseconds)
+g_date_time_add_days (GDateTime *datetime,
+                      gint       days)
 {
-  GDateTime *dt;
-
   g_return_val_if_fail (datetime != NULL, NULL);
 
-  dt = g_date_time_copy (datetime);
-  g_date_time_add_usec (dt, (gint64) milliseconds * USEC_PER_MILLISECOND);
+  if (days < -3660000 || days > 3660000)
+    return NULL;
 
-  return dt;
+  return g_date_time_replace_days (datetime, datetime->days + days);
+}
+
+/**
+ * g_date_time_add_hours:
+ * @datetime: a #GDateTime
+ * @hours: the number of hours to add
+ *
+ * Creates a copy of @datetime and adds the specified number of hours.
+ * Add negative values to subtract hours.
+ *
+ * Returns: the newly created #GDateTime which should be freed with
+ *   g_date_time_unref().
+ *
+ * Since: 2.26
+ */
+GDateTime*
+g_date_time_add_hours (GDateTime *datetime,
+                       gint       hours)
+{
+  return g_date_time_add (datetime, hours * USEC_PER_HOUR);
 }
 
 /**
@@ -915,25 +1224,40 @@ g_date_time_add_milliseconds (const GDateTime *datetime,
  * @datetime: a #GDateTime
  * @minutes: the number of minutes to add
  *
- * Creates a copy of @datetime adding the specified number of minutes.
+ * Creates a copy of @datetime adding the specified number of minutes.
+ * Add negative values to subtract minutes.
+ *
+ * Returns: the newly created #GDateTime which should be freed with
+ *   g_date_time_unref().
+ *
+ * Since: 2.26
+ */
+GDateTime*
+g_date_time_add_minutes (GDateTime *datetime,
+                         gint             minutes)
+{
+  return g_date_time_add (datetime, minutes * USEC_PER_MINUTE);
+}
+
+
+/**
+ * g_date_time_add_seconds:
+ * @datetime: a #GDateTime
+ * @seconds: the number of seconds to add
+ *
+ * Creates a copy of @datetime and adds the specified number of seconds.
+ * Add negative values to subtract seconds.
  *
- * Return value: the newly created #GDateTime which should be freed with
+ * Returns: the newly created #GDateTime which should be freed with
  *   g_date_time_unref().
  *
  * Since: 2.26
  */
 GDateTime*
-g_date_time_add_minutes (const GDateTime *datetime,
-                         gint             minutes)
+g_date_time_add_seconds (GDateTime *datetime,
+                         gdouble    seconds)
 {
-  GDateTime *dt;
-
-  g_return_val_if_fail (datetime != NULL, NULL);
-
-  dt = g_date_time_copy (datetime);
-  g_date_time_add_usec (dt, (gint64) minutes * USEC_PER_MINUTE);
-
-  return dt;
+  return g_date_time_add (datetime, seconds * USEC_PER_SECOND);
 }
 
 /**
@@ -947,61 +1271,106 @@ g_date_time_add_minutes (const GDateTime *datetime,
  * @seconds: the number of seconds to add
  *
  * Creates a new #GDateTime adding the specified values to the current date and
- * time in @datetime.
+ * time in @datetime. Add negative values to subtract.
  *
- * Return value: the newly created #GDateTime that should be freed with
+ * Returns: the newly created #GDateTime that should be freed with
  *   g_date_time_unref().
  *
  * Since: 2.26
  */
 GDateTime *
-g_date_time_add_full (const GDateTime *datetime,
-                      gint             years,
-                      gint             months,
-                      gint             days,
-                      gint             hours,
-                      gint             minutes,
-                      gint             seconds)
-{
-  GDateTime *tmp, *dt;
+g_date_time_add_full (GDateTime *datetime,
+                      gint       years,
+                      gint       months,
+                      gint       days,
+                      gint       hours,
+                      gint       minutes,
+                      gdouble    seconds)
+{
+  gint year, month, day;
+  gint64 full_time;
+  GDateTime *new;
+  gint interval;
 
   g_return_val_if_fail (datetime != NULL, NULL);
+  g_date_time_get_ymd (datetime, &year, &month, &day);
+
+  months += years * 12;
+
+  if (months < -120000 || months > 120000)
+    return NULL;
+
+  if (days < -3660000 || days > 3660000)
+    return NULL;
+
+  year += months / 12;
+  month += months % 12;
+  if (month < 1)
+    {
+      month += 12;
+      year--;
+    }
+  else if (month > 12)
+    {
+      month -= 12;
+      year++;
+    }
+
+  day = MIN (day, days_in_months[GREGORIAN_LEAP (year)][month]);
 
-  dt = g_date_time_add_years (datetime, years);
-  tmp = dt;
+  /* full_time is now in unix (local) time */
+  full_time = datetime->usec / USEC_PER_SECOND + SEC_PER_DAY *
+    (ymd_to_days (year, month, day) + days - UNIX_EPOCH_START);
 
-  dt = g_date_time_add_months (tmp, months);
-  g_date_time_unref (tmp);
-  tmp = dt;
+  interval = g_time_zone_adjust_time (datetime->tz,
+                                      g_time_zone_is_dst (datetime->tz,
+                                                          datetime->interval),
+                                      &full_time);
 
-  dt = g_date_time_add_days (tmp, days);
-  g_date_time_unref (tmp);
-  tmp = dt;
+  /* move to UTC unix time */
+  full_time -= g_time_zone_get_offset (datetime->tz, interval);
 
-  dt = g_date_time_add_hours (tmp, hours);
-  g_date_time_unref (tmp);
-  tmp = dt;
+  /* convert back to an instant, add back fractional seconds */
+  full_time += UNIX_EPOCH_START * SEC_PER_DAY;
+  full_time = full_time * USEC_PER_SECOND +
+              datetime->usec % USEC_PER_SECOND;
 
-  dt = g_date_time_add_minutes (tmp, minutes);
-  g_date_time_unref (tmp);
-  tmp = dt;
+  /* do the actual addition now */
+  full_time += (hours * USEC_PER_HOUR) +
+               (minutes * USEC_PER_MINUTE) +
+               (gint64) (seconds * USEC_PER_SECOND);
 
-  dt = g_date_time_add_seconds (tmp, seconds);
-  g_date_time_unref (tmp);
+  /* find the new interval */
+  interval = g_time_zone_find_interval (datetime->tz,
+                                        G_TIME_TYPE_UNIVERSAL,
+                                        INSTANT_TO_UNIX (full_time));
 
-  return dt;
+  /* convert back into local time */
+  full_time += USEC_PER_SECOND *
+               g_time_zone_get_offset (datetime->tz, interval);
+
+  /* split into days and usec of a new datetime */
+  new = g_date_time_alloc (datetime->tz);
+  new->interval = interval;
+  new->days = full_time / USEC_PER_DAY;
+  new->usec = full_time % USEC_PER_DAY;
+
+  /* XXX validate */
+
+  return new;
 }
 
+/* Compare, difference, hash, equal {{{1 */
 /**
  * g_date_time_compare:
  * @dt1: first #GDateTime to compare
  * @dt2: second #GDateTime to compare
  *
- * qsort()-style comparison for #GDateTime<!-- -->'s. Both #GDateTime<-- -->'s
- * must be non-%NULL.
+ * A comparison function for #GDateTimes that is suitable
+ * as a #GCompareFunc. Both #GDateTimes must be non-%NULL.
  *
- * Return value: 0 for equal, less than zero if dt1 is less than dt2, greater
- *   than zero if dt2 is greator than dt1.
+ * Returns: -1, 0 or 1 if @dt1 is less than, equal to or greater
+ *   than @dt2.
  *
  * Since: 2.26
  */
@@ -1009,107 +1378,59 @@ gint
 g_date_time_compare (gconstpointer dt1,
                      gconstpointer dt2)
 {
-  const GDateTime *a, *b;
+  gint64 difference;
 
-  a = dt1;
-  b = dt2;
+  difference = g_date_time_difference ((GDateTime *) dt1, (GDateTime *) dt2);
 
-  if ((a->period == b->period) &&
-      (a->julian == b->julian) &&
-      (a->usec == b->usec))
-    {
-      return 0;
-    }
-  else if ((a->period > b->period) ||
-           ((a->period == b->period) && (a->julian > b->julian)) ||
-           ((a->period == b->period) && (a->julian == b->julian) && a->usec > b->usec))
-    {
-      return 1;
-    }
-  else
+  if (difference < 0)
     return -1;
-}
-
-/**
- * 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->period = datetime->period;
-  copied->julian = datetime->julian;
-  copied->usec = datetime->usec;
-  copied->tz = g_time_zone_copy (datetime->tz);
+  else if (difference > 0)
+    return 1;
 
-  return copied;
+  else
+    return 0;
 }
 
 /**
- * g_date_time_day:
- * @datetime: a #GDateTime
+ * g_date_time_difference:
+ * @end: a #GDateTime
+ * @begin: a #GDateTime
  *
- * Creates a new #GDateTime at Midnight on the date represented by @datetime.
+ * Calculates the difference in time between @end and @begin.  The
+ * #GTimeSpan that is returned is effectively @end - @begin (ie:
+ * positive if the first parameter is larger).
  *
- * Return value: the newly created #GDateTime which should be freed with
- *   g_date_time_unref().
+ * Returns: the difference between the two #GDateTime, as a time
+ *   span expressed in microseconds.
  *
  * Since: 2.26
  */
-GDateTime*
-g_date_time_day (const GDateTime *datetime)
+GTimeSpan
+g_date_time_difference (GDateTime *end,
+                        GDateTime *begin)
 {
-  GDateTime *date;
-
-  g_return_val_if_fail (datetime != NULL, NULL);
-
-  date = g_date_time_copy (datetime);
-  date->usec = 0;
+  g_return_val_if_fail (begin != NULL, 0);
+  g_return_val_if_fail (end != NULL, 0);
 
-  return date;
+  return g_date_time_to_instant (end) -
+         g_date_time_to_instant (begin);
 }
 
 /**
- * g_date_time_difference:
- * @begin: a #GDateTime
- * @end: a #GDateTime
- *
- * Calculates the known difference in time between @begin and @end.
+ * g_date_time_hash:
+ * @datetime: a #GDateTime
  *
- * Since the exact precision cannot always be known due to incomplete
- * historic information, an attempt is made to calculate the difference.
+ * Hashes @datetime into a #guint, suitable for use within #GHashTable.
  *
- * Return value: the difference between the two #GDateTime, as a time
- *   span expressed in microseconds.
+ * Returns: a #guint containing the hash
  *
  * Since: 2.26
  */
-GTimeSpan
-g_date_time_difference (const GDateTime *begin,
-                        const GDateTime *end)
+guint
+g_date_time_hash (gconstpointer datetime)
 {
-  g_return_val_if_fail (begin != NULL, 0);
-  g_return_val_if_fail (end != NULL, 0);
-
-  if (begin->period != 0 || end->period != 0)
-    {
-      g_warning ("GDateTime only supports the current Julian period");
-      return 0;
-    }
-
-  return ((end->julian - begin->julian) * USEC_PER_DAY) + (end->usec - begin->usec);
+  return g_date_time_to_instant ((GDateTime *) datetime);
 }
 
 /**
@@ -1120,9 +1441,9 @@ g_date_time_difference (const GDateTime *begin,
  * Checks to see if @dt1 and @dt2 are equal.
  *
  * Equal here means that they represent the same moment after converting
- * them to the same timezone.
+ * them to the same time zone.
  *
- * Return value: %TRUE if @dt1 and @dt2 are equal
+ * Returns: %TRUE if @dt1 and @dt2 are equal
  *
  * Since: 2.26
  */
@@ -1130,1084 +1451,1203 @@ gboolean
 g_date_time_equal (gconstpointer dt1,
                    gconstpointer dt2)
 {
-  const GDateTime *a, *b;
-  GDateTime *a_utc, *b_utc;
-  gint64 a_epoch, b_epoch;
-
-  a = dt1;
-  b = dt2;
-
-  a_utc = g_date_time_to_utc ((GDateTime *) a);
-  b_utc = g_date_time_to_utc ((GDateTime *) b);
-
-  a_epoch = g_date_time_to_epoch (a_utc);
-  b_epoch = g_date_time_to_epoch (b_utc);
-
-  g_date_time_unref (a_utc);
-  g_date_time_unref (b_utc);
-
-  return a_epoch == b_epoch;
+  return g_date_time_difference ((GDateTime *) dt1, (GDateTime *) dt2) == 0;
 }
 
+/* Year, Month, Day Getters {{{1 */
 /**
- * g_date_time_get_day_of_week:
- * @datetime: a #GDateTime
- *
- * Retrieves the day of the week represented by @datetime within the gregorian
- * calendar. 1 is Sunday, 2 is Monday, etc.
+ * g_date_time_get_ymd:
+ * @datetime: a #GDateTime.
+ * @year: (out) (allow-none): the return location for the gregorian year, or %NULL.
+ * @month: (out) (allow-none): the return location for the month of the year, or %NULL.
+ * @day: (out) (allow-none): the return location for the day of the month, or %NULL.
  *
- * Return value: the day of the week
+ * Retrieves the Gregorian day, month, and year of a given #GDateTime.
  *
  * Since: 2.26
- */
-gint
-g_date_time_get_day_of_week (const GDateTime *datetime)
-{
-  gint a, y, m,
-       year  = 0,
-       month = 0,
-       day   = 0,
-       dow;
+ **/
+void
+g_date_time_get_ymd (GDateTime *datetime,
+                     gint      *year,
+                     gint      *month,
+                     gint      *day)
+{
+  gint the_year;
+  gint the_month;
+  gint the_day;
+  gint remaining_days;
+  gint y100_cycles;
+  gint y4_cycles;
+  gint y1_cycles;
+  gint preceding;
+  gboolean leap;
 
-  g_return_val_if_fail (datetime != NULL, 0);
+  g_return_if_fail (datetime != NULL);
+
+  remaining_days = datetime->days;
 
   /*
-   * See Calendar FAQ Section 2.6 for algorithm information
-   * http://www.tondering.dk/claus/cal/calendar29.txt
+   * We need to convert an offset in days to its year/month/day representation.
+   * Leap years makes this a little trickier than it should be, so we use
+   * 400, 100 and 4 years cycles here to get to the correct year.
    */
 
-  g_date_time_get_dmy (datetime, &day, &month, &year);
-  a = (14 - month) / 12;
-  y = year - a;
-  m = month + (12 * a) - 2;
-  dow = ((day + y + (y / 4) - (y / 100) + (y / 400) + (31 * m) / 12) % 7);
-
-  /* 1 is Monday and 7 is Sunday */
-  return (dow == 0) ? 7 : dow;
-}
-
-/**
- * g_date_time_get_day_of_month:
- * @datetime: a #GDateTime
- *
- * Retrieves the day of the month represented by @datetime in the gregorian
- * calendar.
- *
- * Return value: the day of the month
- *
- * Since: 2.26
- */
-gint
-g_date_time_get_day_of_month (const GDateTime *datetime)
-{
-  gint           day_of_year,
-                 i;
-  const guint16 *days;
-  guint16        last = 0;
+  /* Our days offset starts sets 0001-01-01 as day 1, if it was day 0 our
+   * math would be simpler, so let's do it */
+  remaining_days--;
 
-  g_return_val_if_fail (datetime != NULL, 0);
+  the_year = (remaining_days / DAYS_IN_400YEARS) * 400 + 1;
+  remaining_days = remaining_days % DAYS_IN_400YEARS;
 
-  days = days_in_year[g_date_time_is_leap_year (datetime) ? 1 : 0];
-  g_date_time_get_week_number (datetime, NULL, NULL, &day_of_year);
+  y100_cycles = remaining_days / DAYS_IN_100YEARS;
+  remaining_days = remaining_days % DAYS_IN_100YEARS;
+  the_year += y100_cycles * 100;
 
-  for (i = 1; i <= 12; i++)
-    {
-      if (days [i] >= day_of_year)
-        return day_of_year - last;
-      last = days [i];
-    }
+  y4_cycles = remaining_days / DAYS_IN_4YEARS;
+  remaining_days = remaining_days % DAYS_IN_4YEARS;
+  the_year += y4_cycles * 4;
 
-  g_warn_if_reached ();
-  return 0;
-}
+  y1_cycles = remaining_days / 365;
+  the_year += y1_cycles;
+  remaining_days = remaining_days % 365;
 
-/**
- * g_date_time_get_day_of_year:
- * @datetime: a #GDateTime
- *
- * Retrieves the day of the year represented by @datetime in the Gregorian
- * calendar.
- *
- * Return value: the day of the year
- *
- * Since: 2.26
- */
-gint
-g_date_time_get_day_of_year (const GDateTime *datetime)
-{
-  gint doy = 0;
+  if (y1_cycles == 4 || y100_cycles == 4) {
+    g_assert (remaining_days == 0);
 
-  g_return_val_if_fail (datetime != NULL, 0);
+    /* special case that indicates that the date is actually one year before,
+     * in the 31th of December */
+    the_year--;
+    the_month = 12;
+    the_day = 31;
+    goto end;
+  }
 
-  g_date_time_get_week_number (datetime, NULL, NULL, &doy);
-  return doy;
-}
+  /* now get the month and the day */
+  leap = y1_cycles == 3 && (y4_cycles != 24 || y100_cycles == 3);
 
-/**
- * g_date_time_get_dmy:
- * @datetime: a #GDateTime.
- * @day: (out): the return location for the day of the month, or %NULL.
- * @month: (out): the return location for the monty of the year, or %NULL.
- * @year: (out): the return location for the gregorian year, or %NULL.
- *
- * Retrieves the Gregorian day, month, and year of a given #GDateTime.
- *
- * Since: 2.26
- */
-void
-g_date_time_get_dmy (const GDateTime *datetime,
-                     gint            *day,
-                     gint            *month,
-                     gint            *year)
-{
-  gint a, b, c, d, e, m;
+  g_assert (leap == GREGORIAN_LEAP(the_year));
 
-  a = datetime->julian + 32044;
-  b = ((4 * a) + 3) / 146097;
-  c = a - ((b * 146097) / 4);
-  d = ((4 * c) + 3) / 1461;
-  e = c - (1461 * d) / 4;
-  m = (5 * e + 2) / 153;
+  the_month = (remaining_days + 50) >> 5;
+  preceding = (days_in_year[0][the_month - 1] + (the_month > 2 && leap));
+  if (preceding > remaining_days)
+    {
+      /* estimate is too large */
+      the_month -= 1;
+      preceding -= leap ? days_in_months[1][the_month]
+                        : days_in_months[0][the_month];
+    }
 
-  if (day != NULL)
-    *day = e - (((153 * m) + 2) / 5) + 1;
+  remaining_days -= preceding;
+  g_assert(0 <= remaining_days);
 
-  if (month != NULL)
-    *month = m + 3 - (12 * (m / 10));
+  the_day = remaining_days + 1;
 
-  if (year != NULL)
-    *year  = (b * 100) + d - 4800 + (m / 10);
+end:
+  if (year)
+    *year = the_year;
+  if (month)
+    *month = the_month;
+  if (day)
+    *day = the_day;
 }
 
 /**
- * g_date_time_get_hour:
- * @datetime: a #GDateTime
+ * g_date_time_get_year:
+ * @datetime: A #GDateTime
  *
- * Retrieves the hour of the day represented by @datetime
+ * Retrieves the year represented by @datetime in the Gregorian calendar.
  *
- * Return value: the hour of the day
+ * Returns: the year represented by @datetime
  *
  * Since: 2.26
  */
 gint
-g_date_time_get_hour (const GDateTime *datetime)
+g_date_time_get_year (GDateTime *datetime)
 {
+  gint year;
+
   g_return_val_if_fail (datetime != NULL, 0);
 
-  return (datetime->usec / USEC_PER_HOUR);
+  g_date_time_get_ymd (datetime, &year, NULL, NULL);
+
+  return year;
 }
 
 /**
- * g_date_time_get_julian:
+ * g_date_time_get_month:
  * @datetime: a #GDateTime
- * @period: (out): a location for the Julian period
- * @julian: (out): a location for the day in the Julian period
- * @hour: (out): a location for the hour of the day
- * @minute: (out): a location for the minute of the hour
- * @second: (out): a location for hte second of the minute
  *
- * Retrieves the Julian period, day, hour, minute, and second which @datetime
- * represents in the Julian calendar.
+ * Retrieves the month of the year represented by @datetime in the Gregorian
+ * calendar.
+ *
+ * Returns: the month represented by @datetime
  *
  * Since: 2.26
  */
-void
-g_date_time_get_julian (const GDateTime *datetime,
-                        gint            *period,
-                        gint            *julian,
-                        gint            *hour,
-                        gint            *minute,
-                        gint            *second)
+gint
+g_date_time_get_month (GDateTime *datetime)
 {
-  g_return_if_fail (datetime != NULL);
-
-  if (period)
-    *period = datetime->period;
-
-  if (julian)
-    *julian = datetime->julian;
+  gint month;
 
-  if (hour)
-    *hour = (datetime->usec / USEC_PER_HOUR);
+  g_return_val_if_fail (datetime != NULL, 0);
 
-  if (minute)
-    *minute = (datetime->usec % USEC_PER_HOUR) / USEC_PER_MINUTE;
+  g_date_time_get_ymd (datetime, NULL, &month, NULL);
 
-  if (second)
-    *second = (datetime->usec % USEC_PER_MINUTE) / USEC_PER_SECOND;
+  return month;
 }
 
 /**
- * g_date_time_get_microsecond:
+ * g_date_time_get_day_of_month:
  * @datetime: a #GDateTime
  *
- * Retrieves the microsecond of the date represented by @datetime
+ * Retrieves the day of the month represented by @datetime in the gregorian
+ * calendar.
  *
- * Return value: the microsecond of the second
+ * Returns: the day of the month
  *
  * Since: 2.26
  */
 gint
-g_date_time_get_microsecond (const GDateTime *datetime)
+g_date_time_get_day_of_month (GDateTime *datetime)
 {
+  gint           day_of_year,
+                 i;
+  const guint16 *days;
+  guint16        last = 0;
+
   g_return_val_if_fail (datetime != NULL, 0);
 
-  return (datetime->usec % USEC_PER_SECOND);
+  days = days_in_year[GREGORIAN_LEAP (g_date_time_get_year (datetime)) ? 1 : 0];
+  g_date_time_get_week_number (datetime, NULL, NULL, &day_of_year);
+
+  for (i = 1; i <= 12; i++)
+    {
+      if (days [i] >= day_of_year)
+        return day_of_year - last;
+      last = days [i];
+    }
+
+  g_warn_if_reached ();
+  return 0;
 }
 
+/* Week of year / day of week getters {{{1 */
 /**
- * g_date_time_get_millisecond:
+ * g_date_time_get_week_numbering_year:
  * @datetime: a #GDateTime
  *
- * Retrieves the millisecond of the date represented by @datetime
+ * Returns the ISO 8601 week-numbering year in which the week containing
+ * @datetime falls.
  *
- * Return value: the millisecond of the second
+ * This function, taken together with g_date_time_get_week_of_year() and
+ * g_date_time_get_day_of_week() can be used to determine the full ISO
+ * week date on which @datetime falls.
  *
- * Since: 2.26
- */
-gint
-g_date_time_get_millisecond (const GDateTime *datetime)
-{
-  g_return_val_if_fail (datetime != NULL, 0);
-
-  return (datetime->usec % USEC_PER_SECOND) / USEC_PER_MILLISECOND;
-}
-
-/**
- * g_date_time_get_minute:
- * @datetime: a #GDateTime
+ * This is usually equal to the normal Gregorian year (as returned by
+ * g_date_time_get_year()), except as detailed below:
  *
- * Retrieves the minute of the hour represented by @datetime
+ * For Thursday, the week-numbering year is always equal to the usual
+ * calendar year.  For other days, the number is such that every day
+ * within a complete week (Monday to Sunday) is contained within the
+ * same week-numbering year.
  *
- * Return value: the minute of the hour
+ * For Monday, Tuesday and Wednesday occurring near the end of the year,
+ * this may mean that the week-numbering year is one greater than the
+ * calendar year (so that these days have the same week-numbering year
+ * as the Thursday occurring early in the next year).
  *
- * Since: 2.26
- */
-gint
-g_date_time_get_minute (const GDateTime *datetime)
-{
-  g_return_val_if_fail (datetime != NULL, 0);
-
-  return (datetime->usec % USEC_PER_HOUR) / USEC_PER_MINUTE;
-}
-
-/**
- * g_date_time_get_month:
- * @datetime: a #GDateTime
+ * For Friday, Saturaday and Sunday occurring near the start of the year,
+ * this may mean that the week-numbering year is one less than the
+ * calendar year (so that these days have the same week-numbering year
+ * as the Thursday occurring late in the previous year).
  *
- * Retrieves the month of the year represented by @datetime in the Gregorian
- * calendar.
+ * An equivalent description is that the week-numbering year is equal to
+ * the calendar year containing the majority of the days in the current
+ * week (Monday to Sunday).
+ *
+ * Note that January 1 0001 in the proleptic Gregorian calendar is a
+ * Monday, so this function never returns 0.
  *
- * Return value: the month represented by @datetime
+ * Returns: the ISO 8601 week-numbering year for @datetime
  *
  * Since: 2.26
- */
+ **/
 gint
-g_date_time_get_month (const GDateTime *datetime)
+g_date_time_get_week_numbering_year (GDateTime *datetime)
 {
-  gint month;
+  gint year, month, day, weekday;
 
-  g_return_val_if_fail (datetime != NULL, 0);
+  g_date_time_get_ymd (datetime, &year, &month, &day);
+  weekday = g_date_time_get_day_of_week (datetime);
 
-  g_date_time_get_dmy (datetime, NULL, &month, NULL);
+  /* January 1, 2, 3 might be in the previous year if they occur after
+   * Thursday.
+   *
+   *   Jan 1:  Friday, Saturday, Sunday    =>  day 1:  weekday 5, 6, 7
+   *   Jan 2:  Saturday, Sunday            =>  day 2:  weekday 6, 7
+   *   Jan 3:  Sunday                      =>  day 3:  weekday 7
+   *
+   * So we have a special case if (day - weekday) <= -4
+   */
+  if (month == 1 && (day - weekday) <= -4)
+    return year - 1;
+
+  /* December 29, 30, 31 might be in the next year if they occur before
+   * Thursday.
+   *
+   *   Dec 31: Monday, Tuesday, Wednesday  =>  day 31: weekday 1, 2, 3
+   *   Dec 30: Monday, Tuesday             =>  day 30: weekday 1, 2
+   *   Dec 29: Monday                      =>  day 29: weekday 1
+   *
+   * So we have a special case if (day - weekday) >= 28
+   */
+  else if (month == 12 && (day - weekday) >= 28)
+    return year + 1;
 
-  return month;
+  else
+    return year;
 }
 
 /**
- * g_date_time_get_second:
+ * g_date_time_get_week_of_year:
  * @datetime: a #GDateTime
  *
- * Retrieves the second of the minute represented by @datetime
- *
- * Return value: the second represented by @datetime
- *
- * Since: 2.26
- */
-gint
-g_date_time_get_second (const GDateTime *datetime)
-{
-  g_return_val_if_fail (datetime != NULL, 0);
-
-  return (datetime->usec % USEC_PER_MINUTE) / USEC_PER_SECOND;
-}
-
-/**
- * g_date_time_get_utc_offset:
- * @datetime: a #GDateTime
+ * Returns the ISO 8601 week number for the week containing @datetime.
+ * The ISO 8601 week number is the same for every day of the week (from
+ * Moday through Sunday).  That can produce some unusual results
+ * (described below).
  *
- * Retrieves the offset from UTC that the local timezone specified by
- * @datetime represents.
+ * The first week of the year is week 1.  This is the week that contains
+ * the first Thursday of the year.  Equivalently, this is the first week
+ * that has more than 4 of its days falling within the calendar year.
  *
- * If @datetime represents UTC time, then the offset is zero.
+ * The value 0 is never returned by this function.  Days contained
+ * within a year but occurring before the first ISO 8601 week of that
+ * year are considered as being contained in the last week of the
+ * previous year.  Similarly, the final days of a calendar year may be
+ * considered as being part of the first ISO 8601 week of the next year
+ * if 4 or more days of that week are contained within the new year.
  *
- * Return value: the offset, expressed as a time span expressed in
- *   microseconds.
+ * Returns: the ISO 8601 week number for @datetime.
  *
  * Since: 2.26
  */
-GTimeSpan
-g_date_time_get_utc_offset (const GDateTime *datetime)
+gint
+g_date_time_get_week_of_year (GDateTime *datetime)
 {
-  gint offset = 0;
+  gint weeknum;
 
   g_return_val_if_fail (datetime != NULL, 0);
 
-  if (datetime->tz != NULL)
-    offset = datetime->tz->offset;
+  g_date_time_get_week_number (datetime, &weeknum, NULL, NULL);
 
-  return (gint64) offset * USEC_PER_SECOND;
+  return weeknum;
 }
 
 /**
- * g_date_time_get_timezone_name:
+ * g_date_time_get_day_of_week:
  * @datetime: a #GDateTime
  *
- * Retrieves the Olson's database timezone name of the timezone specified
- * by @datetime.
+ * Retrieves the ISO 8601 day of the week on which @datetime falls (1 is
+ * Monday, 2 is Tuesday... 7 is Sunday).
  *
- * Return value: (transfer none): the name of the timezone. The returned
- *   string is owned by the #GDateTime and it should not be modified or
- *   freed
+ * Returns: the day of the week
  *
  * Since: 2.26
  */
-G_CONST_RETURN gchar *
-g_date_time_get_timezone_name (const GDateTime *datetime)
+gint
+g_date_time_get_day_of_week (GDateTime *datetime)
 {
-  g_return_val_if_fail (datetime != NULL, NULL);
-
-  if (datetime->tz != NULL)
-    return datetime->tz->name;
+  g_return_val_if_fail (datetime != NULL, 0);
 
-  return "UTC";
+  return (datetime->days - 1) % 7 + 1;
 }
 
+/* Day of year getter {{{1 */
 /**
- * g_date_time_get_year:
- * @datetime: A #GDateTime
+ * g_date_time_get_day_of_year:
+ * @datetime: a #GDateTime
  *
- * Retrieves the year represented by @datetime in the Gregorian calendar.
+ * Retrieves the day of the year represented by @datetime in the Gregorian
+ * calendar.
  *
- * Return value: the year represented by @datetime
+ * Returns: the day of the year
  *
  * Since: 2.26
  */
 gint
-g_date_time_get_year (const GDateTime *datetime)
+g_date_time_get_day_of_year (GDateTime *datetime)
 {
-  gint year;
+  gint doy = 0;
 
   g_return_val_if_fail (datetime != NULL, 0);
 
-  g_date_time_get_dmy (datetime, NULL, NULL, &year);
-
-  return year;
+  g_date_time_get_week_number (datetime, NULL, NULL, &doy);
+  return doy;
 }
 
+/* Time component getters {{{1 */
+
 /**
- * g_date_time_hash:
+ * g_date_time_get_hour:
  * @datetime: a #GDateTime
  *
- * Hashes @datetime into a #guint, suitable for use within #GHashTable.
+ * Retrieves the hour of the day represented by @datetime
  *
- * Return value: a #guint containing the hash
+ * Returns: the hour of the day
  *
  * Since: 2.26
  */
-guint
-g_date_time_hash (gconstpointer datetime)
+gint
+g_date_time_get_hour (GDateTime *datetime)
 {
-  return (guint) (*((guint64 *) datetime));
+  g_return_val_if_fail (datetime != NULL, 0);
+
+  return (datetime->usec / USEC_PER_HOUR);
 }
 
 /**
- * g_date_time_is_leap_year:
+ * g_date_time_get_minute:
  * @datetime: a #GDateTime
  *
- * Determines if @datetime represents a date known to fall within
- * a leap year in the Gregorian calendar.
+ * Retrieves the minute of the hour represented by @datetime
  *
- * Return value: %TRUE if @datetime is a leap year.
+ * Returns: the minute of the hour
  *
  * Since: 2.26
  */
-gboolean
-g_date_time_is_leap_year (const GDateTime *datetime)
+gint
+g_date_time_get_minute (GDateTime *datetime)
 {
-  gint year;
-
-  g_return_val_if_fail (datetime != NULL, FALSE);
-
-  year = g_date_time_get_year (datetime);
+  g_return_val_if_fail (datetime != NULL, 0);
 
-  return GREGORIAN_LEAP (year);
+  return (datetime->usec % USEC_PER_HOUR) / USEC_PER_MINUTE;
 }
 
 /**
- * g_date_time_is_daylight_savings:
+ * g_date_time_get_second:
  * @datetime: a #GDateTime
  *
- * Determines if @datetime represents a date known to fall within daylight
- * savings time in the gregorian calendar.
+ * Retrieves the second of the minute represented by @datetime
  *
- * Return value: %TRUE if @datetime falls within daylight savings time.
+ * Returns: the second represented by @datetime
  *
  * Since: 2.26
  */
-gboolean
-g_date_time_is_daylight_savings (const GDateTime *datetime)
-{
-  g_return_val_if_fail (datetime != NULL, FALSE);
-
-  if (!datetime->tz)
-    return FALSE;
-
-  return datetime->tz->is_dst;
-}
-
-static inline gint
-date_to_julian (gint year,
-                gint month,
-                gint day)
+gint
+g_date_time_get_second (GDateTime *datetime)
 {
-  gint a = (14 - month) / 12;
-  gint y = year + 4800 - a;
-  gint m = month + (12 * a) - 3;
+  g_return_val_if_fail (datetime != NULL, 0);
 
-  return day
-       + (((153 * m) + 2) / 5)
-       + (y * 365)
-       + (y / 4)
-       - (y / 100)
-       + (y / 400)
-       - 32045;
+  return (datetime->usec % USEC_PER_MINUTE) / USEC_PER_SECOND;
 }
 
 /**
- * g_date_time_new_from_date:
- * @year: the Gregorian year
- * @month: the Gregorian month
- * @day: the day in the Gregorian month
+ * g_date_time_get_microsecond:
+ * @datetime: a #GDateTime
  *
- * Creates a new #GDateTime using the specified date within the Gregorian
- * calendar.
+ * Retrieves the microsecond of the date represented by @datetime
  *
- * Return value: the newly created #GDateTime or %NULL if it is outside of
- *   the representable range.
+ * Returns: the microsecond of the second
  *
  * Since: 2.26
  */
-GDateTime *
-g_date_time_new_from_date (gint year,
-                           gint month,
-                           gint day)
+gint
+g_date_time_get_microsecond (GDateTime *datetime)
 {
-  GDateTime *dt;
-
-  g_return_val_if_fail (year > -4712 && year <= 3268, NULL);
-  g_return_val_if_fail (month > 0 && month <= 12, NULL);
-  g_return_val_if_fail (day > 0 && day <= 31, NULL);
-
-  dt = g_date_time_new ();
-  dt->julian = date_to_julian (year, month, day);
-  dt->tz = g_date_time_create_time_zone (dt, NULL);
+  g_return_val_if_fail (datetime != NULL, 0);
 
-  return dt;
+  return (datetime->usec % USEC_PER_SECOND);
 }
 
 /**
- * g_date_time_new_from_epoch:
- * @t: seconds from the Unix epoch
+ * g_date_time_get_seconds:
+ * @datetime: a #GDateTime
  *
- * Creates a new #GDateTime using the time since Jan 1, 1970 specified by @t.
+ * Retrieves the number of seconds since the start of the last minute,
+ * including the fractional part.
  *
- * Return value: the newly created #GDateTime
+ * Returns: the number of seconds
  *
  * Since: 2.26
- */
-GDateTime*
-g_date_time_new_from_epoch (gint64 t) /* IN */
+ **/
+gdouble
+g_date_time_get_seconds (GDateTime *datetime)
 {
-  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 (&timet);
-
-    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
-
-        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 */
+  g_return_val_if_fail (datetime != NULL, 0);
 
-  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);
+  return (datetime->usec % USEC_PER_MINUTE) / 1000000.0;
 }
 
+/* Exporters {{{1 */
 /**
- * g_date_time_new_from_timeval:
- * @tv: #GTimeVal
+ * g_date_time_to_unix:
+ * @datetime: a #GDateTime
  *
- * Creates a new #GDateTime using the date and time specified by #GTimeVal.
+ * Gives the Unix time corresponding to @datetime, rounding down to the
+ * nearest second.
  *
- * Return value: the newly created #GDateTime
+ * Unix time is the number of seconds that have elapsed since 1970-01-01
+ * 00:00:00 UTC, regardless of the time zone associated with @datetime.
+ *
+ * Returns: the Unix time corresponding to @datetime
  *
  * Since: 2.26
- */
-GDateTime *
-g_date_time_new_from_timeval (GTimeVal *tv)
+ **/
+gint64
+g_date_time_to_unix (GDateTime *datetime)
 {
-  GDateTime *datetime;
-
-  g_return_val_if_fail (tv != NULL, NULL);
-
-  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);
-
-  return datetime;
+  return INSTANT_TO_UNIX (g_date_time_to_instant (datetime));
 }
 
 /**
- * g_date_time_new_full:
- * @year: the Gregorian year
- * @month: the Gregorian month
- * @day: the day of the Gregorian month
- * @hour: the hour of the day
- * @minute: the minute of the hour
- * @second: the second of the minute
- * @timezone: (allow-none): the Olson's database timezone name, or %NULL
- *   for local (e.g. America/New_York)
+ * g_date_time_to_timeval:
+ * @datetime: a #GDateTime
+ * @tv: a #GTimeVal to modify
+ *
+ * Stores the instant in time that @datetime represents into @tv.
  *
- * Creates a new #GDateTime using the date and times in the Gregorian calendar.
+ * The time contained in a #GTimeVal is always stored in the form of
+ * seconds elapsed since 1970-01-01 00:00:00 UTC, regardless of the time
+ * zone associated with @datetime.
  *
- * Return value: the newly created #GDateTime
+ * On systems where 'long' is 32bit (ie: all 32bit systems and all
+ * Windows systems), a #GTimeVal is incapable of storing the entire
+ * range of values that #GDateTime is capable of expressing.  On those
+ * systems, this function returns %FALSE to indicate that the time is
+ * out of range.
+ *
+ * On systems where 'long' is 64bit, this function never fails.
+ *
+ * Returns: %TRUE if successful, else %FALSE
  *
  * Since: 2.26
- */
-GDateTime *
-g_date_time_new_full (gint         year,
-                      gint         month,
-                      gint         day,
-                      gint         hour,
-                      gint         minute,
-                      gint         second,
-                      const gchar *timezone)
+ **/
+gboolean
+g_date_time_to_timeval (GDateTime *datetime,
+                        GTimeVal  *tv)
 {
-  GDateTime *dt;
-
-  g_return_val_if_fail (hour >= 0 && hour < 24, NULL);
-  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;
+  tv->tv_sec = INSTANT_TO_UNIX (g_date_time_to_instant (datetime));
+  tv->tv_usec = datetime->usec % USEC_PER_SECOND;
 
-  dt->usec = (hour   * USEC_PER_HOUR)
-           + (minute * USEC_PER_MINUTE)
-           + (second * USEC_PER_SECOND);
-
-  dt->tz = g_date_time_create_time_zone (dt, timezone);
-  if (timezone != NULL && dt->tz == NULL)
-    {
-      /* timezone creation failed */
-      g_date_time_unref (dt);
-      dt = NULL;
-    }
-
-  return dt;
+  return TRUE;
 }
 
+/* Timezone queries {{{1 */
 /**
- * g_date_time_new_now:
+ * g_date_time_get_utc_offset:
+ * @datetime: a #GDateTime
  *
- * Creates a new #GDateTime representing the current date and time.
+ * Determines the offset to UTC in effect at the time and in the time
+ * zone of @datetime.
  *
- * Return value: the newly created #GDateTime which should be freed with
- *   g_date_time_unref().
+ * The offset is the number of microseconds that you add to UTC time to
+ * arrive at local time for the time zone (ie: negative numbers for time
+ * zones west of GMT, positive numbers for east).
+ *
+ * If @datetime represents UTC time, then the offset is always zero.
+ *
+ * Returns: the number of microseconds that should be added to UTC to
+ *          get the local time
  *
  * Since: 2.26
- */
-GDateTime*
-g_date_time_new_now (void)
+ **/
+GTimeSpan
+g_date_time_get_utc_offset (GDateTime *datetime)
 {
-  GTimeVal tv;
+  gint offset;
 
-  g_get_current_time (&tv);
+  g_return_val_if_fail (datetime != NULL, 0);
+
+  offset = g_time_zone_get_offset (datetime->tz, datetime->interval);
 
-  return g_date_time_new_from_timeval (&tv);
+  return (gint64) offset * USEC_PER_SECOND;
 }
 
 /**
- * g_date_time_printf:
- * @datetime: A #GDateTime
- * @format: a valid UTF-8 string, containing the format for the #GDateTime
+ * g_date_time_get_timezone_abbreviation:
+ * @datetime: a #GDateTime
  *
- * Creates a newly allocated string representing the requested @format.
+ * Determines the time zone abbreviation to be used at the time and in
+ * the time zone of @datetime.
  *
- * The following format specifiers are supported:
+ * For example, in Toronto this is currently "EST" during the winter
+ * months and "EDT" during the summer months when daylight savings
+ * time is in effect.
  *
- * %%a  The abbreviated weekday name according to the current locale.
- * %%A  The full weekday name according to the current locale.
- * %%b  The abbreviated month name according to the current locale.
- * %%B  The full month name according to the current locale.
- * %%d  The day of the month as a decimal number (range 01 to 31).
- * %%e  The day of the month as a decimal number (range  1 to 31).
- * %%F  Equivalent to %Y-%m-%d (the ISO 8601 date format).
- * %%h  Equivalent to %b.
- * %%H  The hour as a decimal number using a 24-hour clock (range 00 to 23).
- * %%I  The hour as a decimal number using a 12-hour clock (range 01 to 12).
- * %%j  The day of the year as a decimal number (range 001 to 366).
- * %%k  The hour (24-hour clock) as a decimal number (range 0 to 23);
- *      single digits are preceded by a blank.
- * %%l  The hour (12-hour clock) as a decimal number (range 1 to 12);
- *      single digits are preceded by a blank.
- * %%m  The month as a decimal number (range 01 to 12).
- * %%M  The minute as a decimal number (range 00 to 59).
- * %%N  The micro-seconds as a decimal number.
- * %%p  Either "AM" or "PM" according to the given time  value, or the
- *      corresponding  strings  for the current locale.  Noon is treated
- *      as "PM" and midnight as "AM".
- * %%P  Like %%p but lowercase: "am" or "pm" or a corresponding string for
- *      the current locale.
- * %%r  The time in a.m. or p.m. notation.
- * %%R  The time in 24-hour notation (%H:%M).
- * %%s  The number of seconds since the Epoch, that is, since 1970-01-01
- *      00:00:00 UTC.
- * %%S  The second as a decimal number (range 00 to 60).
- * %%t  A tab character.
- * %%u  The day of the week as a decimal, range 1 to 7, Monday being 1.
- * %%W  The week number of the current year as a decimal number.
- * %%x  The preferred date representation for the current locale without
- *      the time.
- * %%X  The preferred date representation for the current locale without
- *      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).
- * %%%  A literal %% character.
- *
- * Return value: a newly allocated string formatted to the requested format or
- *   %NULL in the case that there was an error.  The string should be freed
- *   with g_free().
+ * Returns: (transfer none): the time zone abbreviation. The returned
+ *          string is owned by the #GDateTime and it should not be
+ *          modified or freed
  *
  * Since: 2.26
- */
-gchar *
-g_date_time_printf (const GDateTime *datetime,
-                    const gchar     *format)
+ **/
+const gchar *
+g_date_time_get_timezone_abbreviation (GDateTime *datetime)
 {
-  GString     *outstr;
-  const gchar *tmp;
-  gchar       *tmp2,
-               c;
-  glong        utf8len;
-  gint         i;
-  gboolean     in_mod;
-
   g_return_val_if_fail (datetime != NULL, NULL);
-  g_return_val_if_fail (format != NULL, NULL);
-  g_return_val_if_fail (g_utf8_validate (format, -1, NULL), NULL);
-
-  outstr = g_string_sized_new (strlen (format) * 2);
-  utf8len = g_utf8_strlen (format, -1);
-  in_mod = FALSE;
-
-  for (i = 0; i < utf8len; i++)
-    {
-      tmp = g_utf8_offset_to_pointer (format, i);
-      c = g_utf8_get_char (tmp);
 
-      switch (c)
-        {
-        case '%':
-          if (!in_mod)
-            {
-              in_mod = TRUE;
-              break;
-            }
-            /* Fall through */
-        default:
-          if (in_mod)
-            {
-              switch (c)
-                {
-                case 'a':
-                  g_string_append (outstr, WEEKDAY_ABBR (datetime));
-                  break;
-                case 'A':
-                  g_string_append (outstr, WEEKDAY_FULL (datetime));
-                  break;
-                case 'b':
-                  g_string_append (outstr, MONTH_ABBR (datetime));
-                  break;
-                case 'B':
-                  g_string_append (outstr, MONTH_FULL (datetime));
-                  break;
-                case 'd':
-                  g_string_append_printf (outstr, "%02d", g_date_time_get_day_of_month (datetime));
-                  break;
-                case 'e':
-                  g_string_append_printf (outstr, "%2d", g_date_time_get_day_of_month (datetime));
-                  break;
-                case 'F':
-                  g_string_append_printf (outstr, "%d-%02d-%02d",
-                                          g_date_time_get_year (datetime),
-                                          g_date_time_get_month (datetime),
-                                          g_date_time_get_day_of_month (datetime));
-                  break;
-                case 'h':
-                  g_string_append (outstr, MONTH_ABBR (datetime));
-                  break;
-                case 'H':
-                  g_string_append_printf (outstr, "%02d", g_date_time_get_hour (datetime));
-                  break;
-                case 'I':
-                  if (g_date_time_get_hour (datetime) == 0)
-                    g_string_append (outstr, "12");
-                  else
-                    g_string_append_printf (outstr, "%02d", g_date_time_get_hour (datetime) % 12);
-                  break;
-                case 'j':
-                  g_string_append_printf (outstr, "%03d", g_date_time_get_day_of_year (datetime));
-                  break;
-                case 'k':
-                  g_string_append_printf (outstr, "%2d", g_date_time_get_hour (datetime));
-                  break;
-                case 'l':
-                  if (g_date_time_get_hour (datetime) == 0)
-                    g_string_append (outstr, "12");
-                  else
-                    g_string_append_printf (outstr, "%2d", g_date_time_get_hour (datetime) % 12);
-                  break;
-                case 'm':
-                  g_string_append_printf (outstr, "%02d", g_date_time_get_month (datetime));
-                  break;
-                case 'M':
-                  g_string_append_printf (outstr, "%02d", g_date_time_get_minute (datetime));
-                  break;
-                case 'N':
-                  g_string_append_printf (outstr, "%"G_GUINT64_FORMAT, datetime->usec % USEC_PER_SECOND);
-                  break;
-                case 'p':
-                  g_string_append (outstr, GET_AMPM (datetime, FALSE));
-                  break;
-                case 'P':
-                  g_string_append (outstr, GET_AMPM (datetime, TRUE));
-                  break;
-                case 'r':
-                  {
-                    gint hour = g_date_time_get_hour (datetime) % 12;
-                    if (hour == 0)
-                      hour = 12;
-                    g_string_append_printf (outstr, "%02d:%02d:%02d %s",
-                                            hour,
-                                            g_date_time_get_minute (datetime),
-                                            g_date_time_get_second (datetime),
-                                            GET_AMPM (datetime, FALSE));
-                  }
-                  break;
-                case 'R':
-                  g_string_append_printf (outstr, "%02d:%02d",
-                                          g_date_time_get_hour (datetime),
-                                          g_date_time_get_minute (datetime));
-                  break;
-                case 's':
-                  g_string_append_printf (outstr, "%" G_GINT64_FORMAT, g_date_time_to_epoch (datetime));
-                  break;
-                case 'S':
-                  g_string_append_printf (outstr, "%02d", g_date_time_get_second (datetime));
-                  break;
-                case 't':
-                  g_string_append_c (outstr, '\t');
-                  break;
-                case 'u':
-                  g_string_append_printf (outstr, "%d", g_date_time_get_day_of_week (datetime));
-                  break;
-                case 'W':
-                  g_string_append_printf (outstr, "%d", g_date_time_get_day_of_year (datetime) / 7);
-                  break;
-                case 'x':
-                  {
-                    tmp2 = GET_PREFERRED_DATE (datetime);
-                    g_string_append (outstr, tmp2);
-                    g_free (tmp2);
-                  }
-                  break;
-                case 'X':
-                  {
-                    tmp2 = GET_PREFERRED_TIME (datetime);
-                    g_string_append (outstr, tmp2);
-                    g_free (tmp2);
-                  }
-                  break;
-                case 'y':
-                  g_string_append_printf (outstr, "%02d", g_date_time_get_year (datetime) % 100);
-                  break;
-                case 'Y':
-                  g_string_append_printf (outstr, "%d", g_date_time_get_year (datetime));
-                  break;
-                case 'Z':
-                  if (datetime->tz != NULL)
-                    g_string_append_printf (outstr, "%s", datetime->tz->name);
-                  else
-                    g_string_append_printf (outstr, "UTC");
-                  break;
-                case '%':
-                  g_string_append_c (outstr, '%');
-                  break;
-                case 'n':
-                  g_string_append_c (outstr, '\n');
-                  break;
-                default:
-                  goto bad_format;
-                }
-              in_mod = FALSE;
-            }
-          else
-            g_string_append_unichar (outstr, c);
-        }
-    }
-
-  return g_string_free (outstr, FALSE);
-
-bad_format:
-  g_string_free (outstr, TRUE);
-  return NULL;
+  return g_time_zone_get_abbreviation (datetime->tz, datetime->interval);
 }
 
 /**
- * g_date_time_ref:
+ * g_date_time_is_daylight_savings:
  * @datetime: a #GDateTime
  *
- * Atomically increments the reference count of @datetime by one.
+ * Determines if daylight savings time is in effect at the time and in
+ * the time zone of @datetime.
  *
- * Return value: the #GDateTime with the reference count increased
+ * Returns: %TRUE if daylight savings time is in effect
  *
  * Since: 2.26
- */
-GDateTime *
-g_date_time_ref (GDateTime *datetime)
+ **/
+gboolean
+g_date_time_is_daylight_savings (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);
+  g_return_val_if_fail (datetime != NULL, FALSE);
 
-  return datetime;
+  return g_time_zone_is_dst (datetime->tz, datetime->interval);
 }
 
+/* Timezone convert {{{1 */
 /**
- * g_date_time_unref:
+ * g_date_time_to_timezone:
  * @datetime: a #GDateTime
+ * @tz: the new #GTimeZone
  *
- * Atomically decrements the reference count of @datetime by one.
+ * Create a new #GDateTime corresponding to the same instant in time as
+ * @datetime, but in the time zone @tz.
  *
- * When the reference count reaches zero, the resources allocated by
- * @datetime are freed
+ * This call can fail in the case that the time goes out of bounds.  For
+ * example, converting 0001-01-01 00:00:00 UTC to a time zone west of
+ * Greenwich will fail (due to the year 0 being out of range).
+ *
+ * You should release the return value by calling g_date_time_unref()
+ * when you are done with it.
+ *
+ * Returns: a new #GDateTime, or %NULL
  *
  * Since: 2.26
- */
-void
-g_date_time_unref (GDateTime *datetime)
+ **/
+GDateTime *
+g_date_time_to_timezone (GDateTime *datetime,
+                         GTimeZone *tz)
 {
-  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);
+  return g_date_time_from_instant (tz, g_date_time_to_instant (datetime));
 }
 
 /**
  * g_date_time_to_local:
  * @datetime: a #GDateTime
  *
- * Creates a new #GDateTime with @datetime converted to local time.
+ * Creates a new #GDateTime corresponding to the same instant in time as
+ * @datetime, but in the local time zone.
+ *
+ * This call is equivalent to calling g_date_time_to_timezone() with the
+ * time zone returned by g_time_zone_new_local().
  *
- * Return value: the newly created #GDateTime
+ * Returns: the newly created #GDateTime
  *
  * Since: 2.26
- */
+ **/
 GDateTime *
-g_date_time_to_local (const GDateTime *datetime)
+g_date_time_to_local (GDateTime *datetime)
 {
-  GDateTime *dt;
-
-  g_return_val_if_fail (datetime != NULL, NULL);
-
-  dt = g_date_time_copy (datetime);
-  if (dt->tz == NULL)
-    {
-      dt->tz = g_date_time_create_time_zone (dt, NULL);
-      if (dt->tz == NULL)
-        return dt;
+  GDateTime *new;
+  GTimeZone *local;
 
-      g_date_time_add_usec (dt, dt->tz->offset * USEC_PER_SECOND);
-    }
+  local = g_time_zone_new_local ();
+  new = g_date_time_to_timezone (datetime, local);
+  g_time_zone_unref (local);
 
-  return dt;
+  return new;
 }
 
 /**
- * g_date_time_to_epoch:
+ * g_date_time_to_utc:
  * @datetime: a #GDateTime
  *
- * Converts @datetime into an integer representing seconds since the
- * Unix epoch
+ * Creates a new #GDateTime corresponding to the same instant in time as
+ * @datetime, but in UTC.
+ *
+ * This call is equivalent to calling g_date_time_to_timezone() with the
+ * time zone returned by g_time_zone_new_utc().
  *
- * Return value: @datetime as seconds since the Unix epoch
+ * Returns: the newly created #GDateTime
  *
  * Since: 2.26
- */
-gint64
-g_date_time_to_epoch (const GDateTime *datetime)
+ **/
+GDateTime *
+g_date_time_to_utc (GDateTime *datetime)
 {
-  struct tm tm;
-  gint      year,
-            month,
-            day;
+  GDateTime *new;
+  GTimeZone *utc;
 
-  g_return_val_if_fail (datetime != NULL, 0);
-  g_return_val_if_fail (datetime->period == 0, 0);
+  utc = g_time_zone_new_utc ();
+  new = g_date_time_to_timezone (datetime, utc);
+  g_time_zone_unref (utc);
 
-  g_date_time_get_dmy (datetime, &day, &month, &year);
+  return new;
+}
 
-  /* FIXME we use gint64, we shold expand these limits */
-  if (year < 1970)
-    return 0;
-  else if (year > 2037)
-    return G_MAXINT64;
+/* Format {{{1 */
+
+static gboolean
+format_z (GString *outstr,
+          gint     offset,
+          guint    colons)
+{
+  gint hours;
+  gint minutes;
+  gint seconds;
+
+  hours = offset / 3600;
+  minutes = ABS (offset) / 60 % 60;
+  seconds = ABS (offset) % 60;
+
+  switch (colons)
+    {
+    case 0:
+      g_string_append_printf (outstr, "%+03d%02d",
+                              hours,
+                              minutes);
+      break;
 
-  memset (&tm, 0, sizeof (tm));
+    case 1:
+      g_string_append_printf (outstr, "%+03d:%02d",
+                              hours,
+                              minutes);
+      break;
+
+    case 2:
+      g_string_append_printf (outstr, "%+03d:%02d:%02d",
+                              hours,
+                              minutes,
+                              seconds);
+      break;
+
+    case 3:
+      g_string_append_printf (outstr, "%+03d", hours);
+
+      if (minutes != 0 || seconds != 0)
+        {
+          g_string_append_printf (outstr, ":%02d", minutes);
+
+          if (seconds != 0)
+            g_string_append_printf (outstr, ":%02d", seconds);
+        }
+      break;
 
-  tm.tm_year = year - 1900;
-  tm.tm_mon = month - 1;
-  tm.tm_mday = day;
-  tm.tm_hour = g_date_time_get_hour (datetime);
-  tm.tm_min = g_date_time_get_minute (datetime);
-  tm.tm_sec = g_date_time_get_second (datetime);
-  tm.tm_isdst = -1;
+    default:
+      return FALSE;
+    }
 
-  return (gint64) mktime (&tm);
+  return TRUE;
 }
 
-/**
- * g_date_time_to_timeval:
- * @datetime: a #GDateTime
- * @tv: A #GTimeVal
- *
- * Converts @datetime into a #GTimeVal and stores the result into @timeval.
- *
- * Since: 2.26
- */
-void
-g_date_time_to_timeval (const GDateTime *datetime,
-                        GTimeVal        *tv)
-{
-  g_return_if_fail (datetime != NULL);
+static void
+format_number (GString  *str,
+               gboolean  use_alt_digits,
+               gchar    *pad,
+               gint      width,
+               guint32   number)
+{
+  const gchar *ascii_digits[10] = {
+    "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
+  };
+  const gchar **digits = ascii_digits;
+  const gchar *tmp[10];
+  gint i = 0;
+
+  g_return_if_fail (width <= 10);
+
+#ifdef HAVE_LANGINFO_OUTDIGIT
+  if (use_alt_digits)
+    {
+      static const gchar *alt_digits[10];
+      static gsize initialised;
+      /* 2^32 has 10 digits */
+
+      if G_UNLIKELY (g_once_init_enter (&initialised))
+        {
+#define DO_DIGIT(n) \
+        alt_digits[n] = nl_langinfo (_NL_CTYPE_OUTDIGIT## n ##_MB)
+          DO_DIGIT(0); DO_DIGIT(1); DO_DIGIT(2); DO_DIGIT(3); DO_DIGIT(4);
+          DO_DIGIT(5); DO_DIGIT(6); DO_DIGIT(7); DO_DIGIT(8); DO_DIGIT(9);
+#undef DO_DIGIT
+          g_once_init_leave (&initialised, TRUE);
+        }
 
-  tv->tv_sec = 0;
-  tv->tv_usec = 0;
+      digits = alt_digits;
+    }
+#endif /* HAVE_LANGINFO_OUTDIGIT */
 
-  if (G_LIKELY (datetime->period == 0))
+  do
     {
-      tv->tv_sec = g_date_time_to_epoch (datetime);
-      tv->tv_usec = datetime->usec % USEC_PER_SECOND;
+      tmp[i++] = digits[number % 10];
+      number /= 10;
     }
+  while (number);
+
+  while (pad && i < width)
+    tmp[i++] = *pad == '0' ? digits[0] : pad;
+
+  /* should really be impossible */
+  g_assert (i <= 10);
+
+  while (i)
+    g_string_append (str, tmp[--i]);
 }
 
-/**
- * g_date_time_to_utc:
- * @datetime: a #GDateTime
- *
- * Creates a new #GDateTime that reprents @datetime in Universal coordinated
- * time.
- *
- * Return value: the newly created #GDateTime which should be freed with
- *   g_date_time_unref().
- *
- * Since: 2.26
+static gboolean g_date_time_format_locale (GDateTime   *datetime,
+                                          const gchar *format,
+                                          GString     *outstr,
+                                          gboolean     locale_is_utf8);
+
+/* g_date_time_format() subroutine that takes a locale-encoded format
+ * string and produces a locale-encoded date/time string.
  */
-GDateTime *
-g_date_time_to_utc (const GDateTime *datetime)
+static gboolean
+g_date_time_locale_format_locale (GDateTime   *datetime,
+                                 const gchar *format,
+                                 GString     *outstr,
+                                 gboolean     locale_is_utf8)
 {
-  GDateTime *dt;
-  GTimeSpan  ts;
+  gchar *utf8_format;
+  gboolean success;
 
-  g_return_val_if_fail (datetime != NULL, NULL);
+  if (locale_is_utf8)
+    return g_date_time_format_locale (datetime, format, outstr,
+                                     locale_is_utf8);
 
-  ts = g_date_time_get_utc_offset (datetime) * -1;
-  dt = g_date_time_add (datetime, ts);
-  dt->tz = NULL;
+  utf8_format = g_locale_to_utf8 (format, -1, NULL, NULL, NULL);
+  if (!utf8_format)
+    return FALSE;
 
-  return dt;
+  success = g_date_time_format_locale (datetime, utf8_format, outstr,
+                                      locale_is_utf8);
+  g_free (utf8_format);
+  return success;
 }
 
-/**
- * g_date_time_new_today:
- *
- * Createsa new #GDateTime that represents Midnight on the current day.
- *
- * Return value: the newly created #GDateTime which should be freed with
- *   g_date_time_unref().
- *
- * Since: 2.26
+/* g_date_time_format() subroutine that takes a UTF-8 format
+ * string and produces a locale-encoded date/time string.
  */
-GDateTime*
-g_date_time_new_today (void)
-{
-  GDateTime *dt;
-
-  dt = g_date_time_new_now ();
-  dt->usec = 0;
+static gboolean
+g_date_time_format_locale (GDateTime   *datetime,
+                          const gchar *format,
+                          GString     *outstr,
+                          gboolean     locale_is_utf8)
+{
+  guint     len;
+  guint     colons;
+  gchar    *tmp;
+  gunichar  c;
+  gboolean  alt_digits = FALSE;
+  gboolean  pad_set = FALSE;
+  gchar    *pad = "";
+  gchar    *ampm;
+  const gchar *tz;
+
+  while (*format)
+    {
+      len = strcspn (format, "%");
+      if (len)
+       {
+         if (locale_is_utf8)
+           g_string_append_len (outstr, format, len);
+         else
+           {
+             tmp = g_locale_from_utf8 (format, len, NULL, NULL, NULL);
+             if (!tmp)
+               return FALSE;
+             g_string_append (outstr, tmp);
+             g_free (tmp);
+           }
+       }
+
+      format += len;
+      if (!*format)
+       break;
+
+      g_assert (*format == '%');
+      format++;
+      if (!*format)
+       break;
+
+      colons = 0;
+      alt_digits = FALSE;
+      pad_set = FALSE;
+
+    next_mod:
+      c = g_utf8_get_char (format);
+      format = g_utf8_next_char (format);
+      switch (c)
+       {
+       case 'a':
+         g_string_append (outstr, WEEKDAY_ABBR (datetime));
+         break;
+       case 'A':
+         g_string_append (outstr, WEEKDAY_FULL (datetime));
+         break;
+       case 'b':
+         g_string_append (outstr, MONTH_ABBR (datetime));
+         break;
+       case 'B':
+         g_string_append (outstr, MONTH_FULL (datetime));
+         break;
+       case 'c':
+         {
+           if (!g_date_time_locale_format_locale (datetime, PREFERRED_DATE_TIME_FMT,
+                                                  outstr, locale_is_utf8))
+             return FALSE;
+         }
+         break;
+       case 'C':
+         format_number (outstr, alt_digits, pad_set ? pad : "0", 2,
+                        g_date_time_get_year (datetime) / 100);
+         break;
+       case 'd':
+         format_number (outstr, alt_digits, pad_set ? pad : "0", 2,
+                        g_date_time_get_day_of_month (datetime));
+         break;
+       case 'e':
+         format_number (outstr, alt_digits, pad_set ? pad : " ", 2,
+                        g_date_time_get_day_of_month (datetime));
+         break;
+       case 'F':
+         g_string_append_printf (outstr, "%d-%02d-%02d",
+                                 g_date_time_get_year (datetime),
+                                 g_date_time_get_month (datetime),
+                                 g_date_time_get_day_of_month (datetime));
+         break;
+       case 'g':
+         format_number (outstr, alt_digits, pad_set ? pad : "0", 2,
+                        g_date_time_get_week_numbering_year (datetime) % 100);
+         break;
+       case 'G':
+         format_number (outstr, alt_digits, pad_set ? pad : 0, 0,
+                        g_date_time_get_week_numbering_year (datetime));
+         break;
+       case 'h':
+         g_string_append (outstr, MONTH_ABBR (datetime));
+         break;
+       case 'H':
+         format_number (outstr, alt_digits, pad_set ? pad : "0", 2,
+                        g_date_time_get_hour (datetime));
+         break;
+       case 'I':
+         format_number (outstr, alt_digits, pad_set ? pad : "0", 2,
+                        (g_date_time_get_hour (datetime) + 11) % 12 + 1);
+         break;
+       case 'j':
+         format_number (outstr, alt_digits, pad_set ? pad : "0", 3,
+                        g_date_time_get_day_of_year (datetime));
+         break;
+       case 'k':
+         format_number (outstr, alt_digits, pad_set ? pad : " ", 2,
+                        g_date_time_get_hour (datetime));
+         break;
+       case 'l':
+         format_number (outstr, alt_digits, pad_set ? pad : " ", 2,
+                        (g_date_time_get_hour (datetime) + 11) % 12 + 1);
+         break;
+       case 'n':
+         g_string_append_c (outstr, '\n');
+         break;
+       case 'm':
+         format_number (outstr, alt_digits, pad_set ? pad : "0", 2,
+                        g_date_time_get_month (datetime));
+         break;
+       case 'M':
+         format_number (outstr, alt_digits, pad_set ? pad : "0", 2,
+                        g_date_time_get_minute (datetime));
+         break;
+       case 'O':
+         alt_digits = TRUE;
+         goto next_mod;
+       case 'p':
+         ampm = (gchar *) GET_AMPM (datetime);
+         if (!locale_is_utf8)
+           {
+             ampm = tmp = g_locale_to_utf8 (ampm, -1, NULL, NULL, NULL);
+             if (!tmp)
+               return FALSE;
+           }
+         ampm = g_utf8_strup (ampm, -1);
+         if (!locale_is_utf8)
+           {
+             g_free (tmp);
+             tmp = g_locale_from_utf8 (ampm, -1, NULL, NULL, NULL);
+             g_free (ampm);
+             if (!tmp)
+               return FALSE;
+             ampm = tmp;
+           }
+         g_string_append (outstr, ampm);
+         g_free (ampm);
+         break;
+       case 'P':
+         ampm = (gchar *) GET_AMPM (datetime);
+         if (!locale_is_utf8)
+           {
+             ampm = tmp = g_locale_to_utf8 (ampm, -1, NULL, NULL, NULL);
+             if (!tmp)
+               return FALSE;
+           }
+         ampm = g_utf8_strdown (ampm, -1);
+         if (!locale_is_utf8)
+           {
+             g_free (tmp);
+             tmp = g_locale_from_utf8 (ampm, -1, NULL, NULL, NULL);
+             g_free (ampm);
+             if (!tmp)
+               return FALSE;
+             ampm = tmp;
+           }
+         g_string_append (outstr, ampm);
+         g_free (ampm);
+         break;
+       case 'r':
+         {
+           if (!g_date_time_locale_format_locale (datetime, PREFERRED_12HR_TIME_FMT,
+                                                  outstr, locale_is_utf8))
+             return FALSE;
+         }
+         break;
+       case 'R':
+         g_string_append_printf (outstr, "%02d:%02d",
+                                 g_date_time_get_hour (datetime),
+                                 g_date_time_get_minute (datetime));
+         break;
+       case 's':
+         g_string_append_printf (outstr, "%" G_GINT64_FORMAT, g_date_time_to_unix (datetime));
+         break;
+       case 'S':
+         format_number (outstr, alt_digits, pad_set ? pad : "0", 2,
+                        g_date_time_get_second (datetime));
+         break;
+       case 't':
+         g_string_append_c (outstr, '\t');
+         break;
+       case 'T':
+         g_string_append_printf (outstr, "%02d:%02d:%02d",
+                                 g_date_time_get_hour (datetime),
+                                 g_date_time_get_minute (datetime),
+                                 g_date_time_get_second (datetime));
+         break;
+       case 'u':
+         format_number (outstr, alt_digits, 0, 0,
+                        g_date_time_get_day_of_week (datetime));
+         break;
+       case 'V':
+         format_number (outstr, alt_digits, pad_set ? pad : "0", 2,
+                        g_date_time_get_week_of_year (datetime));
+         break;
+       case 'w':
+         format_number (outstr, alt_digits, 0, 0,
+                        g_date_time_get_day_of_week (datetime) % 7);
+         break;
+       case 'x':
+         {
+           if (!g_date_time_locale_format_locale (datetime, PREFERRED_DATE_FMT,
+                                                  outstr, locale_is_utf8))
+             return FALSE;
+         }
+         break;
+       case 'X':
+         {
+           if (!g_date_time_locale_format_locale (datetime, PREFERRED_TIME_FMT,
+                                                  outstr, locale_is_utf8))
+             return FALSE;
+         }
+         break;
+       case 'y':
+         format_number (outstr, alt_digits, pad_set ? pad : "0", 2,
+                        g_date_time_get_year (datetime) % 100);
+         break;
+       case 'Y':
+         format_number (outstr, alt_digits, 0, 0,
+                        g_date_time_get_year (datetime));
+         break;
+       case 'z':
+         {
+           gint64 offset;
+           if (datetime->tz != NULL)
+             offset = g_date_time_get_utc_offset (datetime) / USEC_PER_SECOND;
+           else
+             offset = 0;
+           if (!format_z (outstr, (int) offset, colons))
+             return FALSE;
+         }
+         break;
+       case 'Z':
+         tz = g_date_time_get_timezone_abbreviation (datetime);
+         if (!locale_is_utf8)
+           {
+             tz = tmp = g_locale_from_utf8 (tz, -1, NULL, NULL, NULL);
+             if (!tmp)
+               return FALSE;
+           }
+         g_string_append (outstr, tz);
+         if (!locale_is_utf8)
+           g_free (tmp);
+         break;
+       case '%':
+         g_string_append_c (outstr, '%');
+         break;
+       case '-':
+         pad_set = TRUE;
+         pad = "";
+         goto next_mod;
+       case '_':
+         pad_set = TRUE;
+         pad = " ";
+         goto next_mod;
+       case '0':
+         pad_set = TRUE;
+         pad = "0";
+         goto next_mod;
+       case ':':
+         /* Colons are only allowed before 'z' */
+         if (*format && *format != 'z' && *format != ':')
+           return FALSE;
+         colons++;
+         goto next_mod;
+       default:
+         return FALSE;
+       }
+    }
 
-  return dt;
+  return TRUE;
 }
 
 /**
- * g_date_time_new_utc_now:
+ * g_date_time_format:
+ * @datetime: A #GDateTime
+ * @format: a valid UTF-8 string, containing the format for the
+ *          #GDateTime
  *
- * Creates a new #GDateTime that represents the current instant in Universal
- * Coordinated Time (UTC).
+ * Creates a newly allocated string representing the requested @format.
  *
- * Return value: the newly created #GDateTime which should be freed with
- *   g_date_time_unref().
+ * The format strings understood by this function are a subset of the
+ * strftime() format language as specified by C99.  The \%D, \%U and \%W
+ * conversions are not supported, nor is the 'E' modifier.  The GNU
+ * extensions \%k, \%l, \%s and \%P are supported, however, as are the
+ * '0', '_' and '-' modifiers.
+ *
+ * In contrast to strftime(), this function always produces a UTF-8
+ * string, regardless of the current locale.  Note that the rendering of
+ * many formats is locale-dependent and may not match the strftime()
+ * output exactly.
+ *
+ * The following format specifiers are supported:
+ *
+ * - \%a: the abbreviated weekday name according to the current locale
+ * - \%A: the full weekday name according to the current locale
+ * - \%b: the abbreviated month name according to the current locale
+ * - \%B: the full month name according to the current locale
+ * - \%c: the  preferred date and time rpresentation for the current locale
+ * - \%C: the century number (year/100) as a 2-digit integer (00-99)
+ * - \%d: the day of the month as a decimal number (range 01 to 31)
+ * - \%e: the day of the month as a decimal number (range  1 to 31)
+ * - \%F: equivalent to `%Y-%m-%d` (the ISO 8601 date format)
+ * - \%g: the last two digits of the ISO 8601 week-based year as a
+ *   decimal number (00-99). This works well with \%V and \%u.
+ * - \%G: the ISO 8601 week-based year as a decimal number. This works
+ *   well with \%V and \%u.
+ * - \%h: equivalent to \%b
+ * - \%H: the hour as a decimal number using a 24-hour clock (range 00 to 23)
+ * - \%I: the hour as a decimal number using a 12-hour clock (range 01 to 12)
+ * - \%j: the day of the year as a decimal number (range 001 to 366)
+ * - \%k: the hour (24-hour clock) as a decimal number (range 0 to 23);
+ *   single digits are preceded by a blank
+ * - \%l: the hour (12-hour clock) as a decimal number (range 1 to 12);
+ *   single digits are preceded by a blank
+ * - \%m: the month as a decimal number (range 01 to 12)
+ * - \%M: the minute as a decimal number (range 00 to 59)
+ * - \%p: either "AM" or "PM" according to the given time value, or the
+ *   corresponding  strings for the current locale.  Noon is treated as
+ *   "PM" and midnight as "AM".
+ * - \%P: like \%p but lowercase: "am" or "pm" or a corresponding string for
+ *   the current locale
+ * - \%r: the time in a.m. or p.m. notation
+ * - \%R: the time in 24-hour notation (\%H:\%M)
+ * - \%s: the number of seconds since the Epoch, that is, since 1970-01-01
+ *   00:00:00 UTC
+ * - \%S: the second as a decimal number (range 00 to 60)
+ * - \%t: a tab character
+ * - \%T: the time in 24-hour notation with seconds (\%H:\%M:\%S)
+ * - \%u: the ISO 8601 standard day of the week as a decimal, range 1 to 7,
+ *    Monday being 1. This works well with \%G and \%V.
+ * - \%V: the ISO 8601 standard week number of the current year as a decimal
+ *   number, range 01 to 53, where week 1 is the first week that has at
+ *   least 4 days in the new year. See g_date_time_get_week_of_year().
+ *   This works well with \%G and \%u.
+ * - \%w: the day of the week as a decimal, range 0 to 6, Sunday being 0.
+ *   This is not the ISO 8601 standard format -- use \%u instead.
+ * - \%x: the preferred date representation for the current locale without
+ *   the time
+ * - \%X: the preferred time representation for the current locale without
+ *   the date
+ * - \%y: the year as a decimal number without the century
+ * - \%Y: the year as a decimal number including the century
+ * - \%z: the time zone as an offset from UTC (+hhmm)
+ * - \%:z: the time zone as an offset from UTC (+hh:mm).
+ *   This is a gnulib strftime() extension. Since: 2.38
+ * - \%::z: the time zone as an offset from UTC (+hh:mm:ss). This is a
+ *   gnulib strftime() extension. Since: 2.38
+ * - \%:::z: the time zone as an offset from UTC, with : to necessary
+ *   precision (e.g., -04, +05:30). This is a gnulib strftime() extension. Since: 2.38
+ * - \%Z: the time zone or name or abbreviation
+ * - \%\%: a literal \% character
+ *
+ * Some conversion specifications can be modified by preceding the
+ * conversion specifier by one or more modifier characters. The
+ * following modifiers are supported for many of the numeric
+ * conversions:
+ * 
+ * - O: Use alternative numeric symbols, if the current locale supports those.
+ * - _: Pad a numeric result with spaces. This overrides the default padding
+ *   for the specifier.
+ * - -: Do not pad a numeric result. This overrides the default padding
+ *   for the specifier.
+ * - 0: Pad a numeric result with zeros. This overrides the default padding
+ *   for the specifier.
+ *
+ * Returns: a newly allocated string formatted to the requested format
+ *     or %NULL in the case that there was an error. The string
+ *     should be freed with g_free().
  *
  * Since: 2.26
  */
-GDateTime *
-g_date_time_new_utc_now (void)
+gchar *
+g_date_time_format (GDateTime   *datetime,
+                    const gchar *format)
 {
-  GDateTime *utc, *now;
+  GString  *outstr;
+  gchar *utf8;
+  gboolean locale_is_utf8 = g_get_charset (NULL);
+
+  g_return_val_if_fail (datetime != NULL, NULL);
+  g_return_val_if_fail (format != NULL, NULL);
+  g_return_val_if_fail (g_utf8_validate (format, -1, NULL), NULL);
 
-  now = g_date_time_new_now ();
-  utc = g_date_time_to_utc (now);
-  g_date_time_unref (now);
+  outstr = g_string_sized_new (strlen (format) * 2);
+
+  if (!g_date_time_format_locale (datetime, format, outstr, locale_is_utf8))
+    {
+      g_string_free (outstr, TRUE);
+      return NULL;
+    }
 
-  return utc;
+  if (locale_is_utf8)
+    return g_string_free (outstr, FALSE);
+
+  utf8 = g_locale_to_utf8 (outstr->str, outstr->len, NULL, NULL, NULL);
+  g_string_free (outstr, TRUE);
+  return utf8;
 }
+
+
+/* Epilogue {{{1 */
+/* vim:set foldmethod=marker: */