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