Bug 1205586 - new Date().toLocale{,Date,Time}String() should return appropriately differing strings as the local time zone/default locale change. r=till
authorJeff Walden <jwalden@mit.edu>
Fri, 18 Sep 2015 17:16:08 -0700
changeset 263600 4f755f85be41724747223793814f570ec54fd4f4
parent 263599 d30c70405a0e1283db5a547a566222b08e6a3060
child 263601 5e0c5de50004021b23aaf5a498fff6e30e205200
push id65363
push userjwalden@mit.edu
push dateMon, 21 Sep 2015 21:43:18 +0000
treeherdermozilla-inbound@6c90d3eab1f7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstill
bugs1205586
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1205586 - new Date().toLocale{,Date,Time}String() should return appropriately differing strings as the local time zone/default locale change. r=till
js/src/builtin/Date.js
js/src/jsapi-tests/moz.build
js/src/jsapi-tests/testDateToLocaleString.cpp
js/src/vm/SelfHosting.cpp
--- a/js/src/builtin/Date.js
+++ b/js/src/builtin/Date.js
@@ -1,19 +1,83 @@
 /* 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/. */
 
 /*global intl_DateTimeFormat: false, */
 
 
+// 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().
+//   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
+// 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:
+ *
+ *   var opts = ToDateTimeOptions(undefined, required, defaults);
+ *   return new Intl.DateTimeFormat(undefined, opts);
+ *
+ * |format| must be a key from the "formatters" Record described above.
+ */
+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 formatters;
+    if (dateTimeFormatCache.runtimeDefaultLocale !== runtimeDefaultLocale ||
+        dateTimeFormatCache.localTZA !== localTZA)
+    {
+        formatters = dateTimeFormatCache.formatters = new Record();
+        dateTimeFormatCache.runtimeDefaultLocale = runtimeDefaultLocale;
+        dateTimeFormatCache.localTZA = localTZA;
+    } else {
+        formatters = dateTimeFormatCache.formatters;
+    }
+
+    var fmt = formatters[format];
+    if (fmt === undefined) {
+        var options = ToDateTimeOptions(undefined, required, defaults);
+        fmt = formatters[format] = intl_DateTimeFormat(undefined, options);
+    }
+
+    return fmt;
+}
+
+/**
  * Format this Date object into a date and time string, using the locale and
  * formatting options provided.
  *
  * Spec: ECMAScript Language Specification, 5.1 edition, 15.9.5.5.
  * Spec: ECMAScript Internationalization API Specification, 13.3.1.
  */
 function Date_toLocaleString() {
     // Steps 1-2.  Note that valueOf enforces "this time value" restrictions.
@@ -25,21 +89,17 @@ function Date_toLocaleString() {
     var locales = arguments.length > 0 ? arguments[0] : undefined;
     var options = arguments.length > 1 ? arguments[1] : undefined;
 
     // Step 5-6.
     var dateTimeFormat;
     if (locales === undefined && options === undefined) {
         // This cache only optimizes for the old ES5 toLocaleString without
         // locales and options.
-        if (dateTimeFormatCache.dateTimeFormat === undefined) {
-            options = ToDateTimeOptions(options, "any", "all");
-            dateTimeFormatCache.dateTimeFormat = intl_DateTimeFormat(locales, options);
-        }
-        dateTimeFormat = dateTimeFormatCache.dateTimeFormat;
+        dateTimeFormat = GetCachedFormat("dateTimeFormat", "any", "all");
     } else {
         options = ToDateTimeOptions(options, "any", "all");
         dateTimeFormat = intl_DateTimeFormat(locales, options);
     }
 
     // Step 7.
     return intl_FormatDateTime(dateTimeFormat, x);
 }
@@ -62,21 +122,17 @@ function Date_toLocaleDateString() {
     var locales = arguments.length > 0 ? arguments[0] : undefined;
     var options = arguments.length > 1 ? arguments[1] : undefined;
 
     // Step 5-6.
     var dateTimeFormat;
     if (locales === undefined && options === undefined) {
         // This cache only optimizes for the old ES5 toLocaleDateString without
         // locales and options.
-        if (dateTimeFormatCache.dateFormat === undefined) {
-            options = ToDateTimeOptions(options, "date", "date");
-            dateTimeFormatCache.dateFormat = intl_DateTimeFormat(locales, options);
-        }
-        dateTimeFormat = dateTimeFormatCache.dateFormat;
+        dateTimeFormat = GetCachedFormat("dateFormat", "date", "date");
     } else {
         options = ToDateTimeOptions(options, "date", "date");
         dateTimeFormat = intl_DateTimeFormat(locales, options);
     }
 
     // Step 7.
     return intl_FormatDateTime(dateTimeFormat, x);
 }
@@ -99,21 +155,17 @@ function Date_toLocaleTimeString() {
     var locales = arguments.length > 0 ? arguments[0] : undefined;
     var options = arguments.length > 1 ? arguments[1] : undefined;
 
     // Step 5-6.
     var dateTimeFormat;
     if (locales === undefined && options === undefined) {
         // This cache only optimizes for the old ES5 toLocaleTimeString without
         // locales and options.
-        if (dateTimeFormatCache.timeFormat === undefined) {
-            options = ToDateTimeOptions(options, "time", "time");
-            dateTimeFormatCache.timeFormat = intl_DateTimeFormat(locales, options);
-        }
-        dateTimeFormat = dateTimeFormatCache.timeFormat;
+        dateTimeFormat = GetCachedFormat("timeFormat", "time", "time");
     } else {
         options = ToDateTimeOptions(options, "time", "time");
         dateTimeFormat = intl_DateTimeFormat(locales, options);
     }
 
     // Step 7.
     return intl_FormatDateTime(dateTimeFormat, x);
 }
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -13,16 +13,17 @@ UNIFIED_SOURCES += [
     'testArrayBuffer.cpp',
     'testArrayBufferView.cpp',
     'testBug604087.cpp',
     'testCallNonGenericMethodOnProxy.cpp',
     'testChromeBuffer.cpp',
     'testClassGetter.cpp',
     'testCloneScript.cpp',
     'testContexts.cpp',
+    'testDateToLocaleString.cpp',
     'testDebugger.cpp',
     'testDeepFreeze.cpp',
     'testDefineGetterSetterNonEnumerable.cpp',
     'testDefineProperty.cpp',
     'testDefinePropertyIgnoredAttributes.cpp',
     'testDifferentNewTargetInvokeConstructor.cpp',
     'testEnclosingFunction.cpp',
     'testErrorCopying.cpp',
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testDateToLocaleString.cpp
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jsapi-tests/tests.h"
+
+BEGIN_TEST(testDateToLocaleString)
+{
+    // This test should only attempt to run if we have Intl support: necessary
+    // to properly assume that changes to the default locale will predictably
+    // affect the behavior of the locale-sensitive Date methods tested here.
+    JS::Rooted<JS::Value> haveIntl(cx);
+    EVAL("typeof Intl !== 'undefined'", &haveIntl);
+    if (!haveIntl.toBoolean())
+        return true;
+
+    // Pervasive assumption: our Intl support includes "de" (German) and
+    // "en" (English) and treats them differently for purposes of
+    // Date.prototype.toLocale{,Date,Time}String behavior.
+
+    // Start with German.
+    CHECK(JS_SetDefaultLocale(rt, "de"));
+
+    // The (constrained) Date object we'll use to test behavior.
+    EXEC("var d = new Date(Date.UTC(2015, 9 - 1, 17));");
+
+    // Test that toLocaleString behavior changes with default locale changes.
+    EXEC("var deAll = d.toLocaleString();");
+
+    CHECK(JS_SetDefaultLocale(rt, "en"));
+    EXEC("if (d.toLocaleString() === deAll) \n"
+         "  throw 'toLocaleString results should have changed with system locale change';");
+
+    // Test that toLocaleDateString behavior changes with default locale changes.
+    EXEC("var enDate = d.toLocaleDateString();");
+
+    CHECK(JS_SetDefaultLocale(rt, "de"));
+    EXEC("if (d.toLocaleDateString() === enDate) \n"
+         "  throw 'toLocaleDateString results should have changed with system locale change';");
+
+    // Test that toLocaleTimeString behavior changes with default locale changes.
+    EXEC("var deTime = d.toLocaleTimeString();");
+
+    CHECK(JS_SetDefaultLocale(rt, "en"));
+    EXEC("if (d.toLocaleTimeString() === deTime) \n"
+         "  throw 'toLocaleTimeString results should have changed with system locale change';");
+
+    JS_ResetDefaultLocale(rt);
+    return true;
+}
+END_TEST(testDateToLocaleString)
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -1200,16 +1200,26 @@ 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(cx->runtime()->dateTimeInfo.localTZA());
+    return true;
+}
+
+static bool
 intrinsic_IsConstructing(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 0);
 
     ScriptFrameIter iter(cx);
     bool isConstructing = iter.isConstructing();
     args.rval().setBoolean(isConstructing);
@@ -1319,16 +1329,17 @@ static const JSFunctionSpec intrinsic_fu
     JS_FN("OwnPropertyKeys",         intrinsic_OwnPropertyKeys,         1,0),
     JS_FN("ThrowRangeError",         intrinsic_ThrowRangeError,         4,0),
     JS_FN("ThrowTypeError",          intrinsic_ThrowTypeError,          4,0),
     JS_FN("AssertionFailed",         intrinsic_AssertionFailed,         1,0),
     JS_FN("MakeConstructible",       intrinsic_MakeConstructible,       2,0),
     JS_FN("_ConstructorForTypedArray", intrinsic_ConstructorForTypedArray, 1,0),
     JS_FN("DecompileArg",            intrinsic_DecompileArg,            2,0),
     JS_FN("RuntimeDefaultLocale",    intrinsic_RuntimeDefaultLocale,    0,0),
+    JS_FN("LocalTZA",                intrinsic_LocalTZA,                0,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),
     JS_INLINABLE_FN("UnsafeSetReservedSlot",            intrinsic_UnsafeSetReservedSlot,   3,0,