// Copyright 2019 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/time/time.h" #include #include "base/check.h" #include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/numerics/clamped_math.h" #include "build/build_config.h" #include "third_party/icu/source/common/unicode/locid.h" #include "third_party/icu/source/i18n/unicode/calendar.h" #include "third_party/icu/source/i18n/unicode/gregocal.h" #include "third_party/icu/source/i18n/unicode/timezone.h" namespace base { namespace { // Returns a new icu::Calendar instance for the local time zone if |is_local| // and for GMT otherwise. Returns null on error. std::unique_ptr CreateCalendar(bool is_local) { UErrorCode status = U_ZERO_ERROR; std::unique_ptr calendar; // Always use GregorianCalendar and US locale (relevant for day_of_week, // Sunday is the first day) - that's what base::Time::Exploded assumes. if (is_local) { calendar = std::make_unique(icu::Locale::getUS(), status); } else { calendar = std::make_unique( *icu::TimeZone::getGMT(), icu::Locale::getUS(), status); } CHECK(U_SUCCESS(status)); return calendar; } // Explodes the |millis_since_unix_epoch| using an icu::Calendar, and returns // true if the conversion was successful. bool ExplodeUsingIcuCalendar(int64_t millis_since_unix_epoch, bool is_local, Time::Exploded* exploded) { // ICU's year calculation is wrong for years too far in the past (though // other fields seem to be correct). Given that the Time::Explode() for // Windows only works for values on/after 1601-01-01 00:00:00 UTC, just use // that as a reasonable lower-bound here as well. constexpr int64_t kInputLowerBound = -Time::kTimeTToMicrosecondsOffset / Time::kMicrosecondsPerMillisecond; static_assert( Time::kTimeTToMicrosecondsOffset % Time::kMicrosecondsPerMillisecond == 0, "assumption: no epoch offset sub-milliseconds"); // The input to icu::Calendar is a double-typed value. To ensure no loss of // precision when converting int64_t to double, an upper-bound must also be // imposed. static_assert(std::numeric_limits::radix == 2, ""); constexpr int64_t kInputUpperBound = uint64_t{1} << std::numeric_limits::digits; if (millis_since_unix_epoch < kInputLowerBound || millis_since_unix_epoch > kInputUpperBound) { return false; } std::unique_ptr calendar = CreateCalendar(is_local); UErrorCode status = U_ZERO_ERROR; calendar->setTime(millis_since_unix_epoch, status); if (!U_SUCCESS(status)) return false; using CalendarField = decltype(calendar->get(UCAL_YEAR, status)); static_assert(sizeof(Time::Exploded::year) >= sizeof(CalendarField), "Time::Exploded members are not large enough to hold ICU " "calendar fields."); bool got_all_fields = true; exploded->year = calendar->get(UCAL_YEAR, status); got_all_fields &= !!U_SUCCESS(status); // ICU's UCalendarMonths is 0-based. E.g., 0 for January. exploded->month = calendar->get(UCAL_MONTH, status) + 1; got_all_fields &= !!U_SUCCESS(status); // ICU's UCalendarDaysOfWeek is 1-based. E.g., 1 for Sunday. exploded->day_of_week = calendar->get(UCAL_DAY_OF_WEEK, status) - 1; got_all_fields &= !!U_SUCCESS(status); exploded->day_of_month = calendar->get(UCAL_DAY_OF_MONTH, status); got_all_fields &= !!U_SUCCESS(status); exploded->hour = calendar->get(UCAL_HOUR_OF_DAY, status); got_all_fields &= !!U_SUCCESS(status); exploded->minute = calendar->get(UCAL_MINUTE, status); got_all_fields &= !!U_SUCCESS(status); exploded->second = calendar->get(UCAL_SECOND, status); got_all_fields &= !!U_SUCCESS(status); exploded->millisecond = calendar->get(UCAL_MILLISECOND, status); got_all_fields &= !!U_SUCCESS(status); return got_all_fields; } } // namespace // static void Time::ExplodeUsingIcu(int64_t millis_since_unix_epoch, bool is_local, Exploded* exploded) { if (!ExplodeUsingIcuCalendar(millis_since_unix_epoch, is_local, exploded)) { // Error: Return an invalid Exploded. *exploded = {}; } } // static bool Time::FromExplodedUsingIcu(bool is_local, const Exploded& exploded, int64_t* millis_since_unix_epoch) { // ICU's UCalendarMonths is 0-based. E.g., 0 for January. CheckedNumeric month = exploded.month; month--; if (!month.IsValid()) return false; std::unique_ptr calendar = CreateCalendar(is_local); // Cause getTime() to report an error if invalid dates, such as the 31st day // of February, are specified. calendar->setLenient(false); calendar->set(exploded.year, month.ValueOrDie(), exploded.day_of_month, exploded.hour, exploded.minute, exploded.second); calendar->set(UCAL_MILLISECOND, exploded.millisecond); // Ignore exploded.day_of_week UErrorCode status = U_ZERO_ERROR; UDate date = calendar->getTime(status); if (U_FAILURE(status)) return false; *millis_since_unix_epoch = saturated_cast(date); return true; } #if BUILDFLAG(IS_FUCHSIA) void Time::Explode(bool is_local, Exploded* exploded) const { return ExplodeUsingIcu(ToRoundedDownMillisecondsSinceUnixEpoch(), is_local, exploded); } // static bool Time::FromExploded(bool is_local, const Exploded& exploded, Time* time) { int64_t millis_since_unix_epoch; if (FromExplodedUsingIcu(is_local, exploded, &millis_since_unix_epoch)) return FromMillisecondsSinceUnixEpoch(millis_since_unix_epoch, time); *time = Time(0); return false; } #endif // BUILDFLAG(IS_FUCHSIA) } // namespace base