Bug 1208808 - Move time zone adjustment information out of JSRuntime, into global state protected by a spinlock. r=till
☠☠ backed out by ab535db1e8c3 ☠ ☠
authorJeff Walden <jwalden@mit.edu>
Fri, 30 Oct 2015 16:14:32 -0700
changeset 270536 6e21339a66ed059547eee536c57288254bd0ef0f
parent 270535 d7a9a8bd12888bff392d979bb41aef91f81c03f3
child 270537 ab535db1e8c3a4e71cf1dead03706e65398ab7bd
push id67382
push userjwalden@mit.edu
push dateSat, 31 Oct 2015 06:42:43 +0000
treeherdermozilla-inbound@6e21339a66ed [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstill
bugs1208808
milestone45.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 1208808 - Move time zone adjustment information out of JSRuntime, into global state protected by a spinlock. r=till
dom/time/DateCacheCleaner.cpp
dom/time/DateCacheCleaner.h
js/public/Date.h
js/public/Initialization.h
js/src/gdb/gdb-tests.cpp
js/src/jsapi-tests/tests.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jscompartment.cpp
js/src/jsdate.cpp
js/src/moz.build
js/src/shell/js.cpp
js/src/vm/DateObject.h
js/src/vm/DateTime.cpp
js/src/vm/DateTime.h
js/src/vm/Initialization.cpp
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/src/vm/SelfHosting.cpp
xpcom/build/XPCOMInit.cpp
--- a/dom/time/DateCacheCleaner.cpp
+++ b/dom/time/DateCacheCleaner.cpp
@@ -1,17 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "DateCacheCleaner.h"
 
-#include "jsapi.h"
+#include "js/Date.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Hal.h"
 #include "mozilla/StaticPtr.h"
 
 using namespace mozilla::hal;
 
 namespace mozilla {
@@ -28,17 +28,17 @@ public:
 
   ~DateCacheCleaner()
   {
     UnregisterSystemTimezoneChangeObserver(this);
   }
   void Notify(const SystemTimezoneChangeInformation& aSystemTimezoneChangeInfo)
   {
     mozilla::AutoSafeJSContext cx;
-    JS_ClearDateCaches(cx);
+    JS::ResetTimeZone();
   }
 
 };
 
 StaticAutoPtr<DateCacheCleaner> sDateCacheCleaner;
 
 void
 InitializeDateCacheCleaner()
--- a/dom/time/DateCacheCleaner.h
+++ b/dom/time/DateCacheCleaner.h
@@ -2,17 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*
  * InitializeDateCacheCleaner registers DateCacheCleaner to 
  * SystemTimeChangeObserver. When time zone is changed, DateCacheCleaner calls
- * JS_ClearDateCaches to update the time zone information.
+ * JS::ResetTimeZone to update the time zone information.
  */
 
 namespace mozilla {
 namespace dom {
 namespace time {
 void InitializeDateCacheCleaner();
 } //namespace time
 } //namespace dom
--- a/js/public/Date.h
+++ b/js/public/Date.h
@@ -34,16 +34,32 @@
 
 #include "js/Conversions.h"
 #include "js/Value.h"
 
 struct JSContext;
 
 namespace JS {
 
+/**
+ * Re-query the system to determine the current time zone adjustment from UTC,
+ * including any component due to DST.  If the time zone has changed, this will
+ * cause all Date object non-UTC methods and formatting functions to produce
+ * appropriately adjusted results.
+ *
+ * Left to its own devices, SpiderMonkey itself may occasionally call this
+ * method to attempt to keep up with system time changes.  However, no
+ * particular frequency of checking is guaranteed.  Embedders unable to accept
+ * occasional inaccuracies should call this method in response to system time
+ * changes, or immediately before operations requiring instantaneous
+ * correctness, to guarantee correct behavior.
+ */
+extern JS_PUBLIC_API(void)
+ResetTimeZone();
+
 class ClippedTime;
 inline ClippedTime TimeClip(double time);
 
 /*
  * |ClippedTime| represents the limited subset of dates/times described above.
  *
  * An invalid date/time may be created through the |ClippedTime::invalid|
  * method.  Otherwise, a |ClippedTime| may be created using the |TimeClip|
new file mode 100644
--- /dev/null
+++ b/js/public/Initialization.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+/* SpiderMonkey initialization and shutdown APIs. */
+
+#ifndef js_Initialization_h
+#define js_Initialization_h
+
+#include "jstypes.h"
+
+namespace JS {
+namespace detail {
+
+enum class InitState { Uninitialized = 0, Running, ShutDown };
+
+/**
+ * SpiderMonkey's initialization status is tracked here, and it controls things
+ * that should happen only once across all runtimes.  It's an API requirement
+ * that JS_Init (and JS_ShutDown, if called) be called in a thread-aware
+ * manner, so this (internal -- embedders, don't use!) variable doesn't need to
+ * be atomic.
+ */
+extern JS_PUBLIC_DATA(InitState)
+libraryInitState;
+
+} // namespace detail
+} // namespace JS
+
+// These are equivalent to ICU's |UMemAllocFn|, |UMemReallocFn|, and
+// |UMemFreeFn| types.  The first argument (called |context| in the ICU docs)
+// will always be nullptr and should be ignored.
+typedef void* (*JS_ICUAllocFn)(const void*, size_t size);
+typedef void* (*JS_ICUReallocFn)(const void*, void* p, size_t size);
+typedef void (*JS_ICUFreeFn)(const void*, void* p);
+
+/**
+ * This function can be used to track memory used by ICU.  If it is called, it
+ * *must* be called before JS_Init.  Don't use it unless you know what you're
+ * doing!
+ */
+extern JS_PUBLIC_API(bool)
+JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn,
+                         JS_ICUReallocFn reallocFn,
+                         JS_ICUFreeFn freeFn);
+
+/**
+ * Initialize SpiderMonkey, returning true only if initialization succeeded.
+ * Once this method has succeeded, it is safe to call JS_NewRuntime and other
+ * JSAPI methods.
+ *
+ * This method must be called before any other JSAPI method is used on any
+ * thread.  Once it has been used, it is safe to call any JSAPI method, and it
+ * remains safe to do so until JS_ShutDown is correctly called.
+ *
+ * It is currently not possible to initialize SpiderMonkey multiple times (that
+ * is, calling JS_Init/JSAPI methods/JS_ShutDown in that order, then doing so
+ * again).  This restriction may eventually be lifted.
+ */
+extern JS_PUBLIC_API(bool)
+JS_Init(void);
+
+/**
+ * Destroy free-standing resources allocated by SpiderMonkey, not associated
+ * with any runtime, context, or other structure.
+ *
+ * This method should be called after all other JSAPI data has been properly
+ * cleaned up: every new runtime must have been destroyed, every new context
+ * must have been destroyed, and so on.  Calling this method before all other
+ * resources have been destroyed has undefined behavior.
+ *
+ * Failure to call this method, at present, has no adverse effects other than
+ * leaking memory.  This may not always be the case; it's recommended that all
+ * embedders call this method when all other JSAPI operations have completed.
+ *
+ * It is currently not possible to initialize SpiderMonkey multiple times (that
+ * is, calling JS_Init/JSAPI methods/JS_ShutDown in that order, then doing so
+ * again).  This restriction may eventually be lifted.
+ */
+extern JS_PUBLIC_API(void)
+JS_ShutDown(void);
+
+#endif /* js_Initialization_h */
--- a/js/src/gdb/gdb-tests.cpp
+++ b/js/src/gdb/gdb-tests.cpp
@@ -4,16 +4,17 @@
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
 #include "gdb-tests.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
+#include "js/Initialization.h"
 
 using namespace JS;
 
 /* The class of the global object. */
 const JSClass global_class = {
     "global", JSCLASS_GLOBAL_FLAGS,
     nullptr, nullptr, nullptr, nullptr,
     nullptr, nullptr, nullptr, nullptr,
--- a/js/src/jsapi-tests/tests.cpp
+++ b/js/src/jsapi-tests/tests.cpp
@@ -3,16 +3,17 @@
  * 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"
 
 #include <stdio.h>
 
+#include "js/Initialization.h"
 #include "js/RootingAPI.h"
 
 JSAPITest* JSAPITest::list;
 
 bool JSAPITest::init()
 {
     rt = createRuntime();
     if (!rt)
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -57,24 +57,20 @@
 #include "frontend/BytecodeCompiler.h"
 #include "frontend/FullParseHandler.h"  // for JS_BufferIsCompileableUnit
 #include "frontend/Parser.h" // for JS_BufferIsCompileableUnit
 #include "gc/Marking.h"
 #include "jit/JitCommon.h"
 #include "js/CharacterEncoding.h"
 #include "js/Conversions.h"
 #include "js/Date.h"
+#include "js/Initialization.h"
 #include "js/Proxy.h"
 #include "js/SliceBudget.h"
 #include "js/StructuredClone.h"
-#if ENABLE_INTL_API
-#include "unicode/timezone.h"
-#include "unicode/uclean.h"
-#include "unicode/utypes.h"
-#endif // ENABLE_INTL_API
 #include "vm/DateObject.h"
 #include "vm/Debugger.h"
 #include "vm/ErrorObject.h"
 #include "vm/HelperThreads.h"
 #include "vm/Interpreter.h"
 #include "vm/RegExpStatics.h"
 #include "vm/Runtime.h"
 #include "vm/SavedStacks.h"
@@ -440,153 +436,30 @@ JS_IsBuiltinEvalFunction(JSFunction* fun
 JS_PUBLIC_API(bool)
 JS_IsBuiltinFunctionConstructor(JSFunction* fun)
 {
     return fun->isBuiltinFunctionConstructor();
 }
 
 /************************************************************************/
 
-/*
- * SpiderMonkey's initialization status is tracked here, and it controls things
- * that should happen only once across all runtimes.  It's an API requirement
- * that JS_Init (and JS_ShutDown, if called) be called in a thread-aware
- * manner, so this variable doesn't need to be atomic.
- *
- * The only reason at present for the restriction that you can't call
- * JS_Init/stuff/JS_ShutDown multiple times is the Windows PRMJ NowInit
- * initialization code, which uses PR_CallOnce to initialize the PRMJ_Now
- * subsystem.  (For reinitialization to be permitted, we'd need to "reset" the
- * called-once status -- doable, but more trouble than it's worth now.)
- * Initializing that subsystem from JS_Init eliminates the problem, but
- * initialization can take a comparatively long time (15ms or so), so we
- * really don't want to do it in JS_Init, and we really do want to do it only
- * when PRMJ_Now is eventually called.
- */
-enum InitState { Uninitialized, Running, ShutDown };
-static InitState jsInitState = Uninitialized;
-
-#ifdef DEBUG
-static unsigned
-MessageParameterCount(const char* format)
-{
-    unsigned numfmtspecs = 0;
-    for (const char* fmt = format; *fmt != '\0'; fmt++) {
-        if (*fmt == '{' && isdigit(fmt[1]))
-            ++numfmtspecs;
-    }
-    return numfmtspecs;
-}
-
-static void
-CheckMessageParameterCounts()
-{
-    // Assert that each message format has the correct number of braced
-    // parameters.
-# define MSG_DEF(name, count, exception, format)           \
-        MOZ_ASSERT(MessageParameterCount(format) == count);
-# include "js.msg"
-# undef MSG_DEF
-}
-#endif /* DEBUG */
-
-JS_PUBLIC_API(bool)
-JS_Init(void)
-{
-    MOZ_ASSERT(jsInitState == Uninitialized,
-               "must call JS_Init once before any JSAPI operation except "
-               "JS_SetICUMemoryFunctions");
-    MOZ_ASSERT(!JSRuntime::hasLiveRuntimes(),
-               "how do we have live runtimes before JS_Init?");
-
-    PRMJ_NowInit();
-
-#ifdef DEBUG
-    CheckMessageParameterCounts();
-#endif
-
-    using js::TlsPerThreadData;
-    if (!TlsPerThreadData.initialized() && !TlsPerThreadData.init())
-        return false;
-
-#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
-    if (!js::oom::InitThreadType())
-        return false;
-    js::oom::SetThreadType(js::oom::THREAD_TYPE_MAIN);
-#endif
-
-    jit::ExecutableAllocator::initStatic();
-
-    if (!jit::InitializeIon())
-        return false;
-
-#if EXPOSE_INTL_API
-    UErrorCode err = U_ZERO_ERROR;
-    u_init(&err);
-    if (U_FAILURE(err))
-        return false;
-#endif // EXPOSE_INTL_API
-
-    if (!CreateHelperThreadsState())
-        return false;
-
-    if (!FutexRuntime::initialize())
-        return false;
-
-    jsInitState = Running;
-    return true;
-}
-
-JS_PUBLIC_API(void)
-JS_ShutDown(void)
-{
-    MOZ_ASSERT(jsInitState == Running,
-               "JS_ShutDown must only be called after JS_Init and can't race with it");
-#ifdef DEBUG
-    if (JSRuntime::hasLiveRuntimes()) {
-        // Gecko is too buggy to assert this just yet.
-        fprintf(stderr,
-                "WARNING: YOU ARE LEAKING THE WORLD (at least one JSRuntime "
-                "and everything alive inside it, that is) AT JS_ShutDown "
-                "TIME.  FIX THIS!\n");
-    }
-#endif
-
-    FutexRuntime::destroy();
-
-    DestroyHelperThreadsState();
-
-#ifdef JS_TRACE_LOGGING
-    DestroyTraceLoggerThreadState();
-    DestroyTraceLoggerGraphState();
-#endif
-
-    PRMJ_NowShutdown();
-
-#if EXPOSE_INTL_API
-    u_cleanup();
-#endif // EXPOSE_INTL_API
-
-    jsInitState = ShutDown;
-}
-
 #ifdef DEBUG
 JS_FRIEND_API(bool)
 JS::isGCEnabled()
 {
     return !TlsPerThreadData.get()->suppressGC;
 }
 #else
 JS_FRIEND_API(bool) JS::isGCEnabled() { return true; }
 #endif
 
 JS_PUBLIC_API(JSRuntime*)
 JS_NewRuntime(uint32_t maxbytes, uint32_t maxNurseryBytes, JSRuntime* parentRuntime)
 {
-    MOZ_ASSERT(jsInitState == Running,
+    MOZ_ASSERT(JS::detail::libraryInitState == JS::detail::InitState::Running,
                "must call JS_Init prior to creating any JSRuntimes");
 
     // Make sure that all parent runtimes are the topmost parent.
     while (parentRuntime && parentRuntime->parentRuntime)
         parentRuntime = parentRuntime->parentRuntime;
 
     JSRuntime* rt = js_new<JSRuntime>(parentRuntime);
     if (!rt)
@@ -601,32 +474,16 @@ JS_NewRuntime(uint32_t maxbytes, uint32_
 }
 
 JS_PUBLIC_API(void)
 JS_DestroyRuntime(JSRuntime* rt)
 {
     js_delete(rt);
 }
 
-JS_PUBLIC_API(bool)
-JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn, JS_ICUReallocFn reallocFn, JS_ICUFreeFn freeFn)
-{
-    MOZ_ASSERT(jsInitState == Uninitialized,
-               "must call JS_SetICUMemoryFunctions before any other JSAPI "
-               "operation (including JS_Init)");
-
-#if EXPOSE_INTL_API
-    UErrorCode status = U_ZERO_ERROR;
-    u_setMemoryFunctions(/* context = */ nullptr, allocFn, reallocFn, freeFn, &status);
-    return U_SUCCESS(status);
-#else
-    return true;
-#endif
-}
-
 static JS_CurrentEmbedderTimeFunction currentEmbedderTimeFunction;
 
 JS_PUBLIC_API(void)
 JS_SetCurrentEmbedderTimeFunction(JS_CurrentEmbedderTimeFunction timeFn)
 {
     currentEmbedderTimeFunction = timeFn;
 }
 
@@ -5568,24 +5425,16 @@ JS_ObjectIsDate(JSContext* cx, HandleObj
     ESClassValue cls;
     if (!GetBuiltinClass(cx, obj, &cls))
         return false;
 
     *isDate = cls == ESClass_Date;
     return true;
 }
 
-JS_PUBLIC_API(void)
-JS_ClearDateCaches(JSContext* cx)
-{
-    AssertHeapIsIdle(cx);
-    CHECK_REQUEST(cx);
-    cx->runtime()->dateTimeInfo.updateTimeZoneAdjustment();
-}
-
 /************************************************************************/
 
 /*
  * Regular Expressions.
  */
 JS_PUBLIC_API(JSObject*)
 JS_NewRegExpObject(JSContext* cx, HandleObject obj, const char* bytes, size_t length, unsigned flags)
 {
@@ -6348,16 +6197,8 @@ JS::CaptureCurrentStack(JSContext* cx, J
     return true;
 }
 
 JS_PUBLIC_API(Zone*)
 JS::GetObjectZone(JSObject* obj)
 {
     return obj->zone();
 }
-
-JS_PUBLIC_API(void)
-JS::ResetTimeZone()
-{
-#if ENABLE_INTL_API && defined(ICU_TZ_HAS_RECREATE_DEFAULT)
-    icu::TimeZone::recreateDefault();
-#endif
-}
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -35,19 +35,16 @@
 #include "js/Utility.h"
 #include "js/Value.h"
 #include "js/Vector.h"
 
 /************************************************************************/
 
 namespace JS {
 
-extern JS_PUBLIC_API(void)
-ResetTimeZone();
-
 class TwoByteChars;
 
 #ifdef JS_DEBUG
 
 class JS_PUBLIC_API(AutoCheckRequestDepth)
 {
     JSContext* cx;
   public:
@@ -948,81 +945,32 @@ JS_IsBuiltinEvalFunction(JSFunction* fun
 
 /** True iff fun is the Function constructor. */
 extern JS_PUBLIC_API(bool)
 JS_IsBuiltinFunctionConstructor(JSFunction* fun);
 
 /************************************************************************/
 
 /*
- * Initialization, locking, contexts, and memory allocation.
- *
- * It is important that the first runtime and first context be created in a
- * single-threaded fashion, otherwise the behavior of the library is undefined.
- * See: http://developer.mozilla.org/en/docs/Category:JSAPI_Reference
- */
-
-/**
- * Initialize SpiderMonkey, returning true only if initialization succeeded.
- * Once this method has succeeded, it is safe to call JS_NewRuntime and other
- * JSAPI methods.
- *
- * This method must be called before any other JSAPI method is used on any
- * thread.  Once it has been used, it is safe to call any JSAPI method, and it
- * remains safe to do so until JS_ShutDown is correctly called.
+ * Locking, contexts, and memory allocation.
  *
- * It is currently not possible to initialize SpiderMonkey multiple times (that
- * is, calling JS_Init/JSAPI methods/JS_ShutDown in that order, then doing so
- * again).  This restriction may eventually be lifted.
- */
-extern JS_PUBLIC_API(bool)
-JS_Init(void);
-
-/**
- * Destroy free-standing resources allocated by SpiderMonkey, not associated
- * with any runtime, context, or other structure.
- *
- * This method should be called after all other JSAPI data has been properly
- * cleaned up: every new runtime must have been destroyed, every new context
- * must have been destroyed, and so on.  Calling this method before all other
- * resources have been destroyed has undefined behavior.
- *
- * Failure to call this method, at present, has no adverse effects other than
- * leaking memory.  This may not always be the case; it's recommended that all
- * embedders call this method when all other JSAPI operations have completed.
- *
- * It is currently not possible to initialize SpiderMonkey multiple times (that
- * is, calling JS_Init/JSAPI methods/JS_ShutDown in that order, then doing so
- * again).  This restriction may eventually be lifted.
- */
-extern JS_PUBLIC_API(void)
-JS_ShutDown(void);
+ * It is important that SpiderMonkey be initialized, and the first runtime and
+ * first context be created, in a single-threaded fashion.  Otherwise the
+ * behavior of the library is undefined.
+ * See: http://developer.mozilla.org/en/docs/Category:JSAPI_Reference
+ */
 
 extern JS_PUBLIC_API(JSRuntime*)
 JS_NewRuntime(uint32_t maxbytes,
               uint32_t maxNurseryBytes = JS::DefaultNurseryBytes,
               JSRuntime* parentRuntime = nullptr);
 
 extern JS_PUBLIC_API(void)
 JS_DestroyRuntime(JSRuntime* rt);
 
-// These are equivalent to ICU's |UMemAllocFn|, |UMemReallocFn|, and
-// |UMemFreeFn| types.  The first argument (called |context| in the ICU docs)
-// will always be nullptr, and should be ignored.
-typedef void* (*JS_ICUAllocFn)(const void*, size_t size);
-typedef void* (*JS_ICUReallocFn)(const void*, void* p, size_t size);
-typedef void (*JS_ICUFreeFn)(const void*, void* p);
-
-/**
- * This function can be used to track memory used by ICU.
- * Do not use it unless you know what you are doing!
- */
-extern JS_PUBLIC_API(bool)
-JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn, JS_ICUReallocFn reallocFn, JS_ICUFreeFn freeFn);
-
 typedef double (*JS_CurrentEmbedderTimeFunction)();
 
 /**
  * The embedding can specify a time function that will be used in some
  * situations.  The function can return the time however it likes; but
  * the norm is to return times in units of milliseconds since an
  * arbitrary, but consistent, epoch.  If the time function is not set,
  * a built-in default will be used.
@@ -4987,23 +4935,16 @@ JS_NewDateObject(JSContext* cx, int year
  * a wrapper around one, otherwise returns false on failure.
  *
  * This method returns true with |*isDate == false| when passed a proxy whose
  * target is a Date, or when passed a revoked proxy.
  */
 extern JS_PUBLIC_API(bool)
 JS_ObjectIsDate(JSContext* cx, JS::HandleObject obj, bool* isDate);
 
-/**
- * Clears the cache of calculated local time from each Date object.
- * Call to propagate a system timezone change.
- */
-extern JS_PUBLIC_API(void)
-JS_ClearDateCaches(JSContext* cx);
-
 /************************************************************************/
 
 /*
  * Regular Expressions.
  */
 #define JSREG_FOLD      0x01u   /* fold uppercase to lowercase */
 #define JSREG_GLOB      0x02u   /* global exec, creates array of matches */
 #define JSREG_MULTILINE 0x04u   /* treat ^ and $ as begin and end of line */
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -13,16 +13,17 @@
 #include "jsfriendapi.h"
 #include "jsgc.h"
 #include "jsiter.h"
 #include "jswatchpoint.h"
 #include "jswrapper.h"
 
 #include "gc/Marking.h"
 #include "jit/JitCompartment.h"
+#include "js/Date.h"
 #include "js/Proxy.h"
 #include "js/RootingAPI.h"
 #include "proxy/DeadObjectProxy.h"
 #include "vm/Debugger.h"
 #include "vm/StopIterationObject.h"
 #include "vm/WrapperObject.h"
 
 #include "jsatominlines.h"
@@ -115,21 +116,20 @@ bool
 JSCompartment::init(JSContext* maybecx)
 {
     /*
      * maybecx is null when called to create the atoms compartment from
      * JSRuntime::init().
      *
      * As a hack, we clear our timezone cache every time we create a new
      * compartment. This ensures that the cache is always relatively fresh, but
-     * shouldn't interfere with benchmarks which create tons of date objects
+     * shouldn't interfere with benchmarks that create tons of date objects
      * (unless they also create tons of iframes, which seems unlikely).
      */
-    if (maybecx)
-        maybecx->runtime()->dateTimeInfo.updateTimeZoneAdjustment();
+    JS::ResetTimeZone();
 
     if (!crossCompartmentWrappers.init(0)) {
         if (maybecx)
             ReportOutOfMemory(maybecx);
         return false;
     }
 
     if (!regExps.init(maybecx))
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -402,55 +402,56 @@ EquivalentYearForDST(int year)
     if (day < 0)
         day += 7;
 
     return yearStartingWith[IsLeapYear(year)][day];
 }
 
 /* ES5 15.9.1.8. */
 static double
-DaylightSavingTA(double t, DateTimeInfo* dtInfo)
+DaylightSavingTA(double t)
 {
     if (!IsFinite(t))
         return GenericNaN();
 
     /*
      * If earlier than 1970 or after 2038, potentially beyond the ken of
      * many OSes, map it to an equivalent year before asking.
      */
     if (t < 0.0 || t > 2145916800000.0) {
         int year = EquivalentYearForDST(int(YearFromTime(t)));
         double day = MakeDay(year, MonthFromTime(t), DateFromTime(t));
         t = MakeDate(day, TimeWithinDay(t));
     }
 
     int64_t utcMilliseconds = static_cast<int64_t>(t);
-    int64_t offsetMilliseconds = dtInfo->getDSTOffsetMilliseconds(utcMilliseconds);
+    int64_t offsetMilliseconds = DateTimeInfo::getDSTOffsetMilliseconds(utcMilliseconds);
     return static_cast<double>(offsetMilliseconds);
 }
 
 static double
-AdjustTime(double date, DateTimeInfo* dtInfo)
+AdjustTime(double date)
 {
-    double t = DaylightSavingTA(date, dtInfo) + dtInfo->localTZA();
-    t = (dtInfo->localTZA() >= 0) ? fmod(t, msPerDay) : -fmod(msPerDay - t, msPerDay);
+    double localTZA = DateTimeInfo::localTZA();
+    double t = DaylightSavingTA(date) + localTZA;
+    t = (localTZA >= 0) ? fmod(t, msPerDay) : -fmod(msPerDay - t, msPerDay);
     return t;
 }
 
 /* ES5 15.9.1.9. */
 static double
-LocalTime(double t, DateTimeInfo* dtInfo)
+LocalTime(double t)
 {
-    return t + AdjustTime(t, dtInfo);
+    return t + AdjustTime(t);
 }
 
 static double
-UTC(double t, DateTimeInfo* dtInfo)
+UTC(double t)
 {
-    return t - AdjustTime(t - dtInfo->localTZA(), dtInfo);
+    return t - AdjustTime(t - DateTimeInfo::localTZA());
 }
 
 /* ES5 15.9.1.10. */
 static double
 HourFromTime(double t)
 {
     double result = fmod(floor(t/msPerHour), HoursPerDay);
     if (result < 0)
@@ -759,17 +760,17 @@ DaysInMonth(int year, int month)
  *   hh   = two digits of hour (00 through 23) (am/pm NOT allowed)
  *   mm   = two digits of minute (00 through 59)
  *   ss   = two digits of second (00 through 59)
  *   s    = one or more digits representing a decimal fraction of a second
  *   TZD  = time zone designator (Z or +hh:mm or -hh:mm or missing for local)
  */
 template <typename CharT>
 static bool
-ParseISODate(const CharT* s, size_t length, ClippedTime* result, DateTimeInfo* dtInfo)
+ParseISODate(const CharT* s, size_t length, ClippedTime* result)
 {
     size_t i = 0;
     int tzMul = 1;
     int dateMul = 1;
     size_t year = 1970;
     size_t month = 1;
     size_t day = 1;
     size_t hour = 0;
@@ -859,34 +860,34 @@ ParseISODate(const CharT* s, size_t leng
         return false;
 
     month -= 1; /* convert month to 0-based */
 
     double msec = MakeDate(MakeDay(dateMul * double(year), month, day),
                            MakeTime(hour, min, sec, frac * 1000.0));
 
     if (isLocalTime)
-        msec = UTC(msec, dtInfo);
+        msec = UTC(msec);
     else
         msec -= tzMul * (tzHour * msPerHour + tzMin * msPerMinute);
 
     *result = TimeClip(msec);
     return NumbersAreIdentical(msec, result->toDouble());
 
 #undef PEEK
 #undef NEED
 #undef DONE_UNLESS
 #undef NEED_NDIGITS
 }
 
 template <typename CharT>
 static bool
-ParseDate(const CharT* s, size_t length, ClippedTime* result, DateTimeInfo* dtInfo)
+ParseDate(const CharT* s, size_t length, ClippedTime* result)
 {
-    if (ParseISODate(s, length, result, dtInfo))
+    if (ParseISODate(s, length, result))
         return true;
 
     if (length == 0)
         return false;
 
     int year = -1;
     int mon = -1;
     int mday = -1;
@@ -1139,31 +1140,31 @@ ParseDate(const CharT* s, size_t length,
     if (min < 0)
         min = 0;
     if (hour < 0)
         hour = 0;
 
     double msec = MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0));
 
     if (tzOffset == -1) /* no time zone specified, have to use local */
-        msec = UTC(msec, dtInfo);
+        msec = UTC(msec);
     else
         msec += tzOffset * msPerMinute;
 
     *result = TimeClip(msec);
     return true;
 }
 
 static bool
-ParseDate(JSLinearString* s, ClippedTime* result, DateTimeInfo* dtInfo)
+ParseDate(JSLinearString* s, ClippedTime* result)
 {
     AutoCheckCannotGC nogc;
     return s->hasLatin1Chars()
-           ? ParseDate(s->latin1Chars(nogc), s->length(), result, dtInfo)
-           : ParseDate(s->twoByteChars(nogc), s->length(), result, dtInfo);
+           ? ParseDate(s->latin1Chars(nogc), s->length(), result)
+           : ParseDate(s->twoByteChars(nogc), s->length(), result);
 }
 
 static bool
 date_parse(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() == 0) {
         args.rval().setNaN();
@@ -1174,17 +1175,17 @@ date_parse(JSContext* cx, unsigned argc,
     if (!str)
         return false;
 
     JSLinearString* linearStr = str->ensureLinear(cx);
     if (!linearStr)
         return false;
 
     ClippedTime result;
-    if (!ParseDate(linearStr, &result, &cx->runtime()->dateTimeInfo)) {
+    if (!ParseDate(linearStr, &result)) {
         args.rval().setNaN();
         return true;
     }
 
     args.rval().set(TimeValue(result));
     return true;
 }
 
@@ -1214,37 +1215,37 @@ DateObject::setUTCTime(ClippedTime t)
 void
 DateObject::setUTCTime(ClippedTime t, MutableHandleValue vp)
 {
     setUTCTime(t);
     vp.set(TimeValue(t));
 }
 
 void
-DateObject::fillLocalTimeSlots(DateTimeInfo* dtInfo)
+DateObject::fillLocalTimeSlots()
 {
     /* Check if the cache is already populated. */
     if (!getReservedSlot(LOCAL_TIME_SLOT).isUndefined() &&
-        getReservedSlot(TZA_SLOT).toDouble() == dtInfo->localTZA())
+        getReservedSlot(TZA_SLOT).toDouble() == DateTimeInfo::localTZA())
     {
         return;
     }
 
     /* Remember timezone used to generate the local cache. */
-    setReservedSlot(TZA_SLOT, DoubleValue(dtInfo->localTZA()));
+    setReservedSlot(TZA_SLOT, DoubleValue(DateTimeInfo::localTZA()));
 
     double utcTime = UTCTime().toNumber();
 
     if (!IsFinite(utcTime)) {
         for (size_t ind = COMPONENTS_START_SLOT; ind < RESERVED_SLOTS; ind++)
             setReservedSlot(ind, DoubleValue(utcTime));
         return;
     }
 
-    double localTime = LocalTime(utcTime, dtInfo);
+    double localTime = LocalTime(utcTime);
 
     setReservedSlot(LOCAL_TIME_SLOT, DoubleValue(localTime));
 
     int year = (int) floor(localTime /(msPerDay * 365.2425)) + 1970;
     double yearStartTime = TimeFromYear(year);
 
     /* Adjust the year in case the approximation was wrong, as in YearFromTime. */
     int yearDays;
@@ -1344,19 +1345,19 @@ DateObject::fillLocalTimeSlots(DateTimeI
     int minutes = (yearSeconds / 60) % 60;
     setReservedSlot(LOCAL_MINUTES_SLOT, Int32Value(minutes));
 
     int hours = (yearSeconds / (60 * 60)) % 24;
     setReservedSlot(LOCAL_HOURS_SLOT, Int32Value(hours));
 }
 
 inline double
-DateObject::cachedLocalTime(DateTimeInfo* dtInfo)
+DateObject::cachedLocalTime()
 {
-    fillLocalTimeSlots(dtInfo);
+    fillLocalTimeSlots();
     return getReservedSlot(LOCAL_TIME_SLOT).toDouble();
 }
 
 MOZ_ALWAYS_INLINE bool
 IsDate(HandleValue v)
 {
     return v.isObject() && v.toObject().is<DateObject>();
 }
@@ -1377,17 +1378,17 @@ date_getTime(JSContext* cx, unsigned arg
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsDate, DateObject::getTime_impl>(cx, args);
 }
 
 /* static */ MOZ_ALWAYS_INLINE bool
 DateObject::getYear_impl(JSContext* cx, const CallArgs& args)
 {
     DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
-    dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo);
+    dateObj->fillLocalTimeSlots();
 
     Value yearVal = dateObj->getReservedSlot(LOCAL_YEAR_SLOT);
     if (yearVal.isInt32()) {
         /* Follow ECMA-262 to the letter, contrary to IE JScript. */
         int year = yearVal.toInt32() - 1900;
         args.rval().setInt32(year);
     } else {
         args.rval().set(yearVal);
@@ -1402,17 +1403,17 @@ date_getYear(JSContext* cx, unsigned arg
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsDate, DateObject::getYear_impl>(cx, args);
 }
 
 /* static */ MOZ_ALWAYS_INLINE bool
 DateObject::getFullYear_impl(JSContext* cx, const CallArgs& args)
 {
     DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
-    dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo);
+    dateObj->fillLocalTimeSlots();
 
     args.rval().set(dateObj->getReservedSlot(LOCAL_YEAR_SLOT));
     return true;
 }
 
 static bool
 date_getFullYear(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -1437,17 +1438,17 @@ date_getUTCFullYear(JSContext* cx, unsig
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsDate, DateObject::getUTCFullYear_impl>(cx, args);
 }
 
 /* static */ MOZ_ALWAYS_INLINE bool
 DateObject::getMonth_impl(JSContext* cx, const CallArgs& args)
 {
     DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
-    dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo);
+    dateObj->fillLocalTimeSlots();
 
     args.rval().set(dateObj->getReservedSlot(LOCAL_MONTH_SLOT));
     return true;
 }
 
 static bool
 date_getMonth(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -1469,17 +1470,17 @@ date_getUTCMonth(JSContext* cx, unsigned
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsDate, DateObject::getUTCMonth_impl>(cx, args);
 }
 
 /* static */ MOZ_ALWAYS_INLINE bool
 DateObject::getDate_impl(JSContext* cx, const CallArgs& args)
 {
     DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
-    dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo);
+    dateObj->fillLocalTimeSlots();
 
     args.rval().set(dateObj->getReservedSlot(LOCAL_DATE_SLOT));
     return true;
 }
 
 static bool
 date_getDate(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -1504,17 +1505,17 @@ date_getUTCDate(JSContext* cx, unsigned 
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsDate, DateObject::getUTCDate_impl>(cx, args);
 }
 
 /* static */ MOZ_ALWAYS_INLINE bool
 DateObject::getDay_impl(JSContext* cx, const CallArgs& args)
 {
     DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
-    dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo);
+    dateObj->fillLocalTimeSlots();
 
     args.rval().set(dateObj->getReservedSlot(LOCAL_DAY_SLOT));
     return true;
 }
 
 static bool
 date_getDay(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -1539,17 +1540,17 @@ date_getUTCDay(JSContext* cx, unsigned a
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsDate, DateObject::getUTCDay_impl>(cx, args);
 }
 
 /* static */ MOZ_ALWAYS_INLINE bool
 DateObject::getHours_impl(JSContext* cx, const CallArgs& args)
 {
     DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
-    dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo);
+    dateObj->fillLocalTimeSlots();
 
     args.rval().set(dateObj->getReservedSlot(LOCAL_HOURS_SLOT));
     return true;
 }
 
 static bool
 date_getHours(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -1574,17 +1575,17 @@ date_getUTCHours(JSContext* cx, unsigned
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsDate, DateObject::getUTCHours_impl>(cx, args);
 }
 
 /* static */ MOZ_ALWAYS_INLINE bool
 DateObject::getMinutes_impl(JSContext* cx, const CallArgs& args)
 {
     DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
-    dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo);
+    dateObj->fillLocalTimeSlots();
 
     args.rval().set(dateObj->getReservedSlot(LOCAL_MINUTES_SLOT));
     return true;
 }
 
 static bool
 date_getMinutes(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -1611,17 +1612,17 @@ date_getUTCMinutes(JSContext* cx, unsign
 }
 
 /* Date.getSeconds is mapped to getUTCSeconds */
 
 /* static */ MOZ_ALWAYS_INLINE bool
 DateObject::getUTCSeconds_impl(JSContext* cx, const CallArgs& args)
 {
     DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
-    dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo);
+    dateObj->fillLocalTimeSlots();
 
     args.rval().set(dateObj->getReservedSlot(LOCAL_SECONDS_SLOT));
     return true;
 }
 
 static bool
 date_getUTCSeconds(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -1649,17 +1650,17 @@ date_getUTCMilliseconds(JSContext* cx, u
     return CallNonGenericMethod<IsDate, DateObject::getUTCMilliseconds_impl>(cx, args);
 }
 
 /* static */ MOZ_ALWAYS_INLINE bool
 DateObject::getTimezoneOffset_impl(JSContext* cx, const CallArgs& args)
 {
     DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
     double utctime = dateObj->UTCTime().toNumber();
-    double localtime = dateObj->cachedLocalTime(&cx->runtime()->dateTimeInfo);
+    double localtime = dateObj->cachedLocalTime();
 
     /*
      * Return the time zone offset in minutes for the current locale that is
      * appropriate for this time. This value would be a constant except for
      * daylight savings time.
      */
     double result = (utctime - localtime) / msPerMinute;
     args.rval().setNumber(result);
@@ -1722,35 +1723,37 @@ GetMinsOrDefault(JSContext* cx, const Ca
 {
     if (args.length() <= i) {
         *mins = MinFromTime(t);
         return true;
     }
     return ToNumber(cx, args[i], mins);
 }
 
-/* ES5 15.9.5.28. */
+/* ES6 20.3.4.23. */
 MOZ_ALWAYS_INLINE bool
 date_setMilliseconds_impl(JSContext* cx, const CallArgs& args)
 {
     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
 
-    /* Step 1. */
-    double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo);
-
-    /* Step 2. */
-    double milli;
-    if (!ToNumber(cx, args.get(0), &milli))
+    // Steps 1-2.
+    double t = LocalTime(dateObj->UTCTime().toNumber());
+
+    // Steps 3-4.
+    double ms;
+    if (!ToNumber(cx, args.get(0), &ms))
         return false;
-    double time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), milli);
-
-    /* Step 3. */
-    ClippedTime u = TimeClip(UTC(MakeDate(Day(t), time), &cx->runtime()->dateTimeInfo));
-
-    /* Steps 4-5. */
+
+    // Step 5.
+    double time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms);
+
+    // Step 6.
+    ClippedTime u = TimeClip(UTC(MakeDate(Day(t), time)));
+
+    // Steps 7-8.
     dateObj->setUTCTime(u, args.rval());
     return true;
 }
 
 static bool
 date_setMilliseconds(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
@@ -1788,41 +1791,41 @@ date_setUTCMilliseconds(JSContext* cx, u
 }
 
 /* ES5 15.9.5.30. */
 MOZ_ALWAYS_INLINE bool
 date_setSeconds_impl(JSContext* cx, const CallArgs& args)
 {
     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
 
-    /* Step 1. */
-    double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo);
-
-    /* Step 2. */
+    // Steps 1-2.
+    double t = LocalTime(dateObj->UTCTime().toNumber());
+
+    // Steps 3-4.
     double s;
     if (!ToNumber(cx, args.get(0), &s))
         return false;
 
-    /* Step 3. */
+    // Steps 5-6.
     double milli;
     if (!GetMsecsOrDefault(cx, args, 1, t, &milli))
         return false;
 
-    /* Step 4. */
+    // Step 7.
     double date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli));
 
-    /* Step 5. */
-    ClippedTime u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo));
-
-    /* Steps 6-7. */
+    // Step 8.
+    ClippedTime u = TimeClip(UTC(date));
+
+    // Step 9.
     dateObj->setUTCTime(u, args.rval());
     return true;
 }
 
-/* ES5 15.9.5.31. */
+/* ES6 20.3.4.26. */
 static bool
 date_setSeconds(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsDate, date_setSeconds_impl>(cx, args);
 }
 
 MOZ_ALWAYS_INLINE bool
@@ -1862,49 +1865,50 @@ date_setUTCSeconds(JSContext* cx, unsign
     return CallNonGenericMethod<IsDate, date_setUTCSeconds_impl>(cx, args);
 }
 
 MOZ_ALWAYS_INLINE bool
 date_setMinutes_impl(JSContext* cx, const CallArgs& args)
 {
     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
 
-    /* Step 1. */
-    double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo);
-
-    /* Step 2. */
+    // Steps 1-2.
+    double t = LocalTime(dateObj->UTCTime().toNumber());
+
+    // Steps 3-4.
     double m;
     if (!ToNumber(cx, args.get(0), &m))
         return false;
 
-    /* Step 3. */
+    // Steps 5-6.
     double s;
     if (!GetSecsOrDefault(cx, args, 1, t, &s))
         return false;
 
-    /* Step 4. */
+    // Steps 7-8.
     double milli;
     if (!GetMsecsOrDefault(cx, args, 2, t, &milli))
         return false;
 
-    /* Step 5. */
+    // Step 9.
     double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli));
 
-    /* Step 6. */
-    ClippedTime u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo));
-
-    /* Steps 7-8. */
+    // Step 10.
+    ClippedTime u = TimeClip(UTC(date));
+
+    // Steps 11-12.
     dateObj->setUTCTime(u, args.rval());
     return true;
 }
 
-/* ES5 15.9.5.33. */
+/* ES6 20.3.4.24. */
 static bool
 date_setMinutes(JSContext* cx, unsigned argc, Value* vp)
 {
+    // Steps 1-2 (the effectful parts).
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsDate, date_setMinutes_impl>(cx, args);
 }
 
 MOZ_ALWAYS_INLINE bool
 date_setUTCMinutes_impl(JSContext* cx, const CallArgs& args)
 {
     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
@@ -1946,46 +1950,46 @@ date_setUTCMinutes(JSContext* cx, unsign
     return CallNonGenericMethod<IsDate, date_setUTCMinutes_impl>(cx, args);
 }
 
 MOZ_ALWAYS_INLINE bool
 date_setHours_impl(JSContext* cx, const CallArgs& args)
 {
     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
 
-    /* Step 1. */
-    double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo);
-
-    /* Step 2. */
+    // Steps 1-2.
+    double t = LocalTime(dateObj->UTCTime().toNumber());
+
+    // Steps 3-4.
     double h;
     if (!ToNumber(cx, args.get(0), &h))
         return false;
 
-    /* Step 3. */
+    // Steps 5-6.
     double m;
     if (!GetMinsOrDefault(cx, args, 1, t, &m))
         return false;
 
-    /* Step 4. */
+    // Steps 7-8.
     double s;
     if (!GetSecsOrDefault(cx, args, 2, t, &s))
         return false;
 
-    /* Step 5. */
+    // Steps 9-10.
     double milli;
     if (!GetMsecsOrDefault(cx, args, 3, t, &milli))
         return false;
 
-    /* Step 6. */
+    // Step 11.
     double date = MakeDate(Day(t), MakeTime(h, m, s, milli));
 
-    /* Step 6. */
-    ClippedTime u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo));
-
-    /* Steps 7-8. */
+    // Step 12.
+    ClippedTime u = TimeClip(UTC(date));
+
+    // Steps 13-14.
     dateObj->setUTCTime(u, args.rval());
     return true;
 }
 
 /* ES5 15.9.5.35. */
 static bool
 date_setHours(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -2041,28 +2045,28 @@ date_setUTCHours(JSContext* cx, unsigned
 }
 
 MOZ_ALWAYS_INLINE bool
 date_setDate_impl(JSContext* cx, const CallArgs& args)
 {
     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
 
     /* Step 1. */
-    double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo);
+    double t = LocalTime(dateObj->UTCTime().toNumber());
 
     /* Step 2. */
     double date;
     if (!ToNumber(cx, args.get(0), &date))
         return false;
 
     /* Step 3. */
     double newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date), TimeWithinDay(t));
 
     /* Step 4. */
