datetime: fix second parsing failure case when deserialising datetime
[platform/upstream/gstreamer.git] / gst / gstdatetime.c
1 /* GStreamer
2  * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@collabora.co.uk>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "glib-compat-private.h"
25 #include "gst_private.h"
26 #include "gstdatetime.h"
27 #include "gstvalue.h"
28 #include <glib.h>
29 #include <math.h>
30 #include <stdio.h>
31
32 /**
33  * SECTION:gstdatetime
34  * @title: GstDateTime
35  * @short_description: A date, time and timezone structure
36  *
37  * Struct to store date, time and timezone information altogether.
38  * #GstDateTime is refcounted and immutable.
39  *
40  * Date information is handled using the proleptic Gregorian calendar.
41  *
42  * Provides basic creation functions and accessor functions to its fields.
43  *
44  * Since: 0.10.31
45  */
46
47 typedef enum
48 {
49   GST_DATE_TIME_FIELDS_INVALID = 0,
50   GST_DATE_TIME_FIELDS_Y,       /* have year                */
51   GST_DATE_TIME_FIELDS_YM,      /* have year and month      */
52   GST_DATE_TIME_FIELDS_YMD,     /* have year, month and day */
53   GST_DATE_TIME_FIELDS_YMD_HM,
54   GST_DATE_TIME_FIELDS_YMD_HMS
55       /* Note: if we ever add more granularity here, e.g. for microsecs,
56        * the compare function will need updating */
57 } GstDateTimeFields;
58
59 struct _GstDateTime
60 {
61   GDateTime *datetime;
62
63   GstDateTimeFields fields;
64   volatile gint ref_count;
65 };
66
67 /**
68  * gst_date_time_new_from_g_date_time:
69  * @dt: (transfer full): the #GDateTime. The new #GstDateTime takes ownership.
70  *
71  * Creates a new #GstDateTime from a #GDateTime object.
72  *
73  * Free-function: gst_date_time_unref
74  *
75  * Returns: (transfer full): a newly created #GstDateTime, or NULL on error
76  */
77 GstDateTime *
78 gst_date_time_new_from_g_date_time (GDateTime * dt)
79 {
80   GstDateTime *gst_dt;
81
82   if (!dt)
83     return NULL;
84
85   gst_dt = g_slice_new (GstDateTime);
86   gst_dt->datetime = dt;
87   gst_dt->fields = GST_DATE_TIME_FIELDS_YMD_HMS;
88   gst_dt->ref_count = 1;
89   return gst_dt;
90 }
91
92 /**
93  * gst_date_time_to_g_date_time:
94  * @datetime: GstDateTime.
95  *
96  * Creates a new #GDateTime from a fully defined #GstDateTime object.
97  *
98  * Free-function: g_date_time_unref
99  *
100  * Returns: (transfer full): a newly created #GDateTime, or NULL on error
101  */
102 GDateTime *
103 gst_date_time_to_g_date_time (GstDateTime * datetime)
104 {
105   g_return_val_if_fail (datetime != NULL, NULL);
106
107   if (datetime->fields != GST_DATE_TIME_FIELDS_YMD_HMS)
108     return NULL;
109
110   return g_date_time_add (datetime->datetime, 0);
111 }
112
113 /**
114  * gst_date_time_has_year:
115  * @datetime: a #GstDateTime
116  *
117  * Returns: TRUE if @datetime<!-- -->'s year field is set (which should always
118  *     be the case), otherwise FALSE
119  */
120 gboolean
121 gst_date_time_has_year (const GstDateTime * datetime)
122 {
123   g_return_val_if_fail (datetime != NULL, FALSE);
124
125   return (datetime->fields >= GST_DATE_TIME_FIELDS_Y);
126 }
127
128 /**
129  * gst_date_time_has_month:
130  * @datetime: a #GstDateTime
131  *
132  * Returns: TRUE if @datetime<!-- -->'s month field is set, otherwise FALSE
133  */
134 gboolean
135 gst_date_time_has_month (const GstDateTime * datetime)
136 {
137   g_return_val_if_fail (datetime != NULL, FALSE);
138
139   return (datetime->fields >= GST_DATE_TIME_FIELDS_YM);
140 }
141
142 /**
143  * gst_date_time_has_day:
144  * @datetime: a #GstDateTime
145  *
146  * Returns: TRUE if @datetime<!-- -->'s day field is set, otherwise FALSE
147  */
148 gboolean
149 gst_date_time_has_day (const GstDateTime * datetime)
150 {
151   g_return_val_if_fail (datetime != NULL, FALSE);
152
153   return (datetime->fields >= GST_DATE_TIME_FIELDS_YMD);
154 }
155
156 /**
157  * gst_date_time_has_time:
158  * @datetime: a #GstDateTime
159  *
160  * Returns: TRUE if @datetime<!-- -->'s hour and minute fields are set,
161  *     otherwise FALSE
162  */
163 gboolean
164 gst_date_time_has_time (const GstDateTime * datetime)
165 {
166   g_return_val_if_fail (datetime != NULL, FALSE);
167
168   return (datetime->fields >= GST_DATE_TIME_FIELDS_YMD_HM);
169 }
170
171 /**
172  * gst_date_time_has_second:
173  * @datetime: a #GstDateTime
174  *
175  * Returns: TRUE if @datetime<!-- -->'s second field is set, otherwise FALSE
176  */
177 gboolean
178 gst_date_time_has_second (const GstDateTime * datetime)
179 {
180   g_return_val_if_fail (datetime != NULL, FALSE);
181
182   return (datetime->fields >= GST_DATE_TIME_FIELDS_YMD_HMS);
183 }
184
185 /**
186  * gst_date_time_get_year:
187  * @datetime: a #GstDateTime
188  *
189  * Returns the year of this #GstDateTime
190  * Call gst_date_time_has_year before, to avoid warnings.
191  *
192  * Return value: The year of this #GstDateTime
193  * Since: 0.10.31
194  */
195 gint
196 gst_date_time_get_year (const GstDateTime * datetime)
197 {
198   g_return_val_if_fail (datetime != NULL, 0);
199
200   return g_date_time_get_year (datetime->datetime);
201 }
202
203 /**
204  * gst_date_time_get_month:
205  * @datetime: a #GstDateTime
206  *
207  * Returns the month of this #GstDateTime. January is 1, February is 2, etc..
208  * Call gst_date_time_has_month before, to avoid warnings.
209  *
210  * Return value: The month of this #GstDateTime
211  * Since: 0.10.31
212  */
213 gint
214 gst_date_time_get_month (const GstDateTime * datetime)
215 {
216   g_return_val_if_fail (datetime != NULL, 0);
217   g_return_val_if_fail (gst_date_time_has_month (datetime), 0);
218
219   return g_date_time_get_month (datetime->datetime);
220 }
221
222 /**
223  * gst_date_time_get_day:
224  * @datetime: a #GstDateTime
225  *
226  * Returns the day of the month of this #GstDateTime.
227  * Call gst_date_time_has_day before, to avoid warnings.
228  *
229  * Return value: The day of this #GstDateTime
230  * Since: 0.10.31
231  */
232 gint
233 gst_date_time_get_day (const GstDateTime * datetime)
234 {
235   g_return_val_if_fail (datetime != NULL, 0);
236   g_return_val_if_fail (gst_date_time_has_day (datetime), 0);
237
238   return g_date_time_get_day_of_month (datetime->datetime);
239 }
240
241 /**
242  * gst_date_time_get_hour:
243  * @datetime: a #GstDateTime
244  *
245  * Retrieves the hour of the day represented by @datetime in the gregorian
246  * calendar. The return is in the range of 0 to 23.
247  * Call gst_date_time_has_haur before, to avoid warnings.
248  *
249  * Return value: the hour of the day
250  *
251  * Since: 0.10.31
252  */
253 gint
254 gst_date_time_get_hour (const GstDateTime * datetime)
255 {
256   g_return_val_if_fail (datetime != NULL, 0);
257   g_return_val_if_fail (gst_date_time_has_time (datetime), 0);
258
259   return g_date_time_get_hour (datetime->datetime);
260 }
261
262 /**
263  * gst_date_time_get_minute:
264  * @datetime: a #GstDateTime
265  *
266  * Retrieves the minute of the hour represented by @datetime in the gregorian
267  * calendar.
268  * Call gst_date_time_has_minute before, to avoid warnings.
269  *
270  * Return value: the minute of the hour
271  *
272  * Since: 0.10.31
273  */
274 gint
275 gst_date_time_get_minute (const GstDateTime * datetime)
276 {
277   g_return_val_if_fail (datetime != NULL, 0);
278   g_return_val_if_fail (gst_date_time_has_time (datetime), 0);
279
280   return g_date_time_get_minute (datetime->datetime);
281 }
282
283 /**
284  * gst_date_time_get_second:
285  * @datetime: a #GstDateTime
286  *
287  * Retrieves the second of the minute represented by @datetime in the gregorian
288  * calendar.
289  * Call gst_date_time_has_second before, to avoid warnings.
290  *
291  * Return value: the second represented by @datetime
292  *
293  * Since: 0.10.31
294  */
295 gint
296 gst_date_time_get_second (const GstDateTime * datetime)
297 {
298   g_return_val_if_fail (datetime != NULL, 0);
299   g_return_val_if_fail (gst_date_time_has_second (datetime), 0);
300
301   return g_date_time_get_second (datetime->datetime);
302 }
303
304 /**
305  * gst_date_time_get_microsecond:
306  * @datetime: a #GstDateTime
307  *
308  * Retrieves the fractional part of the seconds in microseconds represented by
309  * @datetime in the gregorian calendar.
310  *
311  * Return value: the microsecond of the second
312  *
313  * Since: 0.10.31
314  */
315 gint
316 gst_date_time_get_microsecond (const GstDateTime * datetime)
317 {
318   g_return_val_if_fail (datetime != NULL, 0);
319   g_return_val_if_fail (gst_date_time_has_second (datetime), 0);
320
321   return g_date_time_get_microsecond (datetime->datetime);
322 }
323
324 /**
325  * gst_date_time_get_time_zone_offset:
326  * @datetime: a #GstDateTime
327  *
328  * Retrieves the offset from UTC in hours that the timezone specified
329  * by @datetime represents. Timezones ahead (to the east) of UTC have positive
330  * values, timezones before (to the west) of UTC have negative values.
331  * If @datetime represents UTC time, then the offset is zero.
332  *
333  * Return value: the offset from UTC in hours
334  * Since: 0.10.31
335  */
336 gfloat
337 gst_date_time_get_time_zone_offset (const GstDateTime * datetime)
338 {
339   g_return_val_if_fail (datetime != NULL, 0.0);
340   g_return_val_if_fail (gst_date_time_has_time (datetime), 0.0);
341
342   return (g_date_time_get_utc_offset (datetime->datetime) /
343       G_USEC_PER_SEC) / 3600.0;
344 }
345
346 /**
347  * gst_date_time_new_y:
348  * @year: the gregorian year
349  *
350  * Creates a new #GstDateTime using the date and times in the gregorian calendar
351  * in the local timezone.
352  *
353  * @year should be from 1 to 9999.
354  *
355  * Free-function: gst_date_time_unref
356  *
357  * Return value: (transfer full): the newly created #GstDateTime
358  *
359  * Since:
360  */
361 GstDateTime *
362 gst_date_time_new_y (gint year)
363 {
364   return gst_date_time_new (0.0, year, -1, -1, -1, -1, -1);
365 }
366
367 /**
368  * gst_date_time_new_ym:
369  * @year: the gregorian year
370  * @month: the gregorian month
371  *
372  * Creates a new #GstDateTime using the date and times in the gregorian calendar
373  * in the local timezone.
374  *
375  * @year should be from 1 to 9999, @month should be from 1 to 12.
376  *
377  * If value is -1 then all over value will be ignored. For example
378  * if @month == -1, then #GstDateTime will created only for @year.
379  *
380  * Free-function: gst_date_time_unref
381  *
382  * Return value: (transfer full): the newly created #GstDateTime
383  *
384  * Since:
385  */
386 GstDateTime *
387 gst_date_time_new_ym (gint year, gint month)
388 {
389   return gst_date_time_new (0.0, year, month, -1, -1, -1, -1);
390 }
391
392 /**
393  * gst_date_time_new_ymd:
394  * @year: the gregorian year
395  * @month: the gregorian month
396  * @day: the day of the gregorian month
397  *
398  * Creates a new #GstDateTime using the date and times in the gregorian calendar
399  * in the local timezone.
400  *
401  * @year should be from 1 to 9999, @month should be from 1 to 12, @day from
402  * 1 to 31.
403  *
404  * If value is -1 then all over value will be ignored. For example
405  * if @month == -1, then #GstDateTime will created only for @year. If
406  * @day == -1, then #GstDateTime will created for @year and @month and
407  * so on.
408  *
409  * Free-function: gst_date_time_unref
410  *
411  * Return value: (transfer full): the newly created #GstDateTime
412  *
413  * Since:
414  */
415 GstDateTime *
416 gst_date_time_new_ymd (gint year, gint month, gint day)
417 {
418   return gst_date_time_new (0.0, year, month, day, -1, -1, -1);
419 }
420
421 /**
422  * gst_date_time_new_from_unix_epoch_local_time:
423  * @secs: seconds from the Unix epoch
424  *
425  * Creates a new #GstDateTime using the time since Jan 1, 1970 specified by
426  * @secs. The #GstDateTime is in the local timezone.
427  *
428  * Free-function: gst_date_time_unref
429  *
430  * Return value: (transfer full): the newly created #GstDateTime
431  *
432  * Since: 0.10.31
433  */
434 GstDateTime *
435 gst_date_time_new_from_unix_epoch_local_time (gint64 secs)
436 {
437   GDateTime *datetime;
438
439   datetime = g_date_time_new_from_unix_local (secs);
440   return gst_date_time_new_from_g_date_time (datetime);
441 }
442
443 /**
444  * gst_date_time_new_from_unix_epoch_utc:
445  * @secs: seconds from the Unix epoch
446  *
447  * Creates a new #GstDateTime using the time since Jan 1, 1970 specified by
448  * @secs. The #GstDateTime is in the UTC timezone.
449  *
450  * Free-function: gst_date_time_unref
451  *
452  * Return value: (transfer full): the newly created #GstDateTime
453  *
454  * Since: 0.10.31
455  */
456 GstDateTime *
457 gst_date_time_new_from_unix_epoch_utc (gint64 secs)
458 {
459   GstDateTime *datetime;
460   datetime =
461       gst_date_time_new_from_g_date_time (g_date_time_new_from_unix_utc (secs));
462   return datetime;
463 }
464
465 static GstDateTimeFields
466 gst_date_time_check_fields (gint * year, gint * month, gint * day,
467     gint * hour, gint * minute, gdouble * seconds)
468 {
469   if (*month == -1) {
470     *month = *day = 1;
471     *hour = *minute = *seconds = 0;
472     return GST_DATE_TIME_FIELDS_Y;
473   } else if (*day == -1) {
474     *day = 1;
475     *hour = *minute = *seconds = 0;
476     return GST_DATE_TIME_FIELDS_YM;
477   } else if (*hour == -1) {
478     *hour = *minute = *seconds = 0;
479     return GST_DATE_TIME_FIELDS_YMD;
480   } else if (*seconds == -1) {
481     *seconds = 0;
482     return GST_DATE_TIME_FIELDS_YMD_HM;
483   } else
484     return GST_DATE_TIME_FIELDS_YMD_HMS;
485 }
486
487 /**
488  * gst_date_time_new_local_time:
489  * @year: the gregorian year
490  * @month: the gregorian month, or -1
491  * @day: the day of the gregorian month, or -1
492  * @hour: the hour of the day, or -1
493  * @minute: the minute of the hour, or -1
494  * @seconds: the second of the minute, or -1
495  *
496  * Creates a new #GstDateTime using the date and times in the gregorian calendar
497  * in the local timezone.
498  *
499  * @year should be from 1 to 9999, @month should be from 1 to 12, @day from
500  * 1 to 31, @hour from 0 to 23, @minutes and @seconds from 0 to 59.
501  *
502  * If @month is -1, then the #GstDateTime created will only contain @year,
503  * and all other fields will be considered not set.
504  *
505  * If @day is -1, then the #GstDateTime created will only contain @year and
506  * @month and all other fields will be considered not set.
507  *
508  * If @hour is -1, then the #GstDateTime created will only contain @year and
509  * @month and @day, and the time fields will be considered not set. In this
510  * case @minute and @seconds should also be -1.
511  *
512  * Free-function: gst_date_time_unref
513  *
514  * Return value: (transfer full): the newly created #GstDateTime
515  *
516  * Since: 0.10.31
517  */
518 GstDateTime *
519 gst_date_time_new_local_time (gint year, gint month, gint day, gint hour,
520     gint minute, gdouble seconds)
521 {
522   GstDateTimeFields fields;
523   GstDateTime *datetime;
524
525   g_return_val_if_fail (year > 0 && year <= 9999, NULL);
526   g_return_val_if_fail ((month > 0 && month <= 12) || month == -1, NULL);
527   g_return_val_if_fail ((day > 0 && day <= 31) || day == -1, NULL);
528   g_return_val_if_fail ((hour >= 0 && hour < 24) || hour == -1, NULL);
529   g_return_val_if_fail ((minute >= 0 && minute < 60) || minute == -1, NULL);
530   g_return_val_if_fail ((seconds >= 0 && seconds < 60) || seconds == -1, NULL);
531
532   fields = gst_date_time_check_fields (&year, &month, &day,
533       &hour, &minute, &seconds);
534
535   datetime = gst_date_time_new_from_g_date_time (g_date_time_new_local (year,
536           month, day, hour, minute, seconds));
537
538   datetime->fields = fields;
539   return datetime;
540 }
541
542 /**
543  * gst_date_time_new_now_local_time:
544  *
545  * Creates a new #GstDateTime representing the current date and time.
546  *
547  * Free-function: gst_date_time_unref
548  *
549  * Return value: (transfer full): the newly created #GstDateTime which should
550  *     be freed with gst_date_time_unref().
551  *
552  * Since: 0.10.31
553  */
554 GstDateTime *
555 gst_date_time_new_now_local_time (void)
556 {
557   return gst_date_time_new_from_g_date_time (g_date_time_new_now_local ());
558 }
559
560 /**
561  * gst_date_time_new_now_utc:
562  *
563  * Creates a new #GstDateTime that represents the current instant at Universal
564  * coordinated time.
565  *
566  * Free-function: gst_date_time_unref
567  *
568  * Return value: (transfer full): the newly created #GstDateTime which should
569  *   be freed with gst_date_time_unref().
570  *
571  * Since: 0.10.31
572  */
573 GstDateTime *
574 gst_date_time_new_now_utc (void)
575 {
576   return gst_date_time_new_from_g_date_time (g_date_time_new_now_utc ());
577 }
578
579 gint
580 __gst_date_time_compare (const GstDateTime * dt1, const GstDateTime * dt2)
581 {
582   gint64 diff;
583
584   /* we assume here that GST_DATE_TIME_FIELDS_YMD_HMS is the highest
585    * resolution, and ignore microsecond differences on purpose for now */
586   if (dt1->fields != dt2->fields)
587     return GST_VALUE_UNORDERED;
588
589   /* This will round down to nearest second, which is what we want. We're
590    * not comparing microseconds on purpose here, since we're not
591    * serialising them when doing new_utc_now() + to_string() */
592   diff =
593       g_date_time_to_unix (dt1->datetime) - g_date_time_to_unix (dt2->datetime);
594   if (diff < 0)
595     return GST_VALUE_LESS_THAN;
596   else if (diff > 0)
597     return GST_VALUE_GREATER_THAN;
598   else
599     return GST_VALUE_EQUAL;
600 }
601
602 /**
603  * gst_date_time_new:
604  * @tzoffset: Offset from UTC in hours.
605  * @year: the gregorian year
606  * @month: the gregorian month
607  * @day: the day of the gregorian month
608  * @hour: the hour of the day
609  * @minute: the minute of the hour
610  * @seconds: the second of the minute
611  *
612  * Creates a new #GstDateTime using the date and times in the gregorian calendar
613  * in the supplied timezone.
614  *
615  * @year should be from 1 to 9999, @month should be from 1 to 12, @day from
616  * 1 to 31, @hour from 0 to 23, @minutes and @seconds from 0 to 59.
617  *
618  * Note that @tzoffset is a float and was chosen so for being able to handle
619  * some fractional timezones, while it still keeps the readability of
620  * represeting it in hours for most timezones.
621  *
622  * If value is -1 then all over value will be ignored. For example
623  * if @month == -1, then #GstDateTime will created only for @year. If
624  * @day == -1, then #GstDateTime will created for @year and @month and
625  * so on.
626  *
627  * Free-function: gst_date_time_unref
628  *
629  * Return value: (transfer full): the newly created #GstDateTime
630  *
631  * Since: 0.10.31
632  */
633 GstDateTime *
634 gst_date_time_new (gfloat tzoffset, gint year, gint month, gint day, gint hour,
635     gint minute, gdouble seconds)
636 {
637   GstDateTimeFields fields;
638   gchar buf[6];
639   GTimeZone *tz;
640   GDateTime *dt;
641   GstDateTime *datetime;
642   gint tzhour, tzminute;
643
644   g_return_val_if_fail (year > 0 && year <= 9999, NULL);
645   g_return_val_if_fail ((month > 0 && month <= 12) || month == -1, NULL);
646   g_return_val_if_fail ((day > 0 && day <= 31) || day == -1, NULL);
647   g_return_val_if_fail ((hour >= 0 && hour < 24) || hour == -1, NULL);
648   g_return_val_if_fail ((minute >= 0 && minute < 60) || minute == -1, NULL);
649   g_return_val_if_fail ((seconds >= 0 && seconds < 60) || seconds == -1, NULL);
650   g_return_val_if_fail (tzoffset >= -12.0 && tzoffset <= 12.0, NULL);
651   g_return_val_if_fail ((hour >= 0 && minute >= 0) ||
652       (hour == -1 && minute == -1 && seconds == -1 && tzoffset == 0.0), NULL);
653
654   tzhour = (gint) ABS (tzoffset);
655   tzminute = (gint) ((ABS (tzoffset) - tzhour) * 60);
656
657   g_snprintf (buf, 6, "%c%02d%02d", tzoffset >= 0 ? '+' : '-', tzhour,
658       tzminute);
659
660   tz = g_time_zone_new (buf);
661
662   fields = gst_date_time_check_fields (&year, &month, &day,
663       &hour, &minute, &seconds);
664
665   dt = g_date_time_new (tz, year, month, day, hour, minute, seconds);
666   g_time_zone_unref (tz);
667
668   datetime = gst_date_time_new_from_g_date_time (dt);
669   datetime->fields = fields;
670
671   return datetime;
672 }
673
674
675 /**
676  * gst_date_time_to_iso8601_string:
677  * @datetime: GstDateTime.
678  *
679  * Create a minimal string compatible with ISO-8601. Possible output formats
680  * are (for example): 2012, 2012-06, 2012-06-23, 2012-06-23T23:30Z,
681  * 2012-06-23T23:30+0100, 2012-06-23T23:30:59Z, 2012-06-23T23:30:59+0100
682  *
683  * Returns: a newly allocated string formatted according to ISO 8601 and
684  *     only including the datetime fields that are valid, or NULL in case
685  *     there was an error. The string should be freed with g_free().
686  */
687 gchar *
688 gst_date_time_to_iso8601_string (GstDateTime * datetime)
689 {
690   gfloat gmt_offset;
691
692   switch (datetime->fields) {
693     case GST_DATE_TIME_FIELDS_Y:
694       return g_date_time_format (datetime->datetime, "%Y");
695     case GST_DATE_TIME_FIELDS_YM:
696       return g_date_time_format (datetime->datetime, "%Y-%m");
697     case GST_DATE_TIME_FIELDS_YMD:
698       return g_date_time_format (datetime->datetime, "%F");
699     case GST_DATE_TIME_FIELDS_YMD_HM:
700       gmt_offset = gst_date_time_get_time_zone_offset (datetime);
701       if (gmt_offset == 0)
702         return g_date_time_format (datetime->datetime, "%FT%RZ");
703       else
704         return g_date_time_format (datetime->datetime, "%FT%R%z");
705     case GST_DATE_TIME_FIELDS_YMD_HMS:
706       gmt_offset = gst_date_time_get_time_zone_offset (datetime);
707       if (gmt_offset == 0)
708         return g_date_time_format (datetime->datetime, "%FT%TZ");
709       else
710         return g_date_time_format (datetime->datetime, "%FT%T%z");
711     default:
712       return NULL;
713   }
714 }
715
716 /**
717  * gst_date_time_new_from_iso8601_string:
718  * @string: ISO 8601-formatted datetime string.
719  *
720  * Tries to parse common variants of ISO-8601 datetime strings into a
721  * #GstDateTime.
722  *
723  * Free-function: gst_date_time_unref
724  *
725  * Returns: (transfer full): a newly created #GstDateTime, or NULL on error
726  */
727 GstDateTime *
728 gst_date_time_new_from_iso8601_string (const gchar * string)
729 {
730   gint year = -1, month = -1, day = -1, hour = -1, minute = -1, second = -1;
731   gfloat tzoffset = 0.0;
732   gint len, ret;
733
734   len = strlen (string);
735
736   g_return_val_if_fail (len >= 4, NULL);
737   g_return_val_if_fail (g_ascii_isdigit (*string), NULL);
738
739   GST_DEBUG ("Parsing %s into a datetime", string);
740
741   ret = sscanf (string, "%04d-%02d-%02d", &year, &month, &day);
742
743   if (ret == 0)
744     return NULL;
745
746   if (ret == 3 && day <= 0) {
747     ret = 2;
748     day = -1;
749   }
750
751   if (ret >= 2 && month <= 0) {
752     ret = 1;
753     month = day = -1;
754   }
755
756   if (ret >= 1 && year <= 0)
757     return NULL;
758
759   else if (ret >= 1 && len < 16)
760     /* YMD is 10 chars. XMD + HM will be 16 chars. if it is less,
761      * it make no sense to continue. We will stay with YMD. */
762     goto ymd;
763
764   string += 10;
765   /* Exit if there is no expeceted value on this stage */
766   if (!(*string == 'T' || *string == '-' || *string == ' '))
767     goto ymd;
768
769   /* if hour or minute fails, then we will use onlly ymd. */
770   hour = g_ascii_strtoull (string + 1, (gchar **) & string, 10);
771   if (hour > 24 || *string != ':')
772     goto ymd;
773
774   /* minute */
775   minute = g_ascii_strtoull (string + 1, (gchar **) & string, 10);
776   if (minute > 59)
777     goto ymd;
778
779   /* second */
780   if (*string == ':') {
781     second = g_ascii_strtoull (string + 1, (gchar **) & string, 10);
782     /* if we fail here, we still can reuse hour and minute. We
783      * will still attempt to parse any timezone information */
784     if (second > 59) {
785       second = -1.0;
786     }
787   }
788
789   if (*string == 'Z')
790     goto ymd_hms;
791   else {
792     /* reuse some code from gst-plugins-base/gst-libs/gst/tag/gstxmptag.c */
793     gint gmt_offset_hour = -1, gmt_offset_min = -1, gmt_offset = -1;
794     gchar *plus_pos = NULL;
795     gchar *neg_pos = NULL;
796     gchar *pos = NULL;
797
798     GST_LOG ("Checking for timezone information");
799
800     /* check if there is timezone info */
801     plus_pos = strrchr (string, '+');
802     neg_pos = strrchr (string, '-');
803     if (plus_pos)
804       pos = plus_pos + 1;
805     else if (neg_pos)
806       pos = neg_pos + 1;
807
808     if (pos) {
809       gint ret_tz;
810       if (pos[2] == ':')
811         ret_tz = sscanf (pos, "%d:%d", &gmt_offset_hour, &gmt_offset_min);
812       else
813         ret_tz = sscanf (pos, "%02d%02d", &gmt_offset_hour, &gmt_offset_min);
814
815       GST_DEBUG ("Parsing timezone: %s", pos);
816
817       if (ret_tz == 2) {
818         gmt_offset = gmt_offset_hour * 60 + gmt_offset_min;
819         if (neg_pos != NULL && neg_pos + 1 == pos)
820           gmt_offset *= -1;
821
822         tzoffset = gmt_offset / 60.0;
823
824         GST_LOG ("Timezone offset: %f (%d minutes)", tzoffset, gmt_offset);
825       } else
826         GST_WARNING ("Failed to parse timezone information");
827     }
828   }
829
830 ymd_hms:
831   return gst_date_time_new (tzoffset, year, month, day, hour, minute, second);
832 ymd:
833   return gst_date_time_new_ymd (year, month, day);
834 }
835
836
837 static void
838 gst_date_time_free (GstDateTime * datetime)
839 {
840   g_date_time_unref (datetime->datetime);
841   g_slice_free (GstDateTime, datetime);
842 }
843
844 /**
845  * gst_date_time_ref:
846  * @datetime: a #GstDateTime
847  *
848  * Atomically increments the reference count of @datetime by one.
849  *
850  * Return value: (transfer full): the reference @datetime
851  *
852  * Since: 0.10.31
853  */
854 GstDateTime *
855 gst_date_time_ref (GstDateTime * datetime)
856 {
857   g_return_val_if_fail (datetime != NULL, NULL);
858   g_return_val_if_fail (datetime->ref_count > 0, NULL);
859   g_atomic_int_inc (&datetime->ref_count);
860   return datetime;
861 }
862
863 /**
864  * gst_date_time_unref:
865  * @datetime: (transfer full): a #GstDateTime
866  *
867  * Atomically decrements the reference count of @datetime by one.  When the
868  * reference count reaches zero, the structure is freed.
869  *
870  * Since: 0.10.31
871  */
872 void
873 gst_date_time_unref (GstDateTime * datetime)
874 {
875   g_return_if_fail (datetime != NULL);
876   g_return_if_fail (datetime->ref_count > 0);
877
878   if (g_atomic_int_dec_and_test (&datetime->ref_count))
879     gst_date_time_free (datetime);
880 }