#include <memory>
#include "base/check.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/numerics/clamped_math.h"
#include "base/time/time.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 {
std::unique_ptr<icu::Calendar> CreateCalendar(bool is_local) {
UErrorCode status = U_ZERO_ERROR;
std::unique_ptr<icu::Calendar> calendar;
if (is_local) {
calendar =
std::make_unique<icu::GregorianCalendar>(icu::Locale::getUS(), status);
} else {
calendar = std::make_unique<icu::GregorianCalendar>(
*icu::TimeZone::getGMT(), icu::Locale::getUS(), status);
}
CHECK(U_SUCCESS(status));
return calendar;
}
bool ExplodeUsingIcuCalendar(int64_t millis_since_unix_epoch,
bool is_local,
Time::Exploded* exploded) {
constexpr int64_t kInputLowerBound =
-Time::kTimeTToMicrosecondsOffset / Time::kMicrosecondsPerMillisecond;
static_assert(
Time::kTimeTToMicrosecondsOffset % Time::kMicrosecondsPerMillisecond == 0,
"assumption: no epoch offset sub-milliseconds");
static_assert(std::numeric_limits<double>::radix == 2, "");
constexpr int64_t kInputUpperBound = uint64_t{1}
<< std::numeric_limits<double>::digits;
if (millis_since_unix_epoch < kInputLowerBound ||
millis_since_unix_epoch > kInputUpperBound) {
return false;
}
std::unique_ptr<icu::Calendar> 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);
exploded->month = calendar->get(UCAL_MONTH, status) + 1;
got_all_fields &= !!U_SUCCESS(status);
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;
}
}
void Time::ExplodeUsingIcu(int64_t millis_since_unix_epoch,
bool is_local,
Exploded* exploded) {
if (!ExplodeUsingIcuCalendar(millis_since_unix_epoch, is_local, exploded)) {
*exploded = {};
}
}
bool Time::FromExplodedUsingIcu(bool is_local,
const Exploded& exploded,
int64_t* millis_since_unix_epoch) {
CheckedNumeric<int> month = exploded.month;
month--;
if (!month.IsValid()) {
return false;
}
std::unique_ptr<icu::Calendar> calendar = CreateCalendar(is_local);
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);
UErrorCode status = U_ZERO_ERROR;
UDate date = calendar->getTime(status);
if (U_FAILURE(status)) {
return false;
}
*millis_since_unix_epoch = saturated_cast<int64_t>(date);
return true;
}
#if BUILDFLAG(IS_FUCHSIA)
void Time::Explode(bool is_local, Exploded* exploded) const {
return ExplodeUsingIcu(ToRoundedDownMillisecondsSinceUnixEpoch(), is_local,
exploded);
}
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
}