summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Ancell <robert.ancell@canonical.com>2016-08-25 11:53:54 +1200
committerRobert Ancell <robert.ancell@canonical.com>2017-06-24 16:02:13 +1200
commitd693b46d9ce62faddcd4bf49bfe68a2c9ce1325c (patch)
treed7beadae965cd50fd2f4be1f6ae39636c37f2916
parentc7082b088f524336624e38b66a8b209d82dd6701 (diff)
downloadglib-wip/rancell/iso8601.tar.gz
GDateTime: Support parsing ISO 8601 strings.wip/rancell/iso8601
https://bugzilla.gnome.org/show_bug.cgi?id=753459
-rw-r--r--glib/gdatetime.c450
-rw-r--r--glib/gdatetime.h45
-rw-r--r--glib/tests/gdatetime.c329
3 files changed, 824 insertions, 0 deletions
diff --git a/glib/gdatetime.c b/glib/gdatetime.c
index 745a32a9d..50ebaff5a 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,455 @@ g_date_time_new_from_timeval_utc (const GTimeVal *tv)
return datetime;
}
+static gboolean
+get_iso8601_int (const gchar *text, gint length, gint *value)
+{
+ gint i, v = 0;
+
+ for (i = 0; i < length; i++)
+ {
+ 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, gint length, gdouble *value, GDateTimeParseFlags *flags)
+{
+ gint i;
+ gdouble multiplier = 0.1, v = 0;
+
+ for (i = 0; i < length; i++)
+ {
+ gchar c = text[i];
+ if (c == '.' || c == ',')
+ {
+ i++;
+ *flags |= G_DATE_TIME_PARSE_SUBSECOND;
+ 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 gboolean
+convert_from_iso8601_ordinal (gint year, gint ordinal_day, gint *month, gint *day)
+{
+ gint m;
+
+ if (ordinal_day < 1)
+ return FALSE;
+
+ for (m = 1; m <= 12; m++)
+ {
+ if (ordinal_day <= days_in_year[GREGORIAN_LEAP (year)][m])
+ {
+ *month = m;
+ *day = ordinal_day - days_in_year[GREGORIAN_LEAP (year)][m - 1];
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+convert_from_iso8601_week (gint year, gint week, gint week_day, gint *offset)
+{
+ gint days, week_offset;
+
+ if (week < 1 || week > 52 || week_day < 1 || week_day > 7)
+ return FALSE;
+
+ /* Work out the day week one starts on */
+ days = ymd_to_days (year, 1, 1);
+ week_offset = -(days % 7);
+ if (week_offset < -3)
+ week_offset += 7;
+
+ *offset = week_offset + ((week - 1) * 7) + week_day;
+ return TRUE;
+}
+
+static gboolean
+parse_iso8601_date (const gchar *text, gint length,
+ gint *year, gint *month, gint *day, gint *offset,
+ GDateTimeParseFlags *flags)
+{
+ /* YYYY-MM-DD */
+ if (length == 10 && text[4] == '-' && text[7] == '-')
+ {
+ *flags |= G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_MONTH | G_DATE_TIME_PARSE_DAY;
+ return get_iso8601_int (text, 4, year) &&
+ get_iso8601_int (text + 5, 2, month) &&
+ get_iso8601_int (text + 8, 2, day);
+ }
+ /* YYYY-Www */
+ else if (length == 8 && text[4] == '-' && text[5] == 'W')
+ {
+ gint week;
+ *month = 1;
+ *day = 1;
+ *flags |= G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_WEEK;
+ return get_iso8601_int (text, 4, year) &&
+ get_iso8601_int (text + 6, 2, &week) &&
+ convert_from_iso8601_week (*year, week, 1, offset);
+ }
+ /* YYYY-DDD */
+ else if (length == 8 && text[4] == '-')
+ {
+ gint ordinal_day;
+ *flags |= G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_ORDINAL_DAY;
+ return get_iso8601_int (text, 4, year) &&
+ get_iso8601_int (text + 5, 3, &ordinal_day) &&
+ convert_from_iso8601_ordinal (*year, ordinal_day, month, day);
+ }
+ /* YYYY-Www-D */
+ else if (length == 10 && text[4] == '-' && text[5] == 'W' && text[8] == '-')
+ {
+ gint week, week_day;
+ *month = 1;
+ *day = 1;
+ *flags |= G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_WEEK | G_DATE_TIME_PARSE_WEEK_DAY;
+ return get_iso8601_int (text, 4, year) &&
+ get_iso8601_int (text + 6, 2, &week) &&
+ get_iso8601_int (text + 9, 1, &week_day) &&
+ convert_from_iso8601_week (*year, week, week_day, offset);
+ }
+ /* YYYYWwwD */
+ else if (length == 8 && text[4] == 'W')
+ {
+ gint week, week_day;
+ *month = 1;
+ *day = 1;
+ *flags |= G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_WEEK | G_DATE_TIME_PARSE_WEEK_DAY;
+ return get_iso8601_int (text, 4, year) &&
+ get_iso8601_int (text + 5, 2, &week) &&
+ get_iso8601_int (text + 7, 1, &week_day) &&
+ convert_from_iso8601_week (*year, week, week_day, offset);
+ }
+ /* YYYYMMDD */
+ else if (length == 8)
+ {
+ *flags |= G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_MONTH | G_DATE_TIME_PARSE_DAY;
+ return get_iso8601_int (text, 4, year) &&
+ get_iso8601_int (text + 4, 2, month) &&
+ get_iso8601_int (text + 6, 2, day);
+ }
+ /* --MM-DD */
+ else if (length == 7 && text[0] == '-' && text[1] == '-' && text[4] == '-')
+ {
+ *flags |= G_DATE_TIME_PARSE_MONTH | G_DATE_TIME_PARSE_DAY;
+ return get_iso8601_int (text + 2, 2, month) &&
+ get_iso8601_int (text + 5, 2, day);
+ }
+ /* YYYY-MM */
+ else if (length == 7 && text[4] == '-')
+ {
+ *flags |= G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_MONTH;
+ return get_iso8601_int (text, 4, year) &&
+ get_iso8601_int (text + 5, 2, month);
+ }
+ /* YYYYWww */
+ else if (length == 7 && text[4] == 'W')
+ {
+ gint week;
+ *month = 1;
+ *day = 1;
+ *flags |= G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_WEEK;
+ return get_iso8601_int (text, 4, year) &&
+ get_iso8601_int (text + 5, 2, &week) &&
+ convert_from_iso8601_week (*year, week, 1, offset);
+ }
+ /* YYYYDDD */
+ else if (length == 7)
+ {
+ gint ordinal_day;
+ *flags |= G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_ORDINAL_DAY;
+ return get_iso8601_int (text, 4, year) &&
+ get_iso8601_int (text + 4, 3, &ordinal_day) &&
+ convert_from_iso8601_ordinal (*year, ordinal_day, month, day);
+ }
+ /* --MMDD */
+ else if (length == 6 && text[0] == '-' && text[1] == '-')
+ {
+ *flags |= G_DATE_TIME_PARSE_MONTH | G_DATE_TIME_PARSE_DAY;
+ return get_iso8601_int (text + 2, 2, month) &&
+ get_iso8601_int (text + 4, 2, day);
+ }
+ else
+ return FALSE;
+}
+
+/* Check if text has Unicode minus symbol prefix (−) */
+static gboolean
+has_minus_prefix (const gchar *text)
+{
+ return (guint8) text[0] == 0xe2 && (guint8) text[1] == 0x88 && (guint8) text[2] == 0x92;
+}
+
+static gboolean
+parse_iso8601_timezone (const gchar *text, gint length, GTimeZone **tz)
+{
+ gint offset_hours, offset_minutes;
+
+ /* Z */
+ if (length == 1 && text[0] == 'Z')
+ {
+ offset_hours = 0;
+ offset_minutes = 0;
+ }
+ /* +hh:mm or -hh:mm */
+ else if (length == 6 && (text[0] == '+' || text[0] == '-') && text[3] == ':')
+ {
+ if (!get_iso8601_int (text + 1, 2, &offset_hours) ||
+ !get_iso8601_int (text + 4, 2, &offset_minutes))
+ return FALSE;
+ }
+ /* −hh:mm */
+ else if (length == 8 && has_minus_prefix (text) && text[5] == ':')
+ {
+ if (!get_iso8601_int (text + 3, 2, &offset_hours) ||
+ !get_iso8601_int (text + 6, 2, &offset_minutes))
+ return FALSE;
+ }
+ /* +hhmm or -hhmm */
+ else if (length == 5 && (text[0] == '+' || text[0] == '-'))
+ {
+ if (!get_iso8601_int (text + 1, 2, &offset_hours) ||
+ !get_iso8601_int (text + 3, 2, &offset_minutes))
+ return FALSE;
+ }
+ /* −hhmm */
+ else if (length == 7 && has_minus_prefix (text))
+ {
+ if (!get_iso8601_int (text + 3, 2, &offset_hours) ||
+ !get_iso8601_int (text + 5, 2, &offset_minutes))
+ return FALSE;
+ }
+ /* +hh or -hh */
+ else if (length == 3 && (text[0] == '+' || text[0] == '-'))
+ {
+ if (!get_iso8601_int (text + 1, 2, &offset_hours))
+ return FALSE;
+ offset_minutes = 0;
+ }
+ /* −hh */
+ else if (length == 5 && has_minus_prefix (text))
+ {
+ if (!get_iso8601_int (text + 3, 2, &offset_hours))
+ return FALSE;
+ offset_minutes = 0;
+ }
+ else
+ return FALSE;
+
+ *tz = g_time_zone_new (text);
+
+ return TRUE;
+}
+
+static gboolean
+parse_iso8601_time (const gchar *text, gint length,
+ gint *hour, gint *minute, gdouble *seconds, GTimeZone **tz,
+ GDateTimeParseFlags *flags)
+{
+ gint i;
+
+ /* Check for timezone suffix */
+ for (i = 0; i < length; i++)
+ {
+ if (parse_iso8601_timezone (text + i, length - i, tz))
+ {
+ *flags |= G_DATE_TIME_PARSE_TIMEZONE;
+ length = i;
+ break;
+ }
+ }
+
+ /* hh:mm:ss(.sss) */
+ if (length >= 8 && text[2] == ':' && text[5] == ':')
+ {
+ *flags |= G_DATE_TIME_PARSE_HOUR | G_DATE_TIME_PARSE_MINUTE | G_DATE_TIME_PARSE_SECOND;
+ return get_iso8601_int (text, 2, hour) &&
+ get_iso8601_int (text + 3, 2, minute) &&
+ get_iso8601_seconds (text + 6, length - 6, seconds, flags);
+ }
+ /* hhmmss(.sss) */
+ else if (length >= 6)
+ {
+ *flags |= G_DATE_TIME_PARSE_HOUR | G_DATE_TIME_PARSE_MINUTE | G_DATE_TIME_PARSE_SECOND;
+ return get_iso8601_int (text, 2, hour) &&
+ get_iso8601_int (text + 2, 2, minute) &&
+ get_iso8601_seconds (text + 4, length - 4, seconds, flags);
+ }
+ /* hh:mm */
+ else if (length == 5 && text[2] == ':')
+ {
+ *flags |= G_DATE_TIME_PARSE_HOUR | G_DATE_TIME_PARSE_MINUTE;
+ return get_iso8601_int (text, 2, hour) &&
+ get_iso8601_int (text + 3, 2, minute);
+ }
+ /* hhmm */
+ else if (length == 4)
+ {
+ *flags |= G_DATE_TIME_PARSE_HOUR | G_DATE_TIME_PARSE_MINUTE;
+ return get_iso8601_int (text, 2, hour) &&
+ get_iso8601_int (text + 2, 2, minute);
+ }
+ /* hh */
+ else if (length == 2)
+ {
+ *flags |= G_DATE_TIME_PARSE_HOUR;
+ return get_iso8601_int (text, 2, hour);
+ }
+ else
+ return FALSE;
+}
+
+static GDateTime *
+parse_iso8601 (const gchar *text, gint year, gint month, gint day, GDateTimeParseFlags *flags)
+{
+ gint length, date_length = -1;
+ gint y = year, m = month, d = day, offset = 0, hour = 0, minute = 0;
+ gdouble seconds = 0.0;
+ GTimeZone *tz = NULL;
+ GDateTime *datetime = NULL;
+
+ /* <date>T<time> */
+ for (length = 0; text[length] != '\0'; length++)
+ {
+ if (date_length < 0 && text[length] == 'T')
+ date_length = length;
+ }
+
+ if (date_length < 0)
+ {
+ if (!parse_iso8601_date (text, length, &y, &m, &d, &offset, flags))
+ {
+ *flags = 0;
+ if (!parse_iso8601_time (text, length, &hour, &minute, &seconds, &tz, flags))
+ goto out;
+ }
+ }
+ else
+ {
+ if (!parse_iso8601_date (text, date_length, &y, &m, &d, &offset, flags) ||
+ !parse_iso8601_time (text + date_length + 1, length - (date_length + 1),
+ &hour, &minute, &seconds, &tz, flags))
+ goto out;
+ }
+
+ if (tz == NULL)
+ tz = g_time_zone_new_local ();
+ datetime = g_date_time_new (tz, y, m, d, hour, minute, seconds);
+ if (datetime != NULL && offset != 0)
+ datetime->days += offset;
+
+out:
+ if (tz != NULL)
+ g_time_zone_unref (tz);
+ return datetime;
+}
+
+/**
+ * g_date_time_new_from_iso8601:
+ * @text: an ISO 8601 formatted time string.
+ * @flags: (allow-none): location to write which fields were parsed to generate
+ * this #GDateTime or %NULL.
+ *
+ * Creates a #GDateTime corresponding to the given ISO 8601 formatted string
+ * @text.
+ *
+ * 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: a new #GDateTime, or %NULL
+ *
+ * Since: 2.50
+ **/
+GDateTime *
+g_date_time_new_from_iso8601 (const gchar *text,
+ GDateTimeParseFlags *flags)
+{
+ GDateTime *datetime;
+ GDateTimeParseFlags f = 0;
+
+ g_return_val_if_fail (text != NULL, NULL);
+
+ datetime = parse_iso8601 (text, 0, 0, 0, &f);
+
+ if (flags != NULL)
+ *flags = f;
+ return datetime;
+}
+
+/**
+ * g_date_time_new_from_iso8601_with_year:
+ * @text: an ISO 8601 formatted time string.
+ * @year: the year to use if @text does not specify one.
+ * @month: the month to use if @text does not specify one.
+ * @day: the month to use if @text does not specify one.
+ * @flags: (allow-none): location to write which fields were parsed to generate
+ * this #GDateTime or %NULL.
+ *
+ * Creates a #GDateTime corresponding to the given ISO 8601 formatted string
+ * @text.
+ *
+ * ISO 8601 strings that do not contain dates are times (e.g. "22:10:42") or
+ * dates with just the month and day, e.g. "--08-24".
+ *
+ * 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: a new #GDateTime, or %NULL
+ *
+ * Since: 2.50
+ **/
+GDateTime *
+g_date_time_new_from_iso8601_with_date (const gchar *text,
+ gint year,
+ gint month,
+ gint day,
+ GDateTimeParseFlags *flags)
+{
+ GDateTime *datetime;
+ GDateTimeParseFlags f = 0;
+
+ g_return_val_if_fail (text != NULL, NULL);
+
+ datetime = parse_iso8601 (text, year, month, day, &f);
+
+ if (flags != NULL)
+ *flags = f;
+ return datetime;
+}
+
/* full new functions {{{1 */
/**
diff --git a/glib/gdatetime.h b/glib/gdatetime.h
index 927e29cfc..3297f50b4 100644
--- a/glib/gdatetime.h
+++ b/glib/gdatetime.h
@@ -96,6 +96,41 @@ typedef gint64 GTimeSpan;
*/
typedef struct _GDateTime GDateTime;
+/**
+ * GDateTimeParseFlags:
+ * @G_DATE_TIME_PARSE_YEAR: the parsed date includes the year.
+ * @G_DATE_TIME_PARSE_MONTH: the parsed date includes the month.
+ * @G_DATE_TIME_PARSE_DAY: the parsed date includes the day.
+ * @G_DATE_TIME_PARSE_WEEK: the parsed date is calculated from the week number.
+ * @G_DATE_TIME_PARSE_WEEK_DAY: the parsed date is calculated from the week.
+ * number and day of the week. (Implies G_DATE_TIME_PARSE_WEEK).
+ * @G_DATE_TIME_PARSE_ORDINAL_DAY: the parsed date is calculated from the
+ ordinal day (i.e. 1-365/366).
+ * @G_DATE_TIME_PARSE_HOUR: the parsed date includes the hour.
+ * @G_DATE_TIME_PARSE_MINUTE: the parsed date includes the minute.
+ * @G_DATE_TIME_PARSE_SECOND: the parsed date includes the second.
+ * @G_DATE_TIME_PARSE_SUBSECOND: the parsed date includes the subsecond.
+ * @G_DATE_TIME_PARSE_TIMEZONE: the parsed date includes a timezone.
+ *
+ * Flags used in g_date_time_new_from_iso8601 () and
+ * g_date_time_new_from_iso8601_with_date () to indicate what fields were
+ * parsed from the ISO 8601 string.
+ */
+typedef enum
+{
+ G_DATE_TIME_PARSE_YEAR = 1 << 0,
+ G_DATE_TIME_PARSE_MONTH = 1 << 1,
+ G_DATE_TIME_PARSE_DAY = 1 << 2,
+ G_DATE_TIME_PARSE_WEEK = 1 << 3,
+ G_DATE_TIME_PARSE_WEEK_DAY = 1 << 4,
+ G_DATE_TIME_PARSE_ORDINAL_DAY = 1 << 5,
+ G_DATE_TIME_PARSE_HOUR = 1 << 6,
+ G_DATE_TIME_PARSE_MINUTE = 1 << 7,
+ G_DATE_TIME_PARSE_SECOND = 1 << 8,
+ G_DATE_TIME_PARSE_SUBSECOND = 1 << 9,
+ G_DATE_TIME_PARSE_TIMEZONE = 1 << 10
+} GDateTimeParseFlags;
+
GLIB_AVAILABLE_IN_ALL
void g_date_time_unref (GDateTime *datetime);
GLIB_AVAILABLE_IN_ALL
@@ -118,6 +153,16 @@ 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_50
+GDateTime * g_date_time_new_from_iso8601 (const gchar *text,
+ GDateTimeParseFlags *flags);
+GLIB_AVAILABLE_IN_2_50
+GDateTime * g_date_time_new_from_iso8601_with_date (const gchar *text,
+ gint year,
+ gint month,
+ gint day,
+ GDateTimeParseFlags *flags);
+
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 1a9e3fb28..ef471b57f 100644
--- a/glib/tests/gdatetime.c
+++ b/glib/tests/gdatetime.c
@@ -482,6 +482,334 @@ test_GDateTime_new_from_timeval_utc (void)
}
static void
+test_GDateTime_new_from_iso8601 (void)
+{
+ GDateTime *dt;
+ GDateTimeParseFlags flags;
+
+ /* Need non-empty string */
+ dt = g_date_time_new_from_iso8601 ("", NULL);
+ g_assert (dt == NULL);
+
+ /* Needs to be correctly formatted */
+ dt = g_date_time_new_from_iso8601 ("not a date", NULL);
+ g_assert (dt == NULL);
+
+ /* Can't have whitespace */
+ dt = g_date_time_new_from_iso8601 ("2016 08 24", &flags);
+ g_assert (dt == NULL);
+ dt = g_date_time_new_from_iso8601 ("2016-08-24 ", &flags);
+ g_assert (dt == NULL);
+ dt = g_date_time_new_from_iso8601 (" 2016-08-24", &flags);
+ g_assert (dt == NULL);
+
+ dt = g_date_time_new_from_iso8601 ("2016-08-24", &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 0, 0, 0);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_MONTH | G_DATE_TIME_PARSE_DAY);
+ g_date_time_unref (dt);
+
+ dt = g_date_time_new_from_iso8601 ("20160824", &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 0, 0, 0);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_MONTH | G_DATE_TIME_PARSE_DAY);
+ g_date_time_unref (dt);
+
+ /* Months are two digits */
+ dt = g_date_time_new_from_iso8601 ("2016-1-01", NULL);
+ g_assert (dt == NULL);
+
+ /* Days are two digits */
+ dt = g_date_time_new_from_iso8601 ("2016-01-1", NULL);
+ g_assert (dt == NULL);
+
+ /* Need consistent usage of separators */
+ dt = g_date_time_new_from_iso8601 ("2016-0824", NULL);
+ g_assert (dt == NULL);
+ dt = g_date_time_new_from_iso8601 ("201608-24", NULL);
+ g_assert (dt == NULL);
+
+ /* Check month within valid range */
+ dt = g_date_time_new_from_iso8601 ("2016-00-13", NULL);
+ g_assert (dt == NULL);
+ dt = g_date_time_new_from_iso8601 ("2016-13-13", NULL);
+ g_assert (dt == NULL);
+
+ /* Check day within valid range */
+ dt = g_date_time_new_from_iso8601 ("2016-01-00", NULL);
+ g_assert (dt == NULL);
+ dt = g_date_time_new_from_iso8601 ("2016-01-32", NULL);
+ g_assert (dt == NULL);
+
+ dt = g_date_time_new_from_iso8601 ("2016-237", &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 0, 0, 0);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_ORDINAL_DAY);
+ g_date_time_unref (dt);
+
+ dt = g_date_time_new_from_iso8601 ("2016237", &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 0, 0, 0);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_ORDINAL_DAY);
+ g_date_time_unref (dt);
+
+ /* Days start at 1 */
+ dt = g_date_time_new_from_iso8601 ("2016-000", NULL);
+ g_assert (dt == NULL);
+
+ /* Limited to number of days in the year (2016 is a leap year) */
+ dt = g_date_time_new_from_iso8601 ("2016-367", NULL);
+ g_assert (dt == NULL);
+
+ /* Days are two digits */
+ dt = g_date_time_new_from_iso8601 ("2016-1", NULL);
+ g_assert (dt == NULL);
+ dt = g_date_time_new_from_iso8601 ("2016-12", NULL);
+ g_assert (dt == NULL);
+
+ dt = g_date_time_new_from_iso8601 ("2016-W34", &flags);
+ ASSERT_DATE (dt, 2016, 8, 22);
+ ASSERT_TIME (dt, 0, 0, 0);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_WEEK);
+ g_date_time_unref (dt);
+
+ dt = g_date_time_new_from_iso8601 ("2016W34", &flags);
+ ASSERT_DATE (dt, 2016, 8, 22);
+ ASSERT_TIME (dt, 0, 0, 0);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_WEEK);
+ g_date_time_unref (dt);
+
+ /* Weeks are two digits */
+ dt = g_date_time_new_from_iso8601 ("2016-W3", NULL);
+ g_assert (dt == NULL);
+
+ /* Weeks start at 1 */
+ dt = g_date_time_new_from_iso8601 ("2016-W00", NULL);
+ g_assert (dt == NULL);
+
+ /* Limited to number of weeks in the year */
+ dt = g_date_time_new_from_iso8601 ("2016-W53", NULL);
+ g_assert (dt == NULL);
+
+ /* Check week day changes depending on year */
+ dt = g_date_time_new_from_iso8601 ("2017W34", &flags);
+ ASSERT_DATE (dt, 2017, 8, 21);
+ ASSERT_TIME (dt, 0, 0, 0);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_WEEK);
+ g_date_time_unref (dt);
+
+ /* Check week day changes depending on leap years */
+ dt = g_date_time_new_from_iso8601 ("1900W01", &flags);
+ ASSERT_DATE (dt, 1900, 1, 1);
+ ASSERT_TIME (dt, 0, 0, 0);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_WEEK);
+ g_date_time_unref (dt);
+
+ dt = g_date_time_new_from_iso8601 ("2016-W34-3", &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 0, 0, 0);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_WEEK | G_DATE_TIME_PARSE_WEEK_DAY);
+ g_date_time_unref (dt);
+
+ dt = g_date_time_new_from_iso8601 ("2016W343", &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 0, 0, 0);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_WEEK | G_DATE_TIME_PARSE_WEEK_DAY);
+ g_date_time_unref (dt);
+
+ /* Limited to number of days in the week */
+ dt = g_date_time_new_from_iso8601 ("2016-W34-0", NULL);
+ g_assert (dt == NULL);
+ dt = g_date_time_new_from_iso8601 ("2016-W34-8", NULL);
+ g_assert (dt == NULL);
+
+ /* Days are one digit */
+ dt = g_date_time_new_from_iso8601 ("2016-W34-99", NULL);
+ g_assert (dt == NULL);
+
+ /* YYYY-MM not allowed */
+ dt = g_date_time_new_from_iso8601 ("2016-08", NULL);
+ g_assert (dt == NULL);
+
+ dt = g_date_time_new_from_iso8601_with_date ("--08-24", 2016, 0, 0, &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 0, 0, 0);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_MONTH | G_DATE_TIME_PARSE_DAY);
+ g_date_time_unref (dt);
+
+ dt = g_date_time_new_from_iso8601_with_date ("--0824", 2016, 0, 0, &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 0, 0, 0);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_MONTH | G_DATE_TIME_PARSE_DAY);
+ g_date_time_unref (dt);
+
+ dt = g_date_time_new_from_iso8601_with_date ("22", 2016, 8, 24, &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 22, 0, 0);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_HOUR);
+ g_date_time_unref (dt);
+
+ dt = g_date_time_new_from_iso8601_with_date ("22:10", 2016, 8, 24, &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 22, 10, 0);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_HOUR | G_DATE_TIME_PARSE_MINUTE);
+ g_date_time_unref (dt);
+
+ dt = g_date_time_new_from_iso8601_with_date ("22:10:42", 2016, 8, 24, &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 22, 10, 42);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_HOUR | G_DATE_TIME_PARSE_MINUTE | G_DATE_TIME_PARSE_SECOND);
+ g_date_time_unref (dt);
+
+ dt = g_date_time_new_from_iso8601_with_date ("22:10:42.123456", 2016, 8, 24, &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 22, 10, 42.123456);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_HOUR | G_DATE_TIME_PARSE_MINUTE | G_DATE_TIME_PARSE_SECOND | G_DATE_TIME_PARSE_SUBSECOND);
+ g_date_time_unref (dt);
+
+ dt = g_date_time_new_from_iso8601_with_date ("2210", 2016, 8, 24, &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 22, 10, 0);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_HOUR | G_DATE_TIME_PARSE_MINUTE);
+ g_date_time_unref (dt);
+
+ dt = g_date_time_new_from_iso8601_with_date ("221042", 2016, 8, 24, &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 22, 10, 42);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_HOUR | G_DATE_TIME_PARSE_MINUTE | G_DATE_TIME_PARSE_SECOND);
+ g_date_time_unref (dt);
+
+ dt = g_date_time_new_from_iso8601_with_date ("221042.123456", 2016, 8, 24, &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 22, 10, 42.123456);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_HOUR | G_DATE_TIME_PARSE_MINUTE | G_DATE_TIME_PARSE_SECOND | G_DATE_TIME_PARSE_SUBSECOND);
+ g_date_time_unref (dt);
+
+ /* Can't parse time without date */
+ dt = g_date_time_new_from_iso8601 ("22", &flags);
+ g_assert (dt == NULL);
+ dt = g_date_time_new_from_iso8601 ("22:10", &flags);
+ g_assert (dt == NULL);
+ dt = g_date_time_new_from_iso8601 ("22:10:42", &flags);
+ g_assert (dt == NULL);
+ dt = g_date_time_new_from_iso8601 ("22:10:42.123456", &flags);
+ g_assert (dt == NULL);
+ dt = g_date_time_new_from_iso8601 ("2210", &flags);
+ g_assert (dt == NULL);
+ dt = g_date_time_new_from_iso8601 ("221042", &flags);
+ g_assert (dt == NULL);
+ dt = g_date_time_new_from_iso8601 ("221042.123456", &flags);
+
+ dt = g_date_time_new_from_iso8601 ("2016-08-24T22", &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 22, 0, 0);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_MONTH | G_DATE_TIME_PARSE_DAY |
+ G_DATE_TIME_PARSE_HOUR);
+ g_date_time_unref (dt);
+
+ dt = g_date_time_new_from_iso8601 ("2016-08-24T22:10", &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 22, 10, 0);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_MONTH | G_DATE_TIME_PARSE_DAY |
+ G_DATE_TIME_PARSE_HOUR | G_DATE_TIME_PARSE_MINUTE);
+ g_date_time_unref (dt);
+
+ dt = g_date_time_new_from_iso8601 ("2016-08-24T22:10:42", &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 22, 10, 42);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_MONTH | G_DATE_TIME_PARSE_DAY |
+ G_DATE_TIME_PARSE_HOUR | G_DATE_TIME_PARSE_MINUTE | G_DATE_TIME_PARSE_SECOND);
+ g_date_time_unref (dt);
+
+ dt = g_date_time_new_from_iso8601 ("2016-08-24T22:10:42.123456", &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 22, 10, 42.123456);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_MONTH | G_DATE_TIME_PARSE_DAY |
+ G_DATE_TIME_PARSE_HOUR | G_DATE_TIME_PARSE_MINUTE | G_DATE_TIME_PARSE_SECOND | G_DATE_TIME_PARSE_SUBSECOND);
+ g_date_time_unref (dt);
+
+ dt = g_date_time_new_from_iso8601 ("2016-08-24T2210", &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 22, 10, 0);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_MONTH | G_DATE_TIME_PARSE_DAY |
+ G_DATE_TIME_PARSE_HOUR | G_DATE_TIME_PARSE_MINUTE);
+ g_date_time_unref (dt);
+
+ dt = g_date_time_new_from_iso8601 ("2016-08-24T221042", &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 22, 10, 42);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_MONTH | G_DATE_TIME_PARSE_DAY |
+ G_DATE_TIME_PARSE_HOUR | G_DATE_TIME_PARSE_MINUTE | G_DATE_TIME_PARSE_SECOND);
+ g_date_time_unref (dt);
+
+ dt = g_date_time_new_from_iso8601 ("2016-08-24T221042.123456", &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 22, 10, 42.123456);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_MONTH | G_DATE_TIME_PARSE_DAY |
+ G_DATE_TIME_PARSE_HOUR | G_DATE_TIME_PARSE_MINUTE | G_DATE_TIME_PARSE_SECOND | G_DATE_TIME_PARSE_SUBSECOND);
+ g_date_time_unref (dt);
+
+ /* UTC time uses 'Z' */
+ dt = g_date_time_new_from_iso8601 ("2016-08-24T22Z", &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 22, 0, 0);
+ g_assert_cmpint (g_date_time_get_utc_offset (dt), ==, 0);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_MONTH | G_DATE_TIME_PARSE_DAY |
+ G_DATE_TIME_PARSE_HOUR | G_DATE_TIME_PARSE_TIMEZONE);
+ g_date_time_unref (dt);
+
+ dt = g_date_time_new_from_iso8601 ("2016-08-24T22+12:00", &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 22, 0, 0);
+ g_assert_cmpint (g_date_time_get_utc_offset (dt), ==, 12 * G_TIME_SPAN_HOUR);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_MONTH | G_DATE_TIME_PARSE_DAY |
+ G_DATE_TIME_PARSE_HOUR | G_DATE_TIME_PARSE_TIMEZONE);
+ g_date_time_unref (dt);
+
+ /* Check negative values */
+ dt = g_date_time_new_from_iso8601 ("2016-08-24T22-02", &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 22, 0, 0);
+ g_assert_cmpint (g_date_time_get_utc_offset (dt), ==, -2 * G_TIME_SPAN_HOUR);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_MONTH | G_DATE_TIME_PARSE_DAY |
+ G_DATE_TIME_PARSE_HOUR | G_DATE_TIME_PARSE_TIMEZONE);
+ g_date_time_unref (dt);
+
+ /* Check negative values using Unicode character − */
+ dt = g_date_time_new_from_iso8601 ("2016-08-24T22−02:00", &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 22, 0, 0);
+ g_assert_cmpint (g_date_time_get_utc_offset (dt), ==, -2 * G_TIME_SPAN_HOUR);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_MONTH | G_DATE_TIME_PARSE_DAY |
+ G_DATE_TIME_PARSE_HOUR | G_DATE_TIME_PARSE_TIMEZONE);
+ g_date_time_unref (dt);
+ dt = g_date_time_new_from_iso8601 ("2016-08-24T22−0200", &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 22, 0, 0);
+ g_assert_cmpint (g_date_time_get_utc_offset (dt), ==, -2 * G_TIME_SPAN_HOUR);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_MONTH | G_DATE_TIME_PARSE_DAY |
+ G_DATE_TIME_PARSE_HOUR | G_DATE_TIME_PARSE_TIMEZONE);
+ g_date_time_unref (dt);
+ dt = g_date_time_new_from_iso8601 ("2016-08-24T22−02", &flags);
+ ASSERT_DATE (dt, 2016, 8, 24);
+ ASSERT_TIME (dt, 22, 0, 0);
+ g_assert_cmpint (g_date_time_get_utc_offset (dt), ==, -2 * G_TIME_SPAN_HOUR);
+ g_assert_cmpint (flags, ==, G_DATE_TIME_PARSE_YEAR | G_DATE_TIME_PARSE_MONTH | G_DATE_TIME_PARSE_DAY |
+ G_DATE_TIME_PARSE_HOUR | G_DATE_TIME_PARSE_TIMEZONE);
+ g_date_time_unref (dt);
+
+ /* Timezone seconds not allowed */
+ dt = g_date_time_new_from_iso8601 ("2016-08-24T22-12:00:00", &flags);
+ g_assert (dt == NULL);
+ dt = g_date_time_new_from_iso8601 ("2016-08-24T22-12:00:00.000", &flags);
+ g_assert (dt == NULL);
+
+ /* Timezone hours two digits */
+ dt = g_date_time_new_from_iso8601 ("2016-08-24T22-2", &flags);
+ g_assert (dt == NULL);
+}
+
+static void
test_GDateTime_to_unix (void)
{
GDateTime *dt;
@@ -1759,6 +2087,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);