Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
#include "mozilla/intl/TimeZone.h"
#include "mozilla/Vector.h"
#include <algorithm>
#include <string_view>
#include "unicode/uenum.h"
#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
# include "unicode/basictz.h"
#endif
namespace mozilla::intl {
/* static */
Result<UniquePtr<TimeZone>, ICUError> TimeZone::TryCreate(
Maybe<Span<const char16_t>> aTimeZoneOverride) {
const UChar* zoneID = nullptr;
int32_t zoneIDLen = 0;
if (aTimeZoneOverride) {
zoneIDLen = static_cast<int32_t>(aTimeZoneOverride->Length());
zoneID = aTimeZoneOverride->Elements();
}
#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
UniquePtr<icu::TimeZone> tz;
if (zoneID) {
tz.reset(
icu::TimeZone::createTimeZone(icu::UnicodeString(zoneID, zoneIDLen)));
} else {
tz.reset(icu::TimeZone::createDefault());
}
MOZ_ASSERT(tz);
if (*tz == icu::TimeZone::getUnknown()) {
return Err(ICUError::InternalError);
}
return MakeUnique<TimeZone>(std::move(tz));
#else
// An empty string is used for the root locale. This is regarded as the base
// locale of all locales, and is used as the language/country neutral locale
// for locale sensitive operations.
const char* rootLocale = "";
UErrorCode status = U_ZERO_ERROR;
UCalendar* calendar =
ucal_open(zoneID, zoneIDLen, rootLocale, UCAL_DEFAULT, &status);
if (U_FAILURE(status)) {
return Err(ToICUError(status));
}
//
// A time value supports a slightly smaller range of -8,640,000,000,000,000 to
// 8,640,000,000,000,000 milliseconds.
constexpr double StartOfTime = -8.64e15;
// Ensure all computations are performed in the proleptic Gregorian calendar.
ucal_setGregorianChange(calendar, StartOfTime, &status);
if (U_FAILURE(status)) {
return Err(ToICUError(status));
}
return MakeUnique<TimeZone>(calendar);
#endif
}
Result<int32_t, ICUError> TimeZone::GetRawOffsetMs() {
#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
return mTimeZone->getRawOffset();
#else
// Reset the time in case the calendar has been modified.
UErrorCode status = U_ZERO_ERROR;
ucal_setMillis(mCalendar, ucal_getNow(), &status);
if (U_FAILURE(status)) {
return Err(ToICUError(status));
}
int32_t offset = ucal_get(mCalendar, UCAL_ZONE_OFFSET, &status);
if (U_FAILURE(status)) {
return Err(ToICUError(status));
}
return offset;
#endif
}
Result<int32_t, ICUError> TimeZone::GetDSTOffsetMs(int64_t aUTCMilliseconds) {
UDate date = UDate(aUTCMilliseconds);
#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
constexpr bool dateIsLocalTime = false;
int32_t rawOffset, dstOffset;
UErrorCode status = U_ZERO_ERROR;
mTimeZone->getOffset(date, dateIsLocalTime, rawOffset, dstOffset, status);
if (U_FAILURE(status)) {
return Err(ToICUError(status));
}
return dstOffset;
#else
UErrorCode status = U_ZERO_ERROR;
ucal_setMillis(mCalendar, date, &status);
if (U_FAILURE(status)) {
return Err(ToICUError(status));
}
int32_t dstOffset = ucal_get(mCalendar, UCAL_DST_OFFSET, &status);
if (U_FAILURE(status)) {
return Err(ToICUError(status));
}
return dstOffset;
#endif
}
Result<int32_t, ICUError> TimeZone::GetOffsetMs(int64_t aUTCMilliseconds) {
UDate date = UDate(aUTCMilliseconds);
#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
constexpr bool dateIsLocalTime = false;
int32_t rawOffset, dstOffset;
UErrorCode status = U_ZERO_ERROR;
mTimeZone->getOffset(date, dateIsLocalTime, rawOffset, dstOffset, status);
if (U_FAILURE(status)) {
return Err(ToICUError(status));
}
return rawOffset + dstOffset;
#else
UErrorCode status = U_ZERO_ERROR;
ucal_setMillis(mCalendar, date, &status);
if (U_FAILURE(status)) {
return Err(ToICUError(status));
}
int32_t rawOffset = ucal_get(mCalendar, UCAL_ZONE_OFFSET, &status);
if (U_FAILURE(status)) {
return Err(ToICUError(status));
}
int32_t dstOffset = ucal_get(mCalendar, UCAL_DST_OFFSET, &status);
if (U_FAILURE(status)) {
return Err(ToICUError(status));
}
return rawOffset + dstOffset;
#endif
}
Result<int32_t, ICUError> TimeZone::GetUTCOffsetMs(int64_t aLocalMilliseconds) {
//
// LocalTZA ( t, isUTC )
//
// When t_local represents local time repeating multiple times at a negative
// time zone transition (e.g. when the daylight saving time ends or the time
// zone offset is decreased due to a time zone rule change) or skipped local
// time at a positive time zone transitions (e.g. when the daylight saving
// time starts or the time zone offset is increased due to a time zone rule
// change), t_local must be interpreted using the time zone offset before the
// transition.
constexpr LocalOption skippedTime = LocalOption::Former;
constexpr LocalOption repeatedTime = LocalOption::Former;
return GetUTCOffsetMs(aLocalMilliseconds, skippedTime, repeatedTime);
}
static UTimeZoneLocalOption ToUTimeZoneLocalOption(
TimeZone::LocalOption aOption) {
switch (aOption) {
case TimeZone::LocalOption::Former:
return UTimeZoneLocalOption::UCAL_TZ_LOCAL_FORMER;
case TimeZone::LocalOption::Latter:
return UTimeZoneLocalOption::UCAL_TZ_LOCAL_LATTER;
}
MOZ_CRASH("Unexpected TimeZone::LocalOption");
}
Result<int32_t, ICUError> TimeZone::GetUTCOffsetMs(int64_t aLocalMilliseconds,
LocalOption aSkippedTime,
LocalOption aRepeatedTime) {
UDate date = UDate(aLocalMilliseconds);
UTimeZoneLocalOption skippedTime = ToUTimeZoneLocalOption(aSkippedTime);
UTimeZoneLocalOption repeatedTime = ToUTimeZoneLocalOption(aRepeatedTime);
#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
int32_t rawOffset, dstOffset;
UErrorCode status = U_ZERO_ERROR;
// All ICU TimeZone classes derive from BasicTimeZone, so we can safely
// perform the static_cast.
// can remove this extra cast.
auto* basicTz = static_cast<icu::BasicTimeZone*>(mTimeZone.get());
basicTz->getOffsetFromLocal(date, skippedTime, repeatedTime, rawOffset,
dstOffset, status);
if (U_FAILURE(status)) {
return Err(ToICUError(status));
}
return rawOffset + dstOffset;
#else
UErrorCode status = U_ZERO_ERROR;
ucal_setMillis(mCalendar, date, &status);
if (U_FAILURE(status)) {
return Err(ToICUError(status));
}
int32_t rawOffset, dstOffset;
ucal_getTimeZoneOffsetFromLocal(mCalendar, skippedTime, repeatedTime,
&rawOffset, &dstOffset, &status);
if (U_FAILURE(status)) {
return Err(ToICUError(status));
}
return rawOffset + dstOffset;
#endif
}
Result<Maybe<int64_t>, ICUError> TimeZone::GetPreviousTransition(
int64_t aUTCMilliseconds) {
UDate date = UDate(aUTCMilliseconds);
#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
// All ICU TimeZone classes derive from BasicTimeZone, so we can safely
// perform the static_cast.
auto* basicTz = static_cast<icu::BasicTimeZone*>(mTimeZone.get());
constexpr bool inclusive = false;
icu::TimeZoneTransition transition;
if (!basicTz->getPreviousTransition(date, inclusive, transition)) {
return Maybe<int64_t>();
}
return Some(int64_t(transition.getTime()));
#else
UDate transition = 0;
UErrorCode status = U_ZERO_ERROR;
bool found = ucal_getTimeZoneTransitionDate(
mCalendar, UCAL_TZ_TRANSITION_PREVIOUS, &transition, &status);
if (U_FAILURE(status)) {
return Err(ToICUError(status));
}
if (!found) {
return Maybe<int64_t>();
}
return Some(int64_t(transition));
#endif
}
Result<Maybe<int64_t>, ICUError> TimeZone::GetNextTransition(
int64_t aUTCMilliseconds) {
UDate date = UDate(aUTCMilliseconds);
#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
// All ICU TimeZone classes derive from BasicTimeZone, so we can safely
// perform the static_cast.
auto* basicTz = static_cast<icu::BasicTimeZone*>(mTimeZone.get());
constexpr bool inclusive = false;
icu::TimeZoneTransition transition;
if (!basicTz->getNextTransition(date, inclusive, transition)) {
return Maybe<int64_t>();
}
return Some(int64_t(transition.getTime()));
#else
UDate transition = 0;
UErrorCode status = U_ZERO_ERROR;
bool found = ucal_getTimeZoneTransitionDate(
mCalendar, UCAL_TZ_TRANSITION_NEXT, &transition, &status);
if (U_FAILURE(status)) {
return Err(ToICUError(status));
}
if (!found) {
return Maybe<int64_t>();
}
return Some(int64_t(transition));
#endif
}
using TimeZoneIdentifierVector =
Vector<char16_t, TimeZone::TimeZoneIdentifierLength>;
#if !MOZ_INTL_USE_ICU_CPP_TIMEZONE
static bool IsUnknownTimeZone(const TimeZoneIdentifierVector& timeZone) {
constexpr std::string_view unknownTimeZone = UCAL_UNKNOWN_ZONE_ID;
return timeZone.length() == unknownTimeZone.length() &&
std::equal(timeZone.begin(), timeZone.end(), unknownTimeZone.begin(),
unknownTimeZone.end());
}
static ICUResult SetDefaultTimeZone(TimeZoneIdentifierVector& timeZone) {
// The string mustn't already be null-terminated.
MOZ_ASSERT_IF(!timeZone.empty(), timeZone.end()[-1] != '\0');
// The time zone identifier must be a null-terminated string.
if (!timeZone.append('\0')) {
return Err(ICUError::OutOfMemory);
}
UErrorCode status = U_ZERO_ERROR;
ucal_setDefaultTimeZone(timeZone.begin(), &status);
if (U_FAILURE(status)) {
return Err(ToICUError(status));
}
return Ok{};
}
#endif
Result<bool, ICUError> TimeZone::SetDefaultTimeZone(
Span<const char> aTimeZone) {
#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
icu::UnicodeString tzid(aTimeZone.data(), aTimeZone.size(), US_INV);
if (tzid.isBogus()) {
return Err(ICUError::OutOfMemory);
}
UniquePtr<icu::TimeZone> newTimeZone(icu::TimeZone::createTimeZone(tzid));
MOZ_ASSERT(newTimeZone);
if (*newTimeZone != icu::TimeZone::getUnknown()) {
// adoptDefault() takes ownership of the time zone.
icu::TimeZone::adoptDefault(newTimeZone.release());
return true;
}
#else
TimeZoneIdentifierVector tzid;
if (!tzid.append(aTimeZone.data(), aTimeZone.size())) {
return Err(ICUError::OutOfMemory);
}
// Retrieve the current default time zone in case we need to restore it.
TimeZoneIdentifierVector defaultTimeZone;
MOZ_TRY(FillBufferWithICUCall(defaultTimeZone, ucal_getDefaultTimeZone));
// Try to set the new time zone.
MOZ_TRY(mozilla::intl::SetDefaultTimeZone(tzid));
// Check if the time zone was actually applied.
TimeZoneIdentifierVector newTimeZone;
MOZ_TRY(FillBufferWithICUCall(newTimeZone, ucal_getDefaultTimeZone));
// Return if the new time zone was successfully applied.
if (!IsUnknownTimeZone(newTimeZone)) {
return true;
}
// Otherwise restore the original time zone.
MOZ_TRY(mozilla::intl::SetDefaultTimeZone(defaultTimeZone));
#endif
return false;
}
ICUResult TimeZone::SetDefaultTimeZoneFromHostTimeZone() {
#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
if (icu::TimeZone* defaultZone = icu::TimeZone::detectHostTimeZone()) {
icu::TimeZone::adoptDefault(defaultZone);
}
#else
TimeZoneIdentifierVector hostTimeZone;
MOZ_TRY(FillBufferWithICUCall(hostTimeZone, ucal_getHostTimeZone));
MOZ_TRY(mozilla::intl::SetDefaultTimeZone(hostTimeZone));
#endif
return Ok{};
}
Result<Span<const char>, ICUError> TimeZone::GetTZDataVersion() {
UErrorCode status = U_ZERO_ERROR;
const char* tzdataVersion = ucal_getTZDataVersion(&status);
if (U_FAILURE(status)) {
return Err(ToICUError(status));
}
return MakeStringSpan(tzdataVersion);
}
Result<SpanEnumeration<char>, ICUError> TimeZone::GetAvailableTimeZones(
const char* aRegion) {
// Get the time zones that are commonly used in the given region. Uses the
// UCAL_ZONE_TYPE_ANY filter so we have more fine-grained control over the
// returned time zones and don't omit time zones which are considered links in
// ICU, but are treated as proper zones in IANA.
UErrorCode status = U_ZERO_ERROR;
UEnumeration* enumeration = ucal_openTimeZoneIDEnumeration(
UCAL_ZONE_TYPE_ANY, aRegion, nullptr, &status);
if (U_FAILURE(status)) {
return Err(ToICUError(status));
}
return SpanEnumeration<char>(enumeration);
}
Result<SpanEnumeration<char>, ICUError> TimeZone::GetAvailableTimeZones() {
UErrorCode status = U_ZERO_ERROR;
UEnumeration* enumeration = ucal_openTimeZones(&status);
if (U_FAILURE(status)) {
return Err(ToICUError(status));
}
return SpanEnumeration<char>(enumeration);
}
#if !MOZ_INTL_USE_ICU_CPP_TIMEZONE
TimeZone::~TimeZone() {
MOZ_ASSERT(mCalendar);
ucal_close(mCalendar);
}
#endif
} // namespace mozilla::intl