Bug 1158399 - Expose the [[DateValue]] field in Date objects only through a ClippedTime class that enforces prior TimeClip-ing on the given value. r=evilpie, r=bz, r=dhylands, r=mt, r=froydnj, r=khuey, r=baku, r=smaug
authorJeff Walden <jwalden@mit.edu>
Fri, 01 May 2015 19:12:52 -0700
changeset 257154 404e5944dc5901b62eeaf450648203d28a77aa07
parent 257153 9f4793501b9bbbe60a7d045e82b39f79b055f85a
child 257155 c6581fefb82263ffcbd7ace7a06c048b19d8194d
push id29207
push userryanvm@gmail.com
push dateTue, 11 Aug 2015 14:27:25 +0000
treeherdermozilla-central@d82b5a78686a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersevilpie, bz, dhylands, mt, froydnj, khuey, baku, smaug
bugs1158399
milestone43.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 1158399 - Expose the [[DateValue]] field in Date objects only through a ClippedTime class that enforces prior TimeClip-ing on the given value. r=evilpie, r=bz, r=dhylands, r=mt, r=froydnj, r=khuey, r=baku, r=smaug
dom/base/File.cpp
dom/bindings/Date.cpp
dom/bindings/Date.h
dom/devicestorage/nsDeviceStorage.cpp
dom/filehandle/MetadataHelper.cpp
dom/html/HTMLInputElement.cpp
dom/indexedDB/IDBObjectStore.cpp
dom/indexedDB/Key.cpp
dom/media/webrtc/RTCCertificate.h
dom/time/TimeManager.cpp
js/public/Date.h
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsdate.cpp
js/src/vm/DateObject.h
js/src/vm/SelfHosting.cpp
js/src/vm/StructuredClone.cpp
toolkit/components/startup/nsAppStartup.cpp
--- a/dom/base/File.cpp
+++ b/dom/base/File.cpp
@@ -521,17 +521,17 @@ File::GetPath(nsAString& aPath, ErrorRes
 Date
 File::GetLastModifiedDate(ErrorResult& aRv)
 {
   int64_t value = GetLastModified(aRv);
   if (aRv.Failed()) {
     return Date();
   }
 
-  return Date(value);
+  return Date(JS::TimeClip(value));
 }
 
 int64_t
 File::GetLastModified(ErrorResult& aRv)
 {
   return mImpl->GetLastModified(aRv);
 }
 
--- a/dom/bindings/Date.cpp
+++ b/dom/bindings/Date.cpp
@@ -1,49 +1,42 @@
 /* -*- 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 "mozilla/dom/Date.h"
 
-#include "jsapi.h" // for JS_ObjectIsDate, JS_NewDateObjectMsec
+#include "jsapi.h" // for JS_ObjectIsDate
 #include "jsfriendapi.h" // for DateGetMsecSinceEpoch
+#include "js/Date.h" // for JS::NewDateObject, JS::ClippedTime, JS::TimeClip
 #include "js/RootingAPI.h" // for Rooted, MutableHandle
 #include "js/Value.h" // for Value
 #include "mozilla/FloatingPoint.h" // for IsNaN, UnspecifiedNaN
 
 namespace mozilla {
 namespace dom {
 
-Date::Date()
-  : mMsecSinceEpoch(UnspecifiedNaN<double>())
-{
-}
-
-bool
-Date::IsUndefined() const
-{
-  return IsNaN(mMsecSinceEpoch);
-}
-
 bool
 Date::SetTimeStamp(JSContext* aCx, JSObject* aObject)
 {
   JS::Rooted<JSObject*> obj(aCx, aObject);
   MOZ_ASSERT(JS_ObjectIsDate(aCx, obj));
-  mMsecSinceEpoch = js::DateGetMsecSinceEpoch(aCx, obj);
+  double msecs = js::DateGetMsecSinceEpoch(aCx, obj);
+  JS::ClippedTime time = JS::TimeClip(msecs);
+  MOZ_ASSERT(NumbersAreIdentical(msecs, time.toDouble()));
+  mMsecSinceEpoch = time;
   return true;
 }
 
 bool
 Date::ToDateObject(JSContext* aCx, JS::MutableHandle<JS::Value> aRval) const
 {
-  JSObject* obj = JS_NewDateObjectMsec(aCx, mMsecSinceEpoch);
+  JSObject* obj = JS::NewDateObject(aCx, mMsecSinceEpoch);
   if (!obj) {
     return false;
   }
 
   aRval.setObject(*obj);
   return true;
 }
 
--- a/dom/bindings/Date.h
+++ b/dom/bindings/Date.h
@@ -4,45 +4,58 @@
  * 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/. */
 
 /* Representation for dates. */
 
 #ifndef mozilla_dom_Date_h
 #define mozilla_dom_Date_h
 
+#include "js/Date.h"
 #include "js/TypeDecls.h"
 
 namespace mozilla {
 namespace dom {
 
 class Date
 {
 public:
-  // Not inlining much here to avoid the includes we'd need.
-  Date();
-  explicit Date(double aMilliseconds)
+  Date() {}
+  explicit Date(JS::ClippedTime aMilliseconds)
     : mMsecSinceEpoch(aMilliseconds)
   {}
 
-  bool IsUndefined() const;
-  double TimeStamp() const
+  bool IsUndefined() const
+  {
+    return !mMsecSinceEpoch.isValid();
+  }
+
+  JS::ClippedTime TimeStamp() const
   {
     return mMsecSinceEpoch;
   }
-  void SetTimeStamp(double aMilliseconds)
+
+  // Returns an integer in the range [-8.64e15, +8.64e15] (-0 excluded), *or*
+  // returns NaN.  DO NOT ASSUME THIS IS FINITE!
+  double ToDouble() const
+  {
+    return mMsecSinceEpoch.toDouble();
+  }
+
+  void SetTimeStamp(JS::ClippedTime aMilliseconds)
   {
     mMsecSinceEpoch = aMilliseconds;
   }
+
   // Can return false if CheckedUnwrap fails.  This will NOT throw;
   // callers should do it as needed.
   bool SetTimeStamp(JSContext* aCx, JSObject* aObject);
 
   bool ToDateObject(JSContext* aCx, JS::MutableHandle<JS::Value> aRval) const;
 
 private:
-  double mMsecSinceEpoch;
+  JS::ClippedTime mMsecSinceEpoch;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Date_h
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -4314,17 +4314,17 @@ nsDOMDeviceStorage::EnumerateInternal(co
   nsCOMPtr<nsPIDOMWindow> win = GetOwner();
   if (!win) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   PRTime since = 0;
   if (aOptions.mSince.WasPassed() && !aOptions.mSince.Value().IsUndefined()) {
-    since = PRTime(aOptions.mSince.Value().TimeStamp());
+    since = PRTime(aOptions.mSince.Value().TimeStamp().toDouble());
   }
 
   nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
                                                           mStorageName,
                                                           aPath,
                                                           EmptyString());
   dsf->SetEditable(aEditable);
 
--- a/dom/filehandle/MetadataHelper.cpp
+++ b/dom/filehandle/MetadataHelper.cpp
@@ -5,16 +5,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MetadataHelper.h"
 
 #include "FileHandle.h"
 #include "js/Value.h"
 #include "js/RootingAPI.h"
 #include "jsapi.h"
+#include "js/Date.h"
 #include "mozilla/dom/FileModeBinding.h"
 #include "nsDebug.h"
 #include "nsIFileStreams.h"
 #include "nsIOutputStream.h"
 
 namespace mozilla {
 namespace dom {
 
@@ -44,17 +45,17 @@ MetadataHelper::GetSuccessResult(JSConte
 
     if (!JS_DefineProperty(aCx, obj, "size", val, JSPROP_ENUMERATE)) {
       return NS_ERROR_FAILURE;
     }
   }
 
   if (mParams->LastModifiedRequested()) {
     double msec = mParams->LastModified();
-    JSObject *date = JS_NewDateObjectMsec(aCx, msec);
+    JSObject *date = JS::NewDateObject(aCx, JS::TimeClip(msec));
     NS_ENSURE_TRUE(date, NS_ERROR_OUT_OF_MEMORY);
 
     JS::Rooted<JS::Value> dateRoot(aCx, JS::ObjectValue(*date));
     if (!JS_DefineProperty(aCx, obj, "lastModified", dateRoot,
                            JSPROP_ENUMERATE)) {
       return NS_ERROR_FAILURE;
     }
   }
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -1509,22 +1509,22 @@ HTMLInputElement::ConvertStringToNumber(
       }
     case NS_FORM_INPUT_DATE:
       {
         uint32_t year, month, day;
         if (!GetValueAsDate(aValue, &year, &month, &day)) {
           return false;
         }
 
-        double date = JS::MakeDate(year, month - 1, day);
-        if (IsNaN(date)) {
+        JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day));
+        if (!time.isValid()) {
           return false;
         }
 
-        aResultValue = Decimal::fromDouble(date);
+        aResultValue = Decimal::fromDouble(time.toDouble());
         return true;
       }
     case NS_FORM_INPUT_TIME:
       uint32_t milliseconds;
       if (!ParseTime(aValue, &milliseconds)) {
         return false;
       }
 
@@ -1757,28 +1757,33 @@ HTMLInputElement::GetValueAsDate(ErrorRe
     {
       uint32_t year, month, day;
       nsAutoString value;
       GetValueInternal(value);
       if (!GetValueAsDate(value, &year, &month, &day)) {
         return Nullable<Date>();
       }
 
-      return Nullable<Date>(Date(JS::MakeDate(year, month - 1, day)));
+      JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day));
+      return Nullable<Date>(Date(time));
     }
     case NS_FORM_INPUT_TIME:
     {
       uint32_t millisecond;
       nsAutoString value;
       GetValueInternal(value);
       if (!ParseTime(value, &millisecond)) {
         return Nullable<Date>();
       }
 
-      return Nullable<Date>(Date(millisecond));
+      JS::ClippedTime time = JS::TimeClip(millisecond);
+      MOZ_ASSERT(time.toDouble() == millisecond,
+                 "HTML times are restricted to the day after the epoch and "
+                 "never clip");
+      return Nullable<Date>(Date(time));
     }
   }
 
   MOZ_ASSERT(false, "Unrecognized input type");
   aRv.Throw(NS_ERROR_UNEXPECTED);
   return Nullable<Date>();
 }
 
@@ -1790,17 +1795,17 @@ HTMLInputElement::SetValueAsDate(Nullabl
     return;
   }
 
   if (aDate.IsNull() || aDate.Value().IsUndefined()) {
     aRv = SetValue(EmptyString());
     return;
   }
 
-  SetValue(Decimal::fromDouble(aDate.Value().TimeStamp()));
+  SetValue(Decimal::fromDouble(aDate.Value().TimeStamp().toDouble()));
 }
 
 NS_IMETHODIMP
 HTMLInputElement::GetValueAsNumber(double* aValueAsNumber)
 {
   *aValueAsNumber = ValueAsNumber();
   return NS_OK;
 }
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -15,16 +15,17 @@
 #include "IDBKeyRange.h"
 #include "IDBMutableFile.h"
 #include "IDBRequest.h"
 #include "IDBTransaction.h"
 #include "IndexedDatabase.h"
 #include "IndexedDatabaseInlines.h"
 #include "IndexedDatabaseManager.h"
 #include "js/Class.h"
+#include "js/Date.h"
 #include "js/StructuredClone.h"
 #include "KeyPath.h"
 #include "mozilla/Endian.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/Move.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ContentParent.h"
@@ -744,18 +745,18 @@ public:
     }
 
     JS::Rooted<JSString*> name(aCx,
       JS_NewUCStringCopyN(aCx, aData.name.get(), aData.name.Length()));
     if (NS_WARN_IF(!name)) {
       return false;
     }
 
