diff options
author | Robert Ancell <robert.ancell@canonical.com> | 2016-08-25 11:53:54 +1200 |
---|---|---|
committer | Robert Ancell <robert.ancell@canonical.com> | 2017-06-24 16:02:13 +1200 |
commit | d693b46d9ce62faddcd4bf49bfe68a2c9ce1325c (patch) | |
tree | d7beadae965cd50fd2f4be1f6ae39636c37f2916 | |
parent | c7082b088f524336624e38b66a8b209d82dd6701 (diff) | |
download | glib-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.c | 450 | ||||
-rw-r--r-- | glib/gdatetime.h | 45 | ||||
-rw-r--r-- | glib/tests/gdatetime.c | 329 |
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); |