Bug 1303091: Share static Intl data across compartments. r=Waldo, r=jonco, a=jcristau
authorAndré Bargull <andre.bargull@gmail.com>
Thu, 17 Nov 2016 14:40:18 -0800
changeset 349385 4f4e8f4ae6482d79715aef3f4d18538a905bff54
parent 349384 8402aa196fd69d6a978da9b8f4cc1b95c847db9e
child 349386 a5ac4acad5654221e37247b976bf3bdf37428ed5
push id10375
push userryanvm@gmail.com
push dateWed, 30 Nov 2016 02:52:28 +0000
treeherdermozilla-aurora@1fd9b621e755 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo, jonco, jcristau
bugs1303091
milestone52.0a2
Bug 1303091: Share static Intl data across compartments. r=Waldo, r=jonco, a=jcristau
js/public/MemoryMetrics.h
js/src/builtin/Intl.cpp
js/src/builtin/Intl.h
js/src/builtin/Intl.js
js/src/builtin/IntlTimeZoneData.h
js/src/builtin/IntlTzData.js
js/src/builtin/make_intl_data.py
js/src/gc/RootMarking.cpp
js/src/moz.build
js/src/tests/Intl/DateTimeFormat/timeZone_backward_links.js
js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js
js/src/tests/Intl/DateTimeFormat/timeZone_backzone_links.js
js/src/tests/Intl/DateTimeFormat/timeZone_link.js
js/src/tests/Intl/DateTimeFormat/timeZone_notbackward_links.js
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/src/vm/SelfHosting.cpp
js/xpconnect/src/XPCJSContext.cpp
--- a/js/public/MemoryMetrics.h
+++ b/js/public/MemoryMetrics.h
@@ -507,16 +507,17 @@ struct RuntimeSizes
 #define FOR_EACH_SIZE(macro) \
     macro(_, MallocHeap, object) \
     macro(_, MallocHeap, atomsTable) \
     macro(_, MallocHeap, contexts) \
     macro(_, MallocHeap, temporary) \
     macro(_, MallocHeap, interpreterStack) \
     macro(_, MallocHeap, mathCache) \
     macro(_, MallocHeap, sharedImmutableStringsCache) \
+    macro(_, MallocHeap, sharedIntlData) \
     macro(_, MallocHeap, uncompressedSourceCache) \
     macro(_, MallocHeap, scriptData)
 
     RuntimeSizes()
       : FOR_EACH_SIZE(ZERO_SIZE)
         scriptSourceInfo(),
         code(),
         gc(),
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -17,16 +17,17 @@
 
 #include <string.h>
 
 #include "jsapi.h"
 #include "jsatom.h"
 #include "jscntxt.h"
 #include "jsobj.h"
 
+#include "builtin/IntlTimeZoneData.h"
 #if ENABLE_INTL_API
 #include "unicode/ucal.h"
 #include "unicode/ucol.h"
 #include "unicode/udat.h"
 #include "unicode/udatpg.h"
 #include "unicode/uenum.h"
 #include "unicode/unum.h"
 #include "unicode/unumsys.h"
@@ -1968,99 +1969,335 @@ js::intl_availableCalendars(JSContext* c
         if (!DefineElement(cx, calendars, index++, element))
             return false;
     }
 
     args.rval().setObject(*calendars);
     return true;
 }
 
-/**
- * Create a time zone map key. Time zones are keyed by their upper case name.
- */
-static UniqueChars
-TimeZoneKey(JSContext* cx, const char* timeZone, size_t size)
+template<typename Char>
+static constexpr Char
+ToUpperASCII(Char c)
+{
+    return ('a' <= c && c <= 'z')
+           ? (c & ~0x20)
+           : c;
+}
+
+static_assert(ToUpperASCII('a') == 'A', "verifying 'a' uppercases correctly");
+static_assert(ToUpperASCII('m') == 'M', "verifying 'm' uppercases correctly");
+static_assert(ToUpperASCII('z') == 'Z', "verifying 'z' uppercases correctly");
+static_assert(ToUpperASCII(u'a') == u'A', "verifying u'a' uppercases correctly");
+static_assert(ToUpperASCII(u'k') == u'K', "verifying u'k' uppercases correctly");
+static_assert(ToUpperASCII(u'z') == u'Z', "verifying u'z' uppercases correctly");
+
+template<typename Char1, typename Char2>
+static bool
+EqualCharsIgnoreCaseASCII(const Char1* s1, const Char2* s2, size_t len)
 {
-    auto timeZoneKey = cx->make_pod_array<char>(size);
-    if (!timeZoneKey)
-        return nullptr;
-    PodCopy(timeZoneKey.get(), timeZone, size);
-
-    // Convert time zone name to ASCII upper case.
-    char* chars = timeZoneKey.get();
-    for (unsigned i = 0; i < size; i++) {
-        char c = chars[i];
-        if ('a' <= c && c <= 'z')
-            chars[i] = c & ~0x20;
-        MOZ_ASSERT(unicode::ToUpperCase(c) == chars[i]);
+    for (const Char1* s1end = s1 + len; s1 < s1end; s1++, s2++) {
+        if (ToUpperASCII(*s1) != ToUpperASCII(*s2))
+            return false;
     }
-
-    return timeZoneKey;
+    return true;
+}
+
+template<typename Char>
+static js::HashNumber
+HashStringIgnoreCaseASCII(const Char* s, size_t length)
+{
+    uint32_t hash = 0;
+    for (size_t i = 0; i < length; i++)
+        hash = mozilla::AddToHash(hash, ToUpperASCII(s[i]));
+    return hash;
+}
+
+js::SharedIntlData::TimeZoneHasher::Lookup::Lookup(JSFlatString* timeZone)
+  : isLatin1(timeZone->hasLatin1Chars()), length(timeZone->length())
+{
+    if (isLatin1) {
+        latin1Chars = timeZone->latin1Chars(nogc);
+        hash = HashStringIgnoreCaseASCII(latin1Chars, length);
+    } else {
+        twoByteChars = timeZone->twoByteChars(nogc);
+        hash = HashStringIgnoreCaseASCII(twoByteChars, length);
+    }
 }
 
 bool
-js::intl_availableTimeZones(JSContext* cx, unsigned argc, Value* vp)
+js::SharedIntlData::TimeZoneHasher::match(TimeZoneName key, const Lookup& lookup)
+{
+    if (key->length() != lookup.length)
+        return false;
+
+    // Compare time zone names ignoring ASCII case differences.
+    if (key->hasLatin1Chars()) {
+        const Latin1Char* keyChars = key->latin1Chars(lookup.nogc);
+        if (lookup.isLatin1)
+            return EqualCharsIgnoreCaseASCII(keyChars, lookup.latin1Chars, lookup.length);
+        return EqualCharsIgnoreCaseASCII(keyChars, lookup.twoByteChars, lookup.length);
+    }
+
+    const char16_t* keyChars = key->twoByteChars(lookup.nogc);
+    if (lookup.isLatin1)
+        return EqualCharsIgnoreCaseASCII(lookup.latin1Chars, keyChars, lookup.length);
+    return EqualCharsIgnoreCaseASCII(keyChars, lookup.twoByteChars, lookup.length);
+}
+
+static bool
+IsLegacyICUTimeZone(const char* timeZone)
 {
-    CallArgs args = CallArgsFromVp(argc, vp);
-    MOZ_ASSERT(args.length() == 0);
+    for (const auto& legacyTimeZone : timezone::legacyICUTimeZones) {
+        if (equal(timeZone, legacyTimeZone))
+            return true;
+    }
+    return false;
+}
+
+bool
+js::SharedIntlData::ensureTimeZones(JSContext* cx)
+{
+    if (timeZoneDataInitialized)
+        return true;
+
+    // If initTimeZones() was called previously, but didn't complete due to
+    // OOM, clear all sets/maps and start from scratch.
+    if (availableTimeZones.initialized())
+        availableTimeZones.finish();
+    if (!availableTimeZones.init()) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
 
     UErrorCode status = U_ZERO_ERROR;
     UEnumeration* values = ucal_openTimeZones(&status);
     if (U_FAILURE(status)) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
         return false;
     }
     ScopedICUObject<UEnumeration, uenum_close> toClose(values);
 
-    RootedObject timeZones(cx, NewObjectWithGivenProto<PlainObject>(cx, nullptr));
-    if (!timeZones)
-        return false;
-    RootedString jsTimeZone(cx);
-    RootedValue element(cx);
+    RootedAtom timeZone(cx);
     while (true) {
         int32_t size;
-        const char* timeZone = uenum_next(values, &size, &status);
+        const char* rawTimeZone = uenum_next(values, &size, &status);
         if (U_FAILURE(status)) {
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
             return false;
         }
-        if (timeZone == nullptr)
+
+        if (rawTimeZone == nullptr)
             break;
 
+        // Skip legacy ICU time zone names.
+        if (IsLegacyICUTimeZone(rawTimeZone))
+            continue;
+
         MOZ_ASSERT(size >= 0);
-        UniqueChars timeZoneKey = TimeZoneKey(cx, timeZone, size_t(size));
-        if (!timeZoneKey)
+        timeZone = Atomize(cx, rawTimeZone, size_t(size));
+        if (!timeZone)
+            return false;
+
+        TimeZoneHasher::Lookup lookup(timeZone);
+        TimeZoneSet::AddPtr p = availableTimeZones.lookupForAdd(lookup);
+
+        // ICU shouldn't report any duplicate time zone names, but if it does,
+        // just ignore the duplicate name.
+        if (!p && !availableTimeZones.add(p, timeZone)) {
+            ReportOutOfMemory(cx);
+            return false;
+        }
+    }
+
+    if (ianaZonesTreatedAsLinksByICU.initialized())
+        ianaZonesTreatedAsLinksByICU.finish();
+    if (!ianaZonesTreatedAsLinksByICU.init()) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
+
+    for (const char* rawTimeZone : timezone::ianaZonesTreatedAsLinksByICU) {
+        MOZ_ASSERT(rawTimeZone != nullptr);
+        timeZone = Atomize(cx, rawTimeZone, strlen(rawTimeZone));
+        if (!timeZone)
             return false;
 
-        RootedAtom atom(cx, Atomize(cx, timeZoneKey.get(), size_t(size)));
-        if (!atom)
+        TimeZoneHasher::Lookup lookup(timeZone);
+        TimeZoneSet::AddPtr p = ianaZonesTreatedAsLinksByICU.lookupForAdd(lookup);
+        MOZ_ASSERT(!p, "Duplicate entry in timezone::ianaZonesTreatedAsLinksByICU");
+
+        if (!ianaZonesTreatedAsLinksByICU.add(p, timeZone)) {
+            ReportOutOfMemory(cx);
+            return false;
+        }
+    }
+
+    if (ianaLinksCanonicalizedDifferentlyByICU.initialized())
+        ianaLinksCanonicalizedDifferentlyByICU.finish();
+    if (!ianaLinksCanonicalizedDifferentlyByICU.init()) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
+
+    RootedAtom linkName(cx);
+    RootedAtom& target = timeZone;
+    for (const auto& linkAndTarget : timezone::ianaLinksCanonicalizedDifferentlyByICU) {
+        const char* rawLinkName = linkAndTarget.link;
+        const char* rawTarget = linkAndTarget.target;
+
+        MOZ_ASSERT(rawLinkName != nullptr);
+        linkName = Atomize(cx, rawLinkName, strlen(rawLinkName));
+        if (!linkName)
+            return false;
+
+        MOZ_ASSERT(rawTarget != nullptr);
+        target = Atomize(cx, rawTarget, strlen(rawTarget));
+        if (!target)
+            return false;
+
+        TimeZoneHasher::Lookup lookup(linkName);
+        TimeZoneMap::AddPtr p = ianaLinksCanonicalizedDifferentlyByICU.lookupForAdd(lookup);
+        MOZ_ASSERT(!p, "Duplicate entry in timezone::ianaLinksCanonicalizedDifferentlyByICU");
+
+        if (!ianaLinksCanonicalizedDifferentlyByICU.add(p, linkName, target)) {
+            ReportOutOfMemory(cx);
             return false;
-
-        jsTimeZone = NewStringCopyN<CanGC>(cx, timeZone, size_t(size));
-        if (!jsTimeZone)
-            return false;
-        element.setString(jsTimeZone);
-
-        if (!DefineProperty(cx, timeZones, atom->asPropertyName(), element))
-            return false;
+        }
     }
 
-    args.rval().setObject(*timeZones);
+    MOZ_ASSERT(!timeZoneDataInitialized, "ensureTimeZones is neither reentrant nor thread-safe");
+    timeZoneDataInitialized = true;
+
+    return true;
+}
+
+bool
+js::SharedIntlData::validateTimeZoneName(JSContext* cx, HandleString timeZone,
+                                         MutableHandleString result)
+{
+    if (!ensureTimeZones(cx))
+        return false;
+
+    Rooted<JSFlatString*> timeZoneFlat(cx, timeZone->ensureFlat(cx));
+    if (!timeZoneFlat)
+        return false;
+
+    TimeZoneHasher::Lookup lookup(timeZoneFlat);
+    if (TimeZoneSet::Ptr p = availableTimeZones.lookup(lookup))
+        result.set(*p);
+
+    return true;
+}
+
+bool
+js::SharedIntlData::tryCanonicalizeTimeZoneConsistentWithIANA(JSContext* cx, HandleString timeZone,
+                                                              MutableHandleString result)
+{
+    if (!ensureTimeZones(cx))
+        return false;
+
+    Rooted<JSFlatString*> timeZoneFlat(cx, timeZone->ensureFlat(cx));
+    if (!timeZoneFlat)
+        return false;
+
+    TimeZoneHasher::Lookup lookup(timeZoneFlat);
+    MOZ_ASSERT(availableTimeZones.has(lookup), "Invalid time zone name");
+
+    if (TimeZoneMap::Ptr p = ianaLinksCanonicalizedDifferentlyByICU.lookup(lookup)) {
+        // The effectively supported time zones aren't known at compile time,
+        // when
+        // 1. SpiderMonkey was compiled with "--with-system-icu".
+        // 2. ICU's dynamic time zone data loading feature was used.
+        //    (ICU supports loading time zone files at runtime through the
+        //    ICU_TIMEZONE_FILES_DIR environment variable.)
+        // Ensure ICU supports the new target zone before applying the update.
+        TimeZoneName targetTimeZone = p->value();
+        TimeZoneHasher::Lookup targetLookup(targetTimeZone);
+        if (availableTimeZones.has(targetLookup))
+            result.set(targetTimeZone);
+    } else if (TimeZoneSet::Ptr p = ianaZonesTreatedAsLinksByICU.lookup(lookup)) {
+        result.set(*p);
+    }
+
+    return true;
+}
+
+void
+js::SharedIntlData::destroyInstance()
+{
+    availableTimeZones.finish();
+    ianaZonesTreatedAsLinksByICU.finish();
+    ianaLinksCanonicalizedDifferentlyByICU.finish();
+}
+
+void
+js::SharedIntlData::trace(JSTracer* trc)
+{
+    // Atoms are always tenured.
+    if (!trc->runtime()->isHeapMinorCollecting()) {
+        availableTimeZones.trace(trc);
+        ianaZonesTreatedAsLinksByICU.trace(trc);
+        ianaLinksCanonicalizedDifferentlyByICU.trace(trc);
+    }
+}
+
+size_t
+js::SharedIntlData::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
+{
+    return availableTimeZones.sizeOfExcludingThis(mallocSizeOf) +
+           ianaZonesTreatedAsLinksByICU.sizeOfExcludingThis(mallocSizeOf) +
+           ianaLinksCanonicalizedDifferentlyByICU.sizeOfExcludingThis(mallocSizeOf);
+}
+
+bool
+js::intl_IsValidTimeZoneName(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 1);
+    MOZ_ASSERT(args[0].isString());
+
+    SharedIntlData& sharedIntlData = cx->sharedIntlData;
+
+    RootedString timeZone(cx, args[0].toString());
+    RootedString validatedTimeZone(cx);
+    if (!sharedIntlData.validateTimeZoneName(cx, timeZone, &validatedTimeZone))
+        return false;
+
+    if (validatedTimeZone)
+        args.rval().setString(validatedTimeZone);
+    else
+        args.rval().setNull();
+
     return true;
 }
 
 bool
 js::intl_canonicalizeTimeZone(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 1);
     MOZ_ASSERT(args[0].isString());
 
+    SharedIntlData& sharedIntlData = cx->sharedIntlData;
+
+    // Some time zone names are canonicalized differently by ICU -- handle
+    // those first:
+    RootedString timeZone(cx, args[0].toString());
+    RootedString ianaTimeZone(cx);
+    if (!sharedIntlData.tryCanonicalizeTimeZoneConsistentWithIANA(cx, timeZone, &ianaTimeZone))
+        return false;
+
+    if (ianaTimeZone) {
+        args.rval().setString(ianaTimeZone);
+        return true;
+    }
+
     AutoStableStringChars stableChars(cx);
-    if (!stableChars.initTwoByte(cx, args[0].toString()))
+    if (!stableChars.initTwoByte(cx, timeZone))
         return false;
 
     mozilla::Range<const char16_t> tzchars = stableChars.twoByteRange();
 
     Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
     if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
         return false;
 
@@ -2078,17 +2315,17 @@ js::intl_canonicalizeTimeZone(JSContext*
                                     Char16ToUChar(chars.begin()), size, isSystemID, &status);
     }
     if (U_FAILURE(status)) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
         return false;
     }
 
     MOZ_ASSERT(size >= 0);
-    JSString* str = NewStringCopyN<CanGC>(cx, chars.begin(), size);
+    JSString* str = NewStringCopyN<CanGC>(cx, chars.begin(), size_t(size));
     if (!str)
         return false;
     args.rval().setString(str);
     return true;
 }
 
 bool
 js::intl_defaultTimeZone(JSContext* cx, unsigned argc, Value* vp)
--- a/js/src/builtin/Intl.h
+++ b/js/src/builtin/Intl.h
@@ -2,17 +2,25 @@
  * 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/. */
 
 #ifndef builtin_Intl_h
 #define builtin_Intl_h
 
+#include "mozilla/HashFunctions.h"
+#include "mozilla/MemoryReporting.h"
+
+#include "jsalloc.h"
 #include "NamespaceImports.h"
+
+#include "js/GCAPI.h"
+#include "js/GCHashTable.h"
+
 #if ENABLE_INTL_API
 #include "unicode/utypes.h"
 #endif
 
 /*
  * The Intl module specified by standard ECMA-402,
  * ECMAScript Internationalization API Specification.
  */
@@ -21,16 +29,150 @@ namespace js {
 
 /**
  * Initializes the Intl Object and its standard built-in properties.
  * Spec: ECMAScript Internationalization API Specification, 8.0, 8.1
  */
 extern JSObject*
 InitIntlClass(JSContext* cx, HandleObject obj);
 
+/**
+ * Stores Intl data which can be shared across compartments (but not contexts).
+ *
+ * Used for data which is expensive when computed repeatedly or is not
+ * available through ICU.
+ */
+class SharedIntlData
+{
+    /**
+     * Information tracking the set of the supported time zone names, derived
+     * from the IANA time zone database <https://www.iana.org/time-zones>.
+     *
+     * There are two kinds of IANA time zone names: Zone and Link (denoted as
+     * such in database source files). Zone names are the canonical, preferred
+     * name for a time zone, e.g. Asia/Kolkata. Link names simply refer to
+     * target Zone names for their meaning, e.g. Asia/Calcutta targets
+     * Asia/Kolkata. That a name is a Link doesn't *necessarily* reflect a
+     * sense of deprecation: some Link names also exist partly for convenience,
+     * e.g. UTC and GMT as Link names targeting the Zone name Etc/UTC.
+     *
+     * Two data sources determine the time zone names we support: those ICU
+     * supports and IANA's zone information.
+     *
+     * Unfortunately the names ICU and IANA support, and their Link
+     * relationships from name to target, aren't identical, so we can't simply
+     * implicitly trust ICU's name handling. We must perform various
+     * preprocessing of user-provided zone names and post-processing of
+     * ICU-provided zone names to implement ECMA-402's IANA-consistent behavior.
+     *
+     * Also see <https://ssl.icu-project.org/trac/ticket/12044> and
+     * <http://unicode.org/cldr/trac/ticket/9892>.
+     */
+
+    using TimeZoneName = JSAtom*;
+
+    struct TimeZoneHasher
+    {
+        struct Lookup
+        {
+            union {
+                const JS::Latin1Char* latin1Chars;
+                const char16_t* twoByteChars;
+            };
+            bool isLatin1;
+            size_t length;
+            JS::AutoCheckCannotGC nogc;
+            HashNumber hash;
+
+            explicit Lookup(JSFlatString* timeZone);
+        };
+
+        static js::HashNumber hash(const Lookup& lookup) { return lookup.hash; }
+        static bool match(TimeZoneName key, const Lookup& lookup);
+    };
+
+    using TimeZoneSet = js::GCHashSet<TimeZoneName,
+                                      TimeZoneHasher,
+                                      js::SystemAllocPolicy>;
+
+    using TimeZoneMap = js::GCHashMap<TimeZoneName,
+                                      TimeZoneName,
+                                      TimeZoneHasher,
+                                      js::SystemAllocPolicy>;
+
+    /**
+     * As a threshold matter, available time zones are those time zones ICU
+     * supports, via ucal_openTimeZones. But ICU supports additional non-IANA
+     * time zones described in intl/icu/source/tools/tzcode/icuzones (listed in
+     * IntlTimeZoneData.cpp's |legacyICUTimeZones|) for its own backwards
+     * compatibility purposes. This set consists of ICU's supported time zones,
+     * minus all backwards-compatibility time zones.
+     */
+    TimeZoneSet availableTimeZones;
+
+    /**
+     * IANA treats some time zone names as Zones, that ICU instead treats as
+     * Links. For example, IANA considers "America/Indiana/Indianapolis" to be
+     * a Zone and "America/Fort_Wayne" a Link that targets it, but ICU
+     * considers the former a Link that targets "America/Indianapolis" (which
+     * IANA treats as a Link).
+     *
+     * ECMA-402 requires that we respect IANA data, so if we're asked to
+     * canonicalize a time zone name in this set, we must *not* return ICU's
+     * canonicalization.
+     */
+    TimeZoneSet ianaZonesTreatedAsLinksByICU;
+
+    /**
+     * IANA treats some time zone names as Links to one target, that ICU
+     * instead treats as either Zones, or Links to different targets. An
+     * example of the former is "Asia/Calcutta, which IANA assigns the target
+     * "Asia/Kolkata" but ICU considers its own Zone. An example of the latter
+     * is "America/Virgin", which IANA assigns the target
+     * "America/Port_of_Spain" but ICU assigns the target "America/St_Thomas".
+     *
+     * ECMA-402 requires that we respect IANA data, so if we're asked to
+     * canonicalize a time zone name that's a key in this map, we *must* return
+     * the corresponding value and *must not* return ICU's canonicalization.
+     */
+    TimeZoneMap ianaLinksCanonicalizedDifferentlyByICU;
+
+    bool timeZoneDataInitialized = false;
+
+    /**
+     * Precomputes the available time zone names, because it's too expensive to
+     * call ucal_openTimeZones() repeatedly.
+     */
+    bool ensureTimeZones(JSContext* cx);
+
+  public:
+    /**
+     * Returns the validated time zone name in |result|. If the input time zone
+     * isn't a valid IANA time zone name, |result| remains unchanged.
+     */
+    bool validateTimeZoneName(JSContext* cx, JS::HandleString timeZone,
+                              JS::MutableHandleString result);
+
+    /**
+     * Returns the canonical time zone name in |result|. If no canonical name
+     * was found, |result| remains unchanged.
+     *
+     * This method only handles time zones which are canonicalized differently
+     * by ICU when compared to IANA.
+     */
+    bool tryCanonicalizeTimeZoneConsistentWithIANA(JSContext* cx, JS::HandleString timeZone,
+                                                   JS::MutableHandleString result);
+
+    void destroyInstance();
+
+    void trace(JSTracer* trc);
+
+    size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+};
+
 /*
  * The following functions are for use by self-hosted code.
  */
 
 
 /******************** Collator ********************/
 
 /**
@@ -154,31 +296,27 @@ intl_DateTimeFormat_availableLocales(JSC
  * element 0.
  *
  * Usage: calendars = intl_availableCalendars(locale)
  */
 extern MOZ_MUST_USE bool
 intl_availableCalendars(JSContext* cx, unsigned argc, Value* vp);
 
 /**
- * Return a map of the supported time zone names, derived from the IANA time
- * zone database <https://www.iana.org/time-zones>.
+ * 6.4.1 IsValidTimeZoneName ( timeZone )
  *
- * There are two kinds of IANA time zone names: Zone and Link (denoted as such
- * in database source files). Zone names are the canonical, preferred name for
- * a time zone, e.g. Asia/Kolkata. Link names simply refer to target Zone names
- * for their meaning, e.g. Asia/Calcutta targets Asia/Kolkata. That a name is a
- * Link doesn't *necessarily* reflect a sense of deprecation: some Link names
- * also exist partly for convenience, e.g. UTC and GMT as Link names targeting
- * the Zone name Etc/UTC.
+ * Verifies that the given string is a valid time zone name. If it is a valid
+ * time zone name, its IANA time zone name is returned. Otherwise returns null.
  *
- * Usage: timeZones = intl_availableTimeZones()
+ * ES2017 Intl draft rev 4a23f407336d382ed5e3471200c690c9b020b5f3
+ *
+ * Usage: ianaTimeZone = intl_IsValidTimeZoneName(timeZone)
  */
 extern MOZ_MUST_USE bool
-intl_availableTimeZones(JSContext* cx, unsigned argc, Value* vp);
+intl_IsValidTimeZoneName(JSContext* cx, unsigned argc, Value* vp);
 
 /**
  * Return the canonicalized time zone name. Canonicalization resolves link
  * names to their target time zones.
  *
  * Usage: ianaTimeZone = intl_canonicalizeTimeZone(timeZone)
  */
 extern MOZ_MUST_USE bool
--- a/js/src/builtin/Intl.js
+++ b/js/src/builtin/Intl.js
@@ -623,86 +623,46 @@ function IsWellFormedCurrencyCode(curren
         return false;
     return !regexp_test_no_statics(getIsWellFormedCurrencyCodeRE(), normalized);
 }
 
 
 var timeZoneCache = {
     icuDefaultTimeZone: undefined,
     defaultTimeZone: undefined,
-    timeZones: undefined,
 };
 
 
 /**
- * 6.4.1 IsValidTimeZoneName ( timeZone )
- *
- * Verifies that the given string is a valid time zone name. If it is a valid
- * time zone name, its IANA time zone name is returned. Otherwise returns null.
- *
- * ES2017 Intl draft rev 4a23f407336d382ed5e3471200c690c9b020b5f3
- */
-function IsValidTimeZoneName(timeZone) {
-    assert(typeof timeZone === "string", "IsValidTimeZoneName");
-
-    var timeZones = timeZoneCache.timeZones;
-    if (timeZones === undefined) {
-        timeZones = intl_availableTimeZones();
-        timeZoneCache.timeZones = timeZones;
-    }
-
-    // Return |null| if time zone not found or non-IANA time zone.
-    var tz = timeZones[toASCIIUpperCase(timeZone)];
-    return (tz === undefined || tz in legacyICUTimeZones) ? null : tz;
-}
-
-
-/**
  * 6.4.2 CanonicalizeTimeZoneName ( timeZone )
  *
  * Canonicalizes the given IANA time zone name.
  *
  * ES2017 Intl draft rev 4a23f407336d382ed5e3471200c690c9b020b5f3
  */
 function CanonicalizeTimeZoneName(timeZone) {
     assert(typeof timeZone === "string", "CanonicalizeTimeZoneName");
 
     // Step 1. (Not applicable, the input is already a valid IANA time zone.)
     assert(timeZone !== "Etc/Unknown", "Invalid time zone");
-    assert(timeZone === IsValidTimeZoneName(timeZone), "Time zone name not normalized");
+    assert(timeZone === intl_IsValidTimeZoneName(timeZone), "Time zone name not normalized");
 
     // Step 2.
     var ianaTimeZone = intl_canonicalizeTimeZone(timeZone);
     assert(ianaTimeZone !== "Etc/Unknown", "Invalid canonical time zone");
-    assert(ianaTimeZone === IsValidTimeZoneName(ianaTimeZone), "Unsupported canonical time zone");
+    assert(ianaTimeZone === intl_IsValidTimeZoneName(ianaTimeZone), "Unsupported canonical time zone");
 
     // Step 3.
     if (ianaTimeZone === "Etc/UTC" || ianaTimeZone === "Etc/GMT") {
         // ICU/CLDR canonicalizes Etc/UCT to Etc/GMT, but following IANA and
         // ECMA-402 to the letter means Etc/UCT is a separate time zone.
         if (timeZone === "Etc/UCT" || timeZone === "UCT")
             ianaTimeZone = "Etc/UCT";
         else
             ianaTimeZone = "UTC";
-    } else {
-        // ICU/CLDR doesn't map all names to their new IANA time zone names.
-        // http://bugs.icu-project.org/trac/ticket/12044
-        if (timeZone in tzLinkNamesNonICU) {
-            // Case 1: ICU/CLDR maps the time zone to another time zone, e.g.
-            // America/Virgin is mapped to America/St_Thomas, whereas IANA maps
-            // America/Virgin to America/Port_of_Spain.
-            // Only perform the update when ICU supports the new time zone.
-            if (IsValidTimeZoneName(tzLinkNamesNonICU[timeZone]) !== null) {
-                ianaTimeZone = tzLinkNamesNonICU[timeZone];
-            }
-        } else if (timeZone in tzZoneNamesNonICU) {
-            // Case 2: ICU/CLDR maps the time zone to its old name, e.g.
-            // Asia/Kathmandu is mapped to Asia/Katmandu.
-            ianaTimeZone = timeZone;
-        }
     }
 
     // Step 4.
     return ianaTimeZone;
 }
 
 
 /**
@@ -713,33 +673,33 @@ function CanonicalizeTimeZoneName(timeZo
  * ES2017 Intl draft rev 4a23f407336d382ed5e3471200c690c9b020b5f3
  */
 function DefaultTimeZone() {
     const icuDefaultTimeZone = intl_defaultTimeZone();
     if (timeZoneCache.icuDefaultTimeZone === icuDefaultTimeZone)
         return timeZoneCache.defaultTimeZone;
 
     // Verify that the current ICU time zone is a valid ECMA-402 time zone.
-    var timeZone = IsValidTimeZoneName(icuDefaultTimeZone);
+    var timeZone = intl_IsValidTimeZoneName(icuDefaultTimeZone);
     if (timeZone === null) {
         // Before defaulting to "UTC", try to represent the default time zone
         // using the Etc/GMT + offset format. This format only accepts full
         // hour offsets.
         const msPerHour = 60 * 60 * 1000;
         var offset = intl_defaultTimeZoneOffset();
         assert(offset === (offset | 0),
                "milliseconds offset shouldn't be able to exceed int32_t range");
         var offsetHours = offset / msPerHour, offsetHoursFraction = offset % msPerHour;
         if (offsetHoursFraction === 0) {
             // Etc/GMT + offset uses POSIX-style signs, i.e. a positive offset
             // means a location west of GMT.
             timeZone = "Etc/GMT" + (offsetHours < 0 ? "+" : "-") + std_Math_abs(offsetHours);
 
             // Check if the fallback is valid.
-            timeZone = IsValidTimeZoneName(timeZone);
+            timeZone = intl_IsValidTimeZoneName(timeZone);
         }
 
         // Fallback to "UTC" if everything else fails.
         if (timeZone === null)
             timeZone = "UTC";
     }
 
     // Canonicalize the ICU time zone, e.g. change Etc/UTC to UTC.
@@ -2393,17 +2353,17 @@ function InitializeDateTimeFormat(dateTi
 
     // Steps 15-17.
     var tz = options.timeZone;
     if (tz !== undefined) {
         // Step 15.a.
         tz = ToString(tz);
 
         // Step 15.b.
-        var timeZone = IsValidTimeZoneName(tz);
+        var timeZone = intl_IsValidTimeZoneName(tz);
         if (timeZone === null)
             ThrowRangeError(JSMSG_INVALID_TIME_ZONE, tz);
 
         // Step 15.c.
         tz = CanonicalizeTimeZoneName(timeZone);
     } else {
         // Step 16.
         tz = DefaultTimeZone();
rename from js/src/builtin/IntlTzData.js
rename to js/src/builtin/IntlTimeZoneData.h
--- a/js/src/builtin/IntlTzData.js
+++ b/js/src/builtin/IntlTimeZoneData.h
@@ -1,131 +1,136 @@
 // Generated by make_intl_data.py. DO NOT EDIT.
 // tzdata version = 2016h
-// ICU tzdata version = 2015f
+
+#ifndef builtin_IntlTimeZoneData_h
+#define builtin_IntlTimeZoneData_h
+
+namespace js {
+namespace timezone {
 
 // Format:
-// "ZoneName": true // ICU-Name [time zone file]
-var tzZoneNamesNonICU = {
-    "Africa/Asmara": true, // Africa/Asmera [backzone]
-    "Africa/Timbuktu": true, // Africa/Bamako [backzone]
-    "America/Argentina/Buenos_Aires": true, // America/Buenos_Aires [southamerica]
-    "America/Argentina/Catamarca": true, // America/Catamarca [southamerica]
-    "America/Argentina/ComodRivadavia": true, // America/Catamarca [backzone]
-    "America/Argentina/Cordoba": true, // America/Cordoba [southamerica]
-    "America/Argentina/Jujuy": true, // America/Jujuy [southamerica]
-    "America/Argentina/Mendoza": true, // America/Mendoza [southamerica]
-    "America/Atikokan": true, // America/Coral_Harbour [northamerica]
-    "America/Ensenada": true, // America/Tijuana [backzone]
-    "America/Fort_Nelson": true, // <not present> [northamerica]
-    "America/Indiana/Indianapolis": true, // America/Indianapolis [northamerica]
-    "America/Kentucky/Louisville": true, // America/Louisville [northamerica]
-    "America/Rosario": true, // America/Cordoba [backzone]
-    "Asia/Barnaul": true, // <not present> [europe]
-    "Asia/Chongqing": true, // Asia/Shanghai [backzone]
-    "Asia/Hanoi": true, // <not present> [backzone]
-    "Asia/Harbin": true, // Asia/Shanghai [backzone]
-    "Asia/Ho_Chi_Minh": true, // Asia/Saigon [asia]
-    "Asia/Kashgar": true, // Asia/Urumqi [backzone]
-    "Asia/Kathmandu": true, // Asia/Katmandu [asia]
-    "Asia/Kolkata": true, // Asia/Calcutta [asia]
-    "Asia/Tel_Aviv": true, // Asia/Jerusalem [backzone]
-    "Asia/Tomsk": true, // <not present> [europe]
-    "Asia/Yangon": true, // <not present> [asia]
-    "Atlantic/Faroe": true, // Atlantic/Faeroe [europe]
-    "Atlantic/Jan_Mayen": true, // Arctic/Longyearbyen [backzone]
-    "CET": true, // <not present> [europe]
-    "EET": true, // <not present> [europe]
-    "EST": true, // Etc/GMT+5 [northamerica]
-    "Europe/Astrakhan": true, // <not present> [europe]
-    "Europe/Belfast": true, // Europe/London [backzone]
-    "Europe/Kirov": true, // <not present> [europe]
-    "Europe/Tiraspol": true, // Europe/Chisinau [backzone]
-    "Europe/Ulyanovsk": true, // <not present> [europe]
-    "HST": true, // Etc/GMT+10 [northamerica]
-    "MET": true, // <not present> [europe]
-    "MST": true, // Etc/GMT+7 [northamerica]
-    "Pacific/Chuuk": true, // Pacific/Truk [australasia]
-    "Pacific/Pohnpei": true, // Pacific/Ponape [australasia]
-    "WET": true, // <not present> [europe]
+// "ZoneName" // ICU-Name [time zone file]
+const char* const ianaZonesTreatedAsLinksByICU[] = {
+    "Africa/Asmara", // Africa/Asmera [backzone]
+    "Africa/Timbuktu", // Africa/Bamako [backzone]
+    "America/Argentina/Buenos_Aires", // America/Buenos_Aires [southamerica]
+    "America/Argentina/Catamarca", // America/Catamarca [southamerica]
+    "America/Argentina/ComodRivadavia", // America/Catamarca [backzone]
+    "America/Argentina/Cordoba", // America/Cordoba [southamerica]
+    "America/Argentina/Jujuy", // America/Jujuy [southamerica]
+    "America/Argentina/Mendoza", // America/Mendoza [southamerica]
+    "America/Atikokan", // America/Coral_Harbour [northamerica]
+    "America/Ensenada", // America/Tijuana [backzone]
+    "America/Indiana/Indianapolis", // America/Indianapolis [northamerica]
+    "America/Kentucky/Louisville", // America/Louisville [northamerica]
+    "America/Rosario", // America/Cordoba [backzone]
+    "Asia/Chongqing", // Asia/Shanghai [backzone]
+    "Asia/Harbin", // Asia/Shanghai [backzone]
+    "Asia/Ho_Chi_Minh", // Asia/Saigon [asia]
+    "Asia/Kashgar", // Asia/Urumqi [backzone]
+    "Asia/Kathmandu", // Asia/Katmandu [asia]
+    "Asia/Kolkata", // Asia/Calcutta [asia]
+    "Asia/Tel_Aviv", // Asia/Jerusalem [backzone]
+    "Asia/Yangon", // Asia/Rangoon [asia]
+    "Atlantic/Faroe", // Atlantic/Faeroe [europe]
+    "Atlantic/Jan_Mayen", // Arctic/Longyearbyen [backzone]
+    "EST", // Etc/GMT+5 [northamerica]
+    "Europe/Belfast", // Europe/London [backzone]
+    "Europe/Tiraspol", // Europe/Chisinau [backzone]
+    "HST", // Etc/GMT+10 [northamerica]
+    "MST", // Etc/GMT+7 [northamerica]
+    "Pacific/Chuuk", // Pacific/Truk [australasia]
+    "Pacific/Pohnpei", // Pacific/Ponape [australasia]
 };
 
 // Format:
-// "LinkName": "Target" // ICU-Target [time zone file]
-var tzLinkNamesNonICU = {
-    "Africa/Asmera": "Africa/Asmara", // Africa/Asmera [backward]
-    "America/Buenos_Aires": "America/Argentina/Buenos_Aires", // America/Buenos_Aires [backward]
-    "America/Catamarca": "America/Argentina/Catamarca", // America/Catamarca [backward]
-    "America/Cordoba": "America/Argentina/Cordoba", // America/Cordoba [backward]
-    "America/Fort_Wayne": "America/Indiana/Indianapolis", // America/Indianapolis [backward]
-    "America/Indianapolis": "America/Indiana/Indianapolis", // America/Indianapolis [backward]
-    "America/Jujuy": "America/Argentina/Jujuy", // America/Jujuy [backward]
-    "America/Kralendijk": "America/Curacao", // America/Kralendijk [southamerica]
-    "America/Louisville": "America/Kentucky/Louisville", // America/Louisville [backward]
-    "America/Lower_Princes": "America/Curacao", // America/Lower_Princes [southamerica]
-    "America/Marigot": "America/Port_of_Spain", // America/Marigot [southamerica]
-    "America/Mendoza": "America/Argentina/Mendoza", // America/Mendoza [backward]
-    "America/Santa_Isabel": "America/Tijuana", // America/Santa_Isabel [backward]
-    "America/St_Barthelemy": "America/Port_of_Spain", // America/St_Barthelemy [southamerica]
-    "America/Virgin": "America/Port_of_Spain", // America/St_Thomas [backward]
-    "Antarctica/South_Pole": "Antarctica/McMurdo", // Pacific/Auckland [backward]
-    "Arctic/Longyearbyen": "Europe/Oslo", // Arctic/Longyearbyen [europe]
-    "Asia/Calcutta": "Asia/Kolkata", // Asia/Calcutta [backward]
-    "Asia/Chungking": "Asia/Chongqing", // Asia/Shanghai [backward]
-    "Asia/Katmandu": "Asia/Kathmandu", // Asia/Katmandu [backward]
-    "Asia/Rangoon": "Asia/Yangon", // Asia/Rangoon [backward]
-    "Asia/Saigon": "Asia/Ho_Chi_Minh", // Asia/Saigon [backward]
-    "Atlantic/Faeroe": "Atlantic/Faroe", // Atlantic/Faeroe [backward]
-    "Europe/Bratislava": "Europe/Prague", // Europe/Bratislava [europe]
-    "Europe/Busingen": "Europe/Zurich", // Europe/Busingen [europe]
-    "Europe/Mariehamn": "Europe/Helsinki", // Europe/Mariehamn [europe]
-    "Europe/Podgorica": "Europe/Belgrade", // Europe/Podgorica [europe]
-    "Europe/San_Marino": "Europe/Rome", // Europe/San_Marino [europe]
-    "Europe/Vatican": "Europe/Rome", // Europe/Vatican [europe]
-    "Pacific/Ponape": "Pacific/Pohnpei", // Pacific/Ponape [backward]
-    "Pacific/Truk": "Pacific/Chuuk", // Pacific/Truk [backward]
-    "Pacific/Yap": "Pacific/Chuuk", // Pacific/Truk [backward]
-    "US/East-Indiana": "America/Indiana/Indianapolis", // America/Indianapolis [backward]
+// "LinkName", "Target" // ICU-Target [time zone file]
+struct LinkAndTarget
+{
+    const char* const link;
+    const char* const target;
+};
+
+const LinkAndTarget ianaLinksCanonicalizedDifferentlyByICU[] = {
+    { "Africa/Asmera", "Africa/Asmara" }, // Africa/Asmera [backward]
+    { "America/Buenos_Aires", "America/Argentina/Buenos_Aires" }, // America/Buenos_Aires [backward]
+    { "America/Catamarca", "America/Argentina/Catamarca" }, // America/Catamarca [backward]
+    { "America/Cordoba", "America/Argentina/Cordoba" }, // America/Cordoba [backward]
+    { "America/Fort_Wayne", "America/Indiana/Indianapolis" }, // America/Indianapolis [backward]
+    { "America/Indianapolis", "America/Indiana/Indianapolis" }, // America/Indianapolis [backward]
+    { "America/Jujuy", "America/Argentina/Jujuy" }, // America/Jujuy [backward]
+    { "America/Kralendijk", "America/Curacao" }, // America/Kralendijk [southamerica]
+    { "America/Louisville", "America/Kentucky/Louisville" }, // America/Louisville [backward]
+    { "America/Lower_Princes", "America/Curacao" }, // America/Lower_Princes [southamerica]
+    { "America/Marigot", "America/Port_of_Spain" }, // America/Marigot [southamerica]
+    { "America/Mendoza", "America/Argentina/Mendoza" }, // America/Mendoza [backward]
+    { "America/Santa_Isabel", "America/Tijuana" }, // America/Santa_Isabel [backward]
+    { "America/St_Barthelemy", "America/Port_of_Spain" }, // America/St_Barthelemy [southamerica]
+    { "America/Virgin", "America/Port_of_Spain" }, // America/St_Thomas [backward]
+    { "Antarctica/South_Pole", "Antarctica/McMurdo" }, // Pacific/Auckland [backward]
+    { "Arctic/Longyearbyen", "Europe/Oslo" }, // Arctic/Longyearbyen [europe]
+    { "Asia/Calcutta", "Asia/Kolkata" }, // Asia/Calcutta [backward]
+    { "Asia/Chungking", "Asia/Chongqing" }, // Asia/Shanghai [backward]
+    { "Asia/Katmandu", "Asia/Kathmandu" }, // Asia/Katmandu [backward]
+    { "Asia/Rangoon", "Asia/Yangon" }, // Asia/Rangoon [backward]
+    { "Asia/Saigon", "Asia/Ho_Chi_Minh" }, // Asia/Saigon [backward]
+    { "Atlantic/Faeroe", "Atlantic/Faroe" }, // Atlantic/Faeroe [backward]
+    { "Europe/Bratislava", "Europe/Prague" }, // Europe/Bratislava [europe]
+    { "Europe/Busingen", "Europe/Zurich" }, // Europe/Busingen [europe]
+    { "Europe/Mariehamn", "Europe/Helsinki" }, // Europe/Mariehamn [europe]
+    { "Europe/Podgorica", "Europe/Belgrade" }, // Europe/Podgorica [europe]
+    { "Europe/San_Marino", "Europe/Rome" }, // Europe/San_Marino [europe]
+    { "Europe/Vatican", "Europe/Rome" }, // Europe/Vatican [europe]
+    { "Pacific/Ponape", "Pacific/Pohnpei" }, // Pacific/Ponape [backward]
+    { "Pacific/Truk", "Pacific/Chuuk" }, // Pacific/Truk [backward]
+    { "Pacific/Yap", "Pacific/Chuuk" }, // Pacific/Truk [backward]
+    { "US/East-Indiana", "America/Indiana/Indianapolis" }, // America/Indianapolis [backward]
 };
 
 // Legacy ICU time zones, these are not valid IANA time zone names. We also
 // disallow the old and deprecated System V time zones.
-// http://source.icu-project.org/repos/icu/icu/trunk/source/tools/tzcode/icuzones
-var legacyICUTimeZones = {
-    "ACT": true,
-    "AET": true,
-    "AGT": true,
-    "ART": true,
-    "AST": true,
-    "BET": true,
-    "BST": true,
-    "CAT": true,
-    "CNT": true,
-    "CST": true,
-    "CTT": true,
-    "EAT": true,
-    "ECT": true,
-    "IET": true,
-    "IST": true,
-    "JST": true,
-    "MIT": true,
-    "NET": true,
-    "NST": true,
-    "PLT": true,
-    "PNT": true,
-    "PRT": true,
-    "PST": true,
-    "SST": true,
-    "VST": true,
-    "SystemV/AST4": true,
-    "SystemV/AST4ADT": true,
-    "SystemV/CST6": true,
-    "SystemV/CST6CDT": true,
-    "SystemV/EST5": true,
-    "SystemV/EST5EDT": true,
-    "SystemV/HST10": true,
-    "SystemV/MST7": true,
-    "SystemV/MST7MDT": true,
-    "SystemV/PST8": true,
-    "SystemV/PST8PDT": true,
-    "SystemV/YST9": true,
-    "SystemV/YST9YDT": true,
+// https://ssl.icu-project.org/repos/icu/trunk/icu4c/source/tools/tzcode/icuzones
+const char* const legacyICUTimeZones[] = {
+    "ACT",
+    "AET",
+    "AGT",
+    "ART",
+    "AST",
+    "BET",
+    "BST",
+    "CAT",
+    "CNT",
+    "CST",
+    "CTT",
+    "EAT",
+    "ECT",
+    "IET",
+    "IST",
+    "JST",
+    "MIT",
+    "NET",
+    "NST",
+    "PLT",
+    "PNT",
+    "PRT",
+    "PST",
+    "SST",
+    "VST",
+    "SystemV/AST4",
+    "SystemV/AST4ADT",
+    "SystemV/CST6",
+    "SystemV/CST6CDT",
+    "SystemV/EST5",
+    "SystemV/EST5EDT",
+    "SystemV/HST10",
+    "SystemV/MST7",
+    "SystemV/MST7MDT",
+    "SystemV/PST8",
+    "SystemV/PST8PDT",
+    "SystemV/YST9",
+    "SystemV/YST9YDT",
 };
+
+} // namespace timezone
+} // namespace js
+
+#endif /* builtin_IntlTimeZoneData_h */
--- a/js/src/builtin/make_intl_data.py
+++ b/js/src/builtin/make_intl_data.py
@@ -2,50 +2,49 @@
 # -*- coding: utf-8 -*-
 #
 # 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/.
 
 """ Usage:
     make_intl_data.py langtags [language-subtag-registry.txt]
-    make_intl_data.py tzdata --icu=$MOZ/intl/icu/source \\
-                             --icutz=$MOZ/intl/tzdata/tz2016f \\
-                             --version=2016f
+    make_intl_data.py tzdata
 
     Target "langtags":
     This script extracts information about mappings between deprecated and
     current BCP 47 language tags from the IANA Language Subtag Registry and
     converts it to JavaScript object definitions in IntlData.js. The definitions
     are used in Intl.js.
 
     The IANA Language Subtag Registry is imported from
-    http://www.iana.org/assignments/language-subtag-registry
+    https://www.iana.org/assignments/language-subtag-registry
     and uses the syntax specified in
-    http://tools.ietf.org/html/rfc5646#section-3
+    https://tools.ietf.org/html/rfc5646#section-3
 
 
     Target "tzdata":
     This script computes which time zone informations are not up-to-date in ICU
     and provides the necessary mappings to workaround this problem.
-    http://bugs.icu-project.org/trac/ticket/12044
+    https://ssl.icu-project.org/trac/ticket/12044
 """
 
 from __future__ import print_function
 import os
 import re
 import io
 import codecs
+import sys
 import tarfile
 import tempfile
 import urllib2
 import urlparse
 from contextlib import closing
 from functools import partial
-from itertools import chain, ifilter, ifilterfalse, tee
+from itertools import chain, ifilter, ifilterfalse, imap, tee
 from operator import attrgetter, itemgetter
 
 def readRegistryRecord(registry):
     """ Yields the records of the IANA Language Subtag Registry as dictionaries. """
     record = {}
     for line in registry:
         line = line.strip()
         if line == "":
@@ -132,17 +131,17 @@ def readRegistry(registry):
             subtag = record["Subtag"]
             extlangSubtags.add(subtag)
             if "Preferred-Value" in record:
                 preferred = record["Preferred-Value"]
                 prefix = record["Prefix"]
                 extlangMappings[subtag] = {"preferred": preferred, "prefix": prefix}
         else:
             # No other types are allowed by
-            # http://tools.ietf.org/html/rfc5646#section-3.1.3
+            # https://tools.ietf.org/html/rfc5646#section-3.1.3
             assert False, "Unrecognized Type: {0}".format(record["Type"])
 
     # Check that mappings for language subtags and extlang subtags don't affect
     # each other.
     for lang in languageSubtags:
         if lang in extlangMappings and extlangMappings[lang]["preferred"] != lang:
             raise Exception("Conflict: lang with extlang mapping: " + lang)
     for extlang in extlangSubtags:
@@ -287,16 +286,32 @@ def validateTimeZones(zones, links):
     if intersect:
         raise RuntimeError("Links also present in zones: %s" % intersect)
 
     zoneNames = set(z.name for z in zones)
     linkTargets = set(links.viewvalues())
     if not linkTargets.issubset(zoneNames):
         raise RuntimeError("Link targets not found: %s" % linkTargets.difference(zoneNames))
 
+def partition(iterable, *predicates):
+    def innerPartition(pred, it):
+        it1, it2 = tee(it)
+        return (ifilter(pred, it1), ifilterfalse(pred, it2))
+    if len(predicates) == 0:
+        return iterable
+    (left, right) = innerPartition(predicates[0], iterable)
+    if len(predicates) == 1:
+        return (left, right)
+    return tuple([left] + list(partition(right, *predicates[1:])))
+
+def listIANAFiles(tzdataDir):
+    def isTzFile(d, m, f):
+        return m(f) and d.isfile(d.resolve(f))
+    return ifilter(partial(isTzFile, tzdataDir, re.compile("^[a-z0-9]+$").match), tzdataDir.listdir())
+
 def readIANAFiles(tzdataDir, files):
     """ Read all IANA time zone files from the given iterable. """
     nameSyntax = "[\w/+\-]+"
     pZone = re.compile(r"Zone\s+(?P<name>%s)\s+.*" % nameSyntax)
     pLink = re.compile(r"Link\s+(?P<target>%s)\s+(?P<name>%s)(?:\s+#.*)?" % (nameSyntax, nameSyntax))
 
     def createZone(line, fname):
         match = pZone.match(line)
@@ -316,106 +331,227 @@ def readIANAFiles(tzdataDir, files):
             if line.startswith("Zone"):
                 zones.add(createZone(line, filename))
             if line.startswith("Link"):
                 (link, target) = createLink(line, filename)
                 links[link] = target
 
     return (zones, links)
 
-def readIANATimeZones(tzdataDir):
+def readIANATimeZones(tzdataDir, ignoreBackzone, ignoreFactory):
     """ Read the IANA time zone information from `tzdataDir`. """
-    def partition(pred, it):
-        it1, it2 = tee(it)
-        return (ifilterfalse(pred, it1), ifilter(pred, it2))
-    def isTzFile(d, m, f):
-        return m(f) and d.isfile(d.resolve(f))
 
     backzoneFiles = {"backzone"}
-    (tzfiles, bkfiles) = partition(
-        backzoneFiles.__contains__,
-        ifilter(partial(isTzFile, tzdataDir, re.compile("^[a-z0-9]+$").match), tzdataDir.listdir())
-    )
+    (bkfiles, tzfiles) = partition(listIANAFiles(tzdataDir), backzoneFiles.__contains__)
 
     # Read zone and link infos.
     (zones, links) = readIANAFiles(tzdataDir, tzfiles)
     (backzones, backlinks) = readIANAFiles(tzdataDir, bkfiles)
 
+    # Remove the placeholder time zone "Factory".
+    if ignoreFactory:
+        zones.remove(Zone("Factory"))
+
     # Merge with backzone data.
-    zones |= backzones
-    zones.remove(Zone("Factory"))
-    links = {name: target for name, target in links.iteritems() if name not in backzones}
-    links.update(backlinks)
+    if not ignoreBackzone:
+        zones |= backzones
+        links = {name: target for name, target in links.iteritems() if name not in backzones}
+        links.update(backlinks)
 
     validateTimeZones(zones, links)
 
     return (zones, links)
 
 def readICUResourceFile(filename):
     """ Read an ICU resource file.
 
-        Returns ("table", <table-name>) when a table starts/ends.
-        Returns ("entry", <data>) for each table entry.
+        Yields (<table-name>, <startOrEnd>, <value>) for each table.
     """
-    p = re.compile(r"^(?P<name>.+)\{$")
-    q = re.compile(r'^(?P<quote>"?)(?P<key>.+)(?P=quote)\{"(?P<value>.+)"\}$')
+
+    numberValue = r"-?\d+"
+    stringValue = r'".+?"'
+    asVector = lambda val: r"%s(?:\s*,\s*%s)*" % (val, val)
+    numberVector = asVector(numberValue)
+    stringVector = asVector(stringValue)
+
+    reNumberVector = re.compile(numberVector)
+    reStringVector = re.compile(stringVector)
+    reNumberValue = re.compile(numberValue)
+    reStringValue = re.compile(stringValue)
+    def parseValue(value):
+        m = reNumberVector.match(value)
+        if m:
+            return [int(v) for v in reNumberValue.findall(value)]
+        m = reStringVector.match(value)
+        if m:
+            return [v[1:-1] for v in reStringValue.findall(value)]
+        raise RuntimeError("unknown value type: %s" % value)
+
+    def extractValue(values):
+        if len(values) == 0:
+            return None
+        if len(values) == 1:
+            return values[0]
+        return values
+
+    def line(*args):
+        maybeMultiComments = r"(?:/\*[^*]*\*/)*"
+        maybeSingleComment = r"(?://.*)?"
+        lineStart = "^%s" % maybeMultiComments
+        lineEnd = "%s\s*%s$" % (maybeMultiComments, maybeSingleComment)
+        return re.compile(r"\s*".join(chain([lineStart], args, [lineEnd])))
+
+    tableName = r'(?P<quote>"?)(?P<name>.+?)(?P=quote)'
+    tableValue = r"(?P<value>%s|%s)" % (numberVector, stringVector)
+
+    reStartTable = line(tableName, r"\{")
+    reEndTable = line(r"\}")
+    reSingleValue = line(r",?", tableValue, r",?")
+    reCompactTable = line(tableName, r"\{", tableValue, r"\}")
+    reEmptyLine = line()
 
     tables = []
+    currentTable = lambda: "|".join(tables)
+    values = []
     for line in flines(filename, "utf-8-sig"):
         line = line.strip()
-        if line == "" or line.startswith("//"):
+        if line == "":
+            continue
+
+        m = reEmptyLine.match(line)
+        if m:
             continue
-        m = p.match(line)
+
+        m = reStartTable.match(line)
         if m:
+            assert len(values) == 0
             tables.append(m.group("name"))
-            yield ("table", m.group("name"))
             continue
-        if line == "}":
-            yield ("table", tables.pop())
+
+        m = reEndTable.match(line)
+        if m:
+            yield (currentTable(), extractValue(values))
+            tables.pop()
+            values = []
             continue
-        m = q.match(line)
-        if not m:
-            raise RuntimeError("unknown entry: %s" % line)
-        yield ("entry", (m.group("key"), m.group("value")))
+
+        m = reCompactTable.match(line)
+        if m:
+            assert len(values) == 0
+            tables.append(m.group("name"))
+            yield (currentTable(), extractValue(parseValue(m.group("value"))))
+            tables.pop()
+            continue
 
-def readICUTimeZones(icuTzDir):
+        m = reSingleValue.match(line)
+        if m and tables:
+            values.extend(parseValue(m.group("value")))
+            continue
+
+        raise RuntimeError("unknown entry: %s" % line)
+
+def readICUTimeZonesFromTimezoneTypes(icuTzDir):
     """ Read the ICU time zone information from `icuTzDir`/timezoneTypes.txt
         and returns the tuple (zones, links).
     """
-    inTypeMap = False
-    inTypeMapTimezone = False
-    inTypeAlias = False
-    inTypeAliasTimezone = False
+    typeMapTimeZoneKey = "timezoneTypes:table(nofallback)|typeMap|timezone|"
+    typeAliasTimeZoneKey = "timezoneTypes:table(nofallback)|typeAlias|timezone|"
     toTimeZone = lambda name: Zone(name.replace(":", "/"))
 
-    timezones = set()
-    aliases = dict()
-    for kind, value in readICUResourceFile(os.path.join(icuTzDir, "timezoneTypes.txt")):
-        if kind == "table":
-            if value == "typeMap":
-                inTypeMap = not inTypeMap
-            elif inTypeMap and value == "timezone":
-                inTypeMapTimezone = not inTypeMapTimezone
-            elif value == "typeAlias":
-                inTypeAlias = not inTypeAlias
-            elif inTypeAlias and value == "timezone":
-                inTypeAliasTimezone = not inTypeAliasTimezone
-            continue
-        (fst, snd) = value
-        if inTypeMapTimezone:
-            timezones.add(toTimeZone(fst))
-        if inTypeAliasTimezone:
-            aliases[toTimeZone(fst)] = snd
+    zones = set()
+    links = dict()
+
+    for name, value in readICUResourceFile(os.path.join(icuTzDir, "timezoneTypes.txt")):
+        if name.startswith(typeMapTimeZoneKey):
+            zones.add(toTimeZone(name[len(typeMapTimeZoneKey):]))
+        if name.startswith(typeAliasTimeZoneKey):
+            links[toTimeZone(name[len(typeAliasTimeZoneKey):])] = value
+
+    # Remove the ICU placeholder time zone "Etc/Unknown".
+    zones.remove(Zone("Etc/Unknown"))
+
+    validateTimeZones(zones, links)
+
+    return (zones, links)
+
+def readICUTimeZonesFromZoneInfo(icuTzDir, ignoreFactory):
+    """ Read the ICU time zone information from `icuTzDir`/zoneinfo64.txt
+        and returns the tuple (zones, links).
+    """
+    zoneKey = "zoneinfo64:table(nofallback)|Zones:array|:table"
+    linkKey = "zoneinfo64:table(nofallback)|Zones:array|:int"
+    namesKey = "zoneinfo64:table(nofallback)|Names"
+
+    tzId = 0
+    tzLinks = dict()
+    tzNames = []
+
+    for name, value in readICUResourceFile(os.path.join(icuTzDir, "zoneinfo64.txt")):
+        if name == zoneKey:
+            tzId += 1
+        elif name == linkKey:
+            tzLinks[tzId] = int(value)
+            tzId += 1
+        elif name == namesKey:
+            tzNames.extend(value)
+
+    links = dict((Zone(tzNames[zone]), tzNames[target]) for (zone, target) in tzLinks.iteritems())
+    zones = set([Zone(v) for v in tzNames if Zone(v) not in links])
 
     # Remove the ICU placeholder time zone "Etc/Unknown".
-    timezones.remove(Zone("Etc/Unknown"))
+    zones.remove(Zone("Etc/Unknown"))
+
+    # Remove the placeholder time zone "Factory".
+    if ignoreFactory:
+        zones.remove(Zone("Factory"))
+
+    validateTimeZones(zones, links)
+
+    return (zones, links)
+
+def readICUTimeZones(icuDir, icuTzDir, ignoreFactory):
+    # zoneinfo64.txt contains the supported time zones by ICU. This data is
+    # generated from tzdata files, it doesn't include "backzone" in stock ICU.
+    (zoneinfoZones, zoneinfoLinks) = readICUTimeZonesFromZoneInfo(icuTzDir, ignoreFactory)
+
+    # timezoneTypes.txt contains the canonicalization information for ICU. This
+    # data is generated from CLDR files. It includes data about time zones from
+    # tzdata's "backzone" file.
+    (typesZones, typesLinks) = readICUTimeZonesFromTimezoneTypes(icuTzDir)
+
+    # Information in zoneinfo64 should be a superset of timezoneTypes.
+    inZoneInfo64 = lambda zone: zone in zoneinfoZones or zone in zoneinfoLinks
 
-    validateTimeZones(timezones, aliases)
+    # Remove legacy ICU time zones from zoneinfo64 data.
+    (legacyZones, legacyLinks) = readICULegacyZones(icuDir)
+    zoneinfoZones = set(zone for zone in zoneinfoZones if zone not in legacyZones)
+    zoneinfoLinks = dict((zone, target) for (zone, target) in zoneinfoLinks.iteritems() if zone not in legacyLinks)
+
+    notFoundInZoneInfo64 = [zone for zone in typesZones if not inZoneInfo64(zone)]
+    if notFoundInZoneInfo64:
+        raise RuntimeError("Missing time zones in zoneinfo64.txt: %s" % notFoundInZoneInfo64)
+
+    notFoundInZoneInfo64 = [zone for zone in typesLinks.iterkeys() if not inZoneInfo64(zone)]
+    if notFoundInZoneInfo64:
+        raise RuntimeError("Missing time zones in zoneinfo64.txt: %s" % notFoundInZoneInfo64)
 
-    return (timezones, aliases)
+    # zoneinfo64.txt only defines the supported time zones by ICU, the canonicalization
+    # rules are defined through timezoneTypes.txt. Merge both to get the actual zones
+    # and links used by ICU.
+    icuZones = set(chain(
+                 (zone for zone in zoneinfoZones if zone not in typesLinks),
+                 (zone for zone in typesZones)
+               ))
+    icuLinks = dict(chain(
+                 ((zone, target) for (zone, target) in zoneinfoLinks.iteritems() if zone not in typesZones),
+                 ((zone, target) for (zone, target) in typesLinks.iteritems())
+               ))
+
+    return (icuZones, icuLinks)
+
 
 def readICULegacyZones(icuDir):
     """ Read the ICU legacy time zones from `icuTzDir`/tools/tzcode/icuzones
         and returns the tuple (zones, links).
     """
     tzdir = TzDataDir(os.path.join(icuDir, "tools/tzcode"))
     (zones, links) = readIANAFiles(tzdir, ["icuzones"])
 
@@ -430,193 +566,412 @@ def icuTzDataVersion(icuTzDir):
         p = re.compile(pattern)
         for line in flines(f, "utf-8-sig"):
             m = p.search(line)
             if m:
                 return m.group(1)
         return None
 
     zoneinfo = os.path.join(icuTzDir, "zoneinfo64.txt")
-    if os.path.isfile(zoneinfo):
-        version = searchInFile("^//\s+tz version:\s+([0-9]{4}[a-z])$", zoneinfo)
-    else:
-        version = None
+    if not os.path.isfile(zoneinfo):
+        raise RuntimeError("file not found: %s" % zoneinfo)
+    version = searchInFile("^//\s+tz version:\s+([0-9]{4}[a-z])$", zoneinfo)
+    if version is None:
+        raise RuntimeError("%s does not contain a valid tzdata version string" % zoneinfo)
     return version
 
-def findIncorrectICUZones(zones, links, timezones, aliases):
+def findIncorrectICUZones(ianaZones, ianaLinks, icuZones, icuLinks, ignoreBackzone):
     """ Find incorrect ICU zone entries. """
-    isIANATimeZone = lambda zone: zone in zones or zone in links
-    isICUTimeZone = lambda zone: zone in timezones or zone in aliases
-    isICULink = lambda zone: zone in aliases
+    isIANATimeZone = lambda zone: zone in ianaZones or zone in ianaLinks
+    isICUTimeZone = lambda zone: zone in icuZones or zone in icuLinks
+    isICULink = lambda zone: zone in icuLinks
 
-    # Zones which aren't present in ICU.
-    missingTimeZones = ((zone, "<not present>") for zone in zones if not isICUTimeZone(zone))
+    # All IANA zones should be present in ICU.
+    missingTimeZones = [zone for zone in ianaZones if not isICUTimeZone(zone)]
+    # Normally zones in backzone are also present as links in one of the other
+    # time zone files. The only exception to this rule is the Asia/Hanoi time
+    # zone, this zone is only present in the backzone file.
+    expectedMissing = [] if ignoreBackzone else [Zone("Asia/Hanoi")]
+    if missingTimeZones != expectedMissing:
+        raise RuntimeError("Not all zones are present in ICU, did you forget "
+                           "to run intl/update-tzdata.sh? %s" % missingTimeZones)
+
+    # Zones which are only present in ICU?
+    additionalTimeZones = [zone for zone in icuZones if not isIANATimeZone(zone)]
+    if additionalTimeZones:
+        raise RuntimeError("Additional zones present in ICU, did you forget "
+                           "to run intl/update-tzdata.sh? %s" % additionalTimeZones)
 
     # Zones which are marked as links in ICU.
-    incorrectMapping = ((zone, aliases[zone]) for zone in zones if isICULink(zone))
-
-    # Zones which are only present in ICU?
-    removedTimeZones = [zone for zone in timezones if not isIANATimeZone(zone)]
-    if removedTimeZones:
-        raise RuntimeError("Removed zones? %s" % removedTimeZones)
-
-    result = chain(missingTimeZones, incorrectMapping)
+    result = ((zone, icuLinks[zone]) for zone in ianaZones if isICULink(zone))
 
     # Remove unnecessary UTC mappings.
     utcnames = ["Etc/UTC", "Etc/UCT", "Etc/GMT"]
-    result = ifilterfalse(lambda (zone, alias): zone.name in utcnames, result)
+    result = ifilterfalse(lambda (zone, target): zone.name in utcnames, result)
 
     return sorted(result, key=itemgetter(0))
 
-def findIncorrectICULinks(zones, links, timezones, aliases):
+def findIncorrectICULinks(ianaZones, ianaLinks, icuZones, icuLinks):
     """ Find incorrect ICU link entries. """
-    isIANATimeZone = lambda zone: zone in zones or zone in links
-    isICUTimeZone = lambda zone: zone in timezones or zone in aliases
-    isICULink = lambda zone: zone in aliases
-    isICUZone = lambda zone: zone in timezones
+    isIANATimeZone = lambda zone: zone in ianaZones or zone in ianaLinks
+    isICUTimeZone = lambda zone: zone in icuZones or zone in icuLinks
+    isICULink = lambda zone: zone in icuLinks
+    isICUZone = lambda zone: zone in icuZones
 
-    # Links which aren't present in ICU.
-    missingTimeZones = ((zone, target, "<not present>") for (zone, target) in links.iteritems() if not isICUTimeZone(zone))
-
-    # Links which have a different target in ICU.
-    incorrectMapping = ((zone, target, aliases[zone]) for (zone, target) in links.iteritems() if isICULink(zone) and target != aliases[zone])
-
-    # Links which are zones in ICU.
-    incorrectMapping2 = ((zone, target, zone.name) for (zone, target) in links.iteritems() if isICUZone(zone))
+    # All links should be present in ICU.
+    missingTimeZones = [zone for zone in ianaLinks.iterkeys() if not isICUTimeZone(zone)]
+    if missingTimeZones:
+        raise RuntimeError("Not all zones are present in ICU, did you forget "
+                           "to run intl/update-tzdata.sh? %s" % missingTimeZones)
 
     # Links which are only present in ICU?
-    removedTimeZones = [zone for zone in aliases.iterkeys() if not isIANATimeZone(zone)]
-    if removedTimeZones:
-        raise RuntimeError("Removed zones? %s" % removedTimeZones)
+    additionalTimeZones = [zone for zone in icuLinks.iterkeys() if not isIANATimeZone(zone)]
+    if additionalTimeZones:
+        raise RuntimeError("Additional links present in ICU, did you forget "
+                           "to run intl/update-tzdata.sh? %s" % additionalTimeZones)
 
-    result = chain(missingTimeZones, incorrectMapping, incorrectMapping2)
+    result = chain(
+        # IANA links which have a different target in ICU.
+        ((zone, target, icuLinks[zone]) for (zone, target) in ianaLinks.iteritems() if isICULink(zone) and target != icuLinks[zone]),
+
+        # IANA links which are zones in ICU.
+        ((zone, target, zone.name) for (zone, target) in ianaLinks.iteritems() if isICUZone(zone))
+    )
 
     # Remove unnecessary UTC mappings.
     utcnames = ["Etc/UTC", "Etc/UCT", "Etc/GMT"]
-    result = ifilterfalse(lambda (zone, target, alias): target in utcnames and alias in utcnames, result)
+    result = ifilterfalse(lambda (zone, target, icuTarget): target in utcnames and icuTarget in utcnames, result)
 
     return sorted(result, key=itemgetter(0))
 
-def processTimeZones(tzdataDir, icuDir, icuTzDir, tzversion, out):
-    """ Read the time zone info and create a new IntlTzData.js file. """
+generatedFileWarning = u"// Generated by make_intl_data.py. DO NOT EDIT."
+tzdataVersionComment = u"// tzdata version = {0}"
+
+def processTimeZones(tzdataDir, icuDir, icuTzDir, version, ignoreBackzone, ignoreFactory, out):
+    """ Read the time zone info and create a new time zone cpp file. """
     print("Processing tzdata mapping...")
-    (zones, links) = readIANATimeZones(tzdataDir)
-    (timezones, aliases) = readICUTimeZones(icuTzDir)
-    (legacyzones, legacylinks) = readICULegacyZones(icuDir)
-    icuversion = icuTzDataVersion(icuTzDir)
+    (ianaZones, ianaLinks) = readIANATimeZones(tzdataDir, ignoreBackzone, ignoreFactory)
+    (icuZones, icuLinks) = readICUTimeZones(icuDir, icuTzDir, ignoreFactory)
+    (legacyZones, legacyLinks) = readICULegacyZones(icuDir)
 
-    incorrectZones = findIncorrectICUZones(zones, links, timezones, aliases)
+    incorrectZones = findIncorrectICUZones(ianaZones, ianaLinks, icuZones, icuLinks, ignoreBackzone)
     if not incorrectZones:
         print("<<< No incorrect ICU time zones found, please update Intl.js! >>>")
-        print("<<< Maybe http://bugs.icu-project.org/trac/ticket/12044 was fixed? >>>")
+        print("<<< Maybe https://ssl.icu-project.org/trac/ticket/12044 was fixed? >>>")
 
-    incorrectLinks = findIncorrectICULinks(zones, links, timezones, aliases)
+    incorrectLinks = findIncorrectICULinks(ianaZones, ianaLinks, icuZones, icuLinks)
     if not incorrectLinks:
         print("<<< No incorrect ICU time zone links found, please update Intl.js! >>>")
-        print("<<< Maybe http://bugs.icu-project.org/trac/ticket/12044 was fixed? >>>")
+        print("<<< Maybe https://ssl.icu-project.org/trac/ticket/12044 was fixed? >>>")
 
     print("Writing Intl tzdata file...")
     with io.open(out, mode="w", encoding="utf-8", newline="") as f:
         println = partial(print, file=f)
 
-        println(u"// Generated by make_intl_data.py. DO NOT EDIT.")
-        println(u"// tzdata version = %s" % tzversion)
-        println(u"// ICU tzdata version = %s" % icuversion)
+        println(generatedFileWarning)
+        println(tzdataVersionComment.format(version))
+        println(u"")
+
+        println(u"#ifndef builtin_IntlTimeZoneData_h")
+        println(u"#define builtin_IntlTimeZoneData_h")
+        println(u"")
+
+        println(u"namespace js {")
+        println(u"namespace timezone {")
         println(u"")
 
         println(u"// Format:")
-        println(u'// "ZoneName": true // ICU-Name [time zone file]')
-        println(u"var tzZoneNamesNonICU = {")
-        for (zone, alias) in incorrectZones:
-            println(u'    "%s": true, // %s [%s]' % (zone, alias, zone.filename))
+        println(u'// "ZoneName" // ICU-Name [time zone file]')
+        println(u"const char* const ianaZonesTreatedAsLinksByICU[] = {")
+        for (zone, icuZone) in incorrectZones:
+            println(u'    "%s", // %s [%s]' % (zone, icuZone, zone.filename))
         println(u"};")
         println(u"")
 
         println(u"// Format:")
-        println(u'// "LinkName": "Target" // ICU-Target [time zone file]')
-        println(u"var tzLinkNamesNonICU = {")
-        for (zone, target, alias) in incorrectLinks:
-            println(u'    "%s": "%s", // %s [%s]' % (zone, target, alias, zone.filename))
+        println(u'// "LinkName", "Target" // ICU-Target [time zone file]')
+        println(u"struct LinkAndTarget");
+        println(u"{");
+        println(u"    const char* const link;");
+        println(u"    const char* const target;");
+        println(u"};");
+        println(u"")
+        println(u"const LinkAndTarget ianaLinksCanonicalizedDifferentlyByICU[] = {")
+        for (zone, target, icuTarget) in incorrectLinks:
+            println(u'    { "%s", "%s" }, // %s [%s]' % (zone, target, icuTarget, zone.filename))
         println(u"};")
         println(u"")
 
         println(u"// Legacy ICU time zones, these are not valid IANA time zone names. We also")
         println(u"// disallow the old and deprecated System V time zones.")
-        println(u"// http://source.icu-project.org/repos/icu/icu/trunk/source/tools/tzcode/icuzones")
-        println(u"var legacyICUTimeZones = {")
-        for zone in sorted(legacylinks.keys()) + sorted(list(legacyzones)):
-            println(u'    "%s": true,' % zone)
+        println(u"// https://ssl.icu-project.org/repos/icu/trunk/icu4c/source/tools/tzcode/icuzones")
+        println(u"const char* const legacyICUTimeZones[] = {")
+        for zone in chain(sorted(legacyLinks.keys()), sorted(legacyZones)):
+            println(u'    "%s",' % zone)
+        println(u"};")
+        println(u"")
+
+        println(u"} // namespace timezone")
+        println(u"} // namespace js")
+        println(u"")
+        println(u"#endif /* builtin_IntlTimeZoneData_h */")
+
+def updateBackzoneLinks(tzdataDir, links):
+    (backzoneZones, backzoneLinks) = readIANAFiles(tzdataDir, ["backzone"])
+    (stableZones, updatedLinks, updatedZones) = partition(
+        links.iteritems(),
+        # Link not changed in backzone.
+        lambda (zone, target): zone not in backzoneLinks and zone not in backzoneZones,
+        # Link has a new target.
+        lambda (zone, target): zone in backzoneLinks,
+    )
+    # Keep stable zones and links with updated target.
+    return dict(chain(
+                stableZones,
+                imap(lambda (zone, target): (zone, backzoneLinks[zone]), updatedLinks)
+           ))
+
+def generateTzDataLinkTestContent(testDir, version, fileName, description, links):
+    with io.open(os.path.join(testDir, fileName), mode="w", encoding="utf-8", newline="") as f:
+        println = partial(print, file=f)
+
+        println(u'// |reftest| skip-if(!this.hasOwnProperty("Intl"))')
+        println(u"")
+        println(generatedFileWarning)
+        println(tzdataVersionComment.format(version))
+        println(u"""
+const tzMapper = [
+    x => x,
+    x => x.toUpperCase(),
+    x => x.toLowerCase(),
+];
+""")
+
+        println(description)
+        println(u"const links = {")
+        for (zone, target) in sorted(links, key=itemgetter(0)):
+            println(u'    "%s": "%s",' % (zone, target))
         println(u"};")
 
+        println(u"""
+for (let [linkName, target] of Object.entries(links)) {
+    if (target === "Etc/UTC" || target === "Etc/GMT")
+        target = "UTC";
+
+    for (let map of tzMapper) {
+        let dtf = new Intl.DateTimeFormat(undefined, {timeZone: map(linkName)});
+        let resolvedTimeZone = dtf.resolvedOptions().timeZone;
+        assertEq(resolvedTimeZone, target, `${linkName} -> ${target}`);
+    }
+}
+""")
+        println(u"""
+if (typeof reportCompare === "function")
+    reportCompare(0, 0, "ok");
+""")
+
+def generateTzDataTestBackwardLinks(tzdataDir, version, ignoreBackzone, testDir):
+    (zones, links) = readIANAFiles(tzdataDir, ["backward"])
+    assert len(zones) == 0
+
+    if not ignoreBackzone:
+        links = updateBackzoneLinks(tzdataDir, links)
+
+    generateTzDataLinkTestContent(
+        testDir, version,
+        "timeZone_backward_links.js",
+        u"// Link names derived from IANA Time Zone Database, backward file.",
+        links.iteritems()
+    )
+
+def generateTzDataTestNotBackwardLinks(tzdataDir, version, ignoreBackzone, testDir):
+    tzfiles = ifilterfalse({"backward", "backzone"}.__contains__, listIANAFiles(tzdataDir))
+    (zones, links) = readIANAFiles(tzdataDir, tzfiles)
+
+    if not ignoreBackzone:
+        links = updateBackzoneLinks(tzdataDir, links)
+
+    generateTzDataLinkTestContent(
+        testDir, version,
+        "timeZone_notbackward_links.js",
+        u"// Link names derived from IANA Time Zone Database, excluding backward file.",
+        links.iteritems()
+    )
+
+def generateTzDataTestBackzone(tzdataDir, version, ignoreBackzone, testDir):
+    backzoneFiles = {"backzone"}
+    (bkfiles, tzfiles) = partition(listIANAFiles(tzdataDir), backzoneFiles.__contains__)
+
+    # Read zone and link infos.
+    (zones, links) = readIANAFiles(tzdataDir, tzfiles)
+    (backzones, backlinks) = readIANAFiles(tzdataDir, bkfiles)
+
+    if not ignoreBackzone:
+        comment=u"""\
+// This file was generated with historical, pre-1970 backzone information
+// respected. Therefore, every zone key listed below is its own Zone, not
+// a Link to a modern-day target as IANA ignoring backzones would say.
+
+"""
+    else:
+        comment=u"""\
+// This file was generated while ignoring historical, pre-1970 backzone
+// information. Therefore, every zone key listed below is part of a Link
+// whose target is the corresponding value.
+
+"""
+
+    generateTzDataLinkTestContent(
+        testDir, version,
+        "timeZone_backzone.js",
+        comment + u"// Backzone zones derived from IANA Time Zone Database.",
+        ((zone, zone if not ignoreBackzone else links[zone]) for zone in backzones if zone in links)
+    )
+
+def generateTzDataTestBackzoneLinks(tzdataDir, version, ignoreBackzone, testDir):
+    backzoneFiles = {"backzone"}
+    (bkfiles, tzfiles) = partition(listIANAFiles(tzdataDir), backzoneFiles.__contains__)
+
+    # Read zone and link infos.
+    (zones, links) = readIANAFiles(tzdataDir, tzfiles)
+    (backzones, backlinks) = readIANAFiles(tzdataDir, bkfiles)
+
+    if not ignoreBackzone:
+        comment=u"""\
+// This file was generated with historical, pre-1970 backzone information
+// respected. Therefore, every zone key listed below points to a target
+// in the backzone file and not to its modern-day target as IANA ignoring
+// backzones would say.
+
+"""
+    else:
+        comment=u"""\
+// This file was generated while ignoring historical, pre-1970 backzone
+// information. Therefore, every zone key listed below is part of a Link
+// whose target is the corresponding value ignoring any backzone entries.
+
+"""
+
+    generateTzDataLinkTestContent(
+        testDir, version,
+        "timeZone_backzone_links.js",
+        comment +  u"// Backzone links derived from IANA Time Zone Database.",
+        ((zone, target if not ignoreBackzone else links[zone]) for (zone, target) in backlinks.iteritems())
+    )
+
+def generateTzDataTests(tzdataDir, version, ignoreBackzone, testDir):
+    generateTzDataTestBackwardLinks(tzdataDir, version, ignoreBackzone, testDir)
+    generateTzDataTestNotBackwardLinks(tzdataDir, version, ignoreBackzone, testDir)
+    generateTzDataTestBackzone(tzdataDir, version, ignoreBackzone, testDir)
+    generateTzDataTestBackzoneLinks(tzdataDir, version, ignoreBackzone, testDir)
+
 def updateTzdata(args):
-    """ Update the IntlTzData.js file. """
-    version = args.version
-    if not re.match("^([0-9]{4}[a-z])$", version):
-        raise RuntimeError("illegal version: %s" % version)
-    url = args.url
-    if url is None:
-        url = "https://www.iana.org/time-zones/repository/releases/tzdata%s.tar.gz" % version
+    """ Update the time zone cpp file. """
+
+    # This script must reside in js/src/builtin to work correctly.
+    (thisDir, thisFile) = os.path.split(os.path.abspath(sys.argv[0]))
+    thisDir = os.path.normpath(thisDir)
+    if "/".join(thisDir.split(os.sep)[-3:]) != "js/src/builtin":
+        raise RuntimeError("%s must reside in js/src/builtin" % sys.argv[0])
+    topsrcdir = "/".join(thisDir.split(os.sep)[:-3])
+
+    icuDir = os.path.join(topsrcdir, "intl/icu/source")
+    if not os.path.isdir(icuDir):
+        raise RuntimeError("not a directory: %s" % icuDir)
+
+    icuTzDir = os.path.join(topsrcdir, "intl/tzdata/source")
+    if not os.path.isdir(icuTzDir):
+        raise RuntimeError("not a directory: %s" % icuTzDir)
+
+    dateTimeFormatTestDir = os.path.join(topsrcdir, "js/src/tests/Intl/DateTimeFormat")
+    if not os.path.isdir(dateTimeFormatTestDir):
+        raise RuntimeError("not a directory: %s" % dateTimeFormatTestDir)
+
     tzDir = args.tz
     if tzDir is not None and not (os.path.isdir(tzDir) or os.path.isfile(tzDir)):
         raise RuntimeError("not a directory or file: %s" % tzDir)
-    icuDir = args.icu
-    if not os.path.isdir(icuDir):
-        raise RuntimeError("not a directory: %s" % icuDir)
-    icuTzDir = args.icutz
-    if icuTzDir is None:
-        icuTzDir = os.path.join(icuDir, "data/misc")
-    if not os.path.isdir(icuTzDir):
-        raise RuntimeError("not a directory: %s" % icuTzDir)
+    ignoreBackzone = args.ignore_backzone
+    # TODO: Accept or ignore the placeholder time zone "Factory"?
+    ignoreFactory = False
     out = args.out
 
+    version = icuTzDataVersion(icuTzDir)
+    url = "https://www.iana.org/time-zones/repository/releases/tzdata%s.tar.gz" % version
+
     print("Arguments:")
     print("\ttzdata version: %s" % version)
     print("\ttzdata URL: %s" % url)
     print("\ttzdata directory|file: %s" % tzDir)
     print("\tICU directory: %s" % icuDir)
     print("\tICU timezone directory: %s" % icuTzDir)
+    print("\tIgnore backzone file: %s" % ignoreBackzone)
     print("\tOutput file: %s" % out)
     print("")
 
     def updateFrom(f):
         if os.path.isfile(f) and tarfile.is_tarfile(f):
             with tarfile.open(f, "r:*") as tar:
-                processTimeZones(TzDataFile(tar), icuDir, icuTzDir, version, out)
+                processTimeZones(TzDataFile(tar), icuDir, icuTzDir, version, ignoreBackzone, ignoreFactory, out)
+                generateTzDataTests(TzDataFile(tar), version, ignoreBackzone, dateTimeFormatTestDir)
         elif os.path.isdir(f):
-            processTimeZones(TzDataDir(f), icuDir, icuTzDir, version, out)
+            processTimeZones(TzDataDir(f), icuDir, icuTzDir, version, ignoreBackzone, ignoreFactory, out)
+            generateTzDataTests(TzDataDir(f), version, ignoreBackzone, dateTimeFormatTestDir)
         else:
             raise RuntimeError("unknown format")
 
     if tzDir is None:
         print("Downloading tzdata file...")
         with closing(urllib2.urlopen(url)) as tzfile:
-            fname = urlparse.urlsplit(tzfile.geturl()).path.split('/')[-1]
+            fname = urlparse.urlsplit(tzfile.geturl()).path.split("/")[-1]
             with tempfile.NamedTemporaryFile(suffix=fname) as tztmpfile:
                 print("File stored in %s" % tztmpfile.name)
                 tztmpfile.write(tzfile.read())
                 tztmpfile.flush()
                 updateFrom(tztmpfile.name)
     else:
         updateFrom(tzDir)
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     import argparse
 
+    def EnsureHttps(v):
+        if not v.startswith("https:"):
+            raise argparse.ArgumentTypeError("URL protocol must be https: " % v)
+        return v
+
     parser = argparse.ArgumentParser(description="Update intl data.")
     subparsers = parser.add_subparsers(help="Select update mode")
 
-    parser_tags = subparsers.add_parser("langtags", help="Update language-subtag-registry", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
-    parser_tags.add_argument("--url", metavar="URL", default="http://www.iana.org/assignments/language-subtag-registry", help="Download url for language-subtag-registry.txt")
-    parser_tags.add_argument("--out", default="IntlData.js", help="Output file")
-    parser_tags.add_argument("file", nargs="?", help="Local language-subtag-registry.txt file, if omitted uses <URL>")
+    parser_tags = subparsers.add_parser("langtags",
+                                        help="Update language-subtag-registry")
+    parser_tags.add_argument("--url",
+                             metavar="URL",
+                             default="https://www.iana.org/assignments/language-subtag-registry",
+                             type=EnsureHttps,
+                             help="Download url for language-subtag-registry.txt (default: %(default)s)")
+    parser_tags.add_argument("--out",
+                             default="IntlData.js",
+                             help="Output file (default: %(default)s)")
+    parser_tags.add_argument("file",
+                             nargs="?",
+                             help="Local language-subtag-registry.txt file, if omitted uses <URL>")
     parser_tags.set_defaults(func=updateLangTags)
 
     parser_tz = subparsers.add_parser("tzdata", help="Update tzdata")
-    parser_tz.add_argument("--icu", metavar="ICU", required=True, help="ICU source directory")
-    parser_tz.add_argument("--icutz", help="ICU timezone directory, defaults to <ICU>/data/misc")
-    parser_tz.add_argument("--tz", help="Local tzdata directory or file, if omitted uses <URL>")
-    parser_tz.add_argument("--url", metavar="URL", help="Download url for tzdata, defaults to \"https://www.iana.org/time-zones/repository/releases/tzdata<VERSION>.tar.gz\"")
-    parser_tz.add_argument("--version", metavar="VERSION", required=True, help="tzdata version")
-    parser_tz.add_argument("--out", default="IntlTzData.js", help="Output file, defaults to \"IntlTzData.js\"")
+    parser_tz.add_argument("--tz",
+                           help="Local tzdata directory or file, if omitted downloads tzdata "
+                                "distribution from https://www.iana.org/time-zones/")
+    # ICU doesn't include the backzone file by default, but we still like to
+    # use the backzone time zone names to avoid user confusion. This does lead
+    # to formatting "historic" dates (pre-1970 era) with the wrong time zone,
+    # but that's probably acceptable for now.
+    parser_tz.add_argument("--ignore-backzone",
+                           action="store_true",
+                           help="Ignore tzdata's 'backzone' file. Can be enabled to generate more "
+                                "accurate time zone canonicalization reflecting the actual time "
+                                "zones as used by ICU.")
+    parser_tz.add_argument("--out",
+                           default="IntlTimeZoneData.h",
+                           help="Output file (default: %(default)s)")
     parser_tz.set_defaults(func=updateTzdata)
 
     args = parser.parse_args()
     args.func(args)
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -344,16 +344,19 @@ js::gc::GCRuntime::traceRuntimeCommon(JS
     }
 
     // Trace runtime global roots.
     MarkPersistentRooted(rt, trc);
 
     // Trace the self-hosting global compartment.
     rt->markSelfHostingGlobal(trc);
 
+    // Trace the shared Intl data.
+    rt->traceSharedIntlData(trc);
+
     // Trace anything in the single context. Note that this is actually the
     // same struct as the JSRuntime, but is still split for historical reasons.
     rt->contextFromMainThread()->mark(trc);
 
     // Trace all compartment roots, but not the compartment itself; it is
     // marked via the parent pointer if traceRoots actually traces anything.
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next())
         c->traceRoots(trc, traceOrMark);
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -752,17 +752,16 @@ selfhosted.inputs = [
     'builtin/Array.js',
     'builtin/Classes.js',
     'builtin/Date.js',
     'builtin/Error.js',
     'builtin/Function.js',
     'builtin/Generator.js',
     'builtin/Intl.js',
     'builtin/IntlData.js',
-    'builtin/IntlTzData.js',
     'builtin/Iterator.js',
     'builtin/Map.js',
     'builtin/Module.js',
     'builtin/Number.js',
     'builtin/Object.js',
     'builtin/Reflect.js',
     'builtin/RegExp.js',
     'builtin/RegExpGlobalReplaceOpt.h.js',
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/DateTimeFormat/timeZone_backward_links.js
@@ -0,0 +1,135 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Generated by make_intl_data.py. DO NOT EDIT.
+// tzdata version = 2016h
+
+const tzMapper = [
+    x => x,
+    x => x.toUpperCase(),
+    x => x.toLowerCase(),
+];
+
+// Link names derived from IANA Time Zone Database, backward file.
+const links = {
+    "Africa/Asmera": "Africa/Asmara",
+    "America/Atka": "America/Adak",
+    "America/Buenos_Aires": "America/Argentina/Buenos_Aires",
+    "America/Catamarca": "America/Argentina/Catamarca",
+    "America/Cordoba": "America/Argentina/Cordoba",
+    "America/Fort_Wayne": "America/Indiana/Indianapolis",
+    "America/Indianapolis": "America/Indiana/Indianapolis",
+    "America/Jujuy": "America/Argentina/Jujuy",
+    "America/Knox_IN": "America/Indiana/Knox",
+    "America/Louisville": "America/Kentucky/Louisville",
+    "America/Mendoza": "America/Argentina/Mendoza",
+    "America/Porto_Acre": "America/Rio_Branco",
+    "America/Santa_Isabel": "America/Tijuana",
+    "America/Shiprock": "America/Denver",
+    "America/Virgin": "America/Port_of_Spain",
+    "Antarctica/South_Pole": "Antarctica/McMurdo",
+    "Asia/Ashkhabad": "Asia/Ashgabat",
+    "Asia/Calcutta": "Asia/Kolkata",
+    "Asia/Chungking": "Asia/Chongqing",
+    "Asia/Dacca": "Asia/Dhaka",
+    "Asia/Katmandu": "Asia/Kathmandu",
+    "Asia/Macao": "Asia/Macau",
+    "Asia/Rangoon": "Asia/Yangon",
+    "Asia/Saigon": "Asia/Ho_Chi_Minh",
+    "Asia/Thimbu": "Asia/Thimphu",
+    "Asia/Ujung_Pandang": "Asia/Makassar",
+    "Asia/Ulan_Bator": "Asia/Ulaanbaatar",
+    "Atlantic/Faeroe": "Atlantic/Faroe",
+    "Australia/ACT": "Australia/Sydney",
+    "Australia/Canberra": "Australia/Sydney",
+    "Australia/LHI": "Australia/Lord_Howe",
+    "Australia/NSW": "Australia/Sydney",
+    "Australia/North": "Australia/Darwin",
+    "Australia/Queensland": "Australia/Brisbane",
+    "Australia/South": "Australia/Adelaide",
+    "Australia/Tasmania": "Australia/Hobart",
+    "Australia/Victoria": "Australia/Melbourne",
+    "Australia/West": "Australia/Perth",
+    "Australia/Yancowinna": "Australia/Broken_Hill",
+    "Brazil/Acre": "America/Rio_Branco",
+    "Brazil/DeNoronha": "America/Noronha",
+    "Brazil/East": "America/Sao_Paulo",
+    "Brazil/West": "America/Manaus",
+    "Canada/Atlantic": "America/Halifax",
+    "Canada/Central": "America/Winnipeg",
+    "Canada/East-Saskatchewan": "America/Regina",
+    "Canada/Eastern": "America/Toronto",
+    "Canada/Mountain": "America/Edmonton",
+    "Canada/Newfoundland": "America/St_Johns",
+    "Canada/Pacific": "America/Vancouver",
+    "Canada/Saskatchewan": "America/Regina",
+    "Canada/Yukon": "America/Whitehorse",
+    "Chile/Continental": "America/Santiago",
+    "Chile/EasterIsland": "Pacific/Easter",
+    "Cuba": "America/Havana",
+    "Egypt": "Africa/Cairo",
+    "Eire": "Europe/Dublin",
+    "GB": "Europe/London",
+    "GB-Eire": "Europe/London",
+    "GMT+0": "Etc/GMT",
+    "GMT-0": "Etc/GMT",
+    "GMT0": "Etc/GMT",
+    "Greenwich": "Etc/GMT",
+    "Hongkong": "Asia/Hong_Kong",
+    "Iceland": "Atlantic/Reykjavik",
+    "Iran": "Asia/Tehran",
+    "Israel": "Asia/Jerusalem",
+    "Jamaica": "America/Jamaica",
+    "Japan": "Asia/Tokyo",
+    "Kwajalein": "Pacific/Kwajalein",
+    "Libya": "Africa/Tripoli",
+    "Mexico/BajaNorte": "America/Tijuana",
+    "Mexico/BajaSur": "America/Mazatlan",
+    "Mexico/General": "America/Mexico_City",
+    "NZ": "Pacific/Auckland",
+    "NZ-CHAT": "Pacific/Chatham",
+    "Navajo": "America/Denver",
+    "PRC": "Asia/Shanghai",
+    "Pacific/Ponape": "Pacific/Pohnpei",
+    "Pacific/Samoa": "Pacific/Pago_Pago",
+    "Pacific/Truk": "Pacific/Chuuk",
+    "Pacific/Yap": "Pacific/Chuuk",
+    "Poland": "Europe/Warsaw",
+    "Portugal": "Europe/Lisbon",
+    "ROC": "Asia/Taipei",
+    "ROK": "Asia/Seoul",
+    "Singapore": "Asia/Singapore",
+    "Turkey": "Europe/Istanbul",
+    "UCT": "Etc/UCT",
+    "US/Alaska": "America/Anchorage",
+    "US/Aleutian": "America/Adak",
+    "US/Arizona": "America/Phoenix",
+    "US/Central": "America/Chicago",
+    "US/East-Indiana": "America/Indiana/Indianapolis",
+    "US/Eastern": "America/New_York",
+    "US/Hawaii": "Pacific/Honolulu",
+    "US/Indiana-Starke": "America/Indiana/Knox",
+    "US/Michigan": "America/Detroit",
+    "US/Mountain": "America/Denver",
+    "US/Pacific": "America/Los_Angeles",
+    "US/Samoa": "Pacific/Pago_Pago",
+    "UTC": "Etc/UTC",
+    "Universal": "Etc/UTC",
+    "W-SU": "Europe/Moscow",
+    "Zulu": "Etc/UTC",
+};
+
+for (let [linkName, target] of Object.entries(links)) {
+    if (target === "Etc/UTC" || target === "Etc/GMT")
+        target = "UTC";
+
+    for (let map of tzMapper) {
+        let dtf = new Intl.DateTimeFormat(undefined, {timeZone: map(linkName)});
+        let resolvedTimeZone = dtf.resolvedOptions().timeZone;
+        assertEq(resolvedTimeZone, target, `${linkName} -> ${target}`);
+    }
+}
+
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0, "ok");
+
--- a/js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js
+++ b/js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js
@@ -1,104 +1,116 @@
 // |reftest| skip-if(!this.hasOwnProperty("Intl"))
 
+// Generated by make_intl_data.py. DO NOT EDIT.
+// tzdata version = 2016h
+
 const tzMapper = [
     x => x,
     x => x.toUpperCase(),
     x => x.toLowerCase(),
 ];
 
-// Backzone names derived from IANA Time Zone Database, version tzdata2016g.
-const backZones = [
-    "Africa/Addis_Ababa", // Africa/Nairobi
-    "Africa/Asmara", // Africa/Nairobi
-    "Africa/Bamako", // Africa/Abidjan
-    "Africa/Bangui", // Africa/Lagos
-    "Africa/Banjul", // Africa/Abidjan
-    "Africa/Blantyre", // Africa/Maputo
-    "Africa/Brazzaville", // Africa/Lagos
-    "Africa/Bujumbura", // Africa/Maputo
-    "Africa/Conakry", // Africa/Abidjan
-    "Africa/Dakar", // Africa/Abidjan
-    "Africa/Dar_es_Salaam", // Africa/Nairobi
-    "Africa/Djibouti", // Africa/Nairobi
-    "Africa/Douala", // Africa/Lagos
-    "Africa/Freetown", // Africa/Abidjan
-    "Africa/Gaborone", // Africa/Maputo
-    "Africa/Harare", // Africa/Maputo
-    "Africa/Juba", // Africa/Khartoum
-    "Africa/Kampala", // Africa/Nairobi
-    "Africa/Kigali", // Africa/Maputo
-    "Africa/Kinshasa", // Africa/Lagos
-    "Africa/Libreville", // Africa/Lagos
-    "Africa/Lome", // Africa/Abidjan
-    "Africa/Luanda", // Africa/Lagos
-    "Africa/Lubumbashi", // Africa/Maputo
-    "Africa/Lusaka", // Africa/Maputo
-    "Africa/Malabo", // Africa/Lagos
-    "Africa/Maseru", // Africa/Johannesburg
-    "Africa/Mbabane", // Africa/Johannesburg
-    "Africa/Mogadishu", // Africa/Nairobi
-    "Africa/Niamey", // Africa/Lagos
-    "Africa/Nouakchott", // Africa/Abidjan
-    "Africa/Ouagadougou", // Africa/Abidjan
-    "Africa/Porto-Novo", // Africa/Lagos
-    "Africa/Sao_Tome", // Africa/Abidjan
-    "Africa/Timbuktu", // Africa/Abidjan
-    "America/Anguilla", // America/Port_of_Spain
-    "America/Antigua", // America/Port_of_Spain
-    "America/Argentina/ComodRivadavia", // America/Argentina/Catamarca
-    "America/Aruba", // America/Curacao
-    "America/Cayman", // America/Panama
-    "America/Coral_Harbour", // America/Atikokan
-    "America/Dominica", // America/Port_of_Spain
-    "America/Ensenada", // America/Tijuana
-    "America/Grenada", // America/Port_of_Spain
-    "America/Guadeloupe", // America/Port_of_Spain
-    "America/Montreal", // America/Toronto
-    "America/Montserrat", // America/Port_of_Spain
-    "America/Rosario", // America/Argentina/Cordoba
-    "America/St_Kitts", // America/Port_of_Spain
-    "America/St_Lucia", // America/Port_of_Spain
-    "America/St_Thomas", // America/Port_of_Spain
-    "America/St_Vincent", // America/Port_of_Spain
-    "America/Tortola", // America/Port_of_Spain
-    "Antarctica/McMurdo", // Pacific/Auckland
-    "Asia/Aden", // Asia/Riyadh
-    "Asia/Bahrain", // Asia/Qatar
-    "Asia/Chongqing", // Asia/Shanghai
-    "Asia/Harbin", // Asia/Shanghai
-    "Asia/Kashgar", // Asia/Urumqi
-    "Asia/Kuwait", // Asia/Riyadh
-    "Asia/Muscat", // Asia/Dubai
-    "Asia/Phnom_Penh", // Asia/Bangkok
-    "Asia/Tel_Aviv", // Asia/Jerusalem
-    "Asia/Vientiane", // Asia/Bangkok
-    "Atlantic/Jan_Mayen", // Europe/Oslo
-    "Atlantic/St_Helena", // Africa/Abidjan
-    "Europe/Belfast", // Europe/London
-    "Europe/Guernsey", // Europe/London
-    "Europe/Isle_of_Man", // Europe/London
-    "Europe/Jersey", // Europe/London
-    "Europe/Ljubljana", // Europe/Belgrade
-    "Europe/Sarajevo", // Europe/Belgrade
-    "Europe/Skopje", // Europe/Belgrade
-    "Europe/Tiraspol", // Europe/Chisinau
-    "Europe/Vaduz", // Europe/Zurich
-    "Europe/Zagreb", // Europe/Belgrade
-    "Indian/Antananarivo", // Africa/Nairobi
-    "Indian/Comoro", // Africa/Nairobi
-    "Indian/Mayotte", // Africa/Nairobi
-    "Pacific/Johnston", // Pacific/Honolulu
-    "Pacific/Midway", // Pacific/Pago_Pago
-    "Pacific/Saipan", // Pacific/Guam
-];
+// This file was generated with historical, pre-1970 backzone information
+// respected. Therefore, every zone key listed below is its own Zone, not
+// a Link to a modern-day target as IANA ignoring backzones would say.
 
-for (let timeZone of backZones) {
+// Backzone zones derived from IANA Time Zone Database.
+const links = {
+    "Africa/Addis_Ababa": "Africa/Addis_Ababa",
+    "Africa/Asmara": "Africa/Asmara",
+    "Africa/Bamako": "Africa/Bamako",
+    "Africa/Bangui": "Africa/Bangui",
+    "Africa/Banjul": "Africa/Banjul",
+    "Africa/Blantyre": "Africa/Blantyre",
+    "Africa/Brazzaville": "Africa/Brazzaville",
+    "Africa/Bujumbura": "Africa/Bujumbura",
+    "Africa/Conakry": "Africa/Conakry",
+    "Africa/Dakar": "Africa/Dakar",
+    "Africa/Dar_es_Salaam": "Africa/Dar_es_Salaam",
+    "Africa/Djibouti": "Africa/Djibouti",
+    "Africa/Douala": "Africa/Douala",
+    "Africa/Freetown": "Africa/Freetown",
+    "Africa/Gaborone": "Africa/Gaborone",
+    "Africa/Harare": "Africa/Harare",
+    "Africa/Juba": "Africa/Juba",
+    "Africa/Kampala": "Africa/Kampala",
+    "Africa/Kigali": "Africa/Kigali",
+    "Africa/Kinshasa": "Africa/Kinshasa",
+    "Africa/Libreville": "Africa/Libreville",
+    "Africa/Lome": "Africa/Lome",
+    "Africa/Luanda": "Africa/Luanda",
+    "Africa/Lubumbashi": "Africa/Lubumbashi",
+    "Africa/Lusaka": "Africa/Lusaka",
+    "Africa/Malabo": "Africa/Malabo",
+    "Africa/Maseru": "Africa/Maseru",
+    "Africa/Mbabane": "Africa/Mbabane",
+    "Africa/Mogadishu": "Africa/Mogadishu",
+    "Africa/Niamey": "Africa/Niamey",
+    "Africa/Nouakchott": "Africa/Nouakchott",
+    "Africa/Ouagadougou": "Africa/Ouagadougou",
+    "Africa/Porto-Novo": "Africa/Porto-Novo",
+    "Africa/Sao_Tome": "Africa/Sao_Tome",
+    "Africa/Timbuktu": "Africa/Timbuktu",
+    "America/Anguilla": "America/Anguilla",
+    "America/Antigua": "America/Antigua",
+    "America/Argentina/ComodRivadavia": "America/Argentina/ComodRivadavia",
+    "America/Aruba": "America/Aruba",
+    "America/Cayman": "America/Cayman",
+    "America/Coral_Harbour": "America/Coral_Harbour",
+    "America/Dominica": "America/Dominica",
+    "America/Ensenada": "America/Ensenada",
+    "America/Grenada": "America/Grenada",
+    "America/Guadeloupe": "America/Guadeloupe",
+    "America/Montreal": "America/Montreal",
+    "America/Montserrat": "America/Montserrat",
+    "America/Rosario": "America/Rosario",
+    "America/St_Kitts": "America/St_Kitts",
+    "America/St_Lucia": "America/St_Lucia",
+    "America/St_Thomas": "America/St_Thomas",
+    "America/St_Vincent": "America/St_Vincent",
+    "America/Tortola": "America/Tortola",
+    "Antarctica/McMurdo": "Antarctica/McMurdo",
+    "Asia/Aden": "Asia/Aden",
+    "Asia/Bahrain": "Asia/Bahrain",
+    "Asia/Chongqing": "Asia/Chongqing",
+    "Asia/Harbin": "Asia/Harbin",
+    "Asia/Kashgar": "Asia/Kashgar",
+    "Asia/Kuwait": "Asia/Kuwait",
+    "Asia/Muscat": "Asia/Muscat",
+    "Asia/Phnom_Penh": "Asia/Phnom_Penh",
+    "Asia/Tel_Aviv": "Asia/Tel_Aviv",
+    "Asia/Vientiane": "Asia/Vientiane",
+    "Atlantic/Jan_Mayen": "Atlantic/Jan_Mayen",
+    "Atlantic/St_Helena": "Atlantic/St_Helena",
+    "Europe/Belfast": "Europe/Belfast",
+    "Europe/Guernsey": "Europe/Guernsey",
+    "Europe/Isle_of_Man": "Europe/Isle_of_Man",
+    "Europe/Jersey": "Europe/Jersey",
+    "Europe/Ljubljana": "Europe/Ljubljana",
+    "Europe/Sarajevo": "Europe/Sarajevo",
+    "Europe/Skopje": "Europe/Skopje",
+    "Europe/Tiraspol": "Europe/Tiraspol",
+    "Europe/Vaduz": "Europe/Vaduz",
+    "Europe/Zagreb": "Europe/Zagreb",
+    "Indian/Antananarivo": "Indian/Antananarivo",
+    "Indian/Comoro": "Indian/Comoro",
+    "Indian/Mayotte": "Indian/Mayotte",
+    "Pacific/Johnston": "Pacific/Johnston",
+    "Pacific/Midway": "Pacific/Midway",
+    "Pacific/Saipan": "Pacific/Saipan",
+};
+
+for (let [linkName, target] of Object.entries(links)) {
+    if (target === "Etc/UTC" || target === "Etc/GMT")
+        target = "UTC";
+
     for (let map of tzMapper) {
-        let dtf = new Intl.DateTimeFormat(undefined, {timeZone: map(timeZone)});
-        assertEq(dtf.resolvedOptions().timeZone, timeZone);
+        let dtf = new Intl.DateTimeFormat(undefined, {timeZone: map(linkName)});
+        let resolvedTimeZone = dtf.resolvedOptions().timeZone;
+        assertEq(resolvedTimeZone, target, `${linkName} -> ${target}`);
     }
 }
 
 
 if (typeof reportCompare === "function")
     reportCompare(0, 0, "ok");
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/DateTimeFormat/timeZone_backzone_links.js
@@ -0,0 +1,38 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Generated by make_intl_data.py. DO NOT EDIT.
+// tzdata version = 2016h
+
+const tzMapper = [
+    x => x,
+    x => x.toUpperCase(),
+    x => x.toLowerCase(),
+];
+
+// This file was generated with historical, pre-1970 backzone information
+// respected. Therefore, every zone key listed below points to a target
+// in the backzone file and not to its modern-day target as IANA ignoring
+// backzones would say.
+
+// Backzone links derived from IANA Time Zone Database.
+const links = {
+    "Africa/Asmera": "Africa/Asmara",
+    "Antarctica/South_Pole": "Antarctica/McMurdo",
+    "Asia/Chungking": "Asia/Chongqing",
+};
+
+for (let [linkName, target] of Object.entries(links)) {
+    if (target === "Etc/UTC" || target === "Etc/GMT")
+        target = "UTC";
+
+    for (let map of tzMapper) {
+        let dtf = new Intl.DateTimeFormat(undefined, {timeZone: map(linkName)});
+        let resolvedTimeZone = dtf.resolvedOptions().timeZone;
+        assertEq(resolvedTimeZone, target, `${linkName} -> ${target}`);
+    }
+}
+
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0, "ok");
+
deleted file mode 100644
--- a/js/src/tests/Intl/DateTimeFormat/timeZone_link.js
+++ /dev/null
@@ -1,151 +0,0 @@
-// |reftest| skip-if(!this.hasOwnProperty("Intl"))
-
-const tzMapper = [
-    x => x,
-    x => x.toUpperCase(),
-    x => x.toLowerCase(),
-];
-
-// Link names derived from IANA Time Zone Database, version tzdata2016g.
-const links = [
-    ["Africa/Asmera", "Africa/Asmara"],
-    ["America/Atka", "America/Adak"],
-    ["America/Buenos_Aires", "America/Argentina/Buenos_Aires"],
-    ["America/Catamarca", "America/Argentina/Catamarca"],
-    ["America/Cordoba", "America/Argentina/Cordoba"],
-    ["America/Fort_Wayne", "America/Indiana/Indianapolis"],
-    ["America/Indianapolis", "America/Indiana/Indianapolis"],
-    ["America/Jujuy", "America/Argentina/Jujuy"],
-    ["America/Knox_IN", "America/Indiana/Knox"],
-    ["America/Kralendijk", "America/Curacao"],
-    ["America/Louisville", "America/Kentucky/Louisville"],
-    ["America/Lower_Princes", "America/Curacao"],
-    ["America/Marigot", "America/Port_of_Spain"],
-    ["America/Mendoza", "America/Argentina/Mendoza"],
-    ["America/Porto_Acre", "America/Rio_Branco"],
-    ["America/Santa_Isabel", "America/Tijuana"],
-    ["America/Shiprock", "America/Denver"],
-    ["America/St_Barthelemy", "America/Port_of_Spain"],
-    ["America/Virgin", "America/Port_of_Spain"],
-    ["Antarctica/South_Pole", "Antarctica/McMurdo"],
-    ["Arctic/Longyearbyen", "Europe/Oslo"],
-    ["Asia/Ashkhabad", "Asia/Ashgabat"],
-    ["Asia/Calcutta", "Asia/Kolkata"],
-    ["Asia/Chungking", "Asia/Chongqing"],
-    ["Asia/Dacca", "Asia/Dhaka"],
-    ["Asia/Istanbul", "Europe/Istanbul"],
-    ["Asia/Katmandu", "Asia/Kathmandu"],
-    ["Asia/Macao", "Asia/Macau"],
-    ["Asia/Rangoon", "Asia/Yangon"],
-    ["Asia/Saigon", "Asia/Ho_Chi_Minh"],
-    ["Asia/Thimbu", "Asia/Thimphu"],
-    ["Asia/Ujung_Pandang", "Asia/Makassar"],
-    ["Asia/Ulan_Bator", "Asia/Ulaanbaatar"],
-    ["Atlantic/Faeroe", "Atlantic/Faroe"],
-    ["Australia/ACT", "Australia/Sydney"],
-    ["Australia/Canberra", "Australia/Sydney"],
-    ["Australia/LHI", "Australia/Lord_Howe"],
-    ["Australia/NSW", "Australia/Sydney"],
-    ["Australia/North", "Australia/Darwin"],
-    ["Australia/Queensland", "Australia/Brisbane"],
-    ["Australia/South", "Australia/Adelaide"],
-    ["Australia/Tasmania", "Australia/Hobart"],
-    ["Australia/Victoria", "Australia/Melbourne"],
-    ["Australia/West", "Australia/Perth"],
-    ["Australia/Yancowinna", "Australia/Broken_Hill"],
-    ["Brazil/Acre", "America/Rio_Branco"],
-    ["Brazil/DeNoronha", "America/Noronha"],
-    ["Brazil/East", "America/Sao_Paulo"],
-    ["Brazil/West", "America/Manaus"],
-    ["Canada/Atlantic", "America/Halifax"],
-    ["Canada/Central", "America/Winnipeg"],
-    ["Canada/East-Saskatchewan", "America/Regina"],
-    ["Canada/Eastern", "America/Toronto"],
-    ["Canada/Mountain", "America/Edmonton"],
-    ["Canada/Newfoundland", "America/St_Johns"],
-    ["Canada/Pacific", "America/Vancouver"],
-    ["Canada/Saskatchewan", "America/Regina"],
-    ["Canada/Yukon", "America/Whitehorse"],
-    ["Chile/Continental", "America/Santiago"],
-    ["Chile/EasterIsland", "Pacific/Easter"],
-    ["Cuba", "America/Havana"],
-    ["Egypt", "Africa/Cairo"],
-    ["Eire", "Europe/Dublin"],
-    ["Etc/GMT+0", "Etc/GMT"],
-    ["Etc/GMT-0", "Etc/GMT"],
-    ["Etc/GMT0", "Etc/GMT"],
-    ["Etc/Greenwich", "Etc/GMT"],
-    ["Etc/Universal", "Etc/UTC"],
-    ["Etc/Zulu", "Etc/UTC"],
-    ["Europe/Bratislava", "Europe/Prague"],
-    ["Europe/Busingen", "Europe/Zurich"],
-    ["Europe/Mariehamn", "Europe/Helsinki"],
-    ["Europe/Nicosia", "Asia/Nicosia"],
-    ["Europe/Podgorica", "Europe/Belgrade"],
-    ["Europe/San_Marino", "Europe/Rome"],
-    ["Europe/Vatican", "Europe/Rome"],
-    ["GB", "Europe/London"],
-    ["GB-Eire", "Europe/London"],
-    ["GMT", "Etc/GMT"],
-    ["GMT+0", "Etc/GMT"],
-    ["GMT-0", "Etc/GMT"],
-    ["GMT0", "Etc/GMT"],
-    ["Greenwich", "Etc/GMT"],
-    ["Hongkong", "Asia/Hong_Kong"],
-    ["Iceland", "Atlantic/Reykjavik"],
-    ["Iran", "Asia/Tehran"],
-    ["Israel", "Asia/Jerusalem"],
-    ["Jamaica", "America/Jamaica"],
-    ["Japan", "Asia/Tokyo"],
-    ["Kwajalein", "Pacific/Kwajalein"],
-    ["Libya", "Africa/Tripoli"],
-    ["Mexico/BajaNorte", "America/Tijuana"],
-    ["Mexico/BajaSur", "America/Mazatlan"],
-    ["Mexico/General", "America/Mexico_City"],
-    ["NZ", "Pacific/Auckland"],
-    ["NZ-CHAT", "Pacific/Chatham"],
-    ["Navajo", "America/Denver"],
-    ["PRC", "Asia/Shanghai"],
-    ["Pacific/Ponape", "Pacific/Pohnpei"],
-    ["Pacific/Samoa", "Pacific/Pago_Pago"],
-    ["Pacific/Truk", "Pacific/Chuuk"],
-    ["Pacific/Yap", "Pacific/Chuuk"],
-    ["Poland", "Europe/Warsaw"],
-    ["Portugal", "Europe/Lisbon"],
-    ["ROC", "Asia/Taipei"],
-    ["ROK", "Asia/Seoul"],
-    ["Singapore", "Asia/Singapore"],
-    ["Turkey", "Europe/Istanbul"],
-    ["UCT", "Etc/UCT"],
-    ["US/Alaska", "America/Anchorage"],
-    ["US/Aleutian", "America/Adak"],
-    ["US/Arizona", "America/Phoenix"],
-    ["US/Central", "America/Chicago"],
-    ["US/East-Indiana", "America/Indiana/Indianapolis"],
-    ["US/Eastern", "America/New_York"],
-    ["US/Hawaii", "Pacific/Honolulu"],
-    ["US/Indiana-Starke", "America/Indiana/Knox"],
-    ["US/Michigan", "America/Detroit"],
-    ["US/Mountain", "America/Denver"],
-    ["US/Pacific", "America/Los_Angeles"],
-    ["US/Pacific-New", "America/Los_Angeles"],
-    ["US/Samoa", "Pacific/Pago_Pago"],
-    ["UTC", "Etc/UTC"],
-    ["Universal", "Etc/UTC"],
-    ["W-SU", "Europe/Moscow"],
-    ["Zulu", "Etc/UTC"],
-];
-
-for (let [linkName, target] of links) {
-    if (target === "Etc/UTC" || target === "Etc/GMT")
-        target = "UTC";
-
-    for (let map of tzMapper) {
-        let dtf = new Intl.DateTimeFormat(undefined, {timeZone: map(linkName)});
-        assertEq(dtf.resolvedOptions().timeZone, target, `${linkName} -> ${target}`);
-    }
-}
-
-
-if (typeof reportCompare === "function")
-    reportCompare(0, 0, "ok");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/DateTimeFormat/timeZone_notbackward_links.js
@@ -0,0 +1,51 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Generated by make_intl_data.py. DO NOT EDIT.
+// tzdata version = 2016h
+
+const tzMapper = [
+    x => x,
+    x => x.toUpperCase(),
+    x => x.toLowerCase(),
+];
+
+// Link names derived from IANA Time Zone Database, excluding backward file.
+const links = {
+    "America/Kralendijk": "America/Curacao",
+    "America/Lower_Princes": "America/Curacao",
+    "America/Marigot": "America/Port_of_Spain",
+    "America/St_Barthelemy": "America/Port_of_Spain",
+    "Arctic/Longyearbyen": "Europe/Oslo",
+    "Asia/Istanbul": "Europe/Istanbul",
+    "Etc/GMT+0": "Etc/GMT",
+    "Etc/GMT-0": "Etc/GMT",
+    "Etc/GMT0": "Etc/GMT",
+    "Etc/Greenwich": "Etc/GMT",
+    "Etc/Universal": "Etc/UTC",
+    "Etc/Zulu": "Etc/UTC",
+    "Europe/Bratislava": "Europe/Prague",
+    "Europe/Busingen": "Europe/Zurich",
+    "Europe/Mariehamn": "Europe/Helsinki",
+    "Europe/Nicosia": "Asia/Nicosia",
+    "Europe/Podgorica": "Europe/Belgrade",
+    "Europe/San_Marino": "Europe/Rome",
+    "Europe/Vatican": "Europe/Rome",
+    "GMT": "Etc/GMT",
+    "US/Pacific-New": "America/Los_Angeles",
+};
+
+for (let [linkName, target] of Object.entries(links)) {
+    if (target === "Etc/UTC" || target === "Etc/GMT")
+        target = "UTC";
+
+    for (let map of tzMapper) {
+        let dtf = new Intl.DateTimeFormat(undefined, {timeZone: map(linkName)});
+        let resolvedTimeZone = dtf.resolvedOptions().timeZone;
+        assertEq(resolvedTimeZone, target, `${linkName} -> ${target}`);
+    }
+}
+
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0, "ok");
+
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -366,16 +366,18 @@ JSRuntime::init(uint32_t maxbytes, uint3
 void
 JSRuntime::destroyRuntime()
 {
     MOZ_ASSERT(!isHeapBusy());
     MOZ_ASSERT(childRuntimeCount == 0);
 
     fx.destroyInstance();
 
+    sharedIntlData.destroyInstance();
+
     if (gcInitialized) {
         /*
          * Finish any in-progress GCs first. This ensures the parseWaitingOnGC
          * list is empty in CancelOffThreadParses.
          */
         JSContext* cx = contextFromMainThread();
         if (JS::IsIncrementalGCInProgress(cx))
             FinishGC(cx);
@@ -497,16 +499,18 @@ JSRuntime::addSizeOfIncludingThis(mozill
     if (MathCache* cache = cx->caches.maybeGetMathCache())
         rtSizes->mathCache += cache->sizeOfIncludingThis(mallocSizeOf);
 
     if (sharedImmutableStrings_) {
         rtSizes->sharedImmutableStringsCache +=
             sharedImmutableStrings_->sizeOfExcludingThis(mallocSizeOf);
     }
 
+    rtSizes->sharedIntlData += sharedIntlData.sizeOfExcludingThis(mallocSizeOf);
+
     rtSizes->uncompressedSourceCache +=
         cx->caches.uncompressedSourceCache.sizeOfExcludingThis(mallocSizeOf);
 
 
     rtSizes->scriptData += scriptDataTable(lock).sizeOfExcludingThis(mallocSizeOf);
     for (ScriptDataTable::Range r = scriptDataTable(lock).all(); !r.empty(); r.popFront())
         rtSizes->scriptData += mallocSizeOf(r.front());
 
@@ -666,16 +670,22 @@ JSRuntime::getDefaultLocale()
     while ((p = strchr(lang, '_')))
         *p = '-';
 
     defaultLocale = lang;
     return defaultLocale;
 }
 
 void
+JSRuntime::traceSharedIntlData(JSTracer* trc)
+{
+    sharedIntlData.trace(trc);
+}
+
+void
 JSRuntime::triggerActivityCallback(bool active)
 {
     if (!activityCallback)
         return;
 
     /*
      * The activity callback must not trigger a GC: it would create a cirular
      * dependency between entering a request and Rooted's requirement of being
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -21,16 +21,17 @@
 #include "jsatom.h"
 #include "jsclist.h"
 #include "jsscript.h"
 
 #ifdef XP_DARWIN
 # include "wasm/WasmSignalHandlers.h"
 #endif
 #include "builtin/AtomicsObject.h"
+#include "builtin/Intl.h"
 #include "builtin/Promise.h"
 #include "ds/FixedSizeHash.h"
 #include "frontend/NameCollections.h"
 #include "gc/GCRuntime.h"
 #include "gc/Tracer.h"
 #include "irregexp/RegExpStack.h"
 #include "js/Debug.h"
 #include "js/GCVector.h"
@@ -805,16 +806,21 @@ struct JSRuntime : public JS::shadow::Ru
     bool setDefaultLocale(const char* locale);
 
     /* Reset the default locale to OS defaults. */
     void resetDefaultLocale();
 
     /* Gets current default locale. String remains owned by context. */
     const char* getDefaultLocale();
 
+    /* Shared Intl data for this runtime. */
+    js::SharedIntlData sharedIntlData;
+
+    void traceSharedIntlData(JSTracer* trc);
+
     JSVersion defaultVersion() const { return defaultVersion_; }
     void setDefaultVersion(JSVersion v) { defaultVersion_ = v; }
 
     /* Base address of the native stack for the current thread. */
     const uintptr_t     nativeStackBase;
 
     /* The native stack size limit that runtime should not exceed. */
     size_t              nativeStackQuota[js::StackKindCount];
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2461,28 +2461,28 @@ static const JSFunctionSpec intrinsic_fu
     JS_FN("Store_" #_name, js::StoreReference##_name::Func, 3, 0),      \
     JS_FN("Load_" #_name,  js::LoadReference##_name::Func, 3, 0),
     JS_FOR_EACH_REFERENCE_TYPE_REPR(LOAD_AND_STORE_REFERENCE_FN_DECLS)
 #undef LOAD_AND_STORE_REFERENCE_FN_DECLS
 
     // See builtin/Intl.h for descriptions of the intl_* functions.
     JS_FN("intl_availableCalendars", intl_availableCalendars, 1,0),
     JS_FN("intl_availableCollations", intl_availableCollations, 1,0),
-    JS_FN("intl_availableTimeZones", intl_availableTimeZones, 0,0),
     JS_FN("intl_canonicalizeTimeZone", intl_canonicalizeTimeZone, 1,0),
     JS_FN("intl_Collator", intl_Collator, 2,0),
     JS_FN("intl_Collator_availableLocales", intl_Collator_availableLocales, 0,0),
     JS_FN("intl_CompareStrings", intl_CompareStrings, 3,0),
     JS_FN("intl_DateTimeFormat", intl_DateTimeFormat, 2,0),
     JS_FN("intl_DateTimeFormat_availableLocales", intl_DateTimeFormat_availableLocales, 0,0),
     JS_FN("intl_defaultTimeZone", intl_defaultTimeZone, 0,0),
     JS_FN("intl_defaultTimeZoneOffset", intl_defaultTimeZoneOffset, 0,0),
     JS_FN("intl_FormatDateTime", intl_FormatDateTime, 2,0),
     JS_FN("intl_FormatNumber", intl_FormatNumber, 2,0),
     JS_FN("intl_GetCalendarInfo", intl_GetCalendarInfo, 1,0),
+    JS_FN("intl_IsValidTimeZoneName", intl_IsValidTimeZoneName, 1,0),
     JS_FN("intl_NumberFormat", intl_NumberFormat, 2,0),
     JS_FN("intl_NumberFormat_availableLocales", intl_NumberFormat_availableLocales, 0,0),
     JS_FN("intl_numberingSystem", intl_numberingSystem, 1,0),
     JS_FN("intl_patternForSkeleton", intl_patternForSkeleton, 2,0),
 
     JS_INLINABLE_FN("IsRegExpObject",
                     intrinsic_IsInstanceOfBuiltin<RegExpObject>, 1,0,
                     IsRegExpObject),
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -2377,16 +2377,20 @@ ReportJSRuntimeExplicitTreeStats(const J
     RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/math-cache"),
         KIND_HEAP, rtStats.runtime.mathCache,
         "The math cache.");
 
     RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/shared-immutable-strings-cache"),
         KIND_HEAP, rtStats.runtime.sharedImmutableStringsCache,
         "Immutable strings (such as JS scripts' source text) shared across all JSRuntimes.");
 
+    RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/shared-intl-data"),
+        KIND_HEAP, rtStats.runtime.sharedIntlData,
+        "Shared internationalization data.");
+
     RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/uncompressed-source-cache"),
         KIND_HEAP, rtStats.runtime.uncompressedSourceCache,
         "The uncompressed source code cache.");
 
     RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/script-data"),
         KIND_HEAP, rtStats.runtime.scriptData,
         "The table holding script data shared in the runtime.");