tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

TimeZone.cpp (13212B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "mozilla/intl/TimeZone.h"
      6 
      7 #include "mozilla/Vector.h"
      8 
      9 #include <algorithm>
     10 #include <string_view>
     11 
     12 #include "unicode/uenum.h"
     13 #if MOZ_INTL_USE_ICU_CPP_TIMEZONE
     14 #  include "unicode/basictz.h"
     15 #endif
     16 
     17 namespace mozilla::intl {
     18 
     19 /* static */
     20 Result<UniquePtr<TimeZone>, ICUError> TimeZone::TryCreate(
     21    Maybe<Span<const char>> aTimeZoneOverride) {
     22  const char* zoneID = nullptr;
     23  int32_t zoneIDLen = 0;
     24  if (aTimeZoneOverride) {
     25    zoneIDLen = static_cast<int32_t>(aTimeZoneOverride->Length());
     26    zoneID = aTimeZoneOverride->Elements();
     27  }
     28 
     29 #if MOZ_INTL_USE_ICU_CPP_TIMEZONE
     30  UniquePtr<icu::TimeZone> tz;
     31  if (zoneID) {
     32    tz.reset(icu::TimeZone::createTimeZone(
     33        icu::UnicodeString(zoneID, zoneIDLen, icu::UnicodeString::kInvariant)));
     34  } else {
     35    tz.reset(icu::TimeZone::createDefault());
     36  }
     37  MOZ_ASSERT(tz);
     38 
     39  if (*tz == icu::TimeZone::getUnknown()) {
     40    return Err(ICUError::InternalError);
     41  }
     42 
     43  return MakeUnique<TimeZone>(std::move(tz));
     44 #else
     45  const char16_t* zoneIDChar16 = nullptr;
     46  Vector<char16_t, TimeZoneIdentifierLength> zoneIDChars;
     47  if (zoneID) {
     48    if (!zoneIDChars.append(zoneID, zoneIDLen)) {
     49      return Err(ICUError::OutOfMemory);
     50    }
     51    zoneIDChar16 = zoneIDChars.begin();
     52  }
     53 
     54  // An empty string is used for the root locale. This is regarded as the base
     55  // locale of all locales, and is used as the language/country neutral locale
     56  // for locale sensitive operations.
     57  const char* rootLocale = "";
     58 
     59  UErrorCode status = U_ZERO_ERROR;
     60  UCalendar* calendar =
     61      ucal_open(zoneIDChar16, zoneIDLen, rootLocale, UCAL_DEFAULT, &status);
     62 
     63  if (U_FAILURE(status)) {
     64    return Err(ToICUError(status));
     65  }
     66 
     67  // https://tc39.es/ecma262/#sec-time-values-and-time-range
     68  //
     69  // A time value supports a slightly smaller range of -8,640,000,000,000,000 to
     70  // 8,640,000,000,000,000 milliseconds.
     71  constexpr double StartOfTime = -8.64e15;
     72 
     73  // Ensure all computations are performed in the proleptic Gregorian calendar.
     74  ucal_setGregorianChange(calendar, StartOfTime, &status);
     75 
     76  if (U_FAILURE(status)) {
     77    return Err(ToICUError(status));
     78  }
     79 
     80  return MakeUnique<TimeZone>(calendar);
     81 #endif
     82 }
     83 
     84 Result<int32_t, ICUError> TimeZone::GetRawOffsetMs() {
     85 #if MOZ_INTL_USE_ICU_CPP_TIMEZONE
     86  return mTimeZone->getRawOffset();
     87 #else
     88  // Reset the time in case the calendar has been modified.
     89  UErrorCode status = U_ZERO_ERROR;
     90  ucal_setMillis(mCalendar, ucal_getNow(), &status);
     91  if (U_FAILURE(status)) {
     92    return Err(ToICUError(status));
     93  }
     94 
     95  int32_t offset = ucal_get(mCalendar, UCAL_ZONE_OFFSET, &status);
     96  if (U_FAILURE(status)) {
     97    return Err(ToICUError(status));
     98  }
     99 
    100  return offset;
    101 #endif
    102 }
    103 
    104 Result<int32_t, ICUError> TimeZone::GetDSTOffsetMs(int64_t aUTCMilliseconds) {
    105  UDate date = UDate(aUTCMilliseconds);
    106 
    107 #if MOZ_INTL_USE_ICU_CPP_TIMEZONE
    108  constexpr bool dateIsLocalTime = false;
    109  int32_t rawOffset, dstOffset;
    110  UErrorCode status = U_ZERO_ERROR;
    111 
    112  mTimeZone->getOffset(date, dateIsLocalTime, rawOffset, dstOffset, status);
    113  if (U_FAILURE(status)) {
    114    return Err(ToICUError(status));
    115  }
    116 
    117  return dstOffset;
    118 #else
    119  UErrorCode status = U_ZERO_ERROR;
    120  ucal_setMillis(mCalendar, date, &status);
    121  if (U_FAILURE(status)) {
    122    return Err(ToICUError(status));
    123  }
    124 
    125  int32_t dstOffset = ucal_get(mCalendar, UCAL_DST_OFFSET, &status);
    126  if (U_FAILURE(status)) {
    127    return Err(ToICUError(status));
    128  }
    129 
    130  return dstOffset;
    131 #endif
    132 }
    133 
    134 Result<int32_t, ICUError> TimeZone::GetOffsetMs(int64_t aUTCMilliseconds) {
    135  UDate date = UDate(aUTCMilliseconds);
    136 
    137 #if MOZ_INTL_USE_ICU_CPP_TIMEZONE
    138  constexpr bool dateIsLocalTime = false;
    139  int32_t rawOffset, dstOffset;
    140  UErrorCode status = U_ZERO_ERROR;
    141 
    142  mTimeZone->getOffset(date, dateIsLocalTime, rawOffset, dstOffset, status);
    143  if (U_FAILURE(status)) {
    144    return Err(ToICUError(status));
    145  }
    146 
    147  return rawOffset + dstOffset;
    148 #else
    149  UErrorCode status = U_ZERO_ERROR;
    150  ucal_setMillis(mCalendar, date, &status);
    151  if (U_FAILURE(status)) {
    152    return Err(ToICUError(status));
    153  }
    154 
    155  int32_t rawOffset = ucal_get(mCalendar, UCAL_ZONE_OFFSET, &status);
    156  if (U_FAILURE(status)) {
    157    return Err(ToICUError(status));
    158  }
    159 
    160  int32_t dstOffset = ucal_get(mCalendar, UCAL_DST_OFFSET, &status);
    161  if (U_FAILURE(status)) {
    162    return Err(ToICUError(status));
    163  }
    164 
    165  return rawOffset + dstOffset;
    166 #endif
    167 }
    168 
    169 Result<int32_t, ICUError> TimeZone::GetUTCOffsetMs(int64_t aLocalMilliseconds) {
    170  // https://tc39.es/ecma262/#sec-local-time-zone-adjustment
    171  //
    172  // LocalTZA ( t, isUTC )
    173  //
    174  // When t_local represents local time repeating multiple times at a negative
    175  // time zone transition (e.g. when the daylight saving time ends or the time
    176  // zone offset is decreased due to a time zone rule change) or skipped local
    177  // time at a positive time zone transitions (e.g. when the daylight saving
    178  // time starts or the time zone offset is increased due to a time zone rule
    179  // change), t_local must be interpreted using the time zone offset before the
    180  // transition.
    181  constexpr LocalOption skippedTime = LocalOption::Former;
    182  constexpr LocalOption repeatedTime = LocalOption::Former;
    183 
    184  return GetUTCOffsetMs(aLocalMilliseconds, skippedTime, repeatedTime);
    185 }
    186 
    187 static UTimeZoneLocalOption ToUTimeZoneLocalOption(
    188    TimeZone::LocalOption aOption) {
    189  switch (aOption) {
    190    case TimeZone::LocalOption::Former:
    191      return UTimeZoneLocalOption::UCAL_TZ_LOCAL_FORMER;
    192    case TimeZone::LocalOption::Latter:
    193      return UTimeZoneLocalOption::UCAL_TZ_LOCAL_LATTER;
    194  }
    195  MOZ_CRASH("Unexpected TimeZone::LocalOption");
    196 }
    197 
    198 Result<int32_t, ICUError> TimeZone::GetUTCOffsetMs(int64_t aLocalMilliseconds,
    199                                                   LocalOption aSkippedTime,
    200                                                   LocalOption aRepeatedTime) {
    201  UDate date = UDate(aLocalMilliseconds);
    202  UTimeZoneLocalOption skippedTime = ToUTimeZoneLocalOption(aSkippedTime);
    203  UTimeZoneLocalOption repeatedTime = ToUTimeZoneLocalOption(aRepeatedTime);
    204 
    205 #if MOZ_INTL_USE_ICU_CPP_TIMEZONE
    206  int32_t rawOffset, dstOffset;
    207  UErrorCode status = U_ZERO_ERROR;
    208 
    209  // All ICU TimeZone classes derive from BasicTimeZone, so we can safely
    210  // perform the static_cast.
    211  // Once <https://unicode-org.atlassian.net/browse/ICU-13705> is fixed we
    212  // can remove this extra cast.
    213  auto* basicTz = static_cast<icu::BasicTimeZone*>(mTimeZone.get());
    214  basicTz->getOffsetFromLocal(date, skippedTime, repeatedTime, rawOffset,
    215                              dstOffset, status);
    216  if (U_FAILURE(status)) {
    217    return Err(ToICUError(status));
    218  }
    219 
    220  return rawOffset + dstOffset;
    221 #else
    222  UErrorCode status = U_ZERO_ERROR;
    223  ucal_setMillis(mCalendar, date, &status);
    224  if (U_FAILURE(status)) {
    225    return Err(ToICUError(status));
    226  }
    227 
    228  int32_t rawOffset, dstOffset;
    229  ucal_getTimeZoneOffsetFromLocal(mCalendar, skippedTime, repeatedTime,
    230                                  &rawOffset, &dstOffset, &status);
    231  if (U_FAILURE(status)) {
    232    return Err(ToICUError(status));
    233  }
    234 
    235  return rawOffset + dstOffset;
    236 #endif
    237 }
    238 
    239 Result<Maybe<int64_t>, ICUError> TimeZone::GetPreviousTransition(
    240    int64_t aUTCMilliseconds) {
    241  UDate date = UDate(aUTCMilliseconds);
    242 
    243 #if MOZ_INTL_USE_ICU_CPP_TIMEZONE
    244  // All ICU TimeZone classes derive from BasicTimeZone, so we can safely
    245  // perform the static_cast.
    246  auto* basicTz = static_cast<icu::BasicTimeZone*>(mTimeZone.get());
    247 
    248  constexpr bool inclusive = false;
    249  icu::TimeZoneTransition transition;
    250  if (!basicTz->getPreviousTransition(date, inclusive, transition)) {
    251    return Maybe<int64_t>();
    252  }
    253  return Some(int64_t(transition.getTime()));
    254 #else
    255  UDate transition = 0;
    256  UErrorCode status = U_ZERO_ERROR;
    257  bool found = ucal_getTimeZoneTransitionDate(
    258      mCalendar, UCAL_TZ_TRANSITION_PREVIOUS, &transition, &status);
    259  if (U_FAILURE(status)) {
    260    return Err(ToICUError(status));
    261  }
    262  if (!found) {
    263    return Maybe<int64_t>();
    264  }
    265  return Some(int64_t(transition));
    266 #endif
    267 }
    268 
    269 Result<Maybe<int64_t>, ICUError> TimeZone::GetNextTransition(
    270    int64_t aUTCMilliseconds) {
    271  UDate date = UDate(aUTCMilliseconds);
    272 
    273 #if MOZ_INTL_USE_ICU_CPP_TIMEZONE
    274  // All ICU TimeZone classes derive from BasicTimeZone, so we can safely
    275  // perform the static_cast.
    276  auto* basicTz = static_cast<icu::BasicTimeZone*>(mTimeZone.get());
    277 
    278  constexpr bool inclusive = false;
    279  icu::TimeZoneTransition transition;
    280  if (!basicTz->getNextTransition(date, inclusive, transition)) {
    281    return Maybe<int64_t>();
    282  }
    283  return Some(int64_t(transition.getTime()));
    284 #else
    285  UDate transition = 0;
    286  UErrorCode status = U_ZERO_ERROR;
    287  bool found = ucal_getTimeZoneTransitionDate(
    288      mCalendar, UCAL_TZ_TRANSITION_NEXT, &transition, &status);
    289  if (U_FAILURE(status)) {
    290    return Err(ToICUError(status));
    291  }
    292  if (!found) {
    293    return Maybe<int64_t>();
    294  }
    295  return Some(int64_t(transition));
    296 #endif
    297 }
    298 
    299 using TimeZoneIdentifierVector =
    300    Vector<char16_t, TimeZone::TimeZoneIdentifierLength>;
    301 
    302 #if !MOZ_INTL_USE_ICU_CPP_TIMEZONE
    303 static bool IsUnknownTimeZone(const TimeZoneIdentifierVector& timeZone) {
    304  constexpr std::string_view unknownTimeZone = UCAL_UNKNOWN_ZONE_ID;
    305 
    306  return timeZone.length() == unknownTimeZone.length() &&
    307         std::equal(timeZone.begin(), timeZone.end(), unknownTimeZone.begin(),
    308                    unknownTimeZone.end());
    309 }
    310 
    311 static ICUResult SetDefaultTimeZone(TimeZoneIdentifierVector& timeZone) {
    312  // The string mustn't already be null-terminated.
    313  MOZ_ASSERT_IF(!timeZone.empty(), timeZone.end()[-1] != '\0');
    314 
    315  // The time zone identifier must be a null-terminated string.
    316  if (!timeZone.append('\0')) {
    317    return Err(ICUError::OutOfMemory);
    318  }
    319 
    320  UErrorCode status = U_ZERO_ERROR;
    321  ucal_setDefaultTimeZone(timeZone.begin(), &status);
    322  if (U_FAILURE(status)) {
    323    return Err(ToICUError(status));
    324  }
    325 
    326  return Ok{};
    327 }
    328 #endif
    329 
    330 Result<bool, ICUError> TimeZone::SetDefaultTimeZone(
    331    Span<const char> aTimeZone) {
    332 #if MOZ_INTL_USE_ICU_CPP_TIMEZONE
    333  icu::UnicodeString tzid(aTimeZone.data(), aTimeZone.size(), US_INV);
    334  if (tzid.isBogus()) {
    335    return Err(ICUError::OutOfMemory);
    336  }
    337 
    338  UniquePtr<icu::TimeZone> newTimeZone(icu::TimeZone::createTimeZone(tzid));
    339  MOZ_ASSERT(newTimeZone);
    340 
    341  if (*newTimeZone != icu::TimeZone::getUnknown()) {
    342    // adoptDefault() takes ownership of the time zone.
    343    icu::TimeZone::adoptDefault(newTimeZone.release());
    344    return true;
    345  }
    346 #else
    347  TimeZoneIdentifierVector tzid;
    348  if (!tzid.append(aTimeZone.data(), aTimeZone.size())) {
    349    return Err(ICUError::OutOfMemory);
    350  }
    351 
    352  // Retrieve the current default time zone in case we need to restore it.
    353  TimeZoneIdentifierVector defaultTimeZone;
    354  MOZ_TRY(FillBufferWithICUCall(defaultTimeZone, ucal_getDefaultTimeZone));
    355 
    356  // Try to set the new time zone.
    357  MOZ_TRY(mozilla::intl::SetDefaultTimeZone(tzid));
    358 
    359  // Check if the time zone was actually applied.
    360  TimeZoneIdentifierVector newTimeZone;
    361  MOZ_TRY(FillBufferWithICUCall(newTimeZone, ucal_getDefaultTimeZone));
    362 
    363  // Return if the new time zone was successfully applied.
    364  if (!IsUnknownTimeZone(newTimeZone)) {
    365    return true;
    366  }
    367 
    368  // Otherwise restore the original time zone.
    369  MOZ_TRY(mozilla::intl::SetDefaultTimeZone(defaultTimeZone));
    370 #endif
    371 
    372  return false;
    373 }
    374 
    375 ICUResult TimeZone::SetDefaultTimeZoneFromHostTimeZone() {
    376 #if MOZ_INTL_USE_ICU_CPP_TIMEZONE
    377  if (icu::TimeZone* defaultZone = icu::TimeZone::detectHostTimeZone()) {
    378    icu::TimeZone::adoptDefault(defaultZone);
    379  }
    380 #else
    381  TimeZoneIdentifierVector hostTimeZone;
    382  MOZ_TRY(FillBufferWithICUCall(hostTimeZone, ucal_getHostTimeZone));
    383 
    384  MOZ_TRY(mozilla::intl::SetDefaultTimeZone(hostTimeZone));
    385 #endif
    386 
    387  return Ok{};
    388 }
    389 
    390 Result<Span<const char>, ICUError> TimeZone::GetTZDataVersion() {
    391  UErrorCode status = U_ZERO_ERROR;
    392  const char* tzdataVersion = ucal_getTZDataVersion(&status);
    393  if (U_FAILURE(status)) {
    394    return Err(ToICUError(status));
    395  }
    396  return MakeStringSpan(tzdataVersion);
    397 }
    398 
    399 Result<SpanEnumeration<char>, ICUError> TimeZone::GetAvailableTimeZones(
    400    const char* aRegion) {
    401  // Get the time zones that are commonly used in the given region. Uses the
    402  // UCAL_ZONE_TYPE_ANY filter so we have more fine-grained control over the
    403  // returned time zones and don't omit time zones which are considered links in
    404  // ICU, but are treated as proper zones in IANA.
    405  UErrorCode status = U_ZERO_ERROR;
    406  UEnumeration* enumeration = ucal_openTimeZoneIDEnumeration(
    407      UCAL_ZONE_TYPE_ANY, aRegion, nullptr, &status);
    408  if (U_FAILURE(status)) {
    409    return Err(ToICUError(status));
    410  }
    411 
    412  return SpanEnumeration<char>(enumeration);
    413 }
    414 
    415 Result<SpanEnumeration<char>, ICUError> TimeZone::GetAvailableTimeZones() {
    416  UErrorCode status = U_ZERO_ERROR;
    417  UEnumeration* enumeration = ucal_openTimeZones(&status);
    418  if (U_FAILURE(status)) {
    419    return Err(ToICUError(status));
    420  }
    421 
    422  return SpanEnumeration<char>(enumeration);
    423 }
    424 
    425 #if !MOZ_INTL_USE_ICU_CPP_TIMEZONE
    426 TimeZone::~TimeZone() {
    427  MOZ_ASSERT(mCalendar);
    428  ucal_close(mCalendar);
    429 }
    430 #endif
    431 
    432 }  // namespace mozilla::intl