1*6777b538SAndroid Build Coastguard Worker // Copyright 2019 The Chromium Authors
2*6777b538SAndroid Build Coastguard Worker // Use of this source code is governed by a BSD-style license that can be
3*6777b538SAndroid Build Coastguard Worker // found in the LICENSE file.
4*6777b538SAndroid Build Coastguard Worker
5*6777b538SAndroid Build Coastguard Worker #include "base/time/time.h"
6*6777b538SAndroid Build Coastguard Worker
7*6777b538SAndroid Build Coastguard Worker #include <memory>
8*6777b538SAndroid Build Coastguard Worker
9*6777b538SAndroid Build Coastguard Worker #include "base/check.h"
10*6777b538SAndroid Build Coastguard Worker #include "base/logging.h"
11*6777b538SAndroid Build Coastguard Worker #include "base/memory/ptr_util.h"
12*6777b538SAndroid Build Coastguard Worker #include "base/numerics/clamped_math.h"
13*6777b538SAndroid Build Coastguard Worker #include "build/build_config.h"
14*6777b538SAndroid Build Coastguard Worker #include "third_party/icu/source/common/unicode/locid.h"
15*6777b538SAndroid Build Coastguard Worker #include "third_party/icu/source/i18n/unicode/calendar.h"
16*6777b538SAndroid Build Coastguard Worker #include "third_party/icu/source/i18n/unicode/gregocal.h"
17*6777b538SAndroid Build Coastguard Worker #include "third_party/icu/source/i18n/unicode/timezone.h"
18*6777b538SAndroid Build Coastguard Worker
19*6777b538SAndroid Build Coastguard Worker namespace base {
20*6777b538SAndroid Build Coastguard Worker
21*6777b538SAndroid Build Coastguard Worker namespace {
22*6777b538SAndroid Build Coastguard Worker
23*6777b538SAndroid Build Coastguard Worker // Returns a new icu::Calendar instance for the local time zone if |is_local|
24*6777b538SAndroid Build Coastguard Worker // and for GMT otherwise. Returns null on error.
CreateCalendar(bool is_local)25*6777b538SAndroid Build Coastguard Worker std::unique_ptr<icu::Calendar> CreateCalendar(bool is_local) {
26*6777b538SAndroid Build Coastguard Worker UErrorCode status = U_ZERO_ERROR;
27*6777b538SAndroid Build Coastguard Worker std::unique_ptr<icu::Calendar> calendar;
28*6777b538SAndroid Build Coastguard Worker // Always use GregorianCalendar and US locale (relevant for day_of_week,
29*6777b538SAndroid Build Coastguard Worker // Sunday is the first day) - that's what base::Time::Exploded assumes.
30*6777b538SAndroid Build Coastguard Worker if (is_local) {
31*6777b538SAndroid Build Coastguard Worker calendar =
32*6777b538SAndroid Build Coastguard Worker std::make_unique<icu::GregorianCalendar>(icu::Locale::getUS(), status);
33*6777b538SAndroid Build Coastguard Worker } else {
34*6777b538SAndroid Build Coastguard Worker calendar = std::make_unique<icu::GregorianCalendar>(
35*6777b538SAndroid Build Coastguard Worker *icu::TimeZone::getGMT(), icu::Locale::getUS(), status);
36*6777b538SAndroid Build Coastguard Worker }
37*6777b538SAndroid Build Coastguard Worker CHECK(U_SUCCESS(status));
38*6777b538SAndroid Build Coastguard Worker return calendar;
39*6777b538SAndroid Build Coastguard Worker }
40*6777b538SAndroid Build Coastguard Worker
41*6777b538SAndroid Build Coastguard Worker // Explodes the |millis_since_unix_epoch| using an icu::Calendar, and returns
42*6777b538SAndroid Build Coastguard Worker // true if the conversion was successful.
ExplodeUsingIcuCalendar(int64_t millis_since_unix_epoch,bool is_local,Time::Exploded * exploded)43*6777b538SAndroid Build Coastguard Worker bool ExplodeUsingIcuCalendar(int64_t millis_since_unix_epoch,
44*6777b538SAndroid Build Coastguard Worker bool is_local,
45*6777b538SAndroid Build Coastguard Worker Time::Exploded* exploded) {
46*6777b538SAndroid Build Coastguard Worker // ICU's year calculation is wrong for years too far in the past (though
47*6777b538SAndroid Build Coastguard Worker // other fields seem to be correct). Given that the Time::Explode() for
48*6777b538SAndroid Build Coastguard Worker // Windows only works for values on/after 1601-01-01 00:00:00 UTC, just use
49*6777b538SAndroid Build Coastguard Worker // that as a reasonable lower-bound here as well.
50*6777b538SAndroid Build Coastguard Worker constexpr int64_t kInputLowerBound =
51*6777b538SAndroid Build Coastguard Worker -Time::kTimeTToMicrosecondsOffset / Time::kMicrosecondsPerMillisecond;
52*6777b538SAndroid Build Coastguard Worker static_assert(
53*6777b538SAndroid Build Coastguard Worker Time::kTimeTToMicrosecondsOffset % Time::kMicrosecondsPerMillisecond == 0,
54*6777b538SAndroid Build Coastguard Worker "assumption: no epoch offset sub-milliseconds");
55*6777b538SAndroid Build Coastguard Worker
56*6777b538SAndroid Build Coastguard Worker // The input to icu::Calendar is a double-typed value. To ensure no loss of
57*6777b538SAndroid Build Coastguard Worker // precision when converting int64_t to double, an upper-bound must also be
58*6777b538SAndroid Build Coastguard Worker // imposed.
59*6777b538SAndroid Build Coastguard Worker static_assert(std::numeric_limits<double>::radix == 2, "");
60*6777b538SAndroid Build Coastguard Worker constexpr int64_t kInputUpperBound = uint64_t{1}
61*6777b538SAndroid Build Coastguard Worker << std::numeric_limits<double>::digits;
62*6777b538SAndroid Build Coastguard Worker
63*6777b538SAndroid Build Coastguard Worker if (millis_since_unix_epoch < kInputLowerBound ||
64*6777b538SAndroid Build Coastguard Worker millis_since_unix_epoch > kInputUpperBound) {
65*6777b538SAndroid Build Coastguard Worker return false;
66*6777b538SAndroid Build Coastguard Worker }
67*6777b538SAndroid Build Coastguard Worker
68*6777b538SAndroid Build Coastguard Worker std::unique_ptr<icu::Calendar> calendar = CreateCalendar(is_local);
69*6777b538SAndroid Build Coastguard Worker UErrorCode status = U_ZERO_ERROR;
70*6777b538SAndroid Build Coastguard Worker calendar->setTime(millis_since_unix_epoch, status);
71*6777b538SAndroid Build Coastguard Worker if (!U_SUCCESS(status))
72*6777b538SAndroid Build Coastguard Worker return false;
73*6777b538SAndroid Build Coastguard Worker
74*6777b538SAndroid Build Coastguard Worker using CalendarField = decltype(calendar->get(UCAL_YEAR, status));
75*6777b538SAndroid Build Coastguard Worker static_assert(sizeof(Time::Exploded::year) >= sizeof(CalendarField),
76*6777b538SAndroid Build Coastguard Worker "Time::Exploded members are not large enough to hold ICU "
77*6777b538SAndroid Build Coastguard Worker "calendar fields.");
78*6777b538SAndroid Build Coastguard Worker
79*6777b538SAndroid Build Coastguard Worker bool got_all_fields = true;
80*6777b538SAndroid Build Coastguard Worker exploded->year = calendar->get(UCAL_YEAR, status);
81*6777b538SAndroid Build Coastguard Worker got_all_fields &= !!U_SUCCESS(status);
82*6777b538SAndroid Build Coastguard Worker // ICU's UCalendarMonths is 0-based. E.g., 0 for January.
83*6777b538SAndroid Build Coastguard Worker exploded->month = calendar->get(UCAL_MONTH, status) + 1;
84*6777b538SAndroid Build Coastguard Worker got_all_fields &= !!U_SUCCESS(status);
85*6777b538SAndroid Build Coastguard Worker // ICU's UCalendarDaysOfWeek is 1-based. E.g., 1 for Sunday.
86*6777b538SAndroid Build Coastguard Worker exploded->day_of_week = calendar->get(UCAL_DAY_OF_WEEK, status) - 1;
87*6777b538SAndroid Build Coastguard Worker got_all_fields &= !!U_SUCCESS(status);
88*6777b538SAndroid Build Coastguard Worker exploded->day_of_month = calendar->get(UCAL_DAY_OF_MONTH, status);
89*6777b538SAndroid Build Coastguard Worker got_all_fields &= !!U_SUCCESS(status);
90*6777b538SAndroid Build Coastguard Worker exploded->hour = calendar->get(UCAL_HOUR_OF_DAY, status);
91*6777b538SAndroid Build Coastguard Worker got_all_fields &= !!U_SUCCESS(status);
92*6777b538SAndroid Build Coastguard Worker exploded->minute = calendar->get(UCAL_MINUTE, status);
93*6777b538SAndroid Build Coastguard Worker got_all_fields &= !!U_SUCCESS(status);
94*6777b538SAndroid Build Coastguard Worker exploded->second = calendar->get(UCAL_SECOND, status);
95*6777b538SAndroid Build Coastguard Worker got_all_fields &= !!U_SUCCESS(status);
96*6777b538SAndroid Build Coastguard Worker exploded->millisecond = calendar->get(UCAL_MILLISECOND, status);
97*6777b538SAndroid Build Coastguard Worker got_all_fields &= !!U_SUCCESS(status);
98*6777b538SAndroid Build Coastguard Worker return got_all_fields;
99*6777b538SAndroid Build Coastguard Worker }
100*6777b538SAndroid Build Coastguard Worker
101*6777b538SAndroid Build Coastguard Worker } // namespace
102*6777b538SAndroid Build Coastguard Worker
103*6777b538SAndroid Build Coastguard Worker // static
ExplodeUsingIcu(int64_t millis_since_unix_epoch,bool is_local,Exploded * exploded)104*6777b538SAndroid Build Coastguard Worker void Time::ExplodeUsingIcu(int64_t millis_since_unix_epoch,
105*6777b538SAndroid Build Coastguard Worker bool is_local,
106*6777b538SAndroid Build Coastguard Worker Exploded* exploded) {
107*6777b538SAndroid Build Coastguard Worker if (!ExplodeUsingIcuCalendar(millis_since_unix_epoch, is_local, exploded)) {
108*6777b538SAndroid Build Coastguard Worker // Error: Return an invalid Exploded.
109*6777b538SAndroid Build Coastguard Worker *exploded = {};
110*6777b538SAndroid Build Coastguard Worker }
111*6777b538SAndroid Build Coastguard Worker }
112*6777b538SAndroid Build Coastguard Worker
113*6777b538SAndroid Build Coastguard Worker // static
FromExplodedUsingIcu(bool is_local,const Exploded & exploded,int64_t * millis_since_unix_epoch)114*6777b538SAndroid Build Coastguard Worker bool Time::FromExplodedUsingIcu(bool is_local,
115*6777b538SAndroid Build Coastguard Worker const Exploded& exploded,
116*6777b538SAndroid Build Coastguard Worker int64_t* millis_since_unix_epoch) {
117*6777b538SAndroid Build Coastguard Worker // ICU's UCalendarMonths is 0-based. E.g., 0 for January.
118*6777b538SAndroid Build Coastguard Worker CheckedNumeric<int> month = exploded.month;
119*6777b538SAndroid Build Coastguard Worker month--;
120*6777b538SAndroid Build Coastguard Worker if (!month.IsValid())
121*6777b538SAndroid Build Coastguard Worker return false;
122*6777b538SAndroid Build Coastguard Worker
123*6777b538SAndroid Build Coastguard Worker std::unique_ptr<icu::Calendar> calendar = CreateCalendar(is_local);
124*6777b538SAndroid Build Coastguard Worker
125*6777b538SAndroid Build Coastguard Worker // Cause getTime() to report an error if invalid dates, such as the 31st day
126*6777b538SAndroid Build Coastguard Worker // of February, are specified.
127*6777b538SAndroid Build Coastguard Worker calendar->setLenient(false);
128*6777b538SAndroid Build Coastguard Worker
129*6777b538SAndroid Build Coastguard Worker calendar->set(exploded.year, month.ValueOrDie(), exploded.day_of_month,
130*6777b538SAndroid Build Coastguard Worker exploded.hour, exploded.minute, exploded.second);
131*6777b538SAndroid Build Coastguard Worker calendar->set(UCAL_MILLISECOND, exploded.millisecond);
132*6777b538SAndroid Build Coastguard Worker // Ignore exploded.day_of_week
133*6777b538SAndroid Build Coastguard Worker
134*6777b538SAndroid Build Coastguard Worker UErrorCode status = U_ZERO_ERROR;
135*6777b538SAndroid Build Coastguard Worker UDate date = calendar->getTime(status);
136*6777b538SAndroid Build Coastguard Worker if (U_FAILURE(status))
137*6777b538SAndroid Build Coastguard Worker return false;
138*6777b538SAndroid Build Coastguard Worker
139*6777b538SAndroid Build Coastguard Worker *millis_since_unix_epoch = saturated_cast<int64_t>(date);
140*6777b538SAndroid Build Coastguard Worker return true;
141*6777b538SAndroid Build Coastguard Worker }
142*6777b538SAndroid Build Coastguard Worker
143*6777b538SAndroid Build Coastguard Worker #if BUILDFLAG(IS_FUCHSIA)
144*6777b538SAndroid Build Coastguard Worker
Explode(bool is_local,Exploded * exploded) const145*6777b538SAndroid Build Coastguard Worker void Time::Explode(bool is_local, Exploded* exploded) const {
146*6777b538SAndroid Build Coastguard Worker return ExplodeUsingIcu(ToRoundedDownMillisecondsSinceUnixEpoch(), is_local,
147*6777b538SAndroid Build Coastguard Worker exploded);
148*6777b538SAndroid Build Coastguard Worker }
149*6777b538SAndroid Build Coastguard Worker
150*6777b538SAndroid Build Coastguard Worker // static
FromExploded(bool is_local,const Exploded & exploded,Time * time)151*6777b538SAndroid Build Coastguard Worker bool Time::FromExploded(bool is_local, const Exploded& exploded, Time* time) {
152*6777b538SAndroid Build Coastguard Worker int64_t millis_since_unix_epoch;
153*6777b538SAndroid Build Coastguard Worker if (FromExplodedUsingIcu(is_local, exploded, &millis_since_unix_epoch))
154*6777b538SAndroid Build Coastguard Worker return FromMillisecondsSinceUnixEpoch(millis_since_unix_epoch, time);
155*6777b538SAndroid Build Coastguard Worker *time = Time(0);
156*6777b538SAndroid Build Coastguard Worker return false;
157*6777b538SAndroid Build Coastguard Worker }
158*6777b538SAndroid Build Coastguard Worker
159*6777b538SAndroid Build Coastguard Worker #endif // BUILDFLAG(IS_FUCHSIA)
160*6777b538SAndroid Build Coastguard Worker
161*6777b538SAndroid Build Coastguard Worker } // namespace base
162