/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * 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 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "vm/DateTime.h" #include "mozilla/ScopeExit.h" #include "mozilla/TextUtils.h" #include "mozilla/Unused.h" #include #include #include #include #include #if !defined(XP_WIN) # include # include #endif /* !defined(XP_WIN) */ #include "js/Date.h" #include "js/GCAPI.h" #include "threading/ExclusiveData.h" #if JS_HAS_INTL_API && !MOZ_SYSTEM_ICU # include "unicode/basictz.h" # include "unicode/locid.h" # include "unicode/timezone.h" # include "unicode/unistr.h" #endif /* JS_HAS_INTL_API && !MOZ_SYSTEM_ICU */ #include "util/Text.h" #include "vm/MutexIDs.h" static bool ComputeLocalTime(time_t local, struct tm* ptm) { #if defined(_WIN32) return localtime_s(ptm, &local) == 0; #elif defined(HAVE_LOCALTIME_R) return localtime_r(&local, ptm); #else struct tm* otm = localtime(&local); if (!otm) { return false; } *ptm = *otm; return true; #endif } static bool ComputeUTCTime(time_t t, struct tm* ptm) { #if defined(_WIN32) return gmtime_s(ptm, &t) == 0; #elif defined(HAVE_GMTIME_R) return gmtime_r(&t, ptm); #else struct tm* otm = gmtime(&t); if (!otm) { return false; } *ptm = *otm; return true; #endif } /* * Compute the offset in seconds from the current UTC time to the current local * standard time (i.e. not including any offset due to DST). * * Examples: * * Suppose we are in California, USA on January 1, 2013 at 04:00 PST (UTC-8, no * DST in effect), corresponding to 12:00 UTC. This function would then return * -8 * SecondsPerHour, or -28800. * * Or suppose we are in Berlin, Germany on July 1, 2013 at 17:00 CEST (UTC+2, * DST in effect), corresponding to 15:00 UTC. This function would then return * +1 * SecondsPerHour, or +3600. */ static int32_t UTCToLocalStandardOffsetSeconds() { using js::SecondsPerDay; using js::SecondsPerHour; using js::SecondsPerMinute; // Get the current time. time_t currentMaybeWithDST = time(nullptr); if (currentMaybeWithDST == time_t(-1)) { return 0; } // Break down the current time into its (locally-valued, maybe with DST) // components. struct tm local; if (!ComputeLocalTime(currentMaybeWithDST, &local)) { return 0; } // Compute a |time_t| corresponding to |local| interpreted without DST. time_t currentNoDST; if (local.tm_isdst == 0) { // If |local| wasn't DST, we can use the same time. currentNoDST = currentMaybeWithDST; } else { // If |local| respected DST, we need a time broken down into components // ignoring DST. Turn off DST in the broken-down time. Create a fresh // copy of |local|, because mktime() will reset tm_isdst = 1 and will // adjust tm_hour and tm_hour accordingly. struct tm localNoDST = local; localNoDST.tm_isdst = 0; // Compute a |time_t t| corresponding to the broken-down time with DST // off. This has boundary-condition issues (for about the duration of // a DST offset) near the time a location moves to a different time // zone. But 1) errors will be transient; 2) locations rarely change // time zone; and 3) in the absence of an API that provides the time // zone offset directly, this may be the best we can do. currentNoDST = mktime(&localNoDST); if (currentNoDST == time_t(-1)) { return 0; } } // Break down the time corresponding to the no-DST |local| into UTC-based // components. struct tm utc; if (!ComputeUTCTime(currentNoDST, &utc)) { return 0; } // Finally, compare the seconds-based components of the local non-DST // representation and the UTC representation to determine the actual // difference. int utc_secs = utc.tm_hour * SecondsPerHour + utc.tm_min * SecondsPerMinute; int local_secs = local.tm_hour * SecondsPerHour + local.tm_min * SecondsPerMinute; // Same-day? Just subtract the seconds counts. if (utc.tm_mday == local.tm_mday) { return local_secs - utc_secs; } // If we have more UTC seconds, move local seconds into the UTC seconds' // frame of reference and then subtract. if (utc_secs > local_secs) { return (SecondsPerDay + local_secs) - utc_secs; } // Otherwise we have more local seconds, so move the UTC seconds into the // local seconds' frame of reference and then subtract. return local_secs - (utc_secs + SecondsPerDay); } void js::DateTimeInfo::internalResetTimeZone(ResetTimeZoneMode mode) { // Nothing to do when an update request is already enqueued. if (timeZoneStatus_ == TimeZoneStatus::NeedsUpdate) { return; } // Mark the state as needing an update, but defer the actual update until it's // actually needed to delay any system calls to the last possible moment. This // is beneficial when this method is called during start-up, because it avoids // main-thread I/O blocking the process. if (mode == ResetTimeZoneMode::ResetEvenIfOffsetUnchanged) { timeZoneStatus_ = TimeZoneStatus::NeedsUpdate; } else { timeZoneStatus_ = TimeZoneStatus::UpdateIfChanged; } } void js::DateTimeInfo::updateTimeZone() { MOZ_ASSERT(timeZoneStatus_ != TimeZoneStatus::Valid); bool updateIfChanged = timeZoneStatus_ == TimeZoneStatus::UpdateIfChanged; timeZoneStatus_ = TimeZoneStatus::Valid; /* * The difference between local standard time and UTC will never change for * a given time zone. */ int32_t newOffset = UTCToLocalStandardOffsetSeconds(); if (updateIfChanged && newOffset == utcToLocalStandardOffsetSeconds_) { return; } utcToLocalStandardOffsetSeconds_ = newOffset; dstRange_.reset(); #if JS_HAS_INTL_API && !MOZ_SYSTEM_ICU utcRange_.reset(); localRange_.reset(); { // Tell the analysis the |pFree| function pointer called by uprv_free // cannot GC. JS::AutoSuppressGCAnalysis nogc; timeZone_ = nullptr; } standardName_ = nullptr; daylightSavingsName_ = nullptr; #endif /* JS_HAS_INTL_API && !MOZ_SYSTEM_ICU */ // Propagate the time zone change to ICU, too. { // Tell the analysis calling into ICU cannot GC. JS::AutoSuppressGCAnalysis nogc; internalResyncICUDefaultTimeZone(); } } js::DateTimeInfo::DateTimeInfo() { // Set the time zone status into the invalid state, so we compute the actual // defaults on first access. We don't yet want to initialize neither // nor ICU's time zone classes, because that may cause I/O operations slowing // down the JS engine initialization, which we're currently in the middle of. timeZoneStatus_ = TimeZoneStatus::NeedsUpdate; } js::DateTimeInfo::~DateTimeInfo() = default; int64_t js::DateTimeInfo::toClampedSeconds(int64_t milliseconds) { int64_t seconds = milliseconds / msPerSecond; if (seconds > MaxTimeT) { seconds = MaxTimeT; } else if (seconds < MinTimeT) { /* Go ahead a day to make localtime work (does not work with 0). */ seconds = SecondsPerDay; } return seconds; } int32_t js::DateTimeInfo::computeDSTOffsetMilliseconds(int64_t utcSeconds) { MOZ_ASSERT(utcSeconds >= MinTimeT); MOZ_ASSERT(utcSeconds <= MaxTimeT); #if JS_HAS_INTL_API && !MOZ_SYSTEM_ICU UDate date = UDate(utcSeconds * msPerSecond); constexpr bool dateIsLocalTime = false; int32_t rawOffset, dstOffset; UErrorCode status = U_ZERO_ERROR; timeZone()->getOffset(date, dateIsLocalTime, rawOffset, dstOffset, status); if (U_FAILURE(status)) { return 0; } return dstOffset; #else struct tm tm; if (!ComputeLocalTime(static_cast(utcSeconds), &tm)) { return 0; } // NB: The offset isn't computed correctly when the standard local offset // at |utcSeconds| is different from |utcToLocalStandardOffsetSeconds|. int32_t dayoff = int32_t((utcSeconds + utcToLocalStandardOffsetSeconds_) % SecondsPerDay); int32_t tmoff = tm.tm_sec + (tm.tm_min * SecondsPerMinute) + (tm.tm_hour * SecondsPerHour); int32_t diff = tmoff - dayoff; if (diff < 0) { diff += SecondsPerDay; } else if (uint32_t(diff) >= SecondsPerDay) { diff -= SecondsPerDay; } return diff * msPerSecond; #endif /* JS_HAS_INTL_API && !MOZ_SYSTEM_ICU */ } int32_t js::DateTimeInfo::internalGetDSTOffsetMilliseconds( int64_t utcMilliseconds) { int64_t utcSeconds = toClampedSeconds(utcMilliseconds); return getOrComputeValue(dstRange_, utcSeconds, &DateTimeInfo::computeDSTOffsetMilliseconds); } int32_t js::DateTimeInfo::getOrComputeValue(RangeCache& range, int64_t seconds, ComputeFn compute) { range.sanityCheck(); auto checkSanity = mozilla::MakeScopeExit([&range]() { range.sanityCheck(); }); // NB: Be aware of the initial range values when making changes to this // code: the first call to this method, with those initial range // values, must result in a cache miss. MOZ_ASSERT(seconds != INT64_MIN); if (range.startSeconds <= seconds && seconds <= range.endSeconds) { return range.offsetMilliseconds; } if (range.oldStartSeconds <= seconds && seconds <= range.oldEndSeconds) { return range.oldOffsetMilliseconds; } range.oldOffsetMilliseconds = range.offsetMilliseconds; range.oldStartSeconds = range.startSeconds; range.oldEndSeconds = range.endSeconds; if (range.startSeconds <= seconds) { int64_t newEndSeconds = std::min({range.endSeconds + RangeExpansionAmount, MaxTimeT}); if (newEndSeconds >= seconds) { int32_t endOffsetMilliseconds = (this->*compute)(newEndSeconds); if (endOffsetMilliseconds == range.offsetMilliseconds) { range.endSeconds = newEndSeconds; return range.offsetMilliseconds; } range.offsetMilliseconds = (this->*compute)(seconds); if (range.offsetMilliseconds == endOffsetMilliseconds) { range.startSeconds = seconds; range.endSeconds = newEndSeconds; } else { range.endSeconds = seconds; } return range.offsetMilliseconds; } range.offsetMilliseconds = (this->*compute)(seconds); range.startSeconds = range.endSeconds = seconds; return range.offsetMilliseconds; } int64_t newStartSeconds = std::max({range.startSeconds - RangeExpansionAmount, MinTimeT}); if (newStartSeconds <= seconds) { int32_t startOffsetMilliseconds = (this->*compute)(newStartSeconds); if (startOffsetMilliseconds == range.offsetMilliseconds) { range.startSeconds = newStartSeconds; return range.offsetMilliseconds; } range.offsetMilliseconds = (this->*compute)(seconds); if (range.offsetMilliseconds == startOffsetMilliseconds) { range.startSeconds = newStartSeconds; range.endSeconds = seconds; } else { range.startSeconds = seconds; } return range.offsetMilliseconds; } range.startSeconds = range.endSeconds = seconds; range.offsetMilliseconds = (this->*compute)(seconds); return range.offsetMilliseconds; } void js::DateTimeInfo::RangeCache::reset() { // The initial range values are carefully chosen to result in a cache miss // on first use given the range of possible values. Be careful to keep // these values and the caching algorithm in sync! offsetMilliseconds = 0; startSeconds = endSeconds = INT64_MIN; oldOffsetMilliseconds = 0; oldStartSeconds = oldEndSeconds = INT64_MIN; sanityCheck(); } void js::DateTimeInfo::RangeCache::sanityCheck() { auto assertRange = [](int64_t start, int64_t end) { MOZ_ASSERT(start <= end); MOZ_ASSERT_IF(start == INT64_MIN, end == INT64_MIN); MOZ_ASSERT_IF(end == INT64_MIN, start == INT64_MIN); MOZ_ASSERT_IF(start != INT64_MIN, start >= MinTimeT && end >= MinTimeT); MOZ_ASSERT_IF(start != INT64_MIN, start <= MaxTimeT && end <= MaxTimeT); }; assertRange(startSeconds, endSeconds); assertRange(oldStartSeconds, oldEndSeconds); } #if JS_HAS_INTL_API && !MOZ_SYSTEM_ICU int32_t js::DateTimeInfo::computeUTCOffsetMilliseconds(int64_t localSeconds) { MOZ_ASSERT(localSeconds >= MinTimeT); MOZ_ASSERT(localSeconds <= MaxTimeT); UDate date = UDate(localSeconds * msPerSecond); // ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557 // // 20.3.1.7 LocalTZA // // If |localSeconds| represents either a skipped (at a positive time zone // transition) or repeated (at a negative time zone transition) locale // time, it must be interpreted as a time value before the transition. constexpr int32_t skippedTime = icu::BasicTimeZone::kFormer; constexpr int32_t repeatedTime = icu::BasicTimeZone::kFormer; int32_t rawOffset, dstOffset; UErrorCode status = U_ZERO_ERROR; // All ICU TimeZone classes derive from BasicTimeZone, so we can safely // perform the static_cast. // Once is fixed we // can remove this extra cast. auto* basicTz = static_cast(timeZone()); basicTz->getOffsetFromLocal(date, skippedTime, repeatedTime, rawOffset, dstOffset, status); if (U_FAILURE(status)) { return 0; } return rawOffset + dstOffset; } int32_t js::DateTimeInfo::computeLocalOffsetMilliseconds(int64_t utcSeconds) { MOZ_ASSERT(utcSeconds >= MinTimeT); MOZ_ASSERT(utcSeconds <= MaxTimeT); UDate date = UDate(utcSeconds * msPerSecond); constexpr bool dateIsLocalTime = false; int32_t rawOffset, dstOffset; UErrorCode status = U_ZERO_ERROR; timeZone()->getOffset(date, dateIsLocalTime, rawOffset, dstOffset, status); if (U_FAILURE(status)) { return 0; } return rawOffset + dstOffset; } int32_t js::DateTimeInfo::internalGetOffsetMilliseconds(int64_t milliseconds, TimeZoneOffset offset) { int64_t seconds = toClampedSeconds(milliseconds); return offset == TimeZoneOffset::UTC ? getOrComputeValue(localRange_, seconds, &DateTimeInfo::computeLocalOffsetMilliseconds) : getOrComputeValue(utcRange_, seconds, &DateTimeInfo::computeUTCOffsetMilliseconds); } bool js::DateTimeInfo::internalTimeZoneDisplayName(char16_t* buf, size_t buflen, int64_t utcMilliseconds, const char* locale) { MOZ_ASSERT(buf != nullptr); MOZ_ASSERT(buflen > 0); MOZ_ASSERT(locale != nullptr); // Clear any previously cached names when the default locale changed. if (!locale_ || std::strcmp(locale_.get(), locale) != 0) { locale_ = DuplicateString(locale); if (!locale_) { return false; } standardName_.reset(); daylightSavingsName_.reset(); } bool daylightSavings = internalGetDSTOffsetMilliseconds(utcMilliseconds) != 0; JS::UniqueTwoByteChars& cachedName = daylightSavings ? daylightSavingsName_ : standardName_; if (!cachedName) { // Retrieve the display name for the given locale. icu::UnicodeString displayName; timeZone()->getDisplayName(daylightSavings, icu::TimeZone::LONG, icu::Locale(locale), displayName); size_t capacity = displayName.length() + 1; // Null-terminate. JS::UniqueTwoByteChars displayNameChars(js_pod_malloc(capacity)); if (!displayNameChars) { return false; } // Copy the display name. This operation always succeeds because the // destination buffer is large enough to hold the complete string. UErrorCode status = U_ZERO_ERROR; displayName.extract(displayNameChars.get(), capacity, status); MOZ_ASSERT(U_SUCCESS(status)); MOZ_ASSERT(displayNameChars[capacity - 1] == '\0'); cachedName = std::move(displayNameChars); } // Return an empty string if the display name doesn't fit into the buffer. size_t length = js_strlen(cachedName.get()); if (length < buflen) { std::copy(cachedName.get(), cachedName.get() + length, buf); } else { length = 0; } buf[length] = '\0'; return true; } icu::TimeZone* js::DateTimeInfo::timeZone() { if (!timeZone_) { timeZone_.reset(icu::TimeZone::createDefault()); MOZ_ASSERT(timeZone_); } return timeZone_.get(); } #endif /* JS_HAS_INTL_API && !MOZ_SYSTEM_ICU */ /* static */ js::ExclusiveData* js::DateTimeInfo::instance; bool js::InitDateTimeState() { MOZ_ASSERT(!DateTimeInfo::instance, "we should be initializing only once"); DateTimeInfo::instance = js_new>(mutexid::DateTimeInfoMutex); return !!DateTimeInfo::instance; } /* static */ void js::FinishDateTimeState() { js_delete(DateTimeInfo::instance); DateTimeInfo::instance = nullptr; } void js::ResetTimeZoneInternal(ResetTimeZoneMode mode) { js::DateTimeInfo::resetTimeZone(mode); } JS_PUBLIC_API void JS::ResetTimeZone() { js::ResetTimeZoneInternal(js::ResetTimeZoneMode::ResetEvenIfOffsetUnchanged); } #if defined(XP_WIN) static bool IsOlsonCompatibleWindowsTimeZoneId(const char* tz) { // ICU ignores the TZ environment variable on Windows and instead directly // invokes Win API functions to retrieve the current time zone. But since // we're still using the POSIX-derived localtime_s() function on Windows // and localtime_s() does return a time zone adjusted value based on the // TZ environment variable, we need to manually adjust the default ICU // time zone if TZ is set. // // Windows supports the following format for TZ: tzn[+|-]hh[:mm[:ss]][dzn] // where "tzn" is the time zone name for standard time, the time zone // offset is positive for time zones west of GMT, and "dzn" is the // optional time zone name when daylight savings are observed. Daylight // savings are always based on the U.S. daylight saving rules, that means // for example it's not possible to use "TZ=CET-1CEST" to select the IANA // time zone "CET". // // When comparing this restricted format for TZ to all IANA time zone // names, the following time zones are in the intersection of what's // supported by Windows and is also a valid IANA time zone identifier. // // Even though the time zone offset is marked as mandatory on MSDN, it // appears it defaults to zero when omitted. This in turn means we can // also allow the time zone identifiers "UCT", "UTC", and "GMT". static const char* const allowedIds[] = { // From tzdata's "northamerica" file: "EST5EDT", "CST6CDT", "MST7MDT", "PST8PDT", // From tzdata's "backward" file: "GMT+0", "GMT-0", "GMT0", "UCT", "UTC", // From tzdata's "etcetera" file: "GMT", }; for (const auto& allowedId : allowedIds) { if (std::strcmp(allowedId, tz) == 0) { return true; } } return false; } #elif JS_HAS_INTL_API && !MOZ_SYSTEM_ICU static inline const char* TZContainsAbsolutePath(const char* tzVar) { // A TZ environment variable may be an absolute path. The path // format of TZ may begin with a colon. (ICU handles relative paths.) if (tzVar[0] == ':' && tzVar[1] == '/') { return tzVar + 1; } if (tzVar[0] == '/') { return tzVar; } return nullptr; } /** * Reject the input if it doesn't match the time zone id pattern or legacy time * zone names. * * See . */ static icu::UnicodeString MaybeTimeZoneId(const char* timeZone) { size_t timeZoneLen = std::strlen(timeZone); for (size_t i = 0; i < timeZoneLen; i++) { char c = timeZone[i]; // According to theory.html, '.' is allowed in time zone ids, but the // accompanying zic.c file doesn't allow it. Assume the source file is // correct and disallow '.' here, too. if (mozilla::IsAsciiAlphanumeric(c) || c == '_' || c == '-' || c == '+') { continue; } // Reject leading, trailing, or consecutive '/' characters. if (c == '/' && i > 0 && i + 1 < timeZoneLen && timeZone[i + 1] != '/') { continue; } return icu::UnicodeString(); } return icu::UnicodeString(timeZone, timeZoneLen, US_INV); } /** * Given a presumptive path |tz| to a zoneinfo time zone file * (e.g. /etc/localtime), attempt to compute the time zone encoded by that * path by repeatedly resolving symlinks until a path containing "/zoneinfo/" * followed by time zone looking components is found. If a symlink is broken, * symlink-following recurs too deeply, non time zone looking components are * encountered, or some other error is encountered, return the empty string. * * If a non-empty string is returned, it's only guaranteed to have certain * syntactic validity. It might not actually *be* a time zone name. */ static icu::UnicodeString ReadTimeZoneLink(const char* tz) { // The resolved link name can have different paths depending on the OS. // Follow ICU and only search for "/zoneinfo/"; see $ICU/common/putil.cpp. static constexpr char ZoneInfoPath[] = "/zoneinfo/"; constexpr size_t ZoneInfoPathLength = js_strlen(ZoneInfoPath); // Stop following symlinks after a fixed depth, because some common time // zones are stored in files whose name doesn't match an Olson time zone // name. For example on Ubuntu, "/usr/share/zoneinfo/America/New_York" is a // symlink to "/usr/share/zoneinfo/posixrules" and "posixrules" is not an // Olson time zone name. // Four hops should be a reasonable limit for most use cases. constexpr uint32_t FollowDepthLimit = 4; # ifdef PATH_MAX constexpr size_t PathMax = PATH_MAX; # else constexpr size_t PathMax = 4096; # endif static_assert(PathMax > 0, "PathMax should be larger than zero"); char linkName[PathMax]; constexpr size_t linkNameLen = std::size(linkName) - 1; // -1 to null-terminate. // Return if the TZ value is too large. if (std::strlen(tz) > linkNameLen) { return icu::UnicodeString(); } std::strcpy(linkName, tz); char linkTarget[PathMax]; constexpr size_t linkTargetLen = std::size(linkTarget) - 1; // -1 to null-terminate. uint32_t depth = 0; // Search until we find "/zoneinfo/" in the link name. const char* timeZoneWithZoneInfo; while (!(timeZoneWithZoneInfo = std::strstr(linkName, ZoneInfoPath))) { // Return if the symlink nesting is too deep. if (++depth > FollowDepthLimit) { return icu::UnicodeString(); } // Return on error or if the result was truncated. ssize_t slen = readlink(linkName, linkTarget, linkTargetLen); if (slen < 0 || size_t(slen) >= linkTargetLen) { return icu::UnicodeString(); } // Ensure linkTarget is null-terminated. (readlink may not necessarily // null-terminate the string.) size_t len = size_t(slen); linkTarget[len] = '\0'; // If the target is absolute, continue with that. if (linkTarget[0] == '/') { std::strcpy(linkName, linkTarget); continue; } // If the target is relative, it must be resolved against either the // directory the link was in, or against the current working directory. char* separator = std::strrchr(linkName, '/'); // If the link name is just something like "foo", resolve linkTarget // against the current working directory. if (!separator) { std::strcpy(linkName, linkTarget); continue; } // Remove everything after the final path separator in linkName. separator[1] = '\0'; // Return if the concatenated path name is too large. if (std::strlen(linkName) + len > linkNameLen) { return icu::UnicodeString(); } // Keep it simple and just concatenate the path names. std::strcat(linkName, linkTarget); } const char* timeZone = timeZoneWithZoneInfo + ZoneInfoPathLength; return MaybeTimeZoneId(timeZone); } #endif /* JS_HAS_INTL_API && !MOZ_SYSTEM_ICU */ void js::ResyncICUDefaultTimeZone() { js::DateTimeInfo::resyncICUDefaultTimeZone(); } void js::DateTimeInfo::internalResyncICUDefaultTimeZone() { #if JS_HAS_INTL_API && !MOZ_SYSTEM_ICU if (const char* tz = std::getenv("TZ")) { icu::UnicodeString tzid; # if defined(XP_WIN) // If TZ is set and its value is valid under Windows' and IANA's time zone // identifier rules, update the ICU default time zone to use this value. if (IsOlsonCompatibleWindowsTimeZoneId(tz)) { tzid.setTo(icu::UnicodeString(tz, -1, US_INV)); } else { // If |tz| isn't a supported time zone identifier, use the default Windows // time zone for ICU. // TODO: Handle invalid time zone identifiers (bug 342068). } # else // The TZ environment variable allows both absolute and relative paths, // optionally beginning with a colon (':'). (Relative paths, without the // colon, are just Olson time zone names.) We need to handle absolute paths // ourselves, including handling that they might be symlinks. // if (const char* tzlink = TZContainsAbsolutePath(tz)) { tzid.setTo(ReadTimeZoneLink(tzlink)); } # ifdef ANDROID // ICU ignores the TZ environment variable on Android. If it doesn't contain // an absolute path, try to parse it as a time zone name. else { tzid.setTo(MaybeTimeZoneId(tz)); } # endif # endif /* defined(XP_WIN) */ if (!tzid.isEmpty()) { mozilla::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; } } } if (icu::TimeZone* defaultZone = icu::TimeZone::detectHostTimeZone()) { icu::TimeZone::adoptDefault(defaultZone); } #endif }