summaryrefslogtreecommitdiffstats
path: root/js/public/Date.h
blob: 45c6a88602e078cb872d49a2f50b30a994f76d8e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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"  // mozilla::{IsFinite,}, mozilla::UnspecifiedNaN
#include "mozilla/MathAlgorithms.h"  // mozilla::Abs

#include "js/Conversions.h"  // JS::ToInteger
#include "js/TypeDecls.h"
#include "js/Value.h"  // JS::CanonicalizeNaN, JS::DoubleValue, JS::Value

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 try to detect
 * 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|
 * 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 = mozilla::UnspecifiedNaN<double>();

  explicit ClippedTime(double time) : t(time) {}
  friend ClippedTime TimeClip(double time);

 public:
  // Create an invalid date.
  ClippedTime() = default;

  // Create an invalid date/time, more explicitly; prefer this to the default
  // constructor.
  static ClippedTime invalid() { return ClippedTime(); }

  double toDouble() const { return t; }

  bool isValid() const { return !std::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 time) {
  // Steps 1-2.
  const double MaxTimeMagnitude = 8.64e15;
  if (!std::isfinite(time) || mozilla::Abs(time) > MaxTimeMagnitude) {
    return ClippedTime(mozilla::UnspecifiedNaN<double>());
  }

  // Step 3.
  return ClippedTime(ToInteger(time));
}

// 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 CanonicalizedDoubleValue(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);

/**
 * Create a new Date object for a year/month/day-of-month/hour/minute/second.
 *
 * The created date is initialized with the time value
 *
 *   TimeClip(UTC(MakeDate(MakeDay(year, mon, mday),
 *                MakeTime(hour, min, sec, 0.0))))
 *
 * where each function/operation is as specified in ECMAScript.
 */
extern JS_PUBLIC_API JSObject* NewDateObject(JSContext* cx, int year, int mon,
                                             int mday, int hour, int min,
                                             int sec);

/**
 * On success, returns true, setting |*isDate| to true if |obj| is a Date
 * object or a wrapper around one, or to false if not.  Returns false on
 * failure.
 *
 * This method returns true with |*isDate == false| when passed an ES6 proxy
 * whose target is a Date, or when passed a revoked proxy.
 */
extern JS_PUBLIC_API bool ObjectIsDate(JSContext* cx, Handle<JSObject*> obj,
                                       bool* isDate);

// 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);

// Year is a year, month is 0-11, day is 1-based, and time is in milliseconds.
// 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,
                              double time);

// 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);

// Takes an integer number of milliseconds since the epoch and returns the
// month (0-11).  Can return NaN, and will do so if NaN is passed in.
JS_PUBLIC_API double MonthFromTime(double time);

// Takes an integer number of milliseconds since the epoch and returns the
// day (1-based).  Can return NaN, and will do so if NaN is passed in.
JS_PUBLIC_API double DayFromTime(double time);

// Takes an integer year and returns the number of days from epoch to the given
// year.
// NOTE: The calculation performed by this function is literally that given in
// the ECMAScript specification.  Nonfinite years, years containing fractional
// components, and years outside ECMAScript's date range are not handled with
// any particular intelligence.  Garbage in, garbage out.
JS_PUBLIC_API double DayFromYear(double year);

// Takes an integer number of milliseconds since the epoch and an integer year,
// returns the number of days in that year. If |time| is nonfinite, returns NaN.
// Otherwise |time| *must* correspond to a time within the valid year |year|.
// This should usually be ensured by computing |year| as
// |JS::DayFromYear(time)|.
JS_PUBLIC_API double DayWithinYear(double time, double year);

// The callback will be a wrapper function that accepts a double (the time
// to clamp and jitter) as well as a bool indicating if we should be resisting
// fingerprinting. Inside the JS Engine, other parameters that may be
// needed are all constant, so they are handled inside the wrapper function
using ReduceMicrosecondTimePrecisionCallback = double (*)(double, bool,
                                                          JSContext*);

// Set a callback into the toolkit/components/resistfingerprinting function that
// will centralize time resolution and jitter into one place.
JS_PUBLIC_API void SetReduceMicrosecondTimePrecisionCallback(
    ReduceMicrosecondTimePrecisionCallback callback);

// Sets the time resolution for fingerprinting protection, and whether jitter
// should occur. If resolution is set to zero, then no rounding or jitter will
// occur. This is used if the callback above is not specified.
JS_PUBLIC_API void SetTimeResolutionUsec(uint32_t resolution, bool jitter);

}  // namespace JS

#endif /* js_Date_h */