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