• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23 
24 #include "gst_private.h"
25 #include "glib-compat-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  * [proleptic Gregorian calendar]: https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar
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   GstMiniObject mini_object;
62 
63   GDateTime *datetime;
64 
65   GstDateTimeFields fields;
66 };
67 
68 GType _gst_date_time_type = 0;
69 GST_DEFINE_MINI_OBJECT_TYPE (GstDateTime, gst_date_time);
70 
71 static void gst_date_time_free (GstDateTime * datetime);
72 
73 /**
74  * gst_date_time_new_from_g_date_time:
75  * @dt: (transfer full) (nullable): the #GDateTime.
76  *
77  * Creates a new #GstDateTime from a #GDateTime object.
78  *
79  * Returns: (transfer full) (nullable): a newly created #GstDateTime,
80  * or %NULL if @dt is %NULL.
81  */
82 GstDateTime *
gst_date_time_new_from_g_date_time(GDateTime * dt)83 gst_date_time_new_from_g_date_time (GDateTime * dt)
84 {
85   GstDateTime *gst_dt;
86 
87   if (!dt)
88     return NULL;
89 
90   gst_dt = g_slice_new (GstDateTime);
91 
92   gst_mini_object_init (GST_MINI_OBJECT_CAST (gst_dt), 0, GST_TYPE_DATE_TIME,
93       NULL, NULL, (GstMiniObjectFreeFunction) gst_date_time_free);
94 
95   gst_dt->datetime = dt;
96   gst_dt->fields = GST_DATE_TIME_FIELDS_YMD_HMS;
97   return gst_dt;
98 }
99 
100 /**
101  * gst_date_time_to_g_date_time:
102  * @datetime: GstDateTime.
103  *
104  * Creates a new #GDateTime from a fully defined #GstDateTime object.
105  *
106  * Returns: (transfer full) (nullable): a newly created #GDateTime, or
107  * %NULL on error or if @datetime does not have a year, month, day, hour,
108  * minute and second.
109  */
110 GDateTime *
gst_date_time_to_g_date_time(GstDateTime * datetime)111 gst_date_time_to_g_date_time (GstDateTime * datetime)
112 {
113   g_return_val_if_fail (datetime != NULL, NULL);
114 
115   if (datetime->fields != GST_DATE_TIME_FIELDS_YMD_HMS)
116     return NULL;
117 
118   return g_date_time_add (datetime->datetime, 0);
119 }
120 
121 /**
122  * gst_date_time_has_year:
123  * @datetime: a #GstDateTime
124  *
125  * Returns: %TRUE if @datetime<!-- -->'s year field is set (which should always
126  *     be the case), otherwise %FALSE
127  */
128 gboolean
gst_date_time_has_year(const GstDateTime * datetime)129 gst_date_time_has_year (const GstDateTime * datetime)
130 {
131   g_return_val_if_fail (datetime != NULL, FALSE);
132 
133   return (datetime->fields >= GST_DATE_TIME_FIELDS_Y);
134 }
135 
136 /**
137  * gst_date_time_has_month:
138  * @datetime: a #GstDateTime
139  *
140  * Returns: %TRUE if @datetime<!-- -->'s month field is set, otherwise %FALSE
141  */
142 gboolean
gst_date_time_has_month(const GstDateTime * datetime)143 gst_date_time_has_month (const GstDateTime * datetime)
144 {
145   g_return_val_if_fail (datetime != NULL, FALSE);
146 
147   return (datetime->fields >= GST_DATE_TIME_FIELDS_YM);
148 }
149 
150 /**
151  * gst_date_time_has_day:
152  * @datetime: a #GstDateTime
153  *
154  * Returns: %TRUE if @datetime<!-- -->'s day field is set, otherwise %FALSE
155  */
156 gboolean
gst_date_time_has_day(const GstDateTime * datetime)157 gst_date_time_has_day (const GstDateTime * datetime)
158 {
159   g_return_val_if_fail (datetime != NULL, FALSE);
160 
161   return (datetime->fields >= GST_DATE_TIME_FIELDS_YMD);
162 }
163 
164 /**
165  * gst_date_time_has_time:
166  * @datetime: a #GstDateTime
167  *
168  * Returns: %TRUE if @datetime<!-- -->'s hour and minute fields are set,
169  *     otherwise %FALSE
170  */
171 gboolean
gst_date_time_has_time(const GstDateTime * datetime)172 gst_date_time_has_time (const GstDateTime * datetime)
173 {
174   g_return_val_if_fail (datetime != NULL, FALSE);
175 
176   return (datetime->fields >= GST_DATE_TIME_FIELDS_YMD_HM);
177 }
178 
179 /**
180  * gst_date_time_has_second:
181  * @datetime: a #GstDateTime
182  *
183  * Returns: %TRUE if @datetime<!-- -->'s second field is set, otherwise %FALSE
184  */
185 gboolean
gst_date_time_has_second(const GstDateTime * datetime)186 gst_date_time_has_second (const GstDateTime * datetime)
187 {
188   g_return_val_if_fail (datetime != NULL, FALSE);
189 
190   return (datetime->fields >= GST_DATE_TIME_FIELDS_YMD_HMS);
191 }
192 
193 /**
194  * gst_date_time_get_year:
195  * @datetime: a #GstDateTime
196  *
197  * Returns the year of this #GstDateTime.
198  * Call gst_date_time_has_year() before, to avoid warnings.
199  *
200  * Return value: The year of this #GstDateTime
201  */
202 gint
gst_date_time_get_year(const GstDateTime * datetime)203 gst_date_time_get_year (const GstDateTime * datetime)
204 {
205   g_return_val_if_fail (datetime != NULL, 0);
206 
207   return g_date_time_get_year (datetime->datetime);
208 }
209 
210 /**
211  * gst_date_time_get_month:
212  * @datetime: a #GstDateTime
213  *
214  * Returns the month of this #GstDateTime. January is 1, February is 2, etc..
215  *
216  * Return value: The month of this #GstDateTime, or -1 if none is set.
217  */
218 gint
gst_date_time_get_month(const GstDateTime * datetime)219 gst_date_time_get_month (const GstDateTime * datetime)
220 {
221   g_return_val_if_fail (datetime != NULL, 0);
222 
223   if (!gst_date_time_has_month (datetime))
224     return -1;
225 
226   return g_date_time_get_month (datetime->datetime);
227 }
228 
229 /**
230  * gst_date_time_get_day:
231  * @datetime: a #GstDateTime
232  *
233  * Returns the day of the month of this #GstDateTime.
234  *
235  * Return value: The day of this #GstDateTime, or -1 if none is set.
236  */
237 gint
gst_date_time_get_day(const GstDateTime * datetime)238 gst_date_time_get_day (const GstDateTime * datetime)
239 {
240   g_return_val_if_fail (datetime != NULL, 0);
241 
242   if (!gst_date_time_has_day (datetime))
243     return -1;
244 
245   return g_date_time_get_day_of_month (datetime->datetime);
246 }
247 
248 /**
249  * gst_date_time_get_hour:
250  * @datetime: a #GstDateTime
251  *
252  * Retrieves the hour of the day represented by @datetime in the gregorian
253  * calendar. The return is in the range of 0 to 23.
254  *
255  * Return value: the hour of the day, or -1 if none is set.
256  */
257 gint
gst_date_time_get_hour(const GstDateTime * datetime)258 gst_date_time_get_hour (const GstDateTime * datetime)
259 {
260   g_return_val_if_fail (datetime != NULL, 0);
261 
262   if (!gst_date_time_has_time (datetime))
263     return -1;
264 
265   return g_date_time_get_hour (datetime->datetime);
266 }
267 
268 /**
269  * gst_date_time_get_minute:
270  * @datetime: a #GstDateTime
271  *
272  * Retrieves the minute of the hour represented by @datetime in the gregorian
273  * calendar.
274  *
275  * Return value: the minute of the hour, or -1 if none is set.
276  */
277 gint
gst_date_time_get_minute(const GstDateTime * datetime)278 gst_date_time_get_minute (const GstDateTime * datetime)
279 {
280   g_return_val_if_fail (datetime != NULL, 0);
281 
282   if (!gst_date_time_has_time (datetime))
283     return -1;
284 
285   return g_date_time_get_minute (datetime->datetime);
286 }
287 
288 /**
289  * gst_date_time_get_second:
290  * @datetime: a #GstDateTime
291  *
292  * Retrieves the second of the minute represented by @datetime in the gregorian
293  * calendar.
294  *
295  * Return value: the second represented by @datetime, or -1 if none is set.
296  */
297 gint
gst_date_time_get_second(const GstDateTime * datetime)298 gst_date_time_get_second (const GstDateTime * datetime)
299 {
300   g_return_val_if_fail (datetime != NULL, 0);
301 
302   if (!gst_date_time_has_second (datetime))
303     return -1;
304 
305   return g_date_time_get_second (datetime->datetime);
306 }
307 
308 /**
309  * gst_date_time_get_microsecond:
310  * @datetime: a #GstDateTime
311  *
312  * Retrieves the fractional part of the seconds in microseconds represented by
313  * @datetime in the gregorian calendar.
314  *
315  * Return value: the microsecond of the second, or -1 if none is set.
316  */
317 gint
gst_date_time_get_microsecond(const GstDateTime * datetime)318 gst_date_time_get_microsecond (const GstDateTime * datetime)
319 {
320   g_return_val_if_fail (datetime != NULL, 0);
321 
322   if (!gst_date_time_has_second (datetime))
323     return -1;
324 
325   return g_date_time_get_microsecond (datetime->datetime);
326 }
327 
328 /**
329  * gst_date_time_get_time_zone_offset:
330  * @datetime: a #GstDateTime
331  *
332  * Retrieves the offset from UTC in hours that the timezone specified
333  * by @datetime represents. Timezones ahead (to the east) of UTC have positive
334  * values, timezones before (to the west) of UTC have negative values.
335  * If @datetime represents UTC time, then the offset is zero.
336  *
337  * Return value: the offset from UTC in hours, or %G_MAXDOUBLE if none is set.
338  */
339 gfloat
gst_date_time_get_time_zone_offset(const GstDateTime * datetime)340 gst_date_time_get_time_zone_offset (const GstDateTime * datetime)
341 {
342   g_return_val_if_fail (datetime != NULL, 0.0);
343 
344   if (!gst_date_time_has_time (datetime))
345     return G_MAXDOUBLE;
346 
347   return (g_date_time_get_utc_offset (datetime->datetime) /
348       G_USEC_PER_SEC) / 3600.0;
349 }
350 
351 /**
352  * gst_date_time_new_y:
353  * @year: the gregorian year
354  *
355  * Creates a new #GstDateTime using the date and times in the gregorian calendar
356  * in the local timezone.
357  *
358  * @year should be from 1 to 9999.
359  *
360  * Return value: (transfer full) (nullable): the newly created #GstDateTime,
361  * or %NULL on error.
362  */
363 GstDateTime *
gst_date_time_new_y(gint year)364 gst_date_time_new_y (gint year)
365 {
366   return gst_date_time_new (0.0, year, -1, -1, -1, -1, -1);
367 }
368 
369 /**
370  * gst_date_time_new_ym:
371  * @year: the gregorian year
372  * @month: the gregorian month
373  *
374  * Creates a new #GstDateTime using the date and times in the gregorian calendar
375  * in the local timezone.
376  *
377  * @year should be from 1 to 9999, @month should be from 1 to 12.
378  *
379  * If value is -1 then all over value will be ignored. For example
380  * if @month == -1, then #GstDateTime will created only for @year.
381  *
382  * Return value: (transfer full) (nullable): the newly created #GstDateTime,
383  * or %NULL on error.
384  */
385 GstDateTime *
gst_date_time_new_ym(gint year,gint month)386 gst_date_time_new_ym (gint year, gint month)
387 {
388   return gst_date_time_new (0.0, year, month, -1, -1, -1, -1);
389 }
390 
391 /**
392  * gst_date_time_new_ymd:
393  * @year: the gregorian year
394  * @month: the gregorian month
395  * @day: the day of the gregorian month
396  *
397  * Creates a new #GstDateTime using the date and times in the gregorian calendar
398  * in the local timezone.
399  *
400  * @year should be from 1 to 9999, @month should be from 1 to 12, @day from
401  * 1 to 31.
402  *
403  * If value is -1 then all over value will be ignored. For example
404  * if @month == -1, then #GstDateTime will created only for @year. If
405  * @day == -1, then #GstDateTime will created for @year and @month and
406  * so on.
407  *
408  * Return value: (transfer full) (nullable): the newly created #GstDateTime,
409  * or %NULL on error.
410  */
411 GstDateTime *
gst_date_time_new_ymd(gint year,gint month,gint day)412 gst_date_time_new_ymd (gint year, gint month, gint day)
413 {
414   return gst_date_time_new (0.0, year, month, day, -1, -1, -1);
415 }
416 
417 /**
418  * gst_date_time_new_from_unix_epoch_local_time:
419  * @secs: seconds from the Unix epoch
420  *
421  * Creates a new #GstDateTime using the time since Jan 1, 1970 specified by
422  * @secs. The #GstDateTime is in the local timezone.
423  *
424  * Return value: (transfer full) (nullable): the newly created #GstDateTime,
425  * or %NULL on error.
426  */
427 GstDateTime *
gst_date_time_new_from_unix_epoch_local_time(gint64 secs)428 gst_date_time_new_from_unix_epoch_local_time (gint64 secs)
429 {
430   GDateTime *datetime;
431 
432   datetime = g_date_time_new_from_unix_local (secs);
433   if (!datetime)
434     return NULL;
435 
436   return gst_date_time_new_from_g_date_time (datetime);
437 }
438 
439 /**
440  * gst_date_time_new_from_unix_epoch_utc:
441  * @secs: seconds from the Unix epoch
442  *
443  * Creates a new #GstDateTime using the time since Jan 1, 1970 specified by
444  * @secs. The #GstDateTime is in the UTC timezone.
445  *
446  * Return value: (transfer full) (nullable): the newly created #GstDateTime,
447  * or %NULL on error.
448  */
449 GstDateTime *
gst_date_time_new_from_unix_epoch_utc(gint64 secs)450 gst_date_time_new_from_unix_epoch_utc (gint64 secs)
451 {
452   GDateTime *datetime;
453 
454   datetime = g_date_time_new_from_unix_utc (secs);
455   if (!datetime)
456     return NULL;
457 
458   return gst_date_time_new_from_g_date_time (datetime);
459 }
460 
461 /**
462  * gst_date_time_new_from_unix_epoch_local_time_usecs:
463  * @usecs: microseconds from the Unix epoch
464  *
465  * Creates a new #GstDateTime using the time since Jan 1, 1970 specified by
466  * @usecs. The #GstDateTime is in the local timezone.
467  *
468  * Returns: (transfer full) (nullable): a newly created #GstDateTime, or %NULL
469  * on error.
470  *
471  * Since: 1.18
472  */
473 GstDateTime *
gst_date_time_new_from_unix_epoch_local_time_usecs(gint64 usecs)474 gst_date_time_new_from_unix_epoch_local_time_usecs (gint64 usecs)
475 {
476   GDateTime *dt, *datetime;
477   gint64 secs = usecs / G_USEC_PER_SEC;
478   gint64 usec_part = usecs % G_USEC_PER_SEC;
479 
480   dt = g_date_time_new_from_unix_local (secs);
481   if (!dt)
482     return NULL;
483   datetime = g_date_time_add_seconds (dt, (gdouble) usec_part / G_USEC_PER_SEC);
484   g_date_time_unref (dt);
485   if (!datetime)
486     return NULL;
487 
488   return gst_date_time_new_from_g_date_time (datetime);
489 }
490 
491 /**
492  * gst_date_time_new_from_unix_epoch_utc_usecs:
493  * @usecs: microseconds from the Unix epoch
494  *
495  * Creates a new #GstDateTime using the time since Jan 1, 1970 specified by
496  * @usecs. The #GstDateTime is in UTC.
497  *
498  * Returns: (transfer full) (nullable): a newly created #GstDateTime, or %NULL
499  * on error.
500  *
501  * Since: 1.18
502  */
503 GstDateTime *
gst_date_time_new_from_unix_epoch_utc_usecs(gint64 usecs)504 gst_date_time_new_from_unix_epoch_utc_usecs (gint64 usecs)
505 {
506   GDateTime *dt, *datetime;
507   gint64 secs = usecs / G_USEC_PER_SEC;
508   gint64 usec_part = usecs % G_USEC_PER_SEC;
509 
510   dt = g_date_time_new_from_unix_utc (secs);
511   if (!dt)
512     return NULL;
513   datetime = g_date_time_add_seconds (dt, (gdouble) usec_part / G_USEC_PER_SEC);
514   g_date_time_unref (dt);
515   if (!datetime)
516     return NULL;
517 
518   return gst_date_time_new_from_g_date_time (datetime);
519 }
520 
521 static GstDateTimeFields
gst_date_time_check_fields(gint * year,gint * month,gint * day,gint * hour,gint * minute,gdouble * seconds)522 gst_date_time_check_fields (gint * year, gint * month, gint * day,
523     gint * hour, gint * minute, gdouble * seconds)
524 {
525   if (*month == -1) {
526     *month = *day = 1;
527     *hour = *minute = *seconds = 0;
528     return GST_DATE_TIME_FIELDS_Y;
529   } else if (*day == -1) {
530     *day = 1;
531     *hour = *minute = *seconds = 0;
532     return GST_DATE_TIME_FIELDS_YM;
533   } else if (*hour == -1) {
534     *hour = *minute = *seconds = 0;
535     return GST_DATE_TIME_FIELDS_YMD;
536   } else if (*seconds == -1) {
537     *seconds = 0;
538     return GST_DATE_TIME_FIELDS_YMD_HM;
539   } else
540     return GST_DATE_TIME_FIELDS_YMD_HMS;
541 }
542 
543 /**
544  * gst_date_time_new_local_time:
545  * @year: the gregorian year
546  * @month: the gregorian month, or -1
547  * @day: the day of the gregorian month, or -1
548  * @hour: the hour of the day, or -1
549  * @minute: the minute of the hour, or -1
550  * @seconds: the second of the minute, or -1
551  *
552  * Creates a new #GstDateTime using the date and times in the gregorian calendar
553  * in the local timezone.
554  *
555  * @year should be from 1 to 9999, @month should be from 1 to 12, @day from
556  * 1 to 31, @hour from 0 to 23, @minutes and @seconds from 0 to 59.
557  *
558  * If @month is -1, then the #GstDateTime created will only contain @year,
559  * and all other fields will be considered not set.
560  *
561  * If @day is -1, then the #GstDateTime created will only contain @year and
562  * @month and all other fields will be considered not set.
563  *
564  * If @hour is -1, then the #GstDateTime created will only contain @year and
565  * @month and @day, and the time fields will be considered not set. In this
566  * case @minute and @seconds should also be -1.
567  *
568  * Return value: (transfer full) (nullable): the newly created #GstDateTime,
569  * or %NULL on error.
570  */
571 GstDateTime *
gst_date_time_new_local_time(gint year,gint month,gint day,gint hour,gint minute,gdouble seconds)572 gst_date_time_new_local_time (gint year, gint month, gint day, gint hour,
573     gint minute, gdouble seconds)
574 {
575   GstDateTimeFields fields;
576   GDateTime *dt;
577   GstDateTime *datetime;
578 
579   if (year <= 0 || year > 9999)
580     return NULL;
581 
582   if ((month <= 0 || month > 12) && month != -1)
583     return NULL;
584 
585   if ((day <= 0 || day > 31) && day != -1)
586     return NULL;
587 
588   if ((hour < 0 || hour >= 24) && hour != -1)
589     return NULL;
590 
591   if ((minute < 0 || minute >= 60) && minute != -1)
592     return NULL;
593 
594   if ((seconds < 0 || seconds >= 60) && seconds != -1)
595     return NULL;
596 
597   fields = gst_date_time_check_fields (&year, &month, &day,
598       &hour, &minute, &seconds);
599 
600   dt = g_date_time_new_local (year, month, day, hour, minute, seconds);
601   if (dt == NULL)
602     return NULL;
603 
604   datetime = gst_date_time_new_from_g_date_time (dt);
605   if (datetime == NULL)
606     return NULL;
607 
608   datetime->fields = fields;
609   return datetime;
610 }
611 
612 /**
613  * gst_date_time_new_now_local_time:
614  *
615  * Creates a new #GstDateTime representing the current date and time.
616  *
617  * Return value: (transfer full) (nullable): the newly created #GstDateTime which should
618  *     be freed with gst_date_time_unref(), or %NULL on error.
619  */
620 GstDateTime *
gst_date_time_new_now_local_time(void)621 gst_date_time_new_now_local_time (void)
622 {
623   GDateTime *dt;
624 
625   dt = g_date_time_new_now_local ();
626   if (!dt)
627     return NULL;
628 
629   return gst_date_time_new_from_g_date_time (dt);
630 }
631 
632 /**
633  * gst_date_time_new_now_utc:
634  *
635  * Creates a new #GstDateTime that represents the current instant at Universal
636  * coordinated time.
637  *
638  * Return value: (transfer full) (nullable): the newly created #GstDateTime which should
639  *   be freed with gst_date_time_unref(), or %NULL on error.
640  */
641 GstDateTime *
gst_date_time_new_now_utc(void)642 gst_date_time_new_now_utc (void)
643 {
644   GDateTime *dt;
645 
646   dt = g_date_time_new_now_utc ();
647   if (!dt)
648     return NULL;
649 
650   return gst_date_time_new_from_g_date_time (dt);
651 }
652 
653 gint
__gst_date_time_compare(const GstDateTime * dt1,const GstDateTime * dt2)654 __gst_date_time_compare (const GstDateTime * dt1, const GstDateTime * dt2)
655 {
656   gint64 diff;
657 
658   /* we assume here that GST_DATE_TIME_FIELDS_YMD_HMS is the highest
659    * resolution, and ignore microsecond differences on purpose for now */
660   if (dt1->fields != dt2->fields)
661     return GST_VALUE_UNORDERED;
662 
663   /* This will round down to nearest second, which is what we want. We're
664    * not comparing microseconds on purpose here, since we're not
665    * serialising them when doing new_utc_now() + to_string() */
666   diff =
667       g_date_time_to_unix (dt1->datetime) - g_date_time_to_unix (dt2->datetime);
668   if (diff < 0)
669     return GST_VALUE_LESS_THAN;
670   else if (diff > 0)
671     return GST_VALUE_GREATER_THAN;
672   else
673     return GST_VALUE_EQUAL;
674 }
675 
676 /**
677  * gst_date_time_new:
678  * @tzoffset: Offset from UTC in hours.
679  * @year: the gregorian year
680  * @month: the gregorian month
681  * @day: the day of the gregorian month
682  * @hour: the hour of the day
683  * @minute: the minute of the hour
684  * @seconds: the second of the minute
685  *
686  * Creates a new #GstDateTime using the date and times in the gregorian calendar
687  * in the supplied timezone.
688  *
689  * @year should be from 1 to 9999, @month should be from 1 to 12, @day from
690  * 1 to 31, @hour from 0 to 23, @minutes and @seconds from 0 to 59.
691  *
692  * Note that @tzoffset is a float and was chosen so for being able to handle
693  * some fractional timezones, while it still keeps the readability of
694  * representing it in hours for most timezones.
695  *
696  * If value is -1 then all over value will be ignored. For example
697  * if @month == -1, then #GstDateTime will be created only for @year. If
698  * @day == -1, then #GstDateTime will be created for @year and @month and
699  * so on.
700  *
701  * Return value: (transfer full) (nullable): the newly created #GstDateTime,
702  * or %NULL on error.
703  */
704 GstDateTime *
gst_date_time_new(gfloat tzoffset,gint year,gint month,gint day,gint hour,gint minute,gdouble seconds)705 gst_date_time_new (gfloat tzoffset, gint year, gint month, gint day, gint hour,
706     gint minute, gdouble seconds)
707 {
708   GstDateTimeFields fields;
709   gchar buf[6];
710   GTimeZone *tz;
711   GDateTime *dt;
712   GstDateTime *datetime;
713   gint tzhour, tzminute;
714 
715   if (year <= 0 || year > 9999)
716     return NULL;
717 
718   if ((month <= 0 || month > 12) && month != -1)
719     return NULL;
720 
721   if ((day <= 0 || day > 31) && day != -1)
722     return NULL;
723 
724   if ((hour < 0 || hour >= 24) && hour != -1)
725     return NULL;
726 
727   if ((minute < 0 || minute >= 60) && minute != -1)
728     return NULL;
729 
730   if ((seconds < 0 || seconds >= 60) && seconds != -1)
731     return NULL;
732 
733   if (tzoffset < -12.0 || tzoffset > 12.0)
734     return NULL;
735 
736   if ((hour < 0 || minute < 0) &&
737       (hour != -1 || minute != -1 || seconds != -1 || tzoffset != 0.0))
738     return NULL;
739 
740   tzhour = (gint) ABS (tzoffset);
741   tzminute = (gint) ((ABS (tzoffset) - tzhour) * 60);
742 
743   g_snprintf (buf, 6, "%c%02d%02d", tzoffset >= 0 ? '+' : '-', tzhour,
744       tzminute);
745 
746 #if GLIB_CHECK_VERSION (2, 67, 1)
747   /* g_time_zone_new() would always return UTC if the identifier can't be
748    * parsed, which is rather suboptimal. */
749   tz = g_time_zone_new_identifier (buf);
750   if (!tz)
751     return NULL;
752 #else
753   tz = g_time_zone_new (buf);
754 #endif
755 
756   fields = gst_date_time_check_fields (&year, &month, &day,
757       &hour, &minute, &seconds);
758 
759   dt = g_date_time_new (tz, year, month, day, hour, minute, seconds);
760   g_time_zone_unref (tz);
761 
762   if (!dt)
763     return NULL;                /* date failed validation */
764 
765   datetime = gst_date_time_new_from_g_date_time (dt);
766   datetime->fields = fields;
767 
768   return datetime;
769 }
770 
771 gchar *
__gst_date_time_serialize(GstDateTime * datetime,gboolean serialize_usecs)772 __gst_date_time_serialize (GstDateTime * datetime, gboolean serialize_usecs)
773 {
774   GString *s;
775   gfloat gmt_offset;
776   guint msecs;
777 
778   /* we always have at least the year */
779   s = g_string_new (NULL);
780   g_string_append_printf (s, "%04u", gst_date_time_get_year (datetime));
781 
782   if (datetime->fields == GST_DATE_TIME_FIELDS_Y)
783     goto done;
784 
785   /* add month */
786   g_string_append_printf (s, "-%02u", gst_date_time_get_month (datetime));
787 
788   if (datetime->fields == GST_DATE_TIME_FIELDS_YM)
789     goto done;
790 
791   /* add day of month */
792   g_string_append_printf (s, "-%02u", gst_date_time_get_day (datetime));
793 
794   if (datetime->fields == GST_DATE_TIME_FIELDS_YMD)
795     goto done;
796 
797   /* add time */
798   g_string_append_printf (s, "T%02u:%02u", gst_date_time_get_hour (datetime),
799       gst_date_time_get_minute (datetime));
800 
801   if (datetime->fields == GST_DATE_TIME_FIELDS_YMD_HM)
802     goto add_timezone;
803 
804   /* add seconds */
805   g_string_append_printf (s, ":%02u", gst_date_time_get_second (datetime));
806 
807   /* add microseconds */
808   if (serialize_usecs) {
809     msecs = gst_date_time_get_microsecond (datetime);
810     if (msecs != 0) {
811       g_string_append_printf (s, ".%06u", msecs);
812       /* trim trailing 0s */
813       while (s->str[s->len - 1] == '0')
814         g_string_truncate (s, s->len - 1);
815     }
816   }
817 
818   /* add timezone */
819 
820 add_timezone:
821 
822   gmt_offset = gst_date_time_get_time_zone_offset (datetime);
823   if (gmt_offset == 0) {
824     g_string_append_c (s, 'Z');
825   } else {
826     guint tzhour, tzminute;
827 
828     tzhour = (guint) ABS (gmt_offset);
829     tzminute = (guint) ((ABS (gmt_offset) - tzhour) * 60);
830 
831     g_string_append_c (s, (gmt_offset >= 0) ? '+' : '-');
832     g_string_append_printf (s, "%02u%02u", tzhour, tzminute);
833   }
834 
835 done:
836 
837   return g_string_free (s, FALSE);
838 }
839 
840 /**
841  * gst_date_time_to_iso8601_string:
842  * @datetime: a #GstDateTime.
843  *
844  * Create a minimal string compatible with ISO-8601. Possible output formats
845  * are (for example): `2012`, `2012-06`, `2012-06-23`, `2012-06-23T23:30Z`,
846  * `2012-06-23T23:30+0100`, `2012-06-23T23:30:59Z`, `2012-06-23T23:30:59+0100`
847  *
848  * Returns: (nullable): a newly allocated string formatted according
849  *     to ISO 8601 and only including the datetime fields that are
850  *     valid, or %NULL in case there was an error.
851  */
852 gchar *
gst_date_time_to_iso8601_string(GstDateTime * datetime)853 gst_date_time_to_iso8601_string (GstDateTime * datetime)
854 {
855   g_return_val_if_fail (datetime != NULL, NULL);
856 
857   if (datetime->fields == GST_DATE_TIME_FIELDS_INVALID)
858     return NULL;
859 
860   return __gst_date_time_serialize (datetime, FALSE);
861 }
862 
863 /**
864  * gst_date_time_new_from_iso8601_string:
865  * @string: ISO 8601-formatted datetime string.
866  *
867  * Tries to parse common variants of ISO-8601 datetime strings into a
868  * #GstDateTime. Possible input formats are (for example):
869  * `2012-06-30T22:46:43Z`, `2012`, `2012-06`, `2012-06-30`, `2012-06-30T22:46:43-0430`,
870  * `2012-06-30T22:46Z`, `2012-06-30T22:46-0430`, `2012-06-30 22:46`,
871  * `2012-06-30 22:46:43`, `2012-06-00`, `2012-00-00`, `2012-00-30`, `22:46:43Z`, `22:46Z`,
872  * `22:46:43-0430`, `22:46-0430`, `22:46:30`, `22:46`
873  * If no date is provided, it is assumed to be "today" in the timezone
874  * provided (if any), otherwise UTC.
875  *
876  * Returns: (transfer full) (nullable): a newly created #GstDateTime,
877  * or %NULL on error
878  */
879 GstDateTime *
gst_date_time_new_from_iso8601_string(const gchar * string)880 gst_date_time_new_from_iso8601_string (const gchar * string)
881 {
882   gint year = -1, month = -1, day = -1, hour = -1, minute = -1;
883   gint gmt_offset_hour = -99, gmt_offset_min = -99;
884   gdouble second = -1.0;
885   gfloat tzoffset = 0.0;
886   guint64 usecs;
887   gint len, ret;
888 
889   g_return_val_if_fail (string != NULL, NULL);
890 
891   GST_DEBUG ("Parsing '%s' into a datetime", string);
892 
893   len = strlen (string);
894 
895   /* The input string is expected to start either with a year (4 digits) or
896    * with an hour (2 digits). Hour must be followed by minute. In any case,
897    * the string must be at least 4 characters long and start with 2 digits */
898   if (len < 4 || !g_ascii_isdigit (string[0]) || !g_ascii_isdigit (string[1]))
899     return NULL;
900 
901   if (g_ascii_isdigit (string[2]) && g_ascii_isdigit (string[3])) {
902     ret = sscanf (string, "%04d-%02d-%02d", &year, &month, &day);
903 
904     if (ret == 0)
905       return NULL;
906 
907     if (ret == 3 && day <= 0) {
908       ret = 2;
909       day = -1;
910     }
911 
912     if (ret >= 2 && month <= 0) {
913       ret = 1;
914       month = day = -1;
915     }
916 
917     if (ret >= 1 && (year <= 0 || year > 9999 || month > 12 || day > 31))
918       return NULL;
919 
920     else if (ret >= 1 && len < 16)
921       /* YMD is 10 chars. XMD + HM will be 16 chars. if it is less,
922        * it make no sense to continue. We will stay with YMD. */
923       goto ymd;
924 
925     string += 10;
926     /* Exit if there is no expected value on this stage */
927     if (!(*string == 'T' || *string == '-' || *string == ' '))
928       goto ymd;
929 
930     string += 1;
931   }
932   /* if hour or minute fails, then we will use only ymd. */
933   hour = g_ascii_strtoull (string, (gchar **) & string, 10);
934   if (hour > 24 || *string != ':')
935     goto ymd;
936 
937   /* minute */
938   minute = g_ascii_strtoull (string + 1, (gchar **) & string, 10);
939   if (minute > 59)
940     goto ymd;
941 
942   /* second */
943   if (*string == ':') {
944     second = g_ascii_strtoull (string + 1, (gchar **) & string, 10);
945     /* if we fail here, we still can reuse hour and minute. We
946      * will still attempt to parse any timezone information */
947     if (second > 59) {
948       second = -1.0;
949     } else {
950       /* microseconds */
951       if (*string == '.' || *string == ',') {
952         const gchar *usec_start = string + 1;
953         guint digits;
954 
955         usecs = g_ascii_strtoull (string + 1, (gchar **) & string, 10);
956         if (usecs != G_MAXUINT64 && string > usec_start) {
957           digits = (guint) (string - usec_start);
958           second += (gdouble) usecs / pow (10.0, digits);
959         }
960       }
961     }
962   }
963 
964   if (*string == 'Z')
965     goto ymd_hms;
966   else {
967     /* reuse some code from gst-plugins-base/gst-libs/gst/tag/gstxmptag.c */
968     gint gmt_offset = -1;
969     gchar *plus_pos = NULL;
970     gchar *neg_pos = NULL;
971     gchar *pos = NULL;
972 
973     GST_LOG ("Checking for timezone information");
974 
975     /* check if there is timezone info */
976     plus_pos = strrchr (string, '+');
977     neg_pos = strrchr (string, '-');
978     if (plus_pos)
979       pos = plus_pos + 1;
980     else if (neg_pos)
981       pos = neg_pos + 1;
982 
983     if (pos && strlen (pos) >= 3) {
984       gint ret_tz;
985       if (pos[2] == ':')
986         ret_tz = sscanf (pos, "%d:%d", &gmt_offset_hour, &gmt_offset_min);
987       else
988         ret_tz = sscanf (pos, "%02d%02d", &gmt_offset_hour, &gmt_offset_min);
989 
990       GST_DEBUG ("Parsing timezone: %s", pos);
991 
992       if (ret_tz == 2) {
993         if (neg_pos != NULL && neg_pos + 1 == pos) {
994           gmt_offset_hour *= -1;
995           gmt_offset_min *= -1;
996         }
997         gmt_offset = gmt_offset_hour * 60 + gmt_offset_min;
998 
999         tzoffset = gmt_offset / 60.0;
1000 
1001         GST_LOG ("Timezone offset: %f (%d minutes)", tzoffset, gmt_offset);
1002       } else
1003         GST_WARNING ("Failed to parse timezone information");
1004     }
1005   }
1006 
1007 ymd_hms:
1008   if (year == -1 || month == -1 || day == -1) {
1009     GDateTime *now_utc, *now_in_given_tz;
1010 
1011     /* No date was supplied: make it today */
1012     now_utc = g_date_time_new_now_utc ();
1013     if (!now_utc)
1014       return NULL;
1015 
1016     if (tzoffset != 0.0) {
1017       /* If a timezone offset was supplied, get the date of that timezone */
1018       g_assert (gmt_offset_min != -99);
1019       g_assert (gmt_offset_hour != -99);
1020       now_in_given_tz =
1021           g_date_time_add_minutes (now_utc,
1022           (60 * gmt_offset_hour) + gmt_offset_min);
1023       g_date_time_unref (now_utc);
1024       if (!now_in_given_tz)
1025         return NULL;
1026     } else {
1027       now_in_given_tz = now_utc;
1028     }
1029     g_date_time_get_ymd (now_in_given_tz, &year, &month, &day);
1030     g_date_time_unref (now_in_given_tz);
1031   }
1032   return gst_date_time_new (tzoffset, year, month, day, hour, minute, second);
1033 ymd:
1034   if (year == -1) {
1035     /* No date was supplied and time failed to parse */
1036     return NULL;
1037   }
1038   return gst_date_time_new_ymd (year, month, day);
1039 }
1040 
1041 static void
gst_date_time_free(GstDateTime * datetime)1042 gst_date_time_free (GstDateTime * datetime)
1043 {
1044   g_date_time_unref (datetime->datetime);
1045 
1046 #ifdef USE_POISONING
1047   memset (datetime, 0xff, sizeof (GstDateTime));
1048 #endif
1049 
1050   g_slice_free (GstDateTime, datetime);
1051 }
1052 
1053 /**
1054  * gst_date_time_ref:
1055  * @datetime: a #GstDateTime
1056  *
1057  * Atomically increments the reference count of @datetime by one.
1058  *
1059  * Return value: (transfer full): the reference @datetime
1060  */
1061 GstDateTime *
gst_date_time_ref(GstDateTime * datetime)1062 gst_date_time_ref (GstDateTime * datetime)
1063 {
1064   return (GstDateTime *) gst_mini_object_ref (GST_MINI_OBJECT_CAST (datetime));
1065 }
1066 
1067 /**
1068  * gst_date_time_unref:
1069  * @datetime: (transfer full): a #GstDateTime
1070  *
1071  * Atomically decrements the reference count of @datetime by one.  When the
1072  * reference count reaches zero, the structure is freed.
1073  */
1074 void
gst_date_time_unref(GstDateTime * datetime)1075 gst_date_time_unref (GstDateTime * datetime)
1076 {
1077   gst_mini_object_unref (GST_MINI_OBJECT_CAST (datetime));
1078 }
1079 
1080 void
_priv_gst_date_time_initialize(void)1081 _priv_gst_date_time_initialize (void)
1082 {
1083   _gst_date_time_type = gst_date_time_get_type ();
1084 }
1085