xref: /aosp_15_r20/external/cronet/base/time/time_exploded_icu.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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