-    JS::Rooted<JSObject*> date(aCx,
-      JS_NewDateObjectMsec(aCx, aData.lastModifiedDate));
+    JS::ClippedTime time = JS::TimeClip(aData.lastModifiedDate);
+    JS::Rooted<JSObject*> date(aCx, JS::NewDateObject(aCx, time));
     if (NS_WARN_IF(!date)) {
       return false;
     }
 
     if (NS_WARN_IF(!JS_DefineProperty(aCx, obj, "name", name, 0))) {
       return false;
     }
 
--- a/dom/indexedDB/Key.cpp
+++ b/dom/indexedDB/Key.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 "Key.h"
 
 #include <algorithm>
+#include "js/Date.h"
 #include "js/Value.h"
 #include "jsfriendapi.h"
 #include "mozilla/Endian.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozIStorageStatement.h"
 #include "mozIStorageValueArray.h"
 #include "nsAlgorithm.h"
 #include "nsJSUtils.h"
@@ -232,17 +233,21 @@ Key::DecodeJSValInternal(const unsigned 
     DecodeString(aPos, aEnd, key);
     if (!xpc::StringToJsval(aCx, key, aVal)) {
       IDB_REPORT_INTERNAL_ERR();
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
   }
   else if (*aPos - aTypeOffset == eDate) {
     double msec = static_cast<double>(DecodeNumber(aPos, aEnd));
-    JSObject* date = JS_NewDateObjectMsec(aCx, msec);
+    JS::ClippedTime time = JS::TimeClip(msec);
+    MOZ_ASSERT(msec == time.toDouble(),
+               "encoding from a Date object not containing an invalid date "
+               "means we should always have clipped values");
+    JSObject* date = JS::NewDateObject(aCx, time);
     if (!date) {
       IDB_WARNING("Failed to make date!");
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
 
     aVal.setObject(*date);
   }
   else if (*aPos - aTypeOffset == eFloat) {
--- a/dom/media/webrtc/RTCCertificate.h
+++ b/dom/media/webrtc/RTCCertificate.h
@@ -16,16 +16,17 @@
 #include "ScopedNSSTypes.h"
 
 #include "mozilla/ErrorResult.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/dom/Date.h"
 #include "mozilla/dom/CryptoKey.h"
 #include "mtransport/dtlsidentity.h"
+#include "js/Date.h"
 #include "js/StructuredClone.h"
 #include "js/TypeDecls.h"
 
 namespace mozilla {
 namespace dom {
 
 class ObjectOrString;
 
@@ -49,17 +50,20 @@ public:
                  PRTime aExpires);
 
   nsIGlobalObject* GetParentObject() const { return mGlobal; }
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
   // WebIDL expires attribute.  Note: JS dates are milliseconds since epoch;
   // NSPR PRTime is in microseconds since the same epoch.
-  int64_t Expires() const { return mExpires / PR_USEC_PER_MSEC; }
+  JS::ClippedTime Expires() const
+  {
+    return JS::TimeClip(mExpires / PR_USEC_PER_MSEC);
+  }
 
   // Accessors for use by PeerConnectionImpl.
   RefPtr<DtlsIdentity> CreateDtlsIdentity() const;
   CERTCertificate* Certificate() const { return mCertificate; }
 
   // For nsNSSShutDownObject
   virtual void virtualDestroyNSSReference() override;
   void destructorSafeDestroyNSSReference();
--- a/dom/time/TimeManager.cpp
+++ b/dom/time/TimeManager.cpp
@@ -29,17 +29,17 @@ JSObject*
 TimeManager::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return MozTimeManagerBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
 TimeManager::Set(Date& aDate)
 {
-  Set(aDate.TimeStamp());
+  Set(aDate.ToDouble());
 }
 
 void
 TimeManager::Set(double aTime)
 {
   nsCOMPtr<nsITimeService> timeService = do_GetService(TIMESERVICE_CONTRACTID);
   if (timeService) {
     timeService->Set(aTime);
--- a/js/public/Date.h
+++ b/js/public/Date.h
@@ -1,58 +1,125 @@
 /* -*- 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/. */
 
+/* JavaScript date/time computation and creation functions. */
+
 #ifndef js_Date_h
 #define js_Date_h
 
+/*
+ * Dates in JavaScript are defined by IEEE-754 double precision numbers from
+ * the set:
+ *
+ *   { t ∈ ℕ : -8.64e15 ≤ t ≤ +8.64e15 } ∪ { NaN }
+ *
+ * The single NaN value represents any invalid-date value.  All other values
+ * represent idealized durations in milliseconds since the UTC epoch.  (Leap
+ * seconds are ignored; leap days are not.)  +0 is the only zero in this set.
+ * The limit represented by 8.64e15 milliseconds is 100 million days either
+ * side of 00:00 January 1, 1970 UTC.
+ *
+ * Dates in the above set are represented by the |ClippedTime| class.  The
+ * double type is a superset of the above set, so it *may* (but need not)
+ * represent a date.  Use ECMAScript's |TimeClip| method to produce a date from
+ * a double.
+ *
+ * Date *objects* are simply wrappers around |TimeClip|'d numbers, with a bunch
+ * of accessor methods to the various aspects of the represented date.
+ */
+
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/MathAlgorithms.h"
 
-#include "jstypes.h"
-
 #include "js/Conversions.h"
 #include "js/Value.h"
 
+struct JSContext;
+
 namespace JS {
 
+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|
+ * method.
+ *
+ * In typical use, the user might wish to manipulate a timestamp.  The user
+ * performs a series of operations on it, but the final value might not be a
+ * date as defined above -- it could have overflowed, acquired a fractional
+ * component, &c.  So as a *final* step, the user passes that value through
+ * |TimeClip| to produce a number restricted to JavaScript's date range.
+ *
+ * APIs that accept a JavaScript date value thus accept a |ClippedTime|, not a
+ * double.  This ensures that date/time APIs will only ever receive acceptable
+ * JavaScript dates.  This also forces users to perform any desired clipping,
+ * as only the user knows what behavior is desired when clipping occurs.
+ */
 class ClippedTime
 {
     double t;
 
-    /* ES5 15.9.1.14. */
-    double timeClip(double time) {
-        /* Steps 1-2. */
-        const double MaxTimeMagnitude = 8.64e15;
-        if (!mozilla::IsFinite(time) || mozilla::Abs(time) > MaxTimeMagnitude)
-            return JS::GenericNaN();
-
-        /* Step 3. */
-        return JS::ToInteger(time) + (+0.0);
-    }
+    explicit ClippedTime(double time) : t(time) {}
+    friend ClippedTime TimeClip(double time);
 
   public:
-    ClippedTime() : t(JS::GenericNaN()) {}
-    explicit ClippedTime(double time) : t(timeClip(time)) {}
+    // Create an invalid date.
+    ClippedTime() : t(mozilla::UnspecifiedNaN<double>()) {}
 
-    static ClippedTime NaN() { return ClippedTime(); }
+    // Create an invalid date/time, more explicitly; prefer this to the default
+    // constructor.
+    static ClippedTime invalid() { return ClippedTime(); }
 
-    double value() const { return t; }
+    double toDouble() const { return t; }
+
+    bool isValid() const { return !mozilla::IsNaN(t); }
 };
 
+// ES6 20.3.1.15.
+//
+// Clip a double to JavaScript's date range (or to an invalid date) using the
+// ECMAScript TimeClip algorithm.
 inline ClippedTime
-TimeClip(double d)
+TimeClip(double time)
 {
-    return ClippedTime(d);
+    // Steps 1-2.
+    const double MaxTimeMagnitude = 8.64e15;
+    if (!mozilla::IsFinite(time) || mozilla::Abs(time) > MaxTimeMagnitude)
+        return ClippedTime(mozilla::UnspecifiedNaN<double>());
+
+    // Step 3.
+    return ClippedTime(ToInteger(time) + (+0.0));
 }
 
-// Year is a year, month is 0-11, day is 1-based.  The return value is
-// a number of milliseconds since the epoch.  Can return NaN.
+// Produce a double Value from the given time.  Because times may be NaN,
+// prefer using this to manual canonicalization.
+inline Value
+TimeValue(ClippedTime time)
+{
+    return DoubleValue(JS::CanonicalizeNaN(time.toDouble()));
+}
+
+// Create a new Date object whose [[DateValue]] internal slot contains the
+// clipped |time|.  (Users who must represent times outside that range must use
+// another representation.)
+extern JS_PUBLIC_API(JSObject*)
+NewDateObject(JSContext* cx, ClippedTime time);
+
+// Year is a year, month is 0-11, day is 1-based.  The return value is a number
+// of milliseconds since the epoch.
+//
+// Consistent with the MakeDate algorithm defined in ECMAScript, this value is
+// *not* clipped!  Use JS::TimeClip if you need a clipped date.
 JS_PUBLIC_API(double)
 MakeDate(double year, unsigned month, unsigned day);
 
 // Takes an integer number of milliseconds since the epoch and returns the
 // year.  Can return NaN, and will do so if NaN is passed in.
 JS_PUBLIC_API(double)
 YearFromTime(double time);
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -5495,21 +5495,21 @@ JS_PUBLIC_API(JSObject*)
 JS_NewDateObject(JSContext* cx, int year, int mon, int mday, int hour, int min, int sec)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     return NewDateObject(cx, year, mon, mday, hour, min, sec);
 }
 
 JS_PUBLIC_API(JSObject*)
-JS_NewDateObjectMsec(JSContext* cx, double msec)
+JS::NewDateObject(JSContext* cx, JS::ClippedTime time)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
-    return NewDateObjectMsec(cx, JS::TimeClip(msec));
+    return NewDateObjectMsec(cx, time);
 }
 
 JS_PUBLIC_API(bool)
 JS_ObjectIsDate(JSContext* cx, HandleObject obj)
 {
     assertSameCompartment(cx, obj);
     return ObjectClassIs(obj, ESClass_Date, cx);
 }
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4756,19 +4756,16 @@ SetForEach(JSContext *cx, HandleObject o
 
 /*
  * Dates.
  */
 
 extern JS_PUBLIC_API(JSObject*)
 JS_NewDateObject(JSContext* cx, int year, int mon, int mday, int hour, int min, int sec);
 
-extern JS_PUBLIC_API(JSObject*)
-JS_NewDateObjectMsec(JSContext* cx, double msec);
-
 /*
  * Infallible predicate to test whether obj is a date object.
  */
 extern JS_PUBLIC_API(bool)
 JS_ObjectIsDate(JSContext* cx, JS::HandleObject obj);
 
 /*
  * Clears the cache of calculated local time from each Date object.
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -348,17 +348,17 @@ MakeDate(double day, double time)
 
     /* Step 2. */
     return day * msPerDay + time;
 }
 
 JS_PUBLIC_API(double)
 JS::MakeDate(double year, unsigned month, unsigned day)
 {
-    return TimeClip(::MakeDate(MakeDay(year, month, day), 0)).value();
+    return ::MakeDate(MakeDay(year, month, day), 0);
 }
 
 JS_PUBLIC_API(double)
 JS::YearFromTime(double time)
 {
     return ::YearFromTime(time);
 }
 
@@ -635,17 +635,17 @@ date_UTC(JSContext* cx, unsigned argc, V
     if (!IsNaN(y)) {
         double yint = ToInteger(y);
         if (0 <= yint && yint <= 99)
             yr = 1900 + yint;
     }
 
     // Step 16.
     ClippedTime time = TimeClip(MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli)));
-    args.rval().setDouble(time.value());
+    args.rval().set(TimeValue(time));
     return true;
 }
 
 /*
  * Read and convert decimal digits from s[*i] into *result
  * while *i < limit.
  *
  * Succeed if any digits are converted. Advance *i only
@@ -873,17 +873,17 @@ ParseISODate(const CharT* s, size_t leng
                            MakeTime(hour, min, sec, frac * 1000.0));
 
     if (isLocalTime)
         msec = UTC(msec, dtInfo);
     else
         msec -= tzMul * (tzHour * msPerHour + tzMin * msPerMinute);
 
     *result = TimeClip(msec);
-    return NumbersAreIdentical(msec, result->value());
+    return NumbersAreIdentical(msec, result->toDouble());
 
 #undef PEEK
 #undef NEED
 #undef DONE_UNLESS
 #undef NEED_NDIGITS
 }
 
 template <typename CharT>
@@ -1188,48 +1188,48 @@ date_parse(JSContext* cx, unsigned argc,
         return false;
 
     ClippedTime result;
     if (!ParseDate(linearStr, &result, &cx->runtime()->dateTimeInfo)) {
         args.rval().setNaN();
         return true;
     }
 
-    args.rval().setDouble(result.value());
+    args.rval().set(TimeValue(result));
     return true;
 }
 
 static ClippedTime
 NowAsMillis()
 {
-    return ClippedTime(static_cast<double>(PRMJ_Now()) / PRMJ_USEC_PER_MSEC);
+    return TimeClip(static_cast<double>(PRMJ_Now()) / PRMJ_USEC_PER_MSEC);
 }
 
 bool
 js::date_now(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    args.rval().setDouble(NowAsMillis().value());
+    args.rval().set(TimeValue(NowAsMillis()));
     return true;
 }
 
 void
 DateObject::setUTCTime(ClippedTime t)
 {
     for (size_t ind = COMPONENTS_START_SLOT; ind < RESERVED_SLOTS; ind++)
         setReservedSlot(ind, UndefinedValue());
 
-    setFixedSlot(UTC_TIME_SLOT, DoubleValue(t.value()));
+    setFixedSlot(UTC_TIME_SLOT, TimeValue(t));
 }
 
 void
 DateObject::setUTCTime(ClippedTime t, MutableHandleValue vp)
 {
     setUTCTime(t);
-    vp.setDouble(t.value());
+    vp.set(TimeValue(t));
 }
 
 void
 DateObject::fillLocalTimeSlots(DateTimeInfo* dtInfo)
 {
     /* Check if the cache is already populated. */
     if (!getReservedSlot(LOCAL_TIME_SLOT).isUndefined() &&
         getReservedSlot(TZA_SLOT).toDouble() == dtInfo->localTZA())
@@ -1682,17 +1682,17 @@ date_getTimezoneOffset(JSContext* cx, un
     return CallNonGenericMethod<IsDate, DateObject::getTimezoneOffset_impl>(cx, args);
 }
 
 MOZ_ALWAYS_INLINE bool
 date_setTime_impl(JSContext* cx, CallArgs args)
 {
     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
     if (args.length() == 0) {
-        dateObj->setUTCTime(ClippedTime::NaN(), args.rval());
+        dateObj->setUTCTime(ClippedTime::invalid(), args.rval());
         return true;
     }
 
     double result;
     if (!ToNumber(cx, args[0], &result))
         return false;
 
     dateObj->setUTCTime(TimeClip(result), args.rval());
@@ -2317,17 +2317,17 @@ date_setYear_impl(JSContext* cx, CallArg
 
     /* Step 2. */
     double y;
     if (!ToNumber(cx, args.get(0), &y))
         return false;
 
     /* Step 3. */
     if (IsNaN(y)) {
-        dateObj->setUTCTime(ClippedTime::NaN(), args.rval());
+        dateObj->setUTCTime(ClippedTime::invalid(), args.rval());
         return true;
     }
 
     /* Step 4. */
     double yint = ToInteger(y);
     if (0 <= yint && yint <= 99)
         yint += 1900;
 
@@ -2361,45 +2361,45 @@ static const char * const months[] =
 };
 
 
 // Avoid dependence on PRMJ_FormatTimeUSEnglish, because it
 // requires a PRMJTime... which only has 16-bit years.  Sub-ECMA.
 static void
 print_gmt_string(char* buf, size_t size, double utctime)
 {
-    MOZ_ASSERT(NumbersAreIdentical(TimeClip(utctime).value(), utctime));
+    MOZ_ASSERT(NumbersAreIdentical(TimeClip(utctime).toDouble(), utctime));
     JS_snprintf(buf, size, "%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
                 days[int(WeekDay(utctime))],
                 int(DateFromTime(utctime)),
                 months[int(MonthFromTime(utctime))],
                 int(YearFromTime(utctime)),
                 int(HourFromTime(utctime)),
                 int(MinFromTime(utctime)),
                 int(SecFromTime(utctime)));
 }
 
 static void
 print_iso_string(char* buf, size_t size, double utctime)
 {
-    MOZ_ASSERT(NumbersAreIdentical(TimeClip(utctime).value(), utctime));
+    MOZ_ASSERT(NumbersAreIdentical(TimeClip(utctime).toDouble(), utctime));
     JS_snprintf(buf, size, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ",
                 int(YearFromTime(utctime)),
                 int(MonthFromTime(utctime)) + 1,
                 int(DateFromTime(utctime)),
                 int(HourFromTime(utctime)),
                 int(MinFromTime(utctime)),
                 int(SecFromTime(utctime)),
                 int(msFromTime(utctime)));
 }
 
 static void
 print_iso_extended_string(char* buf, size_t size, double utctime)
 {
-    MOZ_ASSERT(NumbersAreIdentical(TimeClip(utctime).value(), utctime));
+    MOZ_ASSERT(NumbersAreIdentical(TimeClip(utctime).toDouble(), utctime));
     JS_snprintf(buf, size, "%+.6d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ",
                 int(YearFromTime(utctime)),
                 int(MonthFromTime(utctime)) + 1,
                 int(DateFromTime(utctime)),
                 int(HourFromTime(utctime)),
                 int(MinFromTime(utctime)),
                 int(SecFromTime(utctime)),
                 int(msFromTime(utctime)));
@@ -2545,17 +2545,17 @@ date_format(JSContext* cx, double date, 
     char tzbuf[100];
     bool usetz;
     size_t i, tzlen;
     PRMJTime split;
 
     if (!IsFinite(date)) {
         JS_snprintf(buf, sizeof buf, js_NaN_date_str);
     } else {
-        MOZ_ASSERT(NumbersAreIdentical(TimeClip(date).value(), date));
+        MOZ_ASSERT(NumbersAreIdentical(TimeClip(date).toDouble(), date));
 
         double local = LocalTime(date, &cx->runtime()->dateTimeInfo);
 
         /* offset from GMT in minutes.  The offset includes daylight savings,
            if it applies. */
         int minutes = (int) floor(AdjustTime(date, &cx->runtime()->dateTimeInfo) / msPerMinute);
 
         /* map 510 minutes to 0830 hours */
@@ -2985,17 +2985,17 @@ NewDateObject(JSContext* cx, const CallA
 
     args.rval().setObject(*obj);
     return true;
 }
 
 static bool
 ToDateString(JSContext* cx, const CallArgs& args, ClippedTime t)
 {
-    return date_format(cx, t.value(), FORMATSPEC_FULL, args.rval());
+    return date_format(cx, t.toDouble(), FORMATSPEC_FULL, args.rval());
 }
 
 static bool
 DateNoArguments(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(args.length() == 0);
 
     ClippedTime now = NowAsMillis();
@@ -3018,17 +3018,17 @@ DateOneArgument(JSContext* cx, const Cal
             return false;
 
         if (args[0].isString()) {
             JSLinearString* linearStr = args[0].toString()->ensureLinear(cx);
             if (!linearStr)
                 return false;
 
             if (!ParseDate(linearStr, &t, &cx->runtime()->dateTimeInfo))
-                t = ClippedTime::NaN();
+                t = ClippedTime::invalid();
         } else {
             double d;
             if (!ToNumber(cx, args[0], &d))
                 return false;
             t = TimeClip(d);
         }
 
         return NewDateObject(cx, args, t);
--- a/js/src/vm/DateObject.h
+++ b/js/src/vm/DateObject.h
@@ -38,17 +38,24 @@ class DateObject : public NativeObject
     static const uint32_t LOCAL_SECONDS_SLOT = COMPONENTS_START_SLOT + 7;
 
     static const uint32_t RESERVED_SLOTS = LOCAL_SECONDS_SLOT + 1;
 
   public:
     static const Class class_;
     static const Class protoClass_;
 
-    inline const js::Value& UTCTime() const {
+    JS::ClippedTime clippedTime() const {
+        double t = getFixedSlot(UTC_TIME_SLOT).toDouble();
+        JS::ClippedTime clipped = JS::TimeClip(t);
+        MOZ_ASSERT(mozilla::NumbersAreIdentical(clipped.toDouble(), t));
+        return clipped;
+    }
+
+    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);
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -23,16 +23,17 @@
 #include "builtin/MapObject.h"
 #include "builtin/Object.h"
 #include "builtin/Reflect.h"
 #include "builtin/SelfHostingDefines.h"
 #include "builtin/SIMD.h"
 #include "builtin/TypedObject.h"
 #include "builtin/WeakSetObject.h"
 #include "gc/Marking.h"
+#include "js/Date.h"
 #include "vm/Compression.h"
 #include "vm/GeneratorObject.h"
 #include "vm/Interpreter.h"
 #include "vm/String.h"
 #include "vm/TypedArrayCommon.h"
 
 #include "jsfuninlines.h"
 #include "jsscriptinlines.h"
@@ -1772,17 +1773,17 @@ CloneObject(JSContext* cx, HandleNativeO
         if (clone && hasName)
             clone->as<JSFunction>().setExtendedSlot(0, StringValue(selfHostedFunction->atom()));
     } else if (selfHostedObject->is<RegExpObject>()) {
         RegExpObject& reobj = selfHostedObject->as<RegExpObject>();
         RootedAtom source(cx, reobj.getSource());
         MOZ_ASSERT(source->isPermanentAtom());
         clone = RegExpObject::createNoStatics(cx, source, reobj.getFlags(), nullptr, cx->tempLifoAlloc());
     } else if (selfHostedObject->is<DateObject>()) {
-        clone = JS_NewDateObjectMsec(cx, selfHostedObject->as<DateObject>().UTCTime().toNumber());
+        clone = JS::NewDateObject(cx, selfHostedObject->as<DateObject>().clippedTime());
     } else if (selfHostedObject->is<BooleanObject>()) {
         clone = BooleanObject::create(cx, selfHostedObject->as<BooleanObject>().unbox());
     } else if (selfHostedObject->is<NumberObject>()) {
         clone = NumberObject::create(cx, selfHostedObject->as<NumberObject>().unbox());
     } else if (selfHostedObject->is<StringObject>()) {
         JSString* selfHostedString = selfHostedObject->as<StringObject>().unbox();
         if (!selfHostedString->isFlat())
             MOZ_CRASH();
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -1587,17 +1587,17 @@ JSStructuredCloneReader::startRead(Mutab
         break;
       }
 
       case SCTAG_DATE_OBJECT: {
         double d;
         if (!in.readDouble(&d) || !checkDouble(d))
             return false;
         JS::ClippedTime t = JS::TimeClip(d);
-        if (!NumbersAreIdentical(d, t.value())) {
+        if (!NumbersAreIdentical(d, t.toDouble())) {
             JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
                                  JSMSG_SC_BAD_SERIALIZED_DATA, "date");
             return false;
         }
         JSObject* obj = NewDateObjectMsec(context(), t);
         if (!obj)
             return false;
         vp.setObject(*obj);
--- a/toolkit/components/startup/nsAppStartup.cpp
+++ b/toolkit/components/startup/nsAppStartup.cpp
@@ -30,16 +30,17 @@
 #include "prprf.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsWidgetsCID.h"
 #include "nsAppShellCID.h"
 #include "nsXPCOMCIDInternal.h"
 #include "mozilla/Services.h"
 #include "nsIXPConnect.h"
 #include "jsapi.h"
+#include "js/Date.h"
 #include "prenv.h"
 #include "nsAppDirectoryServiceDefs.h"
 
 #if defined(XP_WIN)
 // Prevent collisions with nsAppStartup::GetStartupInfo()
 #undef GetStartupInfo
 #endif
 
@@ -775,17 +776,17 @@ nsAppStartup::GetStartupInfo(JSContext* 
       Telemetry::Accumulate(Telemetry::STARTUP_MEASUREMENT_ERRORS,
         StartupTimeline::MAIN);
     }
 
     if (!stamp.IsNull()) {
       if (stamp >= procTime) {
         PRTime prStamp = ComputeAbsoluteTimestamp(absNow, now, stamp)
           / PR_USEC_PER_MSEC;
-        JS::Rooted<JSObject*> date(aCx, JS_NewDateObjectMsec(aCx, prStamp));
+        JS::Rooted<JSObject*> date(aCx, JS::NewDateObject(aCx, JS::TimeClip(prStamp)));
         JS_DefineProperty(aCx, obj, StartupTimeline::Describe(ev), date, JSPROP_ENUMERATE);
       } else {
         Telemetry::Accumulate(Telemetry::STARTUP_MEASUREMENT_ERRORS, ev);
       }
     }
   }
 
   return NS_OK;