+gchar *
+__gst_date_time_serialize (GstDateTime * datetime, gboolean serialize_usecs)
+{
+ GString *s;
+ gfloat gmt_offset;
+ guint msecs;
+
+ /* we always have at least the year */
+ s = g_string_new (NULL);
+ g_string_append_printf (s, "%04u", gst_date_time_get_year (datetime));
+
+ if (datetime->fields == GST_DATE_TIME_FIELDS_Y)
+ goto done;
+
+ /* add month */
+ g_string_append_printf (s, "-%02u", gst_date_time_get_month (datetime));
+
+ if (datetime->fields == GST_DATE_TIME_FIELDS_YM)
+ goto done;
+
+ /* add day of month */
+ g_string_append_printf (s, "-%02u", gst_date_time_get_day (datetime));
+
+ if (datetime->fields == GST_DATE_TIME_FIELDS_YMD)
+ goto done;
+
+ /* add time */
+ g_string_append_printf (s, "T%02u:%02u", gst_date_time_get_hour (datetime),
+ gst_date_time_get_minute (datetime));
+
+ if (datetime->fields == GST_DATE_TIME_FIELDS_YMD_HM)
+ goto add_timezone;
+
+ /* add seconds */
+ g_string_append_printf (s, ":%02u", gst_date_time_get_second (datetime));
+
+ /* add microseconds */
+ if (serialize_usecs) {
+ msecs = gst_date_time_get_microsecond (datetime);
+ if (msecs != 0) {
+ g_string_append_printf (s, ".%06u", msecs);
+ /* trim trailing 0s */
+ while (s->str[s->len - 1] == '0')
+ g_string_truncate (s, s->len - 1);
+ }
+ }
+
+ /* add timezone */
+
+add_timezone:
+
+ gmt_offset = gst_date_time_get_time_zone_offset (datetime);
+ if (gmt_offset == 0) {
+ g_string_append_c (s, 'Z');
+ } else {
+ guint tzhour, tzminute;
+
+ tzhour = (guint) ABS (gmt_offset);
+ tzminute = (guint) ((ABS (gmt_offset) - tzhour) * 60);
+
+ g_string_append_c (s, (gmt_offset >= 0) ? '+' : '-');
+ g_string_append_printf (s, "%02u%02u", tzhour, tzminute);
+ }
+
+done:
+
+ return g_string_free (s, FALSE);
+}
+
+/**
+ * gst_date_time_to_iso8601_string:
+ * @datetime: GstDateTime.
+ *
+ * Create a minimal string compatible with ISO-8601. Possible output formats
+ * are (for example): 2012, 2012-06, 2012-06-23, 2012-06-23T23:30Z,
+ * 2012-06-23T23:30+0100, 2012-06-23T23:30:59Z, 2012-06-23T23:30:59+0100
+ *
+ * Returns: a newly allocated string formatted according to ISO 8601 and
+ * only including the datetime fields that are valid, or %NULL in case
+ * there was an error. The string should be freed with g_free().
+ */
+gchar *
+gst_date_time_to_iso8601_string (GstDateTime * datetime)
+{
+ g_return_val_if_fail (datetime != NULL, NULL);
+
+ if (datetime->fields == GST_DATE_TIME_FIELDS_INVALID)
+ return NULL;
+
+ return __gst_date_time_serialize (datetime, FALSE);
+}
+
+/**
+ * gst_date_time_new_from_iso8601_string:
+ * @string: ISO 8601-formatted datetime string.
+ *
+ * Tries to parse common variants of ISO-8601 datetime strings into a
+ * #GstDateTime.
+ *
+ * Free-function: gst_date_time_unref
+ *
+ * Returns: (transfer full): a newly created #GstDateTime, or %NULL on error
+ */
+GstDateTime *
+gst_date_time_new_from_iso8601_string (const gchar * string)
+{
+ gint year = -1, month = -1, day = -1, hour = -1, minute = -1;
+ gdouble second = -1.0;
+ gfloat tzoffset = 0.0;
+ guint64 usecs;
+ gint len, ret;
+
+ g_return_val_if_fail (string != NULL, NULL);
+
+ GST_DEBUG ("Parsing '%s' into a datetime", string);
+
+ len = strlen (string);
+
+ if (len < 4 || !g_ascii_isdigit (string[0]) || !g_ascii_isdigit (string[1])
+ || !g_ascii_isdigit (string[2]) || !g_ascii_isdigit (string[3]))
+ return NULL;
+
+ ret = sscanf (string, "%04d-%02d-%02d", &year, &month, &day);
+
+ if (ret == 0)
+ return NULL;
+
+ if (ret == 3 && day <= 0) {
+ ret = 2;
+ day = -1;
+ }
+
+ if (ret >= 2 && month <= 0) {
+ ret = 1;
+ month = day = -1;
+ }
+
+ if (ret >= 1 && year <= 0)
+ return NULL;
+
+ else if (ret >= 1 && len < 16)
+ /* YMD is 10 chars. XMD + HM will be 16 chars. if it is less,
+ * it make no sense to continue. We will stay with YMD. */
+ goto ymd;
+
+ string += 10;
+ /* Exit if there is no expeceted value on this stage */
+ if (!(*string == 'T' || *string == '-' || *string == ' '))
+ goto ymd;
+
+ /* if hour or minute fails, then we will use onlly ymd. */
+ hour = g_ascii_strtoull (string + 1, (gchar **) & string, 10);
+ if (hour > 24 || *string != ':')
+ goto ymd;
+
+ /* minute */
+ minute = g_ascii_strtoull (string + 1, (gchar **) & string, 10);
+ if (minute > 59)
+ goto ymd;
+
+ /* second */
+ if (*string == ':') {
+ second = g_ascii_strtoull (string + 1, (gchar **) & string, 10);
+ /* if we fail here, we still can reuse hour and minute. We
+ * will still attempt to parse any timezone information */
+ if (second > 59) {
+ second = -1.0;
+ } else {
+ /* microseconds */
+ if (*string == '.' || *string == ',') {
+ const gchar *usec_start = string + 1;
+ guint digits;
+
+ usecs = g_ascii_strtoull (string + 1, (gchar **) & string, 10);
+ if (usecs != G_MAXUINT64 && string > usec_start) {
+ digits = (guint) (string - usec_start);
+ second += (gdouble) usecs / pow (10.0, digits);
+ }
+ }
+ }
+ }
+
+ if (*string == 'Z')
+ goto ymd_hms;
+ else {
+ /* reuse some code from gst-plugins-base/gst-libs/gst/tag/gstxmptag.c */
+ gint gmt_offset_hour = -1, gmt_offset_min = -1, gmt_offset = -1;
+ gchar *plus_pos = NULL;
+ gchar *neg_pos = NULL;
+ gchar *pos = NULL;
+
+ GST_LOG ("Checking for timezone information");
+
+ /* check if there is timezone info */
+ plus_pos = strrchr (string, '+');
+ neg_pos = strrchr (string, '-');
+ if (plus_pos)
+ pos = plus_pos + 1;
+ else if (neg_pos)
+ pos = neg_pos + 1;
+
+ if (pos) {
+ gint ret_tz;
+ if (pos[2] == ':')
+ ret_tz = sscanf (pos, "%d:%d", &gmt_offset_hour, &gmt_offset_min);
+ else
+ ret_tz = sscanf (pos, "%02d%02d", &gmt_offset_hour, &gmt_offset_min);
+
+ GST_DEBUG ("Parsing timezone: %s", pos);
+
+ if (ret_tz == 2) {
+ gmt_offset = gmt_offset_hour * 60 + gmt_offset_min;
+ if (neg_pos != NULL && neg_pos + 1 == pos)
+ gmt_offset *= -1;
+
+ tzoffset = gmt_offset / 60.0;
+
+ GST_LOG ("Timezone offset: %f (%d minutes)", tzoffset, gmt_offset);
+ } else
+ GST_WARNING ("Failed to parse timezone information");
+ }
+ }
+
+ymd_hms:
+ return gst_date_time_new (tzoffset, year, month, day, hour, minute, second);
+ymd:
+ return gst_date_time_new_ymd (year, month, day);
+}
+
+