diff --git a/intl/components/src/TimeZone.cpp b/intl/components/src/TimeZone.cpp index 1f44d0548c2e..af3dd079efa1 100644 --- a/intl/components/src/TimeZone.cpp +++ b/intl/components/src/TimeZone.cpp @@ -4,6 +4,11 @@ #include "mozilla/intl/TimeZone.h" +#include "mozilla/Vector.h" + +#include +#include + #include "unicode/uenum.h" #if MOZ_INTL_USE_ICU_CPP_TIMEZONE # include "unicode/basictz.h" @@ -208,6 +213,97 @@ Result TimeZone::GetUTCOffsetMs(int64_t aLocalMilliseconds) { #endif } +using TimeZoneIdentifierVector = + Vector; + +#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 TimeZone::SetDefaultTimeZone( + Span 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 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(FillVectorWithICUCall(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(FillVectorWithICUCall(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(FillVectorWithICUCall(hostTimeZone, ucal_getHostTimeZone)); + + MOZ_TRY(mozilla::intl::SetDefaultTimeZone(hostTimeZone)); +#endif + + return Ok{}; +} + Result, ICUError> TimeZone::GetAvailableTimeZones( const char* aRegion) { // Get the time zones that are commonly used in the given region. Uses the diff --git a/intl/components/src/TimeZone.h b/intl/components/src/TimeZone.h index 97a699202192..5150293b7bc2 100644 --- a/intl/components/src/TimeZone.h +++ b/intl/components/src/TimeZone.h @@ -143,6 +143,28 @@ class TimeZone final { return FillBufferWithICUCall(aBuffer, ucal_getDefaultTimeZone); } + /** + * Set the default time zone. + */ + static Result SetDefaultTimeZone(Span aTimeZone); + + /** + * Set the default time zone using the host system's time zone. + * + * NOTE: This function is not thread-safe. + */ + static ICUResult SetDefaultTimeZoneFromHostTimeZone(); + + /** + * Constant for the typical maximal length of a time zone identifier. + * + * At the time of this writing 32 characters fits every supported time zone: + * + * Intl.supportedValuesOf("timeZone") + * .reduce((acc, v) => Math.max(acc, v.length), 0) + */ + static constexpr size_t TimeZoneIdentifierLength = 32; + /** * Returns the canonical system time zone ID or the normalized custom time * zone ID for the given time zone ID. @@ -157,11 +179,7 @@ class TimeZone final { // ucal_getCanonicalTimeZoneID differs from other API calls and fails when // passed a nullptr or 0 length result. Reserve some space initially so // that a real pointer will be used in the API. - // - // At the time of this writing 32 characters fits every time zone listed - // in: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones - // https://gist.github.com/gregtatum/f926de157a44e5965864da866fe71e63 - if (!aBuffer.reserve(32)) { + if (!aBuffer.reserve(TimeZoneIdentifierLength)) { return Err(ICUError::OutOfMemory); } }