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