-    ClippedTime u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo));
+    ClippedTime u = TimeClip(UTC(newDate));
 
     /* Steps 5-6. */
     dateObj->setUTCTime(u, args.rval());
     return true;
 }
 
 /* ES5 15.9.5.37. */
 static bool
@@ -2125,33 +2129,33 @@ GetMonthOrDefault(JSContext* cx, const C
 
 /* ES5 15.9.5.38. */
 MOZ_ALWAYS_INLINE bool
 date_setMonth_impl(JSContext* cx, const CallArgs& args)
 {
     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
 
     /* Step 1. */
-    double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo);
+    double t = LocalTime(dateObj->UTCTime().toNumber());
 
     /* Step 2. */
     double m;
     if (!ToNumber(cx, args.get(0), &m))
         return false;
 
     /* Step 3. */
     double date;
     if (!GetDateOrDefault(cx, args, 1, t, &date))
         return false;
 
     /* Step 4. */
     double newDate = MakeDate(MakeDay(YearFromTime(t), m, date), TimeWithinDay(t));
 
     /* Step 5. */
-    ClippedTime u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo));
+    ClippedTime u = TimeClip(UTC(newDate));
 
     /* Steps 6-7. */
     dateObj->setUTCTime(u, args.rval());
     return true;
 }
 
 static bool
 date_setMonth(JSContext* cx, unsigned argc, Value* vp)
