1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 /* JavaScript date/time computation and creation functions. */
7 
8 #ifndef js_Date_h
9 #define js_Date_h
10 
11 /*
12  * Dates in JavaScript are defined by IEEE-754 double precision numbers from
13  * the set:
14  *
15  *   { t ∈ ℕ : -8.64e15 ≤ t ≤ +8.64e15 } ∪ { NaN }
16  *
17  * The single NaN value represents any invalid-date value.  All other values
18  * represent idealized durations in milliseconds since the UTC epoch.  (Leap
19  * seconds are ignored; leap days are not.)  +0 is the only zero in this set.
20  * The limit represented by 8.64e15 milliseconds is 100 million days either
21  * side of 00:00 January 1, 1970 UTC.
22  *
23  * Dates in the above set are represented by the |ClippedTime| class.  The
24  * double type is a superset of the above set, so it *may* (but need not)
25  * represent a date.  Use ECMAScript's |TimeClip| method to produce a date from
26  * a double.
27  *
28  * Date *objects* are simply wrappers around |TimeClip|'d numbers, with a bunch
29  * of accessor methods to the various aspects of the represented date.
30  */
31 
32 #include "mozilla/FloatingPoint.h"  // mozilla::{IsFinite,IsNaN}, mozilla::UnspecifiedNaN
33 #include "mozilla/MathAlgorithms.h"  // mozilla::Abs
34 
35 #include "js/Conversions.h"  // JS::ToInteger
36 #include "js/RootingAPI.h"   // JS::Handle
37 #include "js/Value.h"        // JS::CanonicalizeNaN, JS::DoubleValue, JS::Value
38 
39 struct JS_PUBLIC_API JSContext;
40 class JS_PUBLIC_API JSObject;
41 
42 namespace JS {
43 
44 /**
45  * Re-query the system to determine the current time zone adjustment from UTC,
46  * including any component due to DST.  If the time zone has changed, this will
47  * cause all Date object non-UTC methods and formatting functions to produce
48  * appropriately adjusted results.
49  *
50  * Left to its own devices, SpiderMonkey itself may occasionally try to detect
51  * system time changes.  However, no particular frequency of checking is
52  * guaranteed.  Embedders unable to accept occasional inaccuracies should call
53  * this method in response to system time changes, or immediately before
54  * operations requiring instantaneous correctness, to guarantee correct
55  * behavior.
56  */
57 extern JS_PUBLIC_API void ResetTimeZone();
58 
59 class ClippedTime;
60 inline ClippedTime TimeClip(double time);
61 
62 /*
63  * |ClippedTime| represents the limited subset of dates/times described above.
64  *
65  * An invalid date/time may be created through the |ClippedTime::invalid|
66  * method.  Otherwise, a |ClippedTime| may be created using the |TimeClip|
67  * method.
68  *
69  * In typical use, the user might wish to manipulate a timestamp.  The user
70  * performs a series of operations on it, but the final value might not be a
71  * date as defined above -- it could have overflowed, acquired a fractional
72  * component, &c.  So as a *final* step, the user passes that value through
73  * |TimeClip| to produce a number restricted to JavaScript's date range.
74  *
75  * APIs that accept a JavaScript date value thus accept a |ClippedTime|, not a
76  * double.  This ensures that date/time APIs will only ever receive acceptable
77  * JavaScript dates.  This also forces users to perform any desired clipping,
78  * as only the user knows what behavior is desired when clipping occurs.
79  */
80 class ClippedTime {
81   double t = mozilla::UnspecifiedNaN<double>();
82 
ClippedTime(double time)83   explicit ClippedTime(double time) : t(time) {}
84   friend ClippedTime TimeClip(double time);
85 
86  public:
87   // Create an invalid date.
88   ClippedTime() = default;
89 
90   // Create an invalid date/time, more explicitly; prefer this to the default
91   // constructor.
invalid()92   static ClippedTime invalid() { return ClippedTime(); }
93 
toDouble()94   double toDouble() const { return t; }
95 
isValid()96   bool isValid() const { return !mozilla::IsNaN(t); }
97 };
98 
99 // ES6 20.3.1.15.
100 //
101 // Clip a double to JavaScript's date range (or to an invalid date) using the
102 // ECMAScript TimeClip algorithm.
TimeClip(double time)103 inline ClippedTime TimeClip(double time) {
104   // Steps 1-2.
105   const double MaxTimeMagnitude = 8.64e15;
106   if (!mozilla::IsFinite(time) || mozilla::Abs(time) > MaxTimeMagnitude) {
107     return ClippedTime(mozilla::UnspecifiedNaN<double>());
108   }
109 
110   // Step 3.
111   return ClippedTime(ToInteger(time));
112 }
113 
114 // Produce a double Value from the given time.  Because times may be NaN,
115 // prefer using this to manual canonicalization.
TimeValue(ClippedTime time)116 inline Value TimeValue(ClippedTime time) {
117   return CanonicalizedDoubleValue(time.toDouble());
118 }
119 
120 // Create a new Date object whose [[DateValue]] internal slot contains the
121 // clipped |time|.  (Users who must represent times outside that range must use
122 // another representation.)
123 extern JS_PUBLIC_API JSObject* NewDateObject(JSContext* cx, ClippedTime time);
124 
125 /**
126  * Create a new Date object for a year/month/day-of-month/hour/minute/second.
127  *
128  * The created date is initialized with the time value
129  *
130  *   TimeClip(UTC(MakeDate(MakeDay(year, mon, mday),
131  *                MakeTime(hour, min, sec, 0.0))))
132  *
133  * where each function/operation is as specified in ECMAScript.
134  */
135 extern JS_PUBLIC_API JSObject* NewDateObject(JSContext* cx, int year, int mon,
136                                              int mday, int hour, int min,
137                                              int sec);
138 
139 /**
140  * On success, returns true, setting |*isDate| to true if |obj| is a Date
141  * object or a wrapper around one, or to false if not.  Returns false on
142  * failure.
143  *
144  * This method returns true with |*isDate == false| when passed an ES6 proxy
145  * whose target is a Date, or when passed a revoked proxy.
146  */
147 extern JS_PUBLIC_API bool ObjectIsDate(JSContext* cx, Handle<JSObject*> obj,
148                                        bool* isDate);
149 
150 // Year is a year, month is 0-11, day is 1-based.  The return value is a number
151 // of milliseconds since the epoch.
152 //
153 // Consistent with the MakeDate algorithm defined in ECMAScript, this value is
154 // *not* clipped!  Use JS::TimeClip if you need a clipped date.
155 JS_PUBLIC_API double MakeDate(double year, unsigned month, unsigned day);
156 
157 // Year is a year, month is 0-11, day is 1-based, and time is in milliseconds.
158 // The return value is a number of milliseconds since the epoch.
159 //
160 // Consistent with the MakeDate algorithm defined in ECMAScript, this value is
161 // *not* clipped!  Use JS::TimeClip if you need a clipped date.
162 JS_PUBLIC_API double MakeDate(double year, unsigned month, unsigned day,
163                               double time);
164 
165 // Takes an integer number of milliseconds since the epoch and returns the
166 // year.  Can return NaN, and will do so if NaN is passed in.
167 JS_PUBLIC_API double YearFromTime(double time);
168 
169 // Takes an integer number of milliseconds since the epoch and returns the
170 // month (0-11).  Can return NaN, and will do so if NaN is passed in.
171 JS_PUBLIC_API double MonthFromTime(double time);
172 
173 // Takes an integer number of milliseconds since the epoch and returns the
174 // day (1-based).  Can return NaN, and will do so if NaN is passed in.
175 JS_PUBLIC_API double DayFromTime(double time);
176 
177 // Takes an integer year and returns the number of days from epoch to the given
178 // year.
179 // NOTE: The calculation performed by this function is literally that given in
180 // the ECMAScript specification.  Nonfinite years, years containing fractional
181 // components, and years outside ECMAScript's date range are not handled with
182 // any particular intelligence.  Garbage in, garbage out.
183 JS_PUBLIC_API double DayFromYear(double year);
184 
185 // Takes an integer number of milliseconds since the epoch and an integer year,
186 // returns the number of days in that year. If |time| is nonfinite, returns NaN.
187 // Otherwise |time| *must* correspond to a time within the valid year |year|.
188 // This should usually be ensured by computing |year| as
189 // |JS::DayFromYear(time)|.
190 JS_PUBLIC_API double DayWithinYear(double time, double year);
191 
192 // The callback will be a wrapper function that accepts a single double (the
193 // time to clamp and jitter.) Inside the JS Engine, other parameters that may be
194 // needed are all constant, so they are handled inside the wrapper function
195 using ReduceMicrosecondTimePrecisionCallback = double (*)(double, JSContext*);
196 
197 // Set a callback into the toolkit/components/resistfingerprinting function that
198 // will centralize time resolution and jitter into one place.
199 JS_PUBLIC_API void SetReduceMicrosecondTimePrecisionCallback(
200     ReduceMicrosecondTimePrecisionCallback callback);
201 
202 // Sets the time resolution for fingerprinting protection, and whether jitter
203 // should occur. If resolution is set to zero, then no rounding or jitter will
204 // occur. This is used if the callback above is not specified.
205 JS_PUBLIC_API void SetTimeResolutionUsec(uint32_t resolution, bool jitter);
206 
207 }  // namespace JS
208 
209 #endif /* js_Date_h */
210