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