Bug 837961 - Part 2: Implement timeZone support for Intl.DateTimeFormat. r=Waldo
authorAndré Bargull <andre.bargull@gmail.com>
Thu, 06 Oct 2016 22:37:20 -0700
changeset 320036 f7823287275f2c2c4e2329b75057c12fc31cc806
parent 320035 94d5d14da6bac6f013d6cc1187ac218092cea05e
child 320037 7020b2be1285accf881347179245f7ef9e92c474
push id20749
push userryanvm@gmail.com
push dateSat, 29 Oct 2016 13:21:21 +0000
treeherderfx-team@1b170b39ed6b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo
bugs837961
milestone52.0a1
Bug 837961 - Part 2: Implement timeZone support for Intl.DateTimeFormat. r=Waldo
js/src/builtin/Date.js
js/src/builtin/Intl.cpp
js/src/builtin/Intl.h
js/src/builtin/Intl.js
js/src/builtin/IntlTzData.js
js/src/builtin/make_intl_data.py
js/src/jsapi-tests/testGCOutOfMemory.cpp
js/src/moz.build
js/src/tests/Intl/DateTimeFormat/format_timeZone.js
js/src/tests/Intl/DateTimeFormat/timeZone.js
js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js
js/src/tests/Intl/DateTimeFormat/timeZone_link.js
js/src/tests/jstests.list
js/src/vm/SelfHosting.cpp
--- a/js/src/builtin/Date.js
+++ b/js/src/builtin/Date.js
@@ -7,33 +7,36 @@
 
 // This cache, once primed, has these properties:
 //
 //   runtimeDefaultLocale:
 //     Locale information provided by the embedding, guiding SpiderMonkey's
 //     selection of a default locale.  See RuntimeDefaultLocale(), whose
 //     value controls the value returned by DefaultLocale() that's what's
 //     *actually* used.
-//   localTZA:
-//     The local time zone's adjustment from UTC.  See LocalTZA().
+//   icuDefaultTimeZone:
+//     Time zone information provided by ICU. See intl_defaultTimeZone(),
+//     whose value controls the value returned by DefaultTimeZone() that's
+//     what's *actually* used.
 //   formatters:
 //     A Record storing formatters consistent with the above
 //     runtimeDefaultLocale/localTZA values, for use with the appropriate
 //     ES6 toLocale*String Date method when called with its first two
 //     arguments having the value |undefined|.
 //
 // The "formatters" Record has (some subset of) these properties, as determined
 // by all values of the first argument passed to |GetCachedFormat|:
 //
 //   dateTimeFormat: for Date's toLocaleString operation
 //   dateFormat: for Date's toLocaleDateString operation
 //   timeFormat: for Date's toLocaleTimeString operation
 //
-// Using this cache, then, requires 1) verifying the current
-// runtimeDefaultLocale/localTZA are consistent with cached values, then
+// Using this cache, then, requires
+// 1) verifying the current runtimeDefaultLocale/icuDefaultTimeZone are
+//    consistent with cached values, then
 // 2) seeing if the desired formatter is cached and returning it if so, or else
 // 3) create the desired formatter and store and return it.
 var dateTimeFormatCache = new Record();
 
 
 /**
  * Get a cached DateTimeFormat formatter object, created like so:
  *
@@ -45,25 +48,25 @@ var dateTimeFormatCache = new Record();
 function GetCachedFormat(format, required, defaults) {
     assert(format === "dateTimeFormat" ||
            format === "dateFormat" ||
            format === "timeFormat",
            "unexpected format key: please update the comment by " +
            "dateTimeFormatCache");
 
     var runtimeDefaultLocale = RuntimeDefaultLocale();
-    var localTZA = LocalTZA();
+    var icuDefaultTimeZone = intl_defaultTimeZone();
 
     var formatters;
     if (dateTimeFormatCache.runtimeDefaultLocale !== runtimeDefaultLocale ||
-        dateTimeFormatCache.localTZA !== localTZA)
+        dateTimeFormatCache.icuDefaultTimeZone !== icuDefaultTimeZone)
     {
         formatters = dateTimeFormatCache.formatters = new Record();
         dateTimeFormatCache.runtimeDefaultLocale = runtimeDefaultLocale;
-        dateTimeFormatCache.localTZA = localTZA;
+        dateTimeFormatCache.icuDefaultTimeZone = icuDefaultTimeZone;
     } else {
         formatters = dateTimeFormatCache.formatters;
     }
 
     var fmt = formatters[format];
     if (fmt === undefined) {
         var options = ToDateTimeOptions(undefined, required, defaults);
         fmt = formatters[format] = intl_DateTimeFormat(undefined, options);
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -6,16 +6,17 @@
 
 /*
  * The Intl module specified by standard ECMA-402,
  * ECMAScript Internationalization API Specification.
  */
 
 #include "builtin/Intl.h"
 