@@ -2193,39 +2197,39 @@ date_setUTCMonth_impl(JSContext* cx, con
 static bool
 date_setUTCMonth(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsDate, date_setUTCMonth_impl>(cx, args);
 }
 
 static double
-ThisLocalTimeOrZero(Handle<DateObject*> dateObj, DateTimeInfo* dtInfo)
+ThisLocalTimeOrZero(Handle<DateObject*> dateObj)
 {
     double t = dateObj->UTCTime().toNumber();
     if (IsNaN(t))
         return +0;
-    return LocalTime(t, dtInfo);
+    return LocalTime(t);
 }
 
 static double
 ThisUTCTimeOrZero(Handle<DateObject*> dateObj)
 {
     double t = dateObj->as<DateObject>().UTCTime().toNumber();
     return IsNaN(t) ? +0 : t;
 }
 
 /* ES5 15.9.5.40. */
 MOZ_ALWAYS_INLINE bool
 date_setFullYear_impl(JSContext* cx, const CallArgs& args)
 {
     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
 
     /* Step 1. */
-    double t = ThisLocalTimeOrZero(dateObj, &cx->runtime()->dateTimeInfo);
+    double t = ThisLocalTimeOrZero(dateObj);
 
     /* Step 2. */
     double y;
     if (!ToNumber(cx, args.get(0), &y))
         return false;
 
     /* Step 3. */
     double m;
@@ -2236,17 +2240,17 @@ date_setFullYear_impl(JSContext* cx, con
     double date;
     if (!GetDateOrDefault(cx, args, 2, t, &date))
         return false;
 
     /* Step 5. */
     double newDate = MakeDate(MakeDay(y, m, date), TimeWithinDay(t));
 
     /* Step 6. */
-    ClippedTime u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo));
+    ClippedTime u = TimeClip(UTC(newDate));
 
     /* Steps 7-8. */
     dateObj->setUTCTime(u, args.rval());
     return true;
 }
 
 static bool
 date_setFullYear(JSContext* cx, unsigned argc, Value* vp)
