time_exploded_ios.cc 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. // Copyright (c) 2019 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "base/time/time.h"
  5. #include <CoreFoundation/CFDate.h>
  6. #include <CoreFoundation/CFCalendar.h>
  7. #include <CoreFoundation/CFTimeZone.h>
  8. #include "base/mac/scoped_cftyperef.h"
  9. #include "base/numerics/safe_conversions.h"
  10. #if __LP64__
  11. #error Use posix implementation on 64-bit platforms.
  12. #endif // __LP64__
  13. namespace base {
  14. // Note: These implementations of Time::FromExploded() and Time::Explode() are
  15. // only used on iOS now. Since Mac is now always 64-bit, we can use the POSIX
  16. // versions of these functions as time_t is not capped at year 2038 on 64-bit
  17. // builds. The POSIX functions are preferred since they don't suffer from some
  18. // performance problems that are present in these implementations.
  19. // See crbug.com/781601, crbug.com/985061 for more details.
  20. // static
  21. bool Time::FromExploded(bool is_local, const Exploded& exploded, Time* time) {
  22. base::ScopedCFTypeRef<CFTimeZoneRef> time_zone(
  23. is_local
  24. ? CFTimeZoneCopySystem()
  25. : CFTimeZoneCreateWithTimeIntervalFromGMT(kCFAllocatorDefault, 0));
  26. base::ScopedCFTypeRef<CFCalendarRef> gregorian(CFCalendarCreateWithIdentifier(
  27. kCFAllocatorDefault, kCFGregorianCalendar));
  28. CFCalendarSetTimeZone(gregorian, time_zone);
  29. CFAbsoluteTime absolute_time;
  30. // 'S' is not defined in componentDesc in Apple documentation, but can be
  31. // found at http://www.opensource.apple.com/source/CF/CF-855.17/CFCalendar.c
  32. CFCalendarComposeAbsoluteTime(
  33. gregorian, &absolute_time, "yMdHmsS", exploded.year, exploded.month,
  34. exploded.day_of_month, exploded.hour, exploded.minute, exploded.second,
  35. exploded.millisecond);
  36. CFAbsoluteTime seconds = absolute_time + kCFAbsoluteTimeIntervalSince1970;
  37. // CFAbsolutTime is typedef of double. Convert seconds to
  38. // microseconds and then cast to int64. If
  39. // it cannot be suited to int64, then fail to avoid overflows.
  40. double microseconds =
  41. (seconds * kMicrosecondsPerSecond) + kTimeTToMicrosecondsOffset;
  42. if (!IsValueInRangeForNumericType<int64_t>(microseconds)) {
  43. *time = Time(0);
  44. return false;
  45. }
  46. base::Time converted_time = Time(static_cast<int64_t>(microseconds));
  47. // If |exploded.day_of_month| is set to 31
  48. // on a 28-30 day month, it will return the first day of the next month.
  49. // Thus round-trip the time and compare the initial |exploded| with
  50. // |utc_to_exploded| time.
  51. base::Time::Exploded to_exploded;
  52. if (!is_local)
  53. converted_time.UTCExplode(&to_exploded);
  54. else
  55. converted_time.LocalExplode(&to_exploded);
  56. if (ExplodedMostlyEquals(to_exploded, exploded)) {
  57. *time = converted_time;
  58. return true;
  59. }
  60. *time = Time(0);
  61. return false;
  62. }
  63. void Time::Explode(bool is_local, Exploded* exploded) const {
  64. // Avoid rounding issues, by only putting the integral number of seconds
  65. // (rounded towards -infinity) into a |CFAbsoluteTime| (which is a |double|).
  66. int64_t microsecond = us_ % kMicrosecondsPerSecond;
  67. if (microsecond < 0)
  68. microsecond += kMicrosecondsPerSecond;
  69. CFAbsoluteTime seconds = ((us_ - microsecond - kTimeTToMicrosecondsOffset) /
  70. kMicrosecondsPerSecond) -
  71. kCFAbsoluteTimeIntervalSince1970;
  72. base::ScopedCFTypeRef<CFTimeZoneRef> time_zone(
  73. is_local
  74. ? CFTimeZoneCopySystem()
  75. : CFTimeZoneCreateWithTimeIntervalFromGMT(kCFAllocatorDefault, 0));
  76. base::ScopedCFTypeRef<CFCalendarRef> gregorian(CFCalendarCreateWithIdentifier(
  77. kCFAllocatorDefault, kCFGregorianCalendar));
  78. CFCalendarSetTimeZone(gregorian, time_zone);
  79. int second, day_of_week;
  80. // 'E' sets the day of week, but is not defined in componentDesc in Apple
  81. // documentation. It can be found in open source code here:
  82. // http://www.opensource.apple.com/source/CF/CF-855.17/CFCalendar.c
  83. CFCalendarDecomposeAbsoluteTime(gregorian, seconds, "yMdHmsE",
  84. &exploded->year, &exploded->month,
  85. &exploded->day_of_month, &exploded->hour,
  86. &exploded->minute, &second, &day_of_week);
  87. // Make sure seconds are rounded down towards -infinity.
  88. exploded->second = floor(second);
  89. // |Exploded|'s convention for day of week is 0 = Sunday, i.e. different
  90. // from CF's 1 = Sunday.
  91. exploded->day_of_week = (day_of_week - 1) % 7;
  92. // Calculate milliseconds ourselves, since we rounded the |seconds|, making
  93. // sure to round towards -infinity.
  94. exploded->millisecond =
  95. (microsecond >= 0) ? microsecond / kMicrosecondsPerMillisecond
  96. : ((microsecond + 1) / kMicrosecondsPerMillisecond - 1);
  97. }
  98. } // namespace base