# HG changeset patch # User Andre Bargull # Date 1536656519 25200 # Node ID ab00be15ab8ff5a891aeb2b463ee2092040afc35 # Parent 2dc748223c1bbac49202a4c87f7e94969d25226a Bug 1478370: Detect and handle the TZ=:/etc/localtime case for ICU. r=Waldo diff --git a/js/src/tests/non262/Date/time-zone-2038-pst.js b/js/src/tests/non262/Date/time-zone-2038-pst.js --- a/js/src/tests/non262/Date/time-zone-2038-pst.js +++ b/js/src/tests/non262/Date/time-zone-2038-pst.js @@ -1,13 +1,12 @@ // |reftest| skip-if(!xulRuntime.shell) -// Note: The default time zone is set to PST8PDT for all jstests (when run in the shell). - -assertEq(/^(PST|PDT)$/.test(getTimeZone()), true); +assertEq(/^(PST|PDT)$/.test(getTimeZone()), true, + "The default time zone is set to PST8PDT for all jstests (when run in the shell)"); // U.S. daylight saving rules changed in 2007, excerpt from tzdata's // northamerica file: // NAME FROM TO IN ON AT SAVE LETTER/S // US 1967 2006 Oct lastSun 2:00 0 S // US 1967 1973 Apr lastSun 2:00 1:00 D // US 1974 only Jan 6 2:00 1:00 D // US 1975 only Feb 23 2:00 1:00 D diff --git a/js/src/tests/non262/Date/time-zone-etc_localetime.js b/js/src/tests/non262/Date/time-zone-etc_localetime.js new file mode 100644 --- /dev/null +++ b/js/src/tests/non262/Date/time-zone-etc_localetime.js @@ -0,0 +1,29 @@ +// |reftest| skip-if(xulRuntime.OS=="WINNT"||!xulRuntime.shell) + +assertEq(/^(PST|PDT)$/.test(getTimeZone()), true, + "The default time zone is set to PST8PDT for all jstests (when run in the shell)"); + +function timeZoneName() { + var dtf = new Intl.DateTimeFormat("en-US", {timeZoneName: "long"}); + return dtf.formatToParts().filter(x => x.type === "timeZoneName")[0].value; +} + +// Calling setTimeZone() with an undefined argument clears the TZ environment +// variable and by that reveal the actual system time zone. +setTimeZone(undefined); +var systemTimeZone = getTimeZone(); +var systemTimeZoneName = timeZoneName(); + +// Set to an unlikely system time zone, so that the next call to setTimeZone() +// will lead to a time zone change. +setTimeZone("Antarctica/Troll"); + +// Now call with the file path ":/etc/localtime" which is special-cased in +// DateTimeInfo to read the system time zone. +setTimeZone(":/etc/localtime"); + +assertEq(getTimeZone(), systemTimeZone); +assertEq(timeZoneName(), systemTimeZoneName); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/Date/time-zone-pst.js b/js/src/tests/non262/Date/time-zone-pst.js --- a/js/src/tests/non262/Date/time-zone-pst.js +++ b/js/src/tests/non262/Date/time-zone-pst.js @@ -1,13 +1,12 @@ // |reftest| skip-if(!xulRuntime.shell) -// Note: The default time zone is set to PST8PDT for all jstests (when run in the shell). - -assertEq(/^(PST|PDT)$/.test(getTimeZone()), true); +assertEq(/^(PST|PDT)$/.test(getTimeZone()), true, + "The default time zone is set to PST8PDT for all jstests (when run in the shell)"); const msPerMinute = 60 * 1000; function shortTimeZone(str) { return str.replace("(Pacific Standard Time)", "(PST)") .replace("(Pacific Daylight Time)", "(PDT)"); } diff --git a/js/src/vm/DateTime.cpp b/js/src/vm/DateTime.cpp --- a/js/src/vm/DateTime.cpp +++ b/js/src/vm/DateTime.cpp @@ -1,26 +1,31 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * 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/ArrayUtils.h" #include "mozilla/ScopeExit.h" +#include "mozilla/TextUtils.h" #include "mozilla/Unused.h" #include -#if defined(XP_WIN) #include -#endif /* defined(XP_WIN) */ #include #include +#if !defined(XP_WIN) +#include +#include +#endif /* !defined(XP_WIN) */ + #include "jsutil.h" #include "js/Date.h" #include "threading/ExclusiveData.h" #if ENABLE_INTL_API && !MOZ_SYSTEM_ICU #include "unicode/basictz.h" #include "unicode/locid.h" @@ -504,26 +509,30 @@ js::InitDateTimeState() if (!DateTimeInfo::instance) return false; MOZ_ASSERT(!IcuTimeZoneState, "we should be initializing only once"); IcuTimeZoneStatus initialStatus = IcuTimeZoneStatus::Valid; -#if defined(XP_WIN) // Directly set the ICU time zone status into the invalid state when we // need to compute the actual default time zone from the TZ environment // variable. We don't yet want to initialize 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. - const char* tz = std::getenv("TZ"); - if (tz && IsOlsonCompatibleWindowsTimeZoneId(tz)) - initialStatus = IcuTimeZoneStatus::NeedsUpdate; -#endif + if (const char* tz = std::getenv("TZ")) { +#if defined(XP_WIN) + if (IsOlsonCompatibleWindowsTimeZoneId(tz)) + initialStatus = IcuTimeZoneStatus::NeedsUpdate; +#else + if (std::strcmp(tz, ":/etc/localtime") == 0) + initialStatus = IcuTimeZoneStatus::NeedsUpdate; +#endif /* defined(XP_WIN) */ + } IcuTimeZoneState = js_new>(mutexid::IcuTimeZoneStateMutex, initialStatus); if (!IcuTimeZoneState) { js_delete(DateTimeInfo::instance); DateTimeInfo::instance = nullptr; return false; } @@ -607,43 +616,125 @@ IsOlsonCompatibleWindowsTimeZoneId(const "GMT", }; for (const auto& allowedId : allowedIds) { if (std::strcmp(allowedId, tz) == 0) return true; } return false; } -#endif +#elif ENABLE_INTL_API && defined(ICU_TZ_HAS_RECREATE_DEFAULT) +/** + * Examine /etc/localtime. + * + * If it is a symlink to a tzdata zoneinfo file -- a file containing + * "/zoneinfo/" in its path, with subsequent path components that follow the + * pattern of a time zone name, e.g. "America/New_York" (valid) or + * "Fnord/Aliens" (not so much, presently) and so on -- return a string + * containing those subsequent path components. (Which may not be a real + * time zone name.) + * + * Otherwise (if the file doesn't exist, isn't a symlink, points to a file with + * different path, etc.) return an empty string. +*/ +static icu::UnicodeString +ReadSystemTimeZoneId() +{ + // The system time zone file, see `man 3 tzset`. + static constexpr char SystemTimeZoneFile[] = "/etc/localtime"; + + // 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 = mozilla::ArrayLength(ZoneInfoPath) - 1; // exclude NUL + + char buf[PATH_MAX]; + constexpr size_t buflen = mozilla::ArrayLength(buf) - 1; // -1 to null-terminate. + + // Return an empty string on error or if the result was truncated. + ssize_t slen = readlink(SystemTimeZoneFile, buf, buflen); + if (slen < 0 || size_t(slen) >= buflen) + return icu::UnicodeString(); + + // Ensure |buf| is null-terminated. (readlink may not necessarily null + // terminate the string.) + buf[size_t(slen)] = '\0'; + + // Return an empty string if the path doesn't include "/zoneinfo/". + const char* timeZoneWithZoneInfo = std::strstr(buf, ZoneInfoPath); + if (!timeZoneWithZoneInfo) + return icu::UnicodeString(); + + const char* timeZone = timeZoneWithZoneInfo + ZoneInfoPathLength; + size_t timeZoneLen = std::strlen(timeZone); + + // Reject the result if it doesn't match the time zone id pattern or + // legacy time zone names. + // See . + 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); +} +#endif /* ENABLE_INTL_API && defined(ICU_TZ_HAS_RECREATE_DEFAULT) */ void js::ResyncICUDefaultTimeZone() { #if ENABLE_INTL_API && defined(ICU_TZ_HAS_RECREATE_DEFAULT) auto guard = IcuTimeZoneState->lock(); if (guard.get() == IcuTimeZoneStatus::NeedsUpdate) { bool recreate = true; + + 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. - const char* tz = std::getenv("TZ"); - if (tz && IsOlsonCompatibleWindowsTimeZoneId(tz)) { - icu::UnicodeString tzid(tz, -1, US_INV); - 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()); - recreate = false; + // 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 { - // 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 + // Handle ":/etc/localtime" manually because ICU currently doesn't + // support the TZ filespec format. We don't support the complete + // TZ filespec format for simplicity's sake and because + // ":/etc/localtime" should cover most cases. + // + if (std::strcmp(tz, ":/etc/localtime") == 0) + tzid.setTo(ReadSystemTimeZoneId()); +#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()); + recreate = false; + } + } } -#endif + if (recreate) icu::TimeZone::recreateDefault(); guard.get() = IcuTimeZoneStatus::Valid; } #endif }