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