@@ -2299,17 +2303,17 @@ date_setUTCFullYear(JSContext* cx, unsig
 
 /* ES5 Annex B.2.5. */
 MOZ_ALWAYS_INLINE bool
 date_setYear_impl(JSContext* cx, const CallArgs& args)
 {
     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
 
     /* Step 1. */
-    double t = ThisLocalTimeOrZero(dateObj, &cx->runtime()->dateTimeInfo);
+    double t = ThisLocalTimeOrZero(dateObj);
 
     /* Step 2. */
     double y;
     if (!ToNumber(cx, args.get(0), &y))
         return false;
 
     /* Step 3. */
     if (IsNaN(y)) {
@@ -2321,17 +2325,17 @@ date_setYear_impl(JSContext* cx, const C
     double yint = ToInteger(y);
     if (0 <= yint && yint <= 99)
         yint += 1900;
 
     /* Step 5. */
     double day = MakeDay(yint, MonthFromTime(t), DateFromTime(t));
 
     /* Step 6. */
-    double u = UTC(MakeDate(day, TimeWithinDay(t)), &cx->runtime()->dateTimeInfo);
+    double u = UTC(MakeDate(day, TimeWithinDay(t)));
 
     /* Steps 7-8. */
     dateObj->setUTCTime(TimeClip(u), args.rval());
     return true;
 }
 
 static bool
 date_setYear(JSContext* cx, unsigned argc, Value* vp)
@@ -2500,33 +2504,33 @@ date_toJSON(JSContext* cx, unsigned argc
         return false;
     args.rval().set(args2.rval());
     return true;
 }
 
 /* for Date.toLocaleFormat; interface to PRMJTime date struct.
  */
 static void
-new_explode(double timeval, PRMJTime* split, DateTimeInfo* dtInfo)
+new_explode(double timeval, PRMJTime* split)
 {
     double year = YearFromTime(timeval);
 
     split->tm_usec = int32_t(msFromTime(timeval)) * 1000;
     split->tm_sec = int8_t(SecFromTime(timeval));
     split->tm_min = int8_t(MinFromTime(timeval));
     split->tm_hour = int8_t(HourFromTime(timeval));
     split->tm_mday = int8_t(DateFromTime(timeval));
     split->tm_mon = int8_t(MonthFromTime(timeval));
     split->tm_wday = int8_t(WeekDay(timeval));
     split->tm_year = year;
     split->tm_yday = int16_t(DayWithinYear(timeval, year));
 
     /* not sure how this affects things, but it doesn't seem
        to matter. */
-    split->tm_isdst = (DaylightSavingTA(timeval, dtInfo) != 0);
+    split->tm_isdst = (DaylightSavingTA(timeval) != 0);
 }
 
 typedef enum formatspec {
     FORMATSPEC_FULL, FORMATSPEC_DATE, FORMATSPEC_TIME
 } formatspec;
 
 /* helper function */
 static bool
@@ -2538,37 +2542,37 @@ date_format(JSContext* cx, double date, 
     size_t i, tzlen;
     PRMJTime split;
 
     if (!IsFinite(date)) {
         JS_snprintf(buf, sizeof buf, js_NaN_date_str);
     } else {
         MOZ_ASSERT(NumbersAreIdentical(TimeClip(date).toDouble(), date));
 
-        double local = LocalTime(date, &cx->runtime()->dateTimeInfo);
+        double local = LocalTime(date);
 
         /* offset from GMT in minutes.  The offset includes daylight savings,
            if it applies. */
-        int minutes = (int) floor(AdjustTime(date, &cx->runtime()->dateTimeInfo) / msPerMinute);
+        int minutes = (int) floor(AdjustTime(date) / msPerMinute);
 
         /* map 510 minutes to 0830 hours */
         int offset = (minutes / 60) * 100 + minutes % 60;
 
         /* print as "Wed Nov 05 19:38:03 GMT-0800 (PST) 1997" The TZA is
          * printed as 'GMT-0800' rather than as 'PST' to avoid
          * operating-system dependence on strftime (which
          * PRMJ_FormatTimeUSEnglish calls, for %Z only.)  win32 prints
          * PST as 'Pacific Standard Time.'  This way we always know
          * what we're getting, and can parse it if we produce it.
          * The OS TZA string is included as a comment.
          */
 
         /* get a timezone string from the OS to include as a
            comment. */
-        new_explode(date, &split, &cx->runtime()->dateTimeInfo);
+        new_explode(date, &split);
         if (PRMJ_FormatTime(tzbuf, sizeof tzbuf, "(%Z)", &split) != 0) {
 
             /* Decide whether to use the resulting timezone string.
              *
              * Reject it if it contains any non-ASCII, non-alphanumeric
              * characters.  It's then likely in some other character
              * encoding, and we probably won't display it correctly.
              */
@@ -2648,19 +2652,19 @@ ToLocaleFormatHelper(JSContext* cx, Hand
 {
     double utctime = obj->as<DateObject>().UTCTime().toNumber();
 
     char buf[100];
     if (!IsFinite(utctime)) {
         JS_snprintf(buf, sizeof buf, js_NaN_date_str);
     } else {
         int result_len;
-        double local = LocalTime(utctime, &cx->runtime()->dateTimeInfo);
+        double local = LocalTime(utctime);
         PRMJTime split;
-        new_explode(local, &split, &cx->runtime()->dateTimeInfo);
+        new_explode(local, &split);
 
         /* Let PRMJTime format it. */
         result_len = PRMJ_FormatTime(buf, sizeof buf, format, &split);
 
         /* If it failed, default to toString. */
         if (result_len == 0)
             return date_format(cx, utctime, FORMATSPEC_FULL, rval);
 
@@ -2668,17 +2672,17 @@ ToLocaleFormatHelper(JSContext* cx, Hand
         if (strcmp(format, "%x") == 0 && result_len >= 6 &&
             /* Format %x means use OS settings, which may have 2-digit yr, so
                hack end of 3/11/22 or 11.03.22 or 11Mar22 to use 4-digit yr...*/
             !isdigit(buf[result_len - 3]) &&
             isdigit(buf[result_len - 2]) && isdigit(buf[result_len - 1]) &&
             /* ...but not if starts with 4-digit year, like 2022/3/11. */
             !(isdigit(buf[0]) && isdigit(buf[1]) &&
               isdigit(buf[2]) && isdigit(buf[3]))) {
-            double localtime = obj->as<DateObject>().cachedLocalTime(&cx->runtime()->dateTimeInfo);
+            double localtime = obj->as<DateObject>().cachedLocalTime();
             int year = IsNaN(localtime) ? 0 : (int) YearFromTime(localtime);
             JS_snprintf(buf + (result_len - 2), (sizeof buf) - (result_len - 2),
                         "%d", year);
         }
 
     }
 
     if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeToUnicode)
@@ -3065,17 +3069,17 @@ DateOneArgument(JSContext* cx, const Cal
             return false;
 
         ClippedTime t;
         if (args[0].isString()) {
             JSLinearString* linearStr = args[0].toString()->ensureLinear(cx);
             if (!linearStr)
                 return false;
 
-            if (!ParseDate(linearStr, &t, &cx->runtime()->dateTimeInfo))
+            if (!ParseDate(linearStr, &t))
                 t = ClippedTime::invalid();
         } else {
             double d;
             if (!ToNumber(cx, args[0], &d))
                 return false;
             t = TimeClip(d);
         }
 
@@ -3154,17 +3158,17 @@ DateMultipleArguments(JSContext* cx, con
             if (0 <= yint && yint <= 99)
                 yr = 1900 + yint;
         }
 
         // Step 3p.
         double finalDate = MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli));
 
         // Steps 3q-t.
-        return NewDateObject(cx, args, TimeClip(UTC(finalDate, &cx->runtime()->dateTimeInfo)));
+        return NewDateObject(cx, args, TimeClip(UTC(finalDate)));
     }
 
     return ToDateString(cx, args, NowAsMillis());
 }
 
 bool
 js::DateConstructor(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -3266,17 +3270,17 @@ js::NewDateObjectMsec(JSContext* cx, Cli
 }
 
 JS_FRIEND_API(JSObject*)
 js::NewDateObject(JSContext* cx, int year, int mon, int mday,
                   int hour, int min, int sec)
 {
     MOZ_ASSERT(mon < 12);
     double msec_time = MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0.0));
-    return NewDateObjectMsec(cx, TimeClip(UTC(msec_time, &cx->runtime()->dateTimeInfo)));
+    return NewDateObjectMsec(cx, TimeClip(UTC(msec_time)));
 }
 
 JS_FRIEND_API(bool)
 js::DateIsValid(JSContext* cx, HandleObject obj, bool* isValid)
 {
     ESClassValue cls;
     if (!GetBuiltinClass(cx, obj, &cls))
         return false;
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -109,16 +109,17 @@ EXPORTS.js += [
     '../public/Class.h',
     '../public/Conversions.h',
     '../public/Date.h',
     '../public/Debug.h',
     '../public/GCAPI.h',
     '../public/HashTable.h',
     '../public/HeapAPI.h',
     '../public/Id.h',
+    '../public/Initialization.h',
     '../public/LegacyIntTypes.h',
     '../public/MemoryMetrics.h',
     '../public/Principals.h',
     '../public/ProfilingFrameIterator.h',
     '../public/ProfilingStack.h',
     '../public/Proxy.h',
     '../public/RequiredDefines.h',
     '../public/RootingAPI.h',
@@ -357,16 +358,17 @@ UNIFIED_SOURCES += [
 SOURCES += [
     'builtin/RegExp.cpp',
     'frontend/Parser.cpp',
     'gc/StoreBuffer.cpp',
     'jsarray.cpp',
     'jsatom.cpp',
     'jsmath.cpp',
     'jsutil.cpp',
+    'vm/Initialization.cpp',
 ]
 
 if CONFIG['JS_POSIX_NSPR']:
     UNIFIED_SOURCES += [
         'vm/PosixNSPR.cpp',
     ]
 
 if CONFIG['MOZ_INSTRUMENTS']:
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -62,16 +62,17 @@
 #include "gc/GCInternals.h"
 #include "jit/arm/Simulator-arm.h"
 #include "jit/InlinableNatives.h"
 #include "jit/Ion.h"
 #include "jit/JitcodeMap.h"
 #include "jit/OptimizationTracking.h"
 #include "js/Debug.h"
 #include "js/GCAPI.h"
+#include "js/Initialization.h"
 #include "js/StructuredClone.h"
 #include "js/TrackedOptimizationInfo.h"
 #include "perf/jsperf.h"
 #include "shell/jsoptparse.h"
 #include "shell/OSObject.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/Debugger.h"
 #include "vm/HelperThreads.h"
--- a/js/src/vm/DateObject.h
+++ b/js/src/vm/DateObject.h
@@ -9,18 +9,16 @@
 
 #include "jsobj.h"
 
 #include "js/Date.h"
 #include "js/Value.h"
 
 namespace js {
 
-class DateTimeInfo;
-
 class DateObject : public NativeObject
 {
     static const uint32_t UTC_TIME_SLOT = 0;
     static const uint32_t TZA_SLOT = 1;
 
     /*
      * Cached slots holding local properties of the date.
      * These are undefined until the first actual lookup occurs
@@ -53,22 +51,22 @@ class DateObject : public NativeObject
     const js::Value& UTCTime() const {
         return getFixedSlot(UTC_TIME_SLOT);
     }
 
     // Set UTC time to a given time and invalidate cached local time.
     void setUTCTime(JS::ClippedTime t);
     void setUTCTime(JS::ClippedTime t, MutableHandleValue vp);
 
-    inline double cachedLocalTime(DateTimeInfo* dtInfo);
+    inline double cachedLocalTime();
 
     // Cache the local time, year, month, and so forth of the object.
     // If UTC time is not finite (e.g., NaN), the local time
     // slots will be set to the UTC time without conversion.
-    void fillLocalTimeSlots(DateTimeInfo* dtInfo);
+    void fillLocalTimeSlots();
 
     static MOZ_ALWAYS_INLINE bool getTime_impl(JSContext* cx, const CallArgs& args);
     static MOZ_ALWAYS_INLINE bool getYear_impl(JSContext* cx, const CallArgs& args);
     static MOZ_ALWAYS_INLINE bool getFullYear_impl(JSContext* cx, const CallArgs& args);
     static MOZ_ALWAYS_INLINE bool getUTCFullYear_impl(JSContext* cx, const CallArgs& args);
     static MOZ_ALWAYS_INLINE bool getMonth_impl(JSContext* cx, const CallArgs& args);
     static MOZ_ALWAYS_INLINE bool getUTCMonth_impl(JSContext* cx, const CallArgs& args);
     static MOZ_ALWAYS_INLINE bool getDate_impl(JSContext* cx, const CallArgs& args);
--- a/js/src/vm/DateTime.cpp
+++ b/js/src/vm/DateTime.cpp
@@ -5,18 +5,29 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "vm/DateTime.h"
 
 #include <time.h>
 
 #include "jsutil.h"
 
+#include "js/Date.h"
+#if ENABLE_INTL_API
+#include "unicode/timezone.h"
+#endif
+
 using mozilla::UnspecifiedNaN;
 
+/* static */ js::DateTimeInfo
+js::DateTimeInfo::instance;
+
+/* static */ mozilla::Atomic<bool, mozilla::ReleaseAcquire>
+js::DateTimeInfo::AcquireLock::spinLock;
+
 static bool
 ComputeLocalTime(time_t local, struct tm* ptm)
 {
 #if defined(_WIN32)
     return localtime_s(ptm, &local) == 0;
 #elif defined(HAVE_LOCALTIME_R)
     return localtime_r(&local, ptm);
 #else
@@ -126,17 +137,17 @@ UTCToLocalStandardOffsetSeconds()
         return (SecondsPerDay + local_secs) - utc_secs;
 
     // Otherwise we have more local seconds, so move the UTC seconds into the
     // local seconds' frame of reference and then subtract.
     return local_secs - (utc_secs + SecondsPerDay);
 }
 
 void
-js::DateTimeInfo::updateTimeZoneAdjustment()
+js::DateTimeInfo::internalUpdateTimeZoneAdjustment()
 {
     /*
      * The difference between local standard time and UTC will never change for
      * a given time zone.
      */
     utcToLocalStandardOffsetSeconds = UTCToLocalStandardOffsetSeconds();
 
     double newTZA = utcToLocalStandardOffsetSeconds * msPerSecond;
@@ -159,22 +170,29 @@ js::DateTimeInfo::updateTimeZoneAdjustme
 }
 
 /*
  * Since getDSTOffsetMilliseconds guarantees that all times seen will be
  * positive, we can initialize the range at construction time with large
  * negative numbers to ensure the first computation is always a cache miss and
  * doesn't return a bogus offset.
  */
-js::DateTimeInfo::DateTimeInfo()
+/* static */ void
+js::DateTimeInfo::init()
 {
+    DateTimeInfo* dtInfo = &DateTimeInfo::instance;
+
+    MOZ_ASSERT(dtInfo->localTZA_ == 0,
+               "we should be initializing only once, and the static instance "
+               "should have started out zeroed");
+
     // Set to a totally impossible TZA so that the comparison above will fail
     // and all fields will be properly initialized.
-    localTZA_ = UnspecifiedNaN<double>();
-    updateTimeZoneAdjustment();
+    dtInfo->localTZA_ = UnspecifiedNaN<double>();
+    dtInfo->internalUpdateTimeZoneAdjustment();
 }
 
 int64_t
 js::DateTimeInfo::computeDSTOffsetMilliseconds(int64_t utcSeconds)
 {
     MOZ_ASSERT(utcSeconds >= 0);
     MOZ_ASSERT(utcSeconds <= MaxUnixTimeT);
 
@@ -196,17 +214,17 @@ js::DateTimeInfo::computeDSTOffsetMillis
 
     if (diff < 0)
         diff += SecondsPerDay;
 
     return diff * msPerSecond;
 }
 
 int64_t
-js::DateTimeInfo::getDSTOffsetMilliseconds(int64_t utcMilliseconds)
+js::DateTimeInfo::internalGetDSTOffsetMilliseconds(int64_t utcMilliseconds)
 {
     sanityCheck();
 
     int64_t utcSeconds = utcMilliseconds / msPerSecond;
 
     if (utcSeconds > MaxUnixTimeT) {
         utcSeconds = MaxUnixTimeT;
     } else if (utcSeconds < 0) {
@@ -283,8 +301,18 @@ js::DateTimeInfo::sanityCheck()
     MOZ_ASSERT(rangeStartSeconds <= rangeEndSeconds);
     MOZ_ASSERT_IF(rangeStartSeconds == INT64_MIN, rangeEndSeconds == INT64_MIN);
     MOZ_ASSERT_IF(rangeEndSeconds == INT64_MIN, rangeStartSeconds == INT64_MIN);
     MOZ_ASSERT_IF(rangeStartSeconds != INT64_MIN,
                   rangeStartSeconds >= 0 && rangeEndSeconds >= 0);
     MOZ_ASSERT_IF(rangeStartSeconds != INT64_MIN,
                   rangeStartSeconds <= MaxUnixTimeT && rangeEndSeconds <= MaxUnixTimeT);
 }
+
+JS_PUBLIC_API(void)
+JS::ResetTimeZone()
+{
+    DateTimeInfo::updateTimeZoneAdjustment();
+
+#if ENABLE_INTL_API && defined(ICU_TZ_HAS_RECREATE_DEFAULT)
+    icu::TimeZone::recreateDefault();
+#endif
+}
--- a/js/src/vm/DateTime.h
+++ b/js/src/vm/DateTime.h
@@ -2,22 +2,27 @@
  * 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 vm_DateTime_h
 #define vm_DateTime_h
 
+#include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/MathAlgorithms.h"
 
 #include <stdint.h>
 
 #include "js/Conversions.h"
+#include "js/Date.h"
+#include "js/Initialization.h"
 #include "js/Value.h"
 
 namespace js {
 
 /* Constants defined by ES5 15.9.1.10. */
 const double HoursPerDay = 24;
 const double MinutesPerHour = 60;
 const double SecondsPerMinute = 60;
@@ -83,42 +88,90 @@ const double EndOfTime = 8.64e15;
  * range while producing more misses.  Using a month as the interval change is
  * a balance between these two that tries to optimize for the calendar month at
  * a time that a site might display.  (One could imagine an adaptive duration
  * that accommodates near-DST-change dates better; we don't believe the
  * potential win from better caching offsets the loss from extra complexity.)
  */
 class DateTimeInfo
 {
+    static DateTimeInfo instance;
+
+    // Date/time info is shared across all threads in DateTimeInfo::instance,
+    // for consistency with ICU's handling of its default time zone.  Thus we
+    // need something to protect concurrent accesses.
+    //
+    // The spec implicitly assumes DST and time zone adjustment information
+    // never change in the course of a function -- sometimes even across
+    // reentrancy.  So make critical sections as narrow as possible, and use a
+    // bog-standard spinlock with busy-waiting in case of contention for
+    // simplicity.
+    class MOZ_RAII AcquireLock
+    {
+        static mozilla::Atomic<bool, mozilla::ReleaseAcquire> spinLock;
+
+      public:
+        AcquireLock() {
+            while (!spinLock.compareExchange(false, true))
+                continue;
+        }
+        ~AcquireLock() {
+            MOZ_ASSERT(spinLock, "spinlock should have been acquired");
+            spinLock = false;
+        }
+    };
+
+    friend bool ::JS_Init();
+
+    // Initialize global date/time tracking state.  This operation occurs
+    // during, and is restricted to, SpiderMonkey initialization.
+    static void init();
+
   public:
-    DateTimeInfo();
-
     /*
      * Get the DST offset in milliseconds at a UTC time.  This is usually
      * either 0 or |msPerSecond * SecondsPerHour|, but at least one exotic time
      * zone (Lord Howe Island, Australia) has a fractional-hour offset, just to
      * keep things interesting.
      */
-    int64_t getDSTOffsetMilliseconds(int64_t utcMilliseconds);
+    static int64_t getDSTOffsetMilliseconds(int64_t utcMilliseconds) {
+        AcquireLock lock;
 
-    void updateTimeZoneAdjustment();
+        return DateTimeInfo::instance.internalGetDSTOffsetMilliseconds(utcMilliseconds);
+    }
 
     /* ES5 15.9.1.7. */
-    double localTZA() { return localTZA_; }
+    static double localTZA() {
+        AcquireLock lock;
+
+        return DateTimeInfo::instance.localTZA_;
+    }
 
   private:
+    // We don't want anyone accidentally calling *only*
+    // DateTimeInfo::updateTimeZoneAdjustment() to respond to a system time
+    // zone change (missing the necessary poking of ICU as well), so ensure
+    // only JS::ResetTimeZone() can call this via access restrictions.
+    friend void JS::ResetTimeZone();
+
+    static void updateTimeZoneAdjustment() {
+        AcquireLock lock;
+
+        DateTimeInfo::instance.internalUpdateTimeZoneAdjustment();
+    }
+
     /*
      * The current local time zone adjustment, cached because retrieving this
      * dynamically is Slow, and a certain venerable benchmark which shall not
      * be named depends on it being fast.
      *
      * SpiderMonkey occasionally and arbitrarily updates this value from the
      * system time zone to attempt to keep this reasonably up-to-date.  If
      * temporary inaccuracy can't be tolerated, JSAPI clients may call
-     * JS_ClearDateCaches to forcibly sync this with the system time zone.
+     * JS::ResetTimeZone to forcibly sync this with the system time zone.
      */
     double localTZA_;
 
     /*
      * Compute the DST offset at the given UTC time in seconds from the epoch.
      * (getDSTOffsetMilliseconds attempts to return a cached value, but in case
      * of a cache miss it calls this method.  The cache is represented through
      * the offset* and *{Start,End}Seconds fields below.)
@@ -136,14 +189,17 @@ class DateTimeInfo
      * local standard time (i.e. not including any offset due to DST).
      */
     int32_t utcToLocalStandardOffsetSeconds;
 
     static const int64_t MaxUnixTimeT = 2145859200; /* time_t 12/31/2037 */
 
     static const int64_t RangeExpansionAmount = 30 * SecondsPerDay;
 
+    int64_t internalGetDSTOffsetMilliseconds(int64_t utcMilliseconds);
+    void internalUpdateTimeZoneAdjustment();
+
     void sanityCheck();
 };
 
 }  /* namespace js */
 
 #endif /* vm_DateTime_h */
new file mode 100644
--- /dev/null
+++ b/js/src/vm/Initialization.cpp
@@ -0,0 +1,166 @@
+/* -*- 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/. */
+
+/* SpiderMonkey initialization and shutdown code. */
+
+#include "js/Initialization.h"
+
+#include "mozilla/Assertions.h"
+
+#include <ctype.h>
+
+#include "jstypes.h"
+
+#include "builtin/AtomicsObject.h"
+#include "jit/ExecutableAllocator.h"
+#include "jit/Ion.h"
+#include "js/Utility.h"
+#if ENABLE_INTL_API
+#include "unicode/uclean.h"
+#include "unicode/utypes.h"
+#endif // ENABLE_INTL_API
+#include "vm/DateTime.h"
+#include "vm/HelperThreads.h"
+#include "vm/Runtime.h"
+#include "vm/Time.h"
+#include "vm/TraceLogging.h"
+
+using JS::detail::InitState;
+using JS::detail::libraryInitState;
+using js::FutexRuntime;
+
+InitState JS::detail::libraryInitState;
+
+#ifdef DEBUG
+static unsigned
+MessageParameterCount(const char* format)
+{
+    unsigned numfmtspecs = 0;
+    for (const char* fmt = format; *fmt != '\0'; fmt++) {
+        if (*fmt == '{' && isdigit(fmt[1]))
+            ++numfmtspecs;
+    }
+    return numfmtspecs;
+}
+
+static void
+CheckMessageParameterCounts()
+{
+    // Assert that each message format has the correct number of braced
+    // parameters.
+# define MSG_DEF(name, count, exception, format)           \
+        MOZ_ASSERT(MessageParameterCount(format) == count);
+# include "js.msg"
+# undef MSG_DEF
+}
+#endif /* DEBUG */
+
+JS_PUBLIC_API(bool)
+JS_Init(void)
+{
+    MOZ_ASSERT(libraryInitState == InitState::Uninitialized,
+               "must call JS_Init once before any JSAPI operation except "
+               "JS_SetICUMemoryFunctions");
+    MOZ_ASSERT(!JSRuntime::hasLiveRuntimes(),
+               "how do we have live runtimes before JS_Init?");
+
+    PRMJ_NowInit();
+
+#ifdef DEBUG
+    CheckMessageParameterCounts();
+#endif
+
+    using js::TlsPerThreadData;
+    if (!TlsPerThreadData.initialized() && !TlsPerThreadData.init())
+        return false;
+
+#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+    if (!js::oom::InitThreadType())
+        return false;
+    js::oom::SetThreadType(js::oom::THREAD_TYPE_MAIN);
+#endif
+
+    js::jit::ExecutableAllocator::initStatic();
+
+    if (!js::jit::InitializeIon())
+        return false;
+
+    js::DateTimeInfo::init();
+
+#if EXPOSE_INTL_API
+    UErrorCode err = U_ZERO_ERROR;
+    u_init(&err);
+    if (U_FAILURE(err))
+        return false;
+#endif // EXPOSE_INTL_API
+
+    if (!js::CreateHelperThreadsState())
+        return false;
+
+    if (!FutexRuntime::initialize())
+        return false;
+
+    libraryInitState = InitState::Running;
+    return true;
+}
+
+JS_PUBLIC_API(void)
+JS_ShutDown(void)
+{
+    MOZ_ASSERT(libraryInitState == InitState::Running,
+               "JS_ShutDown must only be called after JS_Init and can't race with it");
+#ifdef DEBUG
+    if (JSRuntime::hasLiveRuntimes()) {
+        // Gecko is too buggy to assert this just yet.
+        fprintf(stderr,
+                "WARNING: YOU ARE LEAKING THE WORLD (at least one JSRuntime "
+                "and everything alive inside it, that is) AT JS_ShutDown "
+                "TIME.  FIX THIS!\n");
+    }
+#endif
+
+    FutexRuntime::destroy();
+
+    js::DestroyHelperThreadsState();
+
+#ifdef JS_TRACE_LOGGING
+    js::DestroyTraceLoggerThreadState();
+    js::DestroyTraceLoggerGraphState();
+#endif
+
+    // The only difficult-to-address reason for the restriction that you can't
+    // call JS_Init/stuff/JS_ShutDown multiple times is the Windows PRMJ
+    // NowInit initialization code, which uses PR_CallOnce to initialize the
+    // PRMJ_Now subsystem.  (For reinitialization to be permitted, we'd need to
+    // "reset" the called-once status -- doable, but more trouble than it's
+    // worth now.)  Initializing that subsystem from JS_Init eliminates the
+    // problem, but initialization can take a comparatively long time (15ms or
+    // so), so we really don't want to do it in JS_Init, and we really do want
+    // to do it only when PRMJ_Now is eventually called.
+    PRMJ_NowShutdown();
+
+#if EXPOSE_INTL_API
+    u_cleanup();
+#endif // EXPOSE_INTL_API
+
+    libraryInitState = InitState::ShutDown;
+}
+
+JS_PUBLIC_API(bool)
+JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn, JS_ICUReallocFn reallocFn, JS_ICUFreeFn freeFn)
+{
+    MOZ_ASSERT(libraryInitState == InitState::Uninitialized,
+               "must call JS_SetICUMemoryFunctions before any other JSAPI "
+               "operation (including JS_Init)");
+
+#if EXPOSE_INTL_API
+    UErrorCode status = U_ZERO_ERROR;
+    u_setMemoryFunctions(/* context = */ nullptr, allocFn, reallocFn, freeFn, &status);
+    return U_SUCCESS(status);
+#else
+    return true;
+#endif
+}
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -40,16 +40,17 @@
 #include "jswrapper.h"
 
 #include "asmjs/AsmJSSignalHandlers.h"
 #include "jit/arm/Simulator-arm.h"
 #include "jit/arm64/vixl/Simulator-vixl.h"
 #include "jit/JitCompartment.h"
 #include "jit/mips32/Simulator-mips32.h"
 #include "jit/PcScriptCache.h"
+#include "js/Date.h"
 #include "js/MemoryMetrics.h"
 #include "js/SliceBudget.h"
 #include "vm/Debugger.h"
 
 #include "jscntxtinlines.h"
 #include "jsgcinlines.h"
 
 using namespace js;
@@ -328,17 +329,17 @@ JSRuntime::init(uint32_t maxbytes, uint3
         return false;
 
     /* The garbage collector depends on everything before this point being initialized. */
     gcInitialized = true;
 
     if (!InitRuntimeNumberState(this))
         return false;
 
-    dateTimeInfo.updateTimeZoneAdjustment();
+    JS::ResetTimeZone();
 
 #ifdef JS_SIMULATOR
     simulator_ = js::jit::Simulator::Create();
     if (!simulator_)
         return false;
 #endif
 
     jitSupportsFloatingPoint = js::jit::JitSupportsFloatingPoint();
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -1200,17 +1200,16 @@ struct JSRuntime : public JS::shadow::Ru
     js::ScopeCoordinateNameCache scopeCoordinateNameCache;
     js::NewObjectCache  newObjectCache;
     js::NativeIterCache nativeIterCache;
     js::UncompressedSourceCache uncompressedSourceCache;
     js::EvalCache       evalCache;
     js::LazyScriptCache lazyScriptCache;
 
     js::CompressedSourceSet compressedSourceSet;
-    js::DateTimeInfo    dateTimeInfo;
 
     // Pool of maps used during parse/emit. This may be modified by threads
     // with an ExclusiveContext and requires a lock. Active compilations
     // prevent the pool from being purged during GCs.
   private:
     js::frontend::ParseMapPool parseMapPool_;
     unsigned activeCompilations_;
   public:
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -1258,17 +1258,17 @@ intrinsic_RuntimeDefaultLocale(JSContext
 }
 
 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());
+    args.rval().setDouble(DateTimeInfo::localTZA());
     return true;
 }
 
 static bool
 intrinsic_IsConstructing(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 0);
--- a/xpcom/build/XPCOMInit.cpp
+++ b/xpcom/build/XPCOMInit.cpp
@@ -150,16 +150,17 @@ extern nsresult nsStringInputStreamConst
 #endif
 #ifdef MOZ_WEBM
 #include "nestegg/nestegg.h"
 #endif
 
 #include "GeckoProfiler.h"
 
 #include "jsapi.h"
+#include "js/Initialization.h"
 
 #include "gfxPlatform.h"
 
 using namespace mozilla;
 using base::AtExitManager;
 using mozilla::ipc::BrowserProcessSubThread;
 
 namespace {