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 523 datetime->fields = fields; 524 return datetime; 525 } 526 527 /** 528 * gst_date_time_new_now_local_time: 529 * 530 * Creates a new #GstDateTime representing the current date and time. 531 * 532 * Free-function: gst_date_time_unref 533 * 534 * Return value: (transfer full): the newly created #GstDateTime which should 535 * be freed with gst_date_time_unref(). 536 */ 537 GstDateTime * 538 gst_date_time_new_now_local_time (void) 539 { 540 return gst_date_time_new_from_g_date_time (g_date_time_new_now_local ()); 541 } 542 543 /** 544 * gst_date_time_new_now_utc: 545 * 546 * Creates a new #GstDateTime that represents the current instant at Universal 547 * coordinated time. 548 * 549 * Free-function: gst_date_time_unref 550 * 551 * Return value: (transfer full): the newly created #GstDateTime which should 552 * be freed with gst_date_time_unref(). 553 */ 554 GstDateTime * 555 gst_date_time_new_now_utc (void) 556 { 557 return gst_date_time_new_from_g_date_time (g_date_time_new_now_utc ()); 558 } 559 560 gint 561 __gst_date_time_compare (const GstDateTime * dt1, const GstDateTime * dt2) 562 { 563 gint64 diff; 564 565 /* we assume here that GST_DATE_TIME_FIELDS_YMD_HMS is the highest 566 * resolution, and ignore microsecond differences on purpose for now */ 567 if (dt1->fields != dt2->fields) 568 return GST_VALUE_UNORDERED; 569 570 /* This will round down to nearest second, which is what we want. We're 571 * not comparing microseconds on purpose here, since we're not 572 * serialising them when doing new_utc_now() + to_string() */ 573 diff = 574 g_date_time_to_unix (dt1->datetime) - g_date_time_to_unix (dt2->datetime); 575 if (diff < 0) 576 return GST_VALUE_LESS_THAN; 577 else if (diff > 0) 578 return GST_VALUE_GREATER_THAN; 579 else 580 return GST_VALUE_EQUAL; 581 } 582 583 /** 584 * gst_date_time_new: 585 * @tzoffset: Offset from UTC in hours. 586 * @year: the gregorian year 587 * @month: the gregorian month 588 * @day: the day of the gregorian month 589 * @hour: the hour of the day 590 * @minute: the minute of the hour 591 * @seconds: the second of the minute 592 * 593 * Creates a new #GstDateTime using the date and times in the gregorian calendar 594 * in the supplied timezone. 595 * 596 * @year should be from 1 to 9999, @month should be from 1 to 12, @day from 597 * 1 to 31, @hour from 0 to 23, @minutes and @seconds from 0 to 59. 598 * 599 * Note that @tzoffset is a float and was chosen so for being able to handle 600 * some fractional timezones, while it still keeps the readability of 601 * representing it in hours for most timezones. 602 * 603 * If value is -1 then all over value will be ignored. For example 604 * if @month == -1, then #GstDateTime will created only for @year. If 605 * @day == -1, then #GstDateTime will created for @year and @month and 606 * so on. 607 * 608 * Free-function: gst_date_time_unref 609 * 610 * Return value: (transfer full): the newly created #GstDateTime 611 */ 612 GstDateTime * 613 gst_date_time_new (gfloat tzoffset, gint year, gint month, gint day, gint hour, 614 gint minute, gdouble seconds) 615 { 616 GstDateTimeFields fields; 617 gchar buf[6]; 618 GTimeZone *tz; 619 GDateTime *dt; 620 GstDateTime *datetime; 621 gint tzhour, tzminute; 622 623 g_return_val_if_fail (year > 0 && year <= 9999, NULL); 624 g_return_val_if_fail ((month > 0 && month <= 12) || month == -1, NULL); 625 g_return_val_if_fail ((day > 0 && day <= 31) || day == -1, NULL); 626 g_return_val_if_fail ((hour >= 0 && hour < 24) || hour == -1, NULL); 627 g_return_val_if_fail ((minute >= 0 && minute < 60) || minute == -1, NULL); 628 g_return_val_if_fail ((seconds >= 0 && seconds < 60) || seconds == -1, NULL); 629 g_return_val_if_fail (tzoffset >= -12.0 && tzoffset <= 12.0, NULL); 630 g_return_val_if_fail ((hour >= 0 && minute >= 0) || 631 (hour == -1 && minute == -1 && seconds == -1 && tzoffset == 0.0), NULL); 632 633 tzhour = (gint) ABS (tzoffset); 634 tzminute = (gint) ((ABS (tzoffset) - tzhour) * 60); 635 636 g_snprintf (buf, 6, "%c%02d%02d", tzoffset >= 0 ? '+' : '-', tzhour, 637 tzminute); 638 639 tz = g_time_zone_new (buf); 640 641 fields = gst_date_time_check_fields (&year, &month, &day, 642 &hour, &minute, &seconds); 643 644 dt = g_date_time_new (tz, year, month, day, hour, minute, seconds); 645 g_time_zone_unref (tz); 646 647 datetime = gst_date_time_new_from_g_date_time (dt); 648 datetime->fields = fields; 649 650 return datetime; 651 } 652 653 gchar * 654 __gst_date_time_serialize (GstDateTime * datetime, gboolean serialize_usecs) 655 { 656 GString *s; 657 gfloat gmt_offset; 658 guint msecs; 659 660 /* we always have at least the year */ 661 s = g_string_new (NULL); 662 g_string_append_printf (s, "%04u", gst_date_time_get_year (datetime)); 663 664 if (datetime->fields == GST_DATE_TIME_FIELDS_Y) 665 goto done; 666 667 /* add month */ 668 g_string_append_printf (s, "-%02u", gst_date_time_get_month (datetime)); 669 670 if (datetime->fields == GST_DATE_TIME_FIELDS_YM) 671 goto done; 672 673 /* add day of month */ 674 g_string_append_printf (s, "-%02u", gst_date_time_get_day (datetime)); 675 676 if (datetime->fields == GST_DATE_TIME_FIELDS_YMD) 677 goto done; 678 679 /* add time */ 680 g_string_append_printf (s, "T%02u:%02u", gst_date_time_get_hour (datetime), 681 gst_date_time_get_minute (datetime)); 682 683 if (datetime->fields == GST_DATE_TIME_FIELDS_YMD_HM) 684 goto add_timezone; 685 686 /* add seconds */ 687 g_string_append_printf (s, ":%02u", gst_date_time_get_second (datetime)); 688 689 /* add microseconds */ 690 if (serialize_usecs) { 691 msecs = gst_date_time_get_microsecond (datetime); 692 if (msecs != 0) { 693 g_string_append_printf (s, ".%06u", msecs); 694 /* trim trailing 0s */ 695 while (s->str[s->len - 1] == '0') 696 g_string_truncate (s, s->len - 1); 697 } 698 } 699 700 /* add timezone */ 701 702 add_timezone: 703 704 gmt_offset = gst_date_time_get_time_zone_offset (datetime); 705 if (gmt_offset == 0) { 706 g_string_append_c (s, 'Z'); 707 } else { 708 guint tzhour, tzminute; 709 710 tzhour = (guint) ABS (gmt_offset); 711 tzminute = (guint) ((ABS (gmt_offset) - tzhour) * 60); 712 713 g_string_append_c (s, (gmt_offset >= 0) ? '+' : '-'); 714 g_string_append_printf (s, "%02u%02u", tzhour, tzminute); 715 } 716 717 done: 718 719 return g_string_free (s, FALSE); 720 } 721 722 /** 723 * gst_date_time_to_iso8601_string: 724 * @datetime: GstDateTime. 725 * 726 * Create a minimal string compatible with ISO-8601. Possible output formats 727 * are (for example): 2012, 2012-06, 2012-06-23, 2012-06-23T23:30Z, 728 * 2012-06-23T23:30+0100, 2012-06-23T23:30:59Z, 2012-06-23T23:30:59+0100 729 * 730 * Returns: (nullable): a newly allocated string formatted according 731 * to ISO 8601 and only including the datetime fields that are 732 * valid, or %NULL in case there was an error. The string should 733 * be freed with g_free(). 734 */ 735 gchar * 736 gst_date_time_to_iso8601_string (GstDateTime * datetime) 737 { 738 g_return_val_if_fail (datetime != NULL, NULL); 739 740 if (datetime->fields == GST_DATE_TIME_FIELDS_INVALID) 741 return NULL; 742 743 return __gst_date_time_serialize (datetime, FALSE); 744 } 745 746 /** 747 * gst_date_time_new_from_iso8601_string: 748 * @string: ISO 8601-formatted datetime string. 749 * 750 * Tries to parse common variants of ISO-8601 datetime strings into a 751 * #GstDateTime. 752 * 753 * Free-function: gst_date_time_unref 754 * 755 * Returns: (transfer full) (nullable): a newly created #GstDateTime, 756 * or %NULL on error 757 */ 758 GstDateTime * 759 gst_date_time_new_from_iso8601_string (const gchar * string) 760 { 761 gint year = -1, month = -1, day = -1, hour = -1, minute = -1; 762 gdouble second = -1.0; 763 gfloat tzoffset = 0.0; 764 guint64 usecs; 765 gint len, ret; 766 767 g_return_val_if_fail (string != NULL, NULL); 768 769 GST_DEBUG ("Parsing '%s' into a datetime", string); 770 771 len = strlen (string); 772 773 if (len < 4 || !g_ascii_isdigit (string[0]) || !g_ascii_isdigit (string[1]) 774 || !g_ascii_isdigit (string[2]) || !g_ascii_isdigit (string[3])) 775 return NULL; 776 777 ret = sscanf (string, "%04d-%02d-%02d", &year, &month, &day); 778 779 if (ret == 0) 780 return NULL; 781 782 if (ret == 3 && day <= 0) { 783 ret = 2; 784 day = -1; 785 } 786 787 if (ret >= 2 && month <= 0) { 788 ret = 1; 789 month = day = -1; 790 } 791 792 if (ret >= 1 && year <= 0) 793 return NULL; 794 795 else if (ret >= 1 && len < 16) 796 /* YMD is 10 chars. XMD + HM will be 16 chars. if it is less, 797 * it make no sense to continue. We will stay with YMD. */ 798 goto ymd; 799 800 string += 10; 801 /* Exit if there is no expeceted value on this stage */ 802 if (!(*string == 'T' || *string == '-' || *string == ' ')) 803 goto ymd; 804 805 /* if hour or minute fails, then we will use onlly ymd. */ 806 hour = g_ascii_strtoull (string + 1, (gchar **) & string, 10); 807 if (hour > 24 || *string != ':') 808 goto ymd; 809 810 /* minute */ 811 minute = g_ascii_strtoull (string + 1, (gchar **) & string, 10); 812 if (minute > 59) 813 goto ymd; 814 815 /* second */ 816 if (*string == ':') { 817 second = g_ascii_strtoull (string + 1, (gchar **) & string, 10); 818 /* if we fail here, we still can reuse hour and minute. We 819 * will still attempt to parse any timezone information */ 820 if (second > 59) { 821 second = -1.0; 822 } else { 823 /* microseconds */ 824 if (*string == '.' || *string == ',') { 825 const gchar *usec_start = string + 1; 826 guint digits; 827 828 usecs = g_ascii_strtoull (string + 1, (gchar **) & string, 10); 829 if (usecs != G_MAXUINT64 && string > usec_start) { 830 digits = (guint) (string - usec_start); 831 second += (gdouble) usecs / pow (10.0, digits); 832 } 833 } 834 } 835 } 836 837 if (*string == 'Z') 838 goto ymd_hms; 839 else { 840 /* reuse some code from gst-plugins-base/gst-libs/gst/tag/gstxmptag.c */ 841 gint gmt_offset_hour = -1, gmt_offset_min = -1, gmt_offset = -1; 842 gchar *plus_pos = NULL; 843 gchar *neg_pos = NULL; 844 gchar *pos = NULL; 845 846 GST_LOG ("Checking for timezone information"); 847 848 /* check if there is timezone info */ 849 plus_pos = strrchr (string, '+'); 850 neg_pos = strrchr (string, '-'); 851 if (plus_pos) 852 pos = plus_pos + 1; 853 else if (neg_pos) 854 pos = neg_pos + 1; 855 856 if (pos) { 857 gint ret_tz; 858 if (pos[2] == ':') 859 ret_tz = sscanf (pos, "%d:%d", &gmt_offset_hour, &gmt_offset_min); 860 else 861 ret_tz = sscanf (pos, "%02d%02d", &gmt_offset_hour, &gmt_offset_min); 862 863 GST_DEBUG ("Parsing timezone: %s", pos); 864 865 if (ret_tz == 2) { 866 gmt_offset = gmt_offset_hour * 60 + gmt_offset_min; 867 if (neg_pos != NULL && neg_pos + 1 == pos) 868 gmt_offset *= -1; 869 870 tzoffset = gmt_offset / 60.0; 871 872 GST_LOG ("Timezone offset: %f (%d minutes)", tzoffset, gmt_offset); 873 } else 874 GST_WARNING ("Failed to parse timezone information"); 875 } 876 } 877 878 ymd_hms: 879 return gst_date_time_new (tzoffset, year, month, day, hour, minute, second); 880 ymd: 881 return gst_date_time_new_ymd (year, month, day); 882 } 883 884 static void 885 gst_date_time_free (GstDateTime * datetime) 886 { 887 g_date_time_unref (datetime->datetime); 888 g_slice_free (GstDateTime, datetime); 889 } 890 891 /** 892 * gst_date_time_ref: 893 * @datetime: a #GstDateTime 894 * 895 * Atomically increments the reference count of @datetime by one. 896 * 897 * Return value: (transfer full): the reference @datetime 898 */ 899 GstDateTime * 900 gst_date_time_ref (GstDateTime * datetime) 901 { 902 return (GstDateTime *) gst_mini_object_ref (GST_MINI_OBJECT_CAST (datetime)); 903 } 904 905 /** 906 * gst_date_time_unref: 907 * @datetime: (transfer full): a #GstDateTime 908 * 909 * Atomically decrements the reference count of @datetime by one. When the 910 * reference count reaches zero, the structure is freed. 911 */ 912 void 913 gst_date_time_unref (GstDateTime * datetime) 914 { 915 gst_mini_object_unref (GST_MINI_OBJECT_CAST (datetime)); 916 } 917 918 void 919 _priv_gst_date_time_initialize (void) 920 { 921 _gst_date_time_type = gst_date_time_get_type (); 922 }