+#include "mozilla/PodOperations.h"
 #include "mozilla/Range.h"
 #include "mozilla/ScopeExit.h"
 
 #include <string.h>
 
 #include "jsapi.h"
 #include "jsatom.h"
 #include "jscntxt.h"
@@ -32,26 +33,28 @@
 #include "unicode/unum.h"
 #include "unicode/ustring.h"
 #endif
 #include "vm/DateTime.h"
 #include "vm/GlobalObject.h"
 #include "vm/Interpreter.h"
 #include "vm/Stack.h"
 #include "vm/StringBuffer.h"
+#include "vm/Unicode.h"
 
 #include "jsobjinlines.h"
 
 #include "vm/NativeObject-inl.h"
 
 using namespace js;
 
 using mozilla::IsFinite;
 using mozilla::IsNegativeZero;
 using mozilla::MakeScopeExit;
+using mozilla::PodCopy;
 
 #if ENABLE_INTL_API
 using icu::Locale;
 using icu::NumberingSystem;
 #endif
 
 
 /*
@@ -335,16 +338,44 @@ enum UCalendarDaysOfWeek {
 
 enum UCalendarWeekdayType {
     UCAL_WEEKDAY,
     UCAL_WEEKEND,
     UCAL_WEEKEND_ONSET,
     UCAL_WEEKEND_CEASE
 };
 
+enum UCalendarDateFields {
+    UCAL_ERA,
+    UCAL_YEAR,
+    UCAL_MONTH,
+    UCAL_WEEK_OF_YEAR,
+    UCAL_WEEK_OF_MONTH,
+    UCAL_DATE,
+    UCAL_DAY_OF_YEAR,
+    UCAL_DAY_OF_WEEK,
+    UCAL_DAY_OF_WEEK_IN_MONTH,
+    UCAL_AM_PM,
+    UCAL_HOUR,
+    UCAL_HOUR_OF_DAY,
+    UCAL_MINUTE,
+    UCAL_SECOND,
+    UCAL_MILLISECOND,
+    UCAL_ZONE_OFFSET,
+    UCAL_DST_OFFSET,
+    UCAL_YEAR_WOY,
+    UCAL_DOW_LOCAL,
+    UCAL_EXTENDED_YEAR,
+    UCAL_JULIAN_DAY,
+    UCAL_MILLISECONDS_IN_DAY,
+    UCAL_IS_LEAP_MONTH,
+    UCAL_FIELD_COUNT,
+    UCAL_DAY_OF_MONTH = UCAL_DATE
+};
+
 static UCalendar*
 ucal_open(const UChar* zoneID, int32_t len, const char* locale,
           UCalendarType type, UErrorCode* status)
 {
     MOZ_CRASH("ucal_open: Intl API disabled");
 }
 
 static const char*
@@ -374,16 +405,41 @@ ucal_getDayOfWeekType(const UCalendar *c
 
 static int32_t
 ucal_getAttribute(const UCalendar*    cal,
                   UCalendarAttribute  attr)
 {
     MOZ_CRASH("ucal_getAttribute: Intl API disabled");
 }
 
+static int32_t
+ucal_get(const UCalendar *cal, UCalendarDateFields field, UErrorCode *status)
+{
+    MOZ_CRASH("ucal_get: Intl API disabled");
+}
+
+static UEnumeration*
+ucal_openTimeZones(UErrorCode* status)
+{
+    MOZ_CRASH("ucal_openTimeZones: Intl API disabled");
+}
+
+static int32_t
+ucal_getCanonicalTimeZoneID(const UChar* id, int32_t len, UChar* result, int32_t resultCapacity,
+                            UBool* isSystemID, UErrorCode* status)
+{
+    MOZ_CRASH("ucal_getCanonicalTimeZoneID: Intl API disabled");
+}
+
+static int32_t
+ucal_getDefaultTimeZone(UChar* result, int32_t resultCapacity, UErrorCode* status)
+{
+    MOZ_CRASH("ucal_getDefaultTimeZone: Intl API disabled");
+}
+
 typedef void* UDateTimePatternGenerator;
 
 static UDateTimePatternGenerator*
 udatpg_open(const char* locale, UErrorCode* pErrorCode)
 {
     MOZ_CRASH("udatpg_open: Intl API disabled");
 }
 
@@ -1926,16 +1982,197 @@ 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)
+{
+    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]);
+    }
+
+    return timeZoneKey;
+}
+
+bool
+js::intl_availableTimeZones(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 0);
+
+    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);
+    while (true) {
+        int32_t size;
+        const char* timeZone = uenum_next(values, &size, &status);
+        if (U_FAILURE(status)) {
+            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+            return false;
+        }
+        if (timeZone == nullptr)
+            break;
+
+        MOZ_ASSERT(size >= 0);
+        UniqueChars timeZoneKey = TimeZoneKey(cx, timeZone, size_t(size));
+        if (!timeZoneKey)
+            return false;
+
+        RootedAtom atom(cx, Atomize(cx, timeZoneKey.get(), size_t(size)));
+        if (!atom)
+            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);
+    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());
+
+    AutoStableStringChars stableChars(cx);
+    if (!stableChars.initTwoByte(cx, args[0].toString()))
+        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;
+
+    UBool* isSystemID = nullptr;
+    UErrorCode status = U_ZERO_ERROR;
+    int32_t size = ucal_getCanonicalTimeZoneID(Char16ToUChar(tzchars.start().get()),
+                                               tzchars.length(), Char16ToUChar(chars.begin()),
+                                               INITIAL_CHAR_BUFFER_SIZE, isSystemID, &status);
+    if (status == U_BUFFER_OVERFLOW_ERROR) {
+        MOZ_ASSERT(size >= 0);
+        if (!chars.resize(size_t(size)))
+            return false;
+        status = U_ZERO_ERROR;
+        ucal_getCanonicalTimeZoneID(Char16ToUChar(tzchars.start().get()), tzchars.length(),
+                                    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);
+    if (!str)
+        return false;
+    args.rval().setString(str);
+    return true;
+}
+
+bool
+js::intl_defaultTimeZone(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 0);
+
+    // The current default might be stale, because JS::ResetTimeZone() doesn't
+    // immediately update ICU's default time zone. So perform an update if
+    // needed.
+    js::ResyncICUDefaultTimeZone();
+
+    Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
+    if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
+        return false;
+
+    UErrorCode status = U_ZERO_ERROR;
+    int32_t size = ucal_getDefaultTimeZone(Char16ToUChar(chars.begin()), INITIAL_CHAR_BUFFER_SIZE,
+                                           &status);
+    if (status == U_BUFFER_OVERFLOW_ERROR) {
+        MOZ_ASSERT(size >= 0);
+        if (!chars.resize(size_t(size)))
+            return false;
+        status = U_ZERO_ERROR;
+        ucal_getDefaultTimeZone(Char16ToUChar(chars.begin()), size, &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_t(size));
+    if (!str)
+        return false;
+    args.rval().setString(str);
+    return true;
+}
+
+bool
+js::intl_defaultTimeZoneOffset(JSContext* cx, unsigned argc, Value* vp) {
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 0);
+
+    UErrorCode status = U_ZERO_ERROR;
+    const UChar* uTimeZone = nullptr;
+    int32_t uTimeZoneLength = 0;
+    const char* rootLocale = "";
+    UCalendar* cal = ucal_open(uTimeZone, uTimeZoneLength, rootLocale, UCAL_DEFAULT, &status);
+    if (U_FAILURE(status)) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        return false;
+    }
+    ScopedICUObject<UCalendar, ucal_close> toClose(cal);
+
+    int32_t offset = ucal_get(cal, UCAL_ZONE_OFFSET, &status);
+    if (U_FAILURE(status)) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+        return false;
+    }
+
+    args.rval().setInt32(offset);
+    return true;
+}
+
 bool
 js::intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 2);
     MOZ_ASSERT(args[0].isString());
     MOZ_ASSERT(args[1].isString());
 
@@ -2001,70 +2238,43 @@ NewUDateFormat(JSContext* cx, HandleObje
        return nullptr;
 
     if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
         return nullptr;
     JSAutoByteString locale(cx, value.toString());
     if (!locale)
         return nullptr;
 
-    // UDateFormat options with default values.
-    const UChar* uTimeZone = nullptr;
-    uint32_t uTimeZoneLength = 0;
-    const UChar* uPattern = nullptr;
-    uint32_t uPatternLength = 0;
-
     // We don't need to look at calendar and numberingSystem - they can only be
     // set via the Unicode locale extension and are therefore already set on
     // locale.
 
-    RootedId id(cx, NameToId(cx->names().timeZone));
-    bool hasP;
-    if (!HasProperty(cx, internals, id, &hasP))
+    if (!GetProperty(cx, internals, internals, cx->names().timeZone, &value))
         return nullptr;
 
     AutoStableStringChars timeZoneChars(cx);
-    if (hasP) {
-        if (!GetProperty(cx, internals, internals, cx->names().timeZone, &value))
-            return nullptr;
-        if (!value.isUndefined()) {
-            JSFlatString* flat = value.toString()->ensureFlat(cx);
-            if (!flat || !timeZoneChars.initTwoByte(cx, flat))
-                return nullptr;
-            uTimeZone = Char16ToUChar(timeZoneChars.twoByteRange().start().get());
-            if (!uTimeZone)
-                return nullptr;
-            uTimeZoneLength = u_strlen(uTimeZone);
-        }
-    }
+    Rooted<JSFlatString*> timeZoneFlat(cx, value.toString()->ensureFlat(cx));
+    if (!timeZoneFlat || !timeZoneChars.initTwoByte(cx, timeZoneFlat))
+        return nullptr;
+
+    const UChar* uTimeZone = Char16ToUChar(timeZoneChars.twoByteRange().start().get());
+    uint32_t uTimeZoneLength = u_strlen(uTimeZone);
+
     if (!GetProperty(cx, internals, internals, cx->names().pattern, &value))
         return nullptr;
 
     AutoStableStringChars patternChars(cx);
-    JSFlatString* flat = value.toString()->ensureFlat(cx);
-    if (!flat || !patternChars.initTwoByte(cx, flat))
+    Rooted<JSFlatString*> patternFlat(cx, value.toString()->ensureFlat(cx));
+    if (!patternFlat || !patternChars.initTwoByte(cx, patternFlat))
         return nullptr;
 
-    uPattern = Char16ToUChar(patternChars.twoByteRange().start().get());
-    if (!uPattern)
-        return nullptr;
-    uPatternLength = u_strlen(uPattern);
+    const UChar* uPattern = Char16ToUChar(patternChars.twoByteRange().start().get());
+    uint32_t uPatternLength = u_strlen(uPattern);
 
     UErrorCode status = U_ZERO_ERROR;
-
-    if (!uTimeZone) {
-        // When no time zone was specified, we use ICU's default time zone.
-        // The current default might be stale, because JS::ResetTimeZone()
-        // doesn't immediately update ICU's default time zone.  So perform an
-        // update if needed.
-        js::ResyncICUDefaultTimeZone();
-    }
-
-    // If building with ICU headers before 50.1, use UDAT_IGNORE instead of
-    // UDAT_PATTERN.
     UDateFormat* df =
         udat_open(UDAT_PATTERN, UDAT_PATTERN, icuLocale(locale.ptr()), uTimeZone, uTimeZoneLength,
                   uPattern, uPatternLength, &status);
     if (U_FAILURE(status)) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
         return nullptr;
     }
 
--- a/js/src/builtin/Intl.h
+++ b/js/src/builtin/Intl.h
@@ -154,16 +154,58 @@ 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>.
+ *
+ * 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.
+ *
+ * Usage: timeZones = intl_availableTimeZones()
+ */
+extern MOZ_MUST_USE bool
+intl_availableTimeZones(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
+intl_canonicalizeTimeZone(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * Return the default time zone name. The time zone name is not canonicalized.
+ *
+ * Usage: icuDefaultTimeZone = intl_defaultTimeZone()
+ */
+extern MOZ_MUST_USE bool
+intl_defaultTimeZone(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * Return the raw offset from GMT in milliseconds for the default time zone.
+ *
+ * Usage: defaultTimeZoneOffset = intl_defaultTimeZoneOffset()
+ */
+extern MOZ_MUST_USE bool
+intl_defaultTimeZoneOffset(JSContext* cx, unsigned argc, Value* vp);
+
+/**
  * Return a pattern in the date-time format pattern language of Unicode
  * Technical Standard 35, Unicode Locale Data Markup Language, for the
  * best-fit date-time format pattern corresponding to skeleton for the
  * given locale.
  *
  * Usage: pattern = intl_patternForSkeleton(locale, skeleton)
  */
 extern MOZ_MUST_USE bool
--- a/js/src/builtin/Intl.js
+++ b/js/src/builtin/Intl.js
@@ -39,20 +39,20 @@
 function toASCIIUpperCase(s) {
     assert(typeof s === "string", "toASCIIUpperCase");
 
     // String.prototype.toUpperCase may map non-ASCII characters into ASCII,
     // so go character by character (actually code unit by code unit, but
     // since we only care about ASCII characters here, that's OK).
     var result = "";
     for (var i = 0; i < s.length; i++) {
-        var c = s[i];
-        if ("a" <= c && c <= "z")
-            c = callFunction(std_String_toUpperCase, c);
-        result += c;
+        var c = callFunction(std_String_charCodeAt, s, i);
+        result += (0x61 <= c && c <= 0x7A)
+                  ? callFunction(std_String_fromCharCode, null, c & ~0x20)
+                  : s[i];
     }
     return result;
 }
 
 /**
  * Holder object for encapsulating regexp instances.
  *
  * Regular expression instances should be created after the initialization of
@@ -620,16 +620,143 @@ function IsWellFormedCurrencyCode(curren
     var c = ToString(currency);
     var normalized = toASCIIUpperCase(c);
     if (normalized.length !== 3)
         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");
+
+    // Step 2.
+    var ianaTimeZone = intl_canonicalizeTimeZone(timeZone);
+    assert(ianaTimeZone !== "Etc/Unknown", "Invalid canonical time zone");
+    assert(ianaTimeZone === 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;
+}
+
+
+/**
+ * 6.4.3 DefaultTimeZone ()
+ *
+ * Returns the IANA time zone name for the host environment's current time zone.
+ *
+ * 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);
+    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);
+        }
+
+        // Fallback to "UTC" if everything else fails.
+        if (timeZone === null)
+            timeZone = "UTC";
+    }
+
+    // Canonicalize the ICU time zone, e.g. change Etc/UTC to UTC.
+    var defaultTimeZone = CanonicalizeTimeZoneName(timeZone);
+
+    timeZoneCache.defaultTimeZone = defaultTimeZone;
+    timeZoneCache.icuDefaultTimeZone = icuDefaultTimeZone;
+
+    return defaultTimeZone;
+}
+
+
 /********** Locale and Parameter Negotiation **********/
 
 /**
  * Add old-style language tags without script code for locales that in current
  * usage would include a script subtag.  Also add an entry for the last-ditch
  * locale, in case ICU doesn't directly support it (but does support it through
  * fallback, e.g. supporting "en-GB" indirectly using "en" support).
  */
@@ -2091,17 +2218,17 @@ function resolveDateTimeFormatInternals(
     //
     //     localeOpt: // *first* opt computed in InitializeDateTimeFormat
     //       {
     //         localeMatcher: "lookup" / "best fit",
     //
     //         hour12: true / false,  // optional
     //       }
     //
-    //     timeZone: undefined / "UTC",
+    //     timeZone: IANA time zone name,
     //
     //     formatOpt: // *second* opt computed in InitializeDateTimeFormat
     //       {
     //         // all the properties/values listed in Table 3
     //         // (weekday, era, year, month, day, &c.)
     //       }
     //
     //     formatMatcher: "basic" / "best fit",
@@ -2223,17 +2350,17 @@ function InitializeDateTimeFormat(dateTi
     //   {
     //     requestedLocales: List of locales,
     //
     //     localeOpt: // *first* opt computed in InitializeDateTimeFormat
     //       {
     //         localeMatcher: "lookup" / "best fit",
     //       }
     //
-    //     timeZone: undefined / "UTC",
+    //     timeZone: IANA time zone name,
     //
     //     formatOpt: // *second* opt computed in InitializeDateTimeFormat
     //       {
     //         // all the properties/values listed in Table 3
     //         // (weekday, era, year, month, day, &c.)
     //
     //         hour12: true / false  // optional
     //       }
@@ -2262,19 +2389,29 @@ function InitializeDateTimeFormat(dateTi
     var localeMatcher =
         GetOption(options, "localeMatcher", "string", ["lookup", "best fit"],
                   "best fit");
     localeOpt.localeMatcher = localeMatcher;
 
     // Steps 15-17.
     var tz = options.timeZone;
     if (tz !== undefined) {
-        tz = toASCIIUpperCase(ToString(tz));
-        if (tz !== "UTC")
+        // Step 15.a.
+        tz = ToString(tz);
+
+        // Step 15.b.
+        var timeZone = IsValidTimeZoneName(tz);
+        if (timeZone === null)
             ThrowRangeError(JSMSG_INVALID_TIME_ZONE, tz);
+
+        // Step 15.c.
+        tz = CanonicalizeTimeZoneName(timeZone);
+    } else {
+        // Step 16.
+        tz = DefaultTimeZone();
     }
     lazyDateTimeFormatData.timeZone = tz;
 
     // Step 18.
     var formatOpt = new Record();
     lazyDateTimeFormatData.formatOpt = formatOpt;
 
     // Step 19.
@@ -2287,17 +2424,17 @@ function InitializeDateTimeFormat(dateTi
 
     // Steps 20-21 provided by ICU - see comment after this function.
 
     // Step 22.
     //
     // For some reason (ICU not exposing enough interface?) we drop the
     // requested format matcher on the floor after this.  In any case, even if
     // doing so is justified, we have to do this work here in case it triggers
-    // getters or similar.
+    // getters or similar. (bug 852837)
     var formatMatcher =
         GetOption(options, "formatMatcher", "string", ["basic", "best fit"],
                   "best fit");
 
     // Steps 23-25 provided by ICU, more or less - see comment after this function.
 
     // Step 26.
     var hr12  = GetOption(options, "hour12", "boolean", undefined, undefined);
new file mode 100644
--- /dev/null
+++ b/js/src/builtin/IntlTzData.js
@@ -0,0 +1,131 @@
+// Generated by make_intl_data.py. DO NOT EDIT.
+// tzdata version = 2016h
+// ICU tzdata version = 2015f
+
+// 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]
+};
+
+// 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]
+};
+
+// 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,
+};
--- a/js/src/builtin/make_intl_data.py
+++ b/js/src/builtin/make_intl_data.py
@@ -35,17 +35,17 @@ import re
 import io
 import codecs
 import tarfile
 import tempfile
 import urllib2
 import urlparse
 from contextlib import closing
 from functools import partial
-from itertools import chain, ifilter, ifilterfalse, imap, tee
+from itertools import chain, ifilter, ifilterfalse, 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 == "":
@@ -578,17 +578,17 @@ def updateTzdata(args):
 
     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)
         elif os.path.isdir(f):
             processTimeZones(TzDataDir(f), icuDir, icuTzDir, version, out)
         else:
-            raise RuntimError("unknown format")
+            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]
             with tempfile.NamedTemporaryFile(suffix=fname) as tztmpfile:
                 print("File stored in %s" % tztmpfile.name)
                 tztmpfile.write(tzfile.read())
--- a/js/src/jsapi-tests/testGCOutOfMemory.cpp
+++ b/js/src/jsapi-tests/testGCOutOfMemory.cpp
@@ -50,17 +50,17 @@ BEGIN_TEST(testGCOutOfMemory)
 virtual JSContext* createContext() override {
     // Note that the max nursery size must be less than the whole heap size, or
     // the test will fail because 'max' (the number of allocations required for
     // OOM) will be based on the nursery size, and that will overflow the
     // tenured heap, which will cause the second pass with max/4 allocations to
     // OOM. (Actually, this only happens with nursery zeal, because normally
     // the nursery will start out with only a single chunk before triggering a
     // major GC.)
-    JSContext* cx = JS_NewContext(768 * 1024, 128 * 1024);
+    JSContext* cx = JS_NewContext(1024 * 1024, 128 * 1024);
     if (!cx)
         return nullptr;
     setNativeStackQuota(cx);
     return cx;
 }
 
 virtual void destroyContext() override {
     JS_DestroyContext(cx);
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -751,16 +751,17 @@ 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/format_timeZone.js
@@ -0,0 +1,104 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const defaultLocale = "en-US";
+const defaultDate = Date.UTC(2012, 12-1, 6, 12, 0, 0);
+const defaultOptions = {
+    year: "numeric", month: "numeric", day: "numeric",
+    hour: "numeric", minute: "numeric", second: "numeric",
+};
+const longFormatOptions = Object.assign({}, defaultOptions, {
+    month: "long"
+});
+const tzNameFormatOptions = Object.assign({}, defaultOptions, {
+    timeZoneName: "short"
+});
+
+const tzMapper = [
+    x => x,
+    x => x.toUpperCase(),
+    x => x.toLowerCase(),
+];
+
+const tests = [
+    {
+        timeZone: "UTC",
+        result: "12/6/2012, 12:00:00 PM",
+    },
+    {
+        timeZone: "America/Los_Angeles",
+        result: "12/6/2012, 4:00:00 AM",
+    },
+    {
+        timeZone: "America/New_York",
+        options: tzNameFormatOptions,
+        result: "12/6/2012, 7:00:00 AM EST",
+    },
+    {
+        timeZone: "America/Caracas",
+        result: "12/6/2012, 7:30:00 AM",
+    },
+    {
+        timeZone: "Europe/London",
+        result: "12/6/2012, 12:00:00 PM",
+    },
+    {
+        timeZone: "Africa/Casablanca",
+        locale: "ar-MA-u-ca-islamicc", options: longFormatOptions,
+        result: "22 محرم، 1434 12:00:00",
+    },
+    {
+        timeZone: "Europe/Berlin",
+        locale: "de-DE", options: tzNameFormatOptions,
+        result: "6.12.2012, 13:00:00 MEZ",
+    },
+    {
+        timeZone: "Asia/Kathmandu",
+        result: "12/6/2012, 5:45:00 PM",
+    },
+    {
+        timeZone: "Asia/Bangkok",
+        locale: "th-th-u-nu-thai", options: longFormatOptions,
+        result: "๖ ธันวาคม ๒๕๕๕ ๑๙:๐๐:๐๐",
+    },
+    {
+        timeZone: "Asia/Tokyo",
+        locale: "ja-JP", options: longFormatOptions,
+        result: "2012年12月6日 21:00:00",
+    },
+    {
+        timeZone: "Australia/Lord_Howe",
+        result: "12/6/2012, 11:00:00 PM",
+    },
+    {
+        timeZone: "Australia/Lord_Howe",
+        date: Date.UTC(2012, 7-1, 6, 12, 0, 0),
+        result: "7/6/2012, 10:30:00 PM",
+    },
+    {
+        timeZone: "Pacific/Kiritimati",
+        date: Date.UTC(1978, 12-1, 6, 12, 0, 0),
+        result: "12/6/1978, 1:20:00 AM",
+    },
+    {
+        timeZone: "Africa/Monrovia",
+        date: Date.UTC(1971, 12-1, 6, 12, 0, 0),
+        result: "12/6/1971, 11:15:30 AM",
+    },
+    {
+        timeZone: "Asia/Riyadh",
+        date: Date.UTC(1946, 12-1, 6, 12, 0, 0),
+        result: "12/6/1946, 3:06:52 PM",
+    },
+];
+
+for (let {timeZone, result, locale = defaultLocale, date = defaultDate, options = defaultOptions} of tests) {
+    for (let map of tzMapper) {
+        let dtf = new Intl.DateTimeFormat(locale, Object.assign({timeZone: map(timeZone)}, options));
+        assertEq(dtf.format(date), result);
+        assertEq(dtf.resolvedOptions().timeZone, timeZone);
+    }
+}
+
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0, "ok");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/DateTimeFormat/timeZone.js
@@ -0,0 +1,152 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+const tzMapper = [
+    x => x,
+    x => x.toUpperCase(),
+    x => x.toLowerCase(),
+];
+
+
+const utcTimeZones = [
+    // Etc/UTC and Etc/GMT are normalized to UTC.
+    "Etc/UTC", "Etc/GMT",
+
+    // Links to Etc/GMT. (tzdata/etcetera)
+    "GMT", "Etc/Greenwich", "Etc/GMT-0", "Etc/GMT+0", "Etc/GMT0",
+
+    // Links to Etc/UTC. (tzdata/etcetera)
+    "Etc/Universal", "Etc/Zulu",
+
+    // Links to Etc/GMT. (tzdata/backward)
+    "GMT+0", "GMT-0", "GMT0", "Greenwich",
+
+    // Links to Etc/UTC. (tzdata/backward)
+    "UTC", "Universal", "Zulu",
+];
+
+for (let timeZone of utcTimeZones) {
+    for (let map of tzMapper) {
+        let dtf = new Intl.DateTimeFormat(undefined, {timeZone: map(timeZone)});
+        assertEq(dtf.resolvedOptions().timeZone, "UTC");
+    }
+}
+
+
+// ECMA-402 doesn't normalize Etc/UCT to UTC.
+const uctTimeZones = [
+    "Etc/UCT", "UCT",
+];
+
+for (let timeZone of uctTimeZones) {
+    for (let map of tzMapper) {
+        let dtf = new Intl.DateTimeFormat(undefined, {timeZone: map(timeZone)});
+        assertEq(dtf.resolvedOptions().timeZone, "Etc/UCT");
+    }
+}
+
+
+const invalidTimeZones = [
+    "", "null", "undefined", "UTC\0",
+
+    // ICU time zone name for invalid time zones.
+    "Etc/Unknown",
+
+    // ICU custom time zones.
+    "GMT-1", "GMT+1", "GMT-10", "GMT+10",
+    "GMT-10:00", "GMT+10:00",
+    "GMT-1000", "GMT+1000",
+
+    // Legacy ICU time zones.
+    "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",
+
+    // Deprecated IANA time zones.
+    "SystemV/AST4ADT", "SystemV/EST5EDT", "SystemV/CST6CDT", "SystemV/MST7MDT",
+    "SystemV/PST8PDT", "SystemV/YST9YDT", "SystemV/AST4", "SystemV/EST5",
+    "SystemV/CST6", "SystemV/MST7", "SystemV/PST8", "SystemV/YST9", "SystemV/HST10",
+];
+
+for (let timeZone of invalidTimeZones) {
+    for (let map of tzMapper) {
+        assertThrowsInstanceOf(() => {
+            new Intl.DateTimeFormat(undefined, {timeZone: map(timeZone)});
+        }, RangeError);
+    }
+}
+
+
+// GMT[+-]hh is invalid, but Etc/GMT[+-]hh is a valid IANA time zone.
+for (let gmtOffset = -14; gmtOffset <= 12; ++gmtOffset) {
+    // Skip Etc/GMT0.
+    if (gmtOffset === 0)
+        continue;
+
+    let timeZone = `Etc/GMT${gmtOffset > 0 ? "+" : ""}${gmtOffset}`;
+    for (let map of tzMapper) {
+        let dtf = new Intl.DateTimeFormat(undefined, {timeZone: map(timeZone)});
+        assertEq(dtf.resolvedOptions().timeZone, timeZone);
+    }
+}
+
+
+const invalidEtcGMTNames = [
+  // Out of bounds GMT offset.
+  "Etc/GMT-15", "Etc/GMT+13",
+
+  // Etc/GMT[+-]hh:mm isn't a IANA time zone name.
+  "Etc/GMT-10:00", "Etc/GMT+10:00",
+  "Etc/GMT-1000", "Etc/GMT+1000",
+];
+
+for (let timeZone of invalidEtcGMTNames) {
+    for (let map of tzMapper) {
+        assertThrowsInstanceOf(() => {
+            new Intl.DateTimeFormat(undefined, {timeZone: map(timeZone)});
+        }, RangeError);
+    }
+}
+
+
+// RangeError is thrown for primitive values, because ToString(<primitive>)
+// isn't a valid time zone name.
+for (let nonStrings of [null, 0, 0.5, true, false]) {
+    assertThrowsInstanceOf(() => {
+        new Intl.DateTimeFormat(undefined, {timeZone: nonStrings});
+    }, RangeError);
+}
+
+// ToString(<symbol>) throws TypeError.
+assertThrowsInstanceOf(() => {
+    new Intl.DateTimeFormat(undefined, {timeZone: Symbol()});
+}, TypeError);
+
+// |undefined| or absent "timeZone" option selects the default time zone.
+{
+    let {timeZone: tzAbsent} = new Intl.DateTimeFormat(undefined, {}).resolvedOptions();
+    let {timeZone: tzUndefined} = new Intl.DateTimeFormat(undefined, {timeZone: undefined}).resolvedOptions();
+
+    assertEq(typeof tzAbsent, "string");
+    assertEq(typeof tzUndefined, "string");
+    assertEq(tzUndefined, tzAbsent);
+
+    // The default time zone isn't a link name.
+    let {timeZone: tzDefault} = new Intl.DateTimeFormat(undefined, {timeZone: tzAbsent}).resolvedOptions();
+    assertEq(tzDefault, tzAbsent);
+}
+
+// Objects are converted through ToString().
+{
+    let timeZone = "Europe/Warsaw";
+    let obj = {
+        toString() {
+            return timeZone;
+        }
+    };
+    let dtf = new Intl.DateTimeFormat(undefined, {timeZone: obj});
+    assertEq(dtf.resolvedOptions().timeZone, timeZone);
+}
+
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0, "ok");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/DateTimeFormat/timeZone_backzone.js
@@ -0,0 +1,104 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+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
+];
+
+for (let timeZone of backZones) {
+    for (let map of tzMapper) {
+        let dtf = new Intl.DateTimeFormat(undefined, {timeZone: map(timeZone)});
+        assertEq(dtf.resolvedOptions().timeZone, timeZone);
+    }
+}
+
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0, "ok");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/Intl/DateTimeFormat/timeZone_link.js
@@ -0,0 +1,151 @@
+// |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");
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -29,16 +29,21 @@ skip script test262/intl402/ch11/11.3/11
 skip script test262/intl402/ch12/12.3/12.3.3_L15.js
 skip script test262/intl402/ch12/12.3/12.3.2_L15.js
 skip script test262/intl402/ch12/12.3/12.3.2_1_a_L15.js
 skip script test262/intl402/ch12/12.1/12.1_L15.js
 skip script test262/intl402/ch12/12.2/12.2.2_L15.js
 skip script test262/ch13/13.2/13.2-15-1.js
 skip script test262/ch11/11.4/11.4.1/11.4.1-5-a-28-s.js
 
