1 // Copyright 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 
5 #include "base/time/time.h"
6 
7 #include <memory>
8 
9 #include "base/logging.h"
10 #include "base/memory/ptr_util.h"
11 #include "third_party/icu/source/i18n/unicode/calendar.h"
12 #include "third_party/icu/source/i18n/unicode/timezone.h"
13 
14 namespace base {
15 
16 static_assert(
17     sizeof(Time::Exploded::year) == sizeof(int32_t),
18     "The sizes of Time::Exploded members and ICU date fields do not match.");
19 
20 namespace {
21 
22 // Returns a new icu::Calendar instance for the local time zone if |is_local|
23 // and for GMT otherwise. Returns null on error.
CreateCalendar(bool is_local)24 std::unique_ptr<icu::Calendar> CreateCalendar(bool is_local) {
25   UErrorCode status = U_ZERO_ERROR;
26   std::unique_ptr<icu::Calendar> calendar =
27       base::WrapUnique(is_local ? icu::Calendar::createInstance(status)
28                                 : icu::Calendar::createInstance(
29                                       *icu::TimeZone::getGMT(), status));
30   CHECK(U_SUCCESS(status));
31 
32   return calendar;
33 }
34 
35 }  // namespace
36 
Explode(bool is_local,Exploded * exploded) const37 void Time::Explode(bool is_local, Exploded* exploded) const {
38   std::unique_ptr<icu::Calendar> calendar = CreateCalendar(is_local);
39 
40   UErrorCode status = U_ZERO_ERROR;
41   calendar->setTime(ToRoundedDownMillisecondsSinceUnixEpoch(), status);
42   DCHECK(U_SUCCESS(status));
43 
44   exploded->year = calendar->get(UCAL_YEAR, status);
45   DCHECK(U_SUCCESS(status));
46 
47   // ICU's UCalendarMonths is 0-based. E.g., 0 for January.
48   exploded->month = calendar->get(UCAL_MONTH, status) + 1;
49   DCHECK(U_SUCCESS(status));
50   // ICU's UCalendarDaysOfWeek is 1-based. E.g., 1 for Sunday.
51   exploded->day_of_week = calendar->get(UCAL_DAY_OF_WEEK, status) - 1;
52   DCHECK(U_SUCCESS(status));
53   exploded->day_of_month = calendar->get(UCAL_DAY_OF_MONTH, status);
54   DCHECK(U_SUCCESS(status));
55   exploded->hour = calendar->get(UCAL_HOUR_OF_DAY, status);
56   DCHECK(U_SUCCESS(status));
57   exploded->minute = calendar->get(UCAL_MINUTE, status);
58   DCHECK(U_SUCCESS(status));
59   exploded->second = calendar->get(UCAL_SECOND, status);
60   DCHECK(U_SUCCESS(status));
61   exploded->millisecond = calendar->get(UCAL_MILLISECOND, status);
62   DCHECK(U_SUCCESS(status));
63 }
64 
65 // static
FromExploded(bool is_local,const Exploded & exploded,Time * time)66 bool Time::FromExploded(bool is_local, const Exploded& exploded, Time* time) {
67   // ICU's UCalendarMonths is 0-based. E.g., 0 for January.
68   CheckedNumeric<int> month = exploded.month;
69   month--;
70   if (!month.IsValid()) {
71     *time = Time(0);
72     return false;
73   }
74 
75   std::unique_ptr<icu::Calendar> calendar = CreateCalendar(is_local);
76 
77   // Cause getTime() to report an error if invalid dates, such as the 31st day
78   // of February, are specified.
79   calendar->setLenient(false);
80 
81   calendar->set(exploded.year, month.ValueOrDie(), exploded.day_of_month,
82                 exploded.hour, exploded.minute, exploded.second);
83   calendar->set(UCAL_MILLISECOND, exploded.millisecond);
84   // Ignore exploded.day_of_week
85 
86   UErrorCode status = U_ZERO_ERROR;
87   UDate date = calendar->getTime(status);
88   if (U_FAILURE(status)) {
89     *time = Time(0);
90     return false;
91   }
92 
93   return FromMillisecondsSinceUnixEpoch(date, time);
94 }
95 
96 }  // namespace base
97