[glib/wip/rancell/iso8601-2] GDateTime: Support parsing ISO 8601 strings.



commit aa770498eb04cc9550af33f81b7d6546c097e2b2
Author: Robert Ancell <robert ancell canonical com>
Date:   Thu Aug 25 11:53:54 2016 +1200

    GDateTime: Support parsing ISO 8601 strings.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=753459

 docs/reference/glib/glib-sections.txt |    1 +
 glib/gdatetime.c                      |  310 +++++++++++++++++++++++++++++++++
 glib/gdatetime.h                      |    4 +
 glib/tests/gdatetime.c                |  241 +++++++++++++++++++++++++
 4 files changed, 556 insertions(+), 0 deletions(-)
---
diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt
index e09d4d3..e8a7fc8 100644
--- a/docs/reference/glib/glib-sections.txt
+++ b/docs/reference/glib/glib-sections.txt
@@ -1667,6 +1667,7 @@ g_date_time_new_from_unix_utc
 <SUBSECTION>
 g_date_time_new_from_timeval_local
 g_date_time_new_from_timeval_utc
+g_date_time_new_from_iso8601
 
 <SUBSECTION>
 g_date_time_new
diff --git a/glib/gdatetime.c b/glib/gdatetime.c
index 745a32a..ace4fa2 100644
--- a/glib/gdatetime.c
+++ b/glib/gdatetime.c
@@ -22,6 +22,7 @@
  *          Thiago Santos <thiago sousa santos collabora co uk>
  *          Emmanuele Bassi <ebassi linux intel com>
  *          Ryan Lortie <desrt desrt ca>
+ *          Robert Ancell <robert ancell canonical com>
  */
 
 /* Algorithms within this file are based on the Calendar FAQ by
@@ -902,6 +903,315 @@ g_date_time_new_from_timeval_utc (const GTimeVal *tv)
   return datetime;
 }
 
+static gboolean
+get_iso8601_int (const gchar *text, gsize length, gint *value)
+{
+  gint i, v = 0;
+
+  for (i = 0; i < length; i++)
+    {
+      const gchar c = text[i];
+      if (c < '0' || c > '9')
+        return FALSE;
+      v = v * 10 + (c - '0');
+    }
+
+  *value = v;
+  return TRUE;
+}
+
+static gboolean
+get_iso8601_seconds (const gchar *text, gsize length, gdouble *value)
+{
+  gint i;
+  gdouble multiplier = 0.1, v = 0;
+
+  for (i = 0; i < length; i++)
+    {
+      gchar c = text[i];
+      if (c == '.' || c == ',')
+        {
+          i++;
+          break;
+        }
+      if (c < '0' || c > '9')
+        return FALSE;
+      v = v * 10 + (c - '0');
+    }
+
+  for (; i < length; i++)
+    {
+      gchar c = text[i];
+      if (c < '0' || c > '9')
+        return FALSE;
+      v += (c - '0') * multiplier;
+      multiplier *= 0.1;
+    }
+
+  *value = v;
+  return TRUE;
+}
+
+static GDateTime *
+g_date_time_new_ordinal (GTimeZone *tz, gint year, gint ordinal_day, gint hour, gint minute, gdouble seconds)
+{
+  GDateTime *dt;
+
+  if (ordinal_day < 1 || ordinal_day > (GREGORIAN_LEAP (year) ? 366 : 365))
+    return NULL;
+
+  dt = g_date_time_new (tz, year, 1, 1, hour, minute, seconds);
+  dt->days += ordinal_day - 1;
+
+  return dt;
+}
+
+static GDateTime *
+g_date_time_new_week (GTimeZone *tz, gint year, gint week, gint week_day, gint hour, gint minute, gdouble 
seconds)
+{
+  gint64 p;
+  gint max_week, jan4_week_day, ordinal_day;
+  GDateTime *dt;
+
+  p = (year * 365 + (year / 4) - (year / 100) + (year / 400)) % 7;
+  max_week = p == 4 ? 53 : 52;
+
+  if (week < 1 || week > max_week || week_day < 1 || week_day > 7)
+    return NULL;
+
+  dt = g_date_time_new (tz, year, 1, 4, 0, 0, 0);
+  g_date_time_get_week_number (dt, NULL, &jan4_week_day, NULL);
+  ordinal_day = (week * 7) + week_day - (jan4_week_day + 3);
+  if (ordinal_day < 0)
+    {
+      year--;
+      ordinal_day += GREGORIAN_LEAP (year) ? 366 : 365;
+    }
+  else if (ordinal_day > (GREGORIAN_LEAP (year) ? 366 : 365))
+    {
+      ordinal_day -= (GREGORIAN_LEAP (year) ? 366 : 365);
+      year++;
+    }
+
+  return g_date_time_new_ordinal (tz, year, ordinal_day, hour, minute, seconds);
+}
+
+static GDateTime *
+parse_iso8601_date (const gchar *text, gsize length,
+                    gint hour, gint minute, gdouble seconds, GTimeZone *tz)
+{
+  /* YYYY-MM-DD */
+  if (length == 10 && text[4] == '-' && text[7] == '-')
+    {
+      int year, month, day;
+      if (!get_iso8601_int (text, 4, &year) ||
+          !get_iso8601_int (text + 5, 2, &month) ||
+          !get_iso8601_int (text + 8, 2, &day))
+        return NULL;
+      return g_date_time_new (tz, year, month, day, hour, minute, seconds);
+    }
+  /* YYYY-DDD */
+  else if (length == 8 && text[4] == '-')
+    {
+      gint year, ordinal_day;
+      if (!get_iso8601_int (text, 4, &year) ||
+          !get_iso8601_int (text + 5, 3, &ordinal_day))
+        return NULL;
+      return g_date_time_new_ordinal (tz, year, ordinal_day, hour, minute, seconds);
+    }
+  /* YYYY-Www-D */
+  else if (length == 10 && text[4] == '-' && text[5] == 'W' && text[8] == '-')
+    {
+      gint year, week, week_day;
+      if (!get_iso8601_int (text, 4, &year) ||
+          !get_iso8601_int (text + 6, 2, &week) ||
+          !get_iso8601_int (text + 9, 1, &week_day))
+        return NULL;
+      return g_date_time_new_week (tz, year, week, week_day, hour, minute, seconds);
+    }
+  /* YYYYWwwD */
+  else if (length == 8 && text[4] == 'W')
+    {
+      gint year, week, week_day;
+      if (!get_iso8601_int (text, 4, &year) ||
+          !get_iso8601_int (text + 5, 2, &week) ||
+          !get_iso8601_int (text + 7, 1, &week_day))
+        return NULL;
+      return g_date_time_new_week (tz, year, week, week_day, hour, minute, seconds);
+    }
+  /* YYYYMMDD */
+  else if (length == 8)
+    {
+      int year, month, day;
+      if (!get_iso8601_int (text, 4, &year) ||
+          !get_iso8601_int (text + 4, 2, &month) ||
+          !get_iso8601_int (text + 6, 2, &day))
+        return NULL;
+      return g_date_time_new (tz, year, month, day, hour, minute, seconds);
+    }
+  /* YYYYDDD */
+  else if (length == 7)
+    {
+      gint year, ordinal_day;
+      if (!get_iso8601_int (text, 4, &year) ||
+          !get_iso8601_int (text + 4, 3, &ordinal_day))
+        return NULL;
+      return g_date_time_new_ordinal (tz, year, ordinal_day, hour, minute, seconds);
+    }
+  else
+    return FALSE;
+}
+
+static GTimeZone *
+parse_iso8601_timezone (const gchar *text, gsize length, gssize *tz_offset)
+{
+  gint i, tz_length, offset_hours, offset_minutes;
+
+  /* UTC uses Z suffix  */
+  if (length > 0 && text[length - 1] == 'Z')
+    {
+      *tz_offset = length - 1;
+      return g_time_zone_new_utc ();
+    }
+
+  /* Look for '+' or '-' of offset */
+  for (i = length - 1; i >= 0; i--)
+    if (text[i] == '+' || text[i] == '-')
+      break;
+  if (i < 0)
+    return NULL;
+  tz_length = length - i;
+
+  /* +hh:mm or -hh:mm */
+  if (tz_length == 6 && text[i+3] == ':')
+    {
+      if (!get_iso8601_int (text + i + 1, 2, &offset_hours) ||
+          !get_iso8601_int (text + i + 4, 2, &offset_minutes))
+        return NULL;
+    }
+  /* +hhmm or -hhmm */
+  else if (tz_length == 5)
+    {
+      if (!get_iso8601_int (text + i + 1, 2, &offset_hours) ||
+          !get_iso8601_int (text + i + 3, 2, &offset_minutes))
+        return NULL;
+    }
+  /* +hh or -hh */
+  else if (tz_length == 3)
+    {
+      if (!get_iso8601_int (text + i + 1, 2, &offset_hours))
+        return NULL;
+      offset_minutes = 0;
+    }
+  else
+    return NULL;
+
+  *tz_offset = i;
+  return g_time_zone_new (text + i);
+}
+
+static gboolean
+parse_iso8601_time (const gchar *text, gsize length,
+                    gint *hour, gint *minute, gdouble *seconds, GTimeZone **tz)
+{
+  gssize tz_offset = -1;
+
+  /* Check for timezone suffix */
+  *tz = parse_iso8601_timezone (text, length, &tz_offset);
+  if (tz_offset >= 0)
+    length = tz_offset;
+
+  /* hh:mm:ss(.sss) */
+  if (length >= 8 && text[2] == ':' && text[5] == ':')
+    {
+      return get_iso8601_int (text, 2, hour) &&
+             get_iso8601_int (text + 3, 2, minute) &&
+             get_iso8601_seconds (text + 6, length - 6, seconds);
+    }
+  /* hhmmss(.sss) */
+  else if (length >= 6)
+    {
+      return get_iso8601_int (text, 2, hour) &&
+             get_iso8601_int (text + 2, 2, minute) &&
+             get_iso8601_seconds (text + 4, length - 4, seconds);
+    }
+  else
+    return FALSE;
+}
+
+/**
+ * g_date_time_new_from_iso8601:
+ * @text: an ISO 8601 formatted time string.
+ * @default_tz: (nullable): a #GTimeZone to use if the text doesn't contain a timezone, or %NULL.
+ *
+ * Creates a #GDateTime corresponding to the given [ISO 8601 formatted 
string](https://en.wikipedia.org/wiki/ISO_8601)
+ * @text. Only the following subset of ISO 8601 is supported:
+ *
+ * <date>T<time> or <date>t<time> or <date> <time>
+ *
+ * <date> is in the form:
+ * YYYY-MM-DD - Year/month/day, e.g. 2016-08-24.
+ * YYYYMMDD   - Same as above without dividers.
+ * YYYY-DDD   - Ordinal day where DDD is from 001 to 366, e.g. 2016-237.
+ * YYYYDDD    - Same as above without dividers.
+ * YYYY-Www-D - Week day where ww is from 01 to 52 and D from 1-7, e.g.
+                2016-W34-3.
+ * YYYYWwwD   - Same as above without dividers.
+ *
+ * <time> is in the form:
+ * hh:mm:ss(.sss) - Hours, minutes, seconds (subseconds), e.g. 22:10:42.123.
+ * hhmmss(.sss)   - Same as above without dividers.
+ *
+ * Time can a timezone suffix in the form:
+ * Z                - UTC.
+ * +hh:mm or -hh:mm - Offset from UTC in hours and minutes, e.g. +12:00.
+ * +hh or -hh       - Offset from UTC in hours, e.g. +12.
+ *
+ * This call can fail (returning %NULL) if @text is not a valid ISO 8601
+ * formatted string.
+ *
+ * You should release the return value by calling g_date_time_unref()
+ * when you are done with it.
+ *
+ * Returns: (transfer full): (nullable): a new #GDateTime, or %NULL
+ *
+ * Since: 2.54
+ */
+GDateTime *
+g_date_time_new_from_iso8601 (const gchar *text, GTimeZone *default_tz)
+{
+  gint length, date_length = -1;
+  gint hour = 0, minute = 0;
+  gdouble seconds = 0.0;
+  GTimeZone *tz = NULL;
+  GDateTime *datetime = NULL;
+
+  g_return_val_if_fail (text != NULL, NULL);
+
+  /* Count length of string and find date / time separator ('T', 't', or ' ') */
+  for (length = 0; text[length] != '\0'; length++)
+    {
+      if (date_length < 0 && (text[length] == 'T' || text[length] == 't' || text[length] == ' '))
+        date_length = length;
+    }
+
+  if (date_length < 0)
+    return NULL;
+
+  if (!parse_iso8601_time (text + date_length + 1, length - (date_length + 1),
+                           &hour, &minute, &seconds, &tz))
+    goto out;
+  if (tz == NULL && default_tz == NULL)
+    return NULL;
+
+  datetime = parse_iso8601_date (text, date_length, hour, minute, seconds, tz ? tz : default_tz);
+
+out:
+    if (tz != NULL)
+      g_time_zone_unref (tz);
+    return datetime;
+}
+
 /* full new functions {{{1 */
 
 /**
diff --git a/glib/gdatetime.h b/glib/gdatetime.h
index 927e29c..02f1fc7 100644
--- a/glib/gdatetime.h
+++ b/glib/gdatetime.h
@@ -118,6 +118,10 @@ GDateTime *             g_date_time_new_from_timeval_local              (const G
 GLIB_AVAILABLE_IN_ALL
 GDateTime *             g_date_time_new_from_timeval_utc                (const GTimeVal *tv);
 
+GLIB_AVAILABLE_IN_2_54
+GDateTime *             g_date_time_new_from_iso8601                    (const gchar    *text,
+                                                                         GTimeZone      *default_tz);
+
 GLIB_AVAILABLE_IN_ALL
 GDateTime *             g_date_time_new                                 (GTimeZone      *tz,
                                                                          gint            year,
diff --git a/glib/tests/gdatetime.c b/glib/tests/gdatetime.c
index 4cb5a0a..7b2a419 100644
--- a/glib/tests/gdatetime.c
+++ b/glib/tests/gdatetime.c
@@ -25,11 +25,13 @@
 #include <locale.h>
 
 #define ASSERT_DATE(dt,y,m,d) G_STMT_START { \
+  g_assert ((dt)); \
   g_assert_cmpint ((y), ==, g_date_time_get_year ((dt))); \
   g_assert_cmpint ((m), ==, g_date_time_get_month ((dt))); \
   g_assert_cmpint ((d), ==, g_date_time_get_day_of_month ((dt))); \
 } G_STMT_END
 #define ASSERT_TIME(dt,H,M,S) G_STMT_START { \
+  g_assert ((dt)); \
   g_assert_cmpint ((H), ==, g_date_time_get_hour ((dt))); \
   g_assert_cmpint ((M), ==, g_date_time_get_minute ((dt))); \
   g_assert_cmpint ((S), ==, g_date_time_get_second ((dt))); \
@@ -476,6 +478,244 @@ test_GDateTime_new_from_timeval_utc (void)
 }
 
 static void
+test_GDateTime_new_from_iso8601 (void)
+{
+  GDateTime *dt;
+  GTimeZone *tz;
+
+  /* Need non-empty string */
+  dt = g_date_time_new_from_iso8601 ("", NULL);
+  g_assert_null (dt);
+
+  /* Needs to be correctly formatted */
+  dt = g_date_time_new_from_iso8601 ("not a date", NULL);
+  g_assert_null (dt);
+
+  /* Check common case */
+  dt = g_date_time_new_from_iso8601 ("2016-08-24T22:10:42Z", NULL);
+  ASSERT_DATE (dt, 2016, 8, 24);
+  ASSERT_TIME (dt, 22, 10, 42);
+  g_date_time_unref (dt);
+
+  /* Timezone is required in text or passed as arg */
+  tz = g_time_zone_new_utc ();
+  dt = g_date_time_new_from_iso8601 ("2016-08-24T22:10:42", tz);
+  ASSERT_DATE (dt, 2016, 8, 24);
+  g_date_time_unref (dt);
+  g_time_zone_unref (tz);
+  dt = g_date_time_new_from_iso8601 ("2016-08-24T22:10:42", NULL);
+  g_assert_null (dt);
+
+  /* Can't have whitespace */
+  dt = g_date_time_new_from_iso8601 ("2016 08 24T22:10:42Z", NULL);
+  g_assert_null (dt);
+  dt = g_date_time_new_from_iso8601 ("2016-08-24T22:10:42Z ", NULL);
+  g_assert_null (dt);
+  dt = g_date_time_new_from_iso8601 (" 2016-08-24T22:10:42Z", NULL);
+  g_assert_null (dt);
+
+  /* Check lowercase time separator or space allowed */
+  dt = g_date_time_new_from_iso8601 ("2016-08-24t22:10:42Z", NULL);
+  ASSERT_DATE (dt, 2016, 8, 24);
+  ASSERT_TIME (dt, 22, 10, 42);
+  g_date_time_unref (dt);
+  dt = g_date_time_new_from_iso8601 ("2016-08-24 22:10:42Z", NULL);
+  ASSERT_DATE (dt, 2016, 8, 24);
+  ASSERT_TIME (dt, 22, 10, 42);
+  g_date_time_unref (dt);
+
+  /* Check dates without separators allowed */
+  dt = g_date_time_new_from_iso8601 ("20160824T22:10:42Z", NULL);
+  ASSERT_DATE (dt, 2016, 8, 24);
+  ASSERT_TIME (dt, 22, 10, 42);
+  g_date_time_unref (dt);
+
+  /* Months are two digits */
+  dt = g_date_time_new_from_iso8601 ("2016-1-01T22:10:42Z", NULL);
+  g_assert_null (dt);
+
+  /* Days are two digits */
+  dt = g_date_time_new_from_iso8601 ("2016-01-1T22:10:42Z", NULL);
+  g_assert_null (dt);
+
+  /* Need consistent usage of separators */
+  dt = g_date_time_new_from_iso8601 ("2016-0824T22:10:42Z", NULL);
+  g_assert_null (dt);
+  dt = g_date_time_new_from_iso8601 ("201608-24T22:10:42Z", NULL);
+  g_assert_null (dt);
+
+  /* Check month within valid range */
+  dt = g_date_time_new_from_iso8601 ("2016-00-13T22:10:42Z", NULL);
+  g_assert_null (dt);
+  dt = g_date_time_new_from_iso8601 ("2016-13-13T22:10:42Z", NULL);
+  g_assert_null (dt);
+
+  /* Check day within valid range */
+  dt = g_date_time_new_from_iso8601 ("2016-01-00T22:10:42Z", NULL);
+  g_assert_null (dt);
+  dt = g_date_time_new_from_iso8601 ("2016-01-32T22:10:42Z", NULL);
+  g_assert_null (dt);
+
+  /* Check ordinal days work */
+  dt = g_date_time_new_from_iso8601 ("2016-237T22:10:42Z", NULL);
+  ASSERT_DATE (dt, 2016, 8, 24);
+  ASSERT_TIME (dt, 22, 10, 42);
+  g_date_time_unref (dt);
+  dt = g_date_time_new_from_iso8601 ("2016237T22:10:42Z", NULL);
+  ASSERT_DATE (dt, 2016, 8, 24);
+  ASSERT_TIME (dt, 22, 10, 42);
+  g_date_time_unref (dt);
+
+  /* Check ordinal leap days */
+  dt = g_date_time_new_from_iso8601 ("2016-366T22:10:42Z", NULL);
+  ASSERT_DATE (dt, 2016, 12, 31);
+  ASSERT_TIME (dt, 22, 10, 42);
+  g_date_time_unref (dt);
+  dt = g_date_time_new_from_iso8601 ("2017-365T22:10:42Z", NULL);
+  ASSERT_DATE (dt, 2017, 12, 31);
+  ASSERT_TIME (dt, 22, 10, 42);
+  g_date_time_unref (dt);
+  dt = g_date_time_new_from_iso8601 ("2017-366T22:10:42Z", NULL);
+  g_assert_null (dt);
+
+  /* Days start at 1 */
+  dt = g_date_time_new_from_iso8601 ("2016-000T22:10:42Z", NULL);
+  g_assert_null (dt);
+
+  /* Limited to number of days in the year (2016 is a leap year) */
+  dt = g_date_time_new_from_iso8601 ("2016-367T22:10:42Z", NULL);
+  g_assert_null (dt);
+
+  /* Days are two digits */
+  dt = g_date_time_new_from_iso8601 ("2016-1T22:10:42Z", NULL);
+  g_assert_null (dt);
+  dt = g_date_time_new_from_iso8601 ("2016-12T22:10:42Z", NULL);
+  g_assert_null (dt);
+
+  /* Check week days work */
+  dt = g_date_time_new_from_iso8601 ("2016-W34-3T22:10:42Z", NULL);
+  ASSERT_DATE (dt, 2016, 8, 24);
+  ASSERT_TIME (dt, 22, 10, 42);
+  g_date_time_unref (dt);
+  dt = g_date_time_new_from_iso8601 ("2016W343T22:10:42Z", NULL);
+  ASSERT_DATE (dt, 2016, 8, 24);
+  ASSERT_TIME (dt, 22, 10, 42);
+  g_date_time_unref (dt);
+
+  /* We don't support weeks without weekdays (valid ISO 8601) */
+  dt = g_date_time_new_from_iso8601 ("2016-W34T22:10:42Z", NULL);
+  g_assert_null (dt);
+  dt = g_date_time_new_from_iso8601 ("2016W34T22:10:42Z", NULL);
+  g_assert_null (dt);
+
+  /* Weeks are two digits */
+  dt = g_date_time_new_from_iso8601 ("2016-W3-1T22:10:42Z", NULL);
+  g_assert_null (dt);
+
+  /* Weeks start at 1 */
+  dt = g_date_time_new_from_iso8601 ("2016-W00-1T22:10:42Z", NULL);
+  g_assert_null (dt);
+
+  /* Week one might be in the previous year */
+  dt = g_date_time_new_from_iso8601 ("2015-W01-1T22:10:42Z", NULL);
+  ASSERT_DATE (dt, 2014, 12, 29);
+  g_date_time_unref (dt);
+
+  /* Last week might be in next year */
+  dt = g_date_time_new_from_iso8601 ("2015-W53-7T22:10:42Z", NULL);
+  ASSERT_DATE (dt, 2016, 1, 3);
+  g_date_time_unref (dt);
+
+  /* Week 53 doesn't always exist */
+  dt = g_date_time_new_from_iso8601 ("2016-W53-1T22:10:42Z", NULL);
+  g_assert_null (dt);
+
+  /* Limited to number of days in the week */
+  dt = g_date_time_new_from_iso8601 ("2016-W34-0T22:10:42Z", NULL);
+  g_assert_null (dt);
+  dt = g_date_time_new_from_iso8601 ("2016-W34-8T22:10:42Z", NULL);
+  g_assert_null (dt);
+
+  /* Days are one digit */
+  dt = g_date_time_new_from_iso8601 ("2016-W34-99T22:10:42Z", NULL);
+  g_assert_null (dt);
+
+  /* Check week day changes depending on year */
+  dt = g_date_time_new_from_iso8601 ("2017-W34-1T22:10:42Z", NULL);
+  ASSERT_DATE (dt, 2017, 8, 21);
+  g_date_time_unref (dt);
+
+  /* Check week day changes depending on leap years */
+  dt = g_date_time_new_from_iso8601 ("1900-W01-1T22:10:42Z", NULL);
+  ASSERT_DATE (dt, 1900, 1, 1);
+  g_date_time_unref (dt);
+
+  /* YYYY-MM not allowed (NOT valid ISO 8601) */
+  dt = g_date_time_new_from_iso8601 ("2016-08T22:10:42Z", NULL);
+  g_assert_null (dt);
+
+  /* We don't support omitted year (valid ISO 8601) */
+  dt = g_date_time_new_from_iso8601 ("--08-24T22:10:42Z", NULL);
+  g_assert_null (dt);
+  dt = g_date_time_new_from_iso8601 ("--0824T22:10:42Z", NULL);
+  g_assert_null (dt);
+
+  /* Check subseconds work */
+  dt = g_date_time_new_from_iso8601 ("2016-08-24T22:10:42.123456Z", NULL);
+  ASSERT_DATE (dt, 2016, 8, 24);
+  ASSERT_TIME (dt, 22, 10, 42.123456);
+  g_date_time_unref (dt);
+
+  /* Check time separators optional */
+  dt = g_date_time_new_from_iso8601 ("2016-08-24T221042.123456Z", NULL);
+  ASSERT_DATE (dt, 2016, 8, 24);
+  ASSERT_TIME (dt, 22, 10, 42.123456);
+  g_date_time_unref (dt);
+
+  /* We don't support times without minutes / seconds (valid ISO 8601) */
+  dt = g_date_time_new_from_iso8601 ("2016-08-24T22Z", NULL);
+  g_assert_null (dt);
+  dt = g_date_time_new_from_iso8601 ("2016-08-24T22:10Z", NULL);
+  g_assert_null (dt);
+  dt = g_date_time_new_from_iso8601 ("2016-08-24T2210Z", NULL);
+  g_assert_null (dt);
+
+  /* UTC time uses 'Z' */
+  dt = g_date_time_new_from_iso8601 ("2016-08-24T22:10:42Z", NULL);
+  ASSERT_DATE (dt, 2016, 8, 24);
+  ASSERT_TIME (dt, 22, 10, 42);
+  g_assert_cmpint (g_date_time_get_utc_offset (dt), ==, 0);
+  g_date_time_unref (dt);
+
+  /* Check timezone works */
+  dt = g_date_time_new_from_iso8601 ("2016-08-24T22:10:42+12:00", NULL);
+  ASSERT_DATE (dt, 2016, 8, 24);
+  ASSERT_TIME (dt, 22, 10, 42);
+  g_assert_cmpint (g_date_time_get_utc_offset (dt), ==, 12 * G_TIME_SPAN_HOUR);
+  g_date_time_unref (dt);
+  dt = g_date_time_new_from_iso8601 ("2016-08-24T22:10:42+12", NULL);
+  ASSERT_DATE (dt, 2016, 8, 24);
+  ASSERT_TIME (dt, 22, 10, 42);
+  g_assert_cmpint (g_date_time_get_utc_offset (dt), ==, 12 * G_TIME_SPAN_HOUR);
+  g_date_time_unref (dt);
+  dt = g_date_time_new_from_iso8601 ("2016-08-24T22:10:42-02", NULL);
+  ASSERT_DATE (dt, 2016, 8, 24);
+  ASSERT_TIME (dt, 22, 10, 42);
+  g_assert_cmpint (g_date_time_get_utc_offset (dt), ==, -2 * G_TIME_SPAN_HOUR);
+  g_date_time_unref (dt);
+
+  /* Timezone seconds not allowed */
+  dt = g_date_time_new_from_iso8601 ("2016-08-24T22-12:00:00", NULL);
+  g_assert_null (dt);
+  dt = g_date_time_new_from_iso8601 ("2016-08-24T22-12:00:00.000", NULL);
+  g_assert_null (dt);
+
+  /* Timezone hours two digits */
+  dt = g_date_time_new_from_iso8601 ("2016-08-24T22-2Z", NULL);
+  g_assert_null (dt);
+}
+
+static void
 test_GDateTime_to_unix (void)
 {
   GDateTime *dt;
@@ -1753,6 +1993,7 @@ main (gint   argc,
   g_test_add_func ("/GDateTime/new_from_timeval", test_GDateTime_new_from_timeval);
   g_test_add_func ("/GDateTime/new_from_timeval_utc", test_GDateTime_new_from_timeval_utc);
   g_test_add_func ("/GDateTime/new_from_timeval/overflow", test_GDateTime_new_from_timeval_overflow);
+  g_test_add_func ("/GDateTime/new_from_iso8601", test_GDateTime_new_from_iso8601);
   g_test_add_func ("/GDateTime/new_full", test_GDateTime_new_full);
   g_test_add_func ("/GDateTime/now", test_GDateTime_now);
   g_test_add_func ("/GDateTime/printf", test_GDateTime_printf);


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