+# ECMA-402 1st ed. required |timeZoneName: undefined|, but newer versions have
+# changed it to being the default time zone, cf. the NOTE in
+# Intl.DateTimeFormat.prototype.resolvedOptions().
+skip script test262/intl402/ch12/12.3/12.3.3.js
+
 #######################################################################
 # Tests disabled due to jstest limitations wrt imported test262 tests #
 #######################################################################
 
 # These tests are disabled because jstest doesn't understand @negative (without
 # a pattern) yet.
 skip script test262/ch07/7.2/S7.2_A5_T1.js
 skip script test262/ch07/7.2/S7.2_A5_T2.js
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -1952,26 +1952,16 @@ intrinsic_RuntimeDefaultLocale(JSContext
     if (!jslocale)
         return false;
 
     args.rval().setString(jslocale);
     return true;
 }
 
 static bool
-intrinsic_LocalTZA(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-    MOZ_ASSERT(args.length() == 0, "the LocalTZA intrinsic takes no arguments");
-
-    args.rval().setDouble(DateTimeInfo::localTZA());
-    return true;
-}
-
-static bool
 intrinsic_AddContentTelemetry(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 2);
 
     int id = args[0].toInt32();
     MOZ_ASSERT(id < JS_TELEMETRY_END);
     MOZ_ASSERT(id >= 0);
@@ -2390,17 +2380,16 @@ static const JSFunctionSpec intrinsic_fu
     JS_FN("DumpMessage",             intrinsic_DumpMessage,             1,0),
     JS_FN("OwnPropertyKeys",         intrinsic_OwnPropertyKeys,         1,0),
     JS_FN("MakeDefaultConstructor",  intrinsic_MakeDefaultConstructor,  2,0),
     JS_FN("_ConstructorForTypedArray", intrinsic_ConstructorForTypedArray, 1,0),
     JS_FN("_NameForTypedArray",      intrinsic_NameForTypedArray, 1,0),
     JS_FN("DecompileArg",            intrinsic_DecompileArg,            2,0),
     JS_FN("_FinishBoundFunctionInit", intrinsic_FinishBoundFunctionInit, 3,0),
     JS_FN("RuntimeDefaultLocale",    intrinsic_RuntimeDefaultLocale,    0,0),
-    JS_FN("LocalTZA",                intrinsic_LocalTZA,                0,0),
     JS_FN("AddContentTelemetry",     intrinsic_AddContentTelemetry,     2,0),
 
     JS_INLINABLE_FN("_IsConstructing", intrinsic_IsConstructing,        0,0,
                     IntrinsicIsConstructing),
     JS_INLINABLE_FN("SubstringKernel", intrinsic_SubstringKernel,       3,0,
                     IntrinsicSubstringKernel),
     JS_INLINABLE_FN("_DefineDataProperty",              intrinsic_DefineDataProperty,      4,0,
                     IntrinsicDefineDataProperty),
@@ -2577,21 +2566,25 @@ 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_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),