Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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/. */
/*
* JS date methods.
*
* "For example, OS/360 devotes 26 bytes of the permanently
* resident date-turnover routine to the proper handling of
* December 31 on leap years (when it is Day 366). That
* might have been left to the operator."
*
* Frederick Brooks, 'The Second-System Effect'.
*/
#include "jsdate.h"
#include "mozilla/Atomics.h"
#include "mozilla/Casting.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Sprintf.h"
#include "mozilla/TextUtils.h"
#include <algorithm>
#include <cstring>
#include <iterator>
#include <math.h>
#include <string.h>
#include "jsapi.h"
#include "jsfriendapi.h"
#include "jsnum.h"
#include "jstypes.h"
#ifdef JS_HAS_TEMPORAL_API
# include "builtin/temporal/Instant.h"
#endif
#include "js/CallAndConstruct.h" // JS::IsCallable
#include "js/Conversions.h"
#include "js/Date.h"
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/LocaleSensitive.h"
#include "js/Object.h" // JS::GetBuiltinClass
#include "js/PropertySpec.h"
#include "js/Wrapper.h"
#include "util/DifferentialTesting.h"
#include "util/StringBuilder.h"
#include "util/Text.h"
#include "vm/DateObject.h"
#include "vm/DateTime.h"
#include "vm/GlobalObject.h"
#include "vm/Interpreter.h"
#include "vm/JSContext.h"
#include "vm/JSObject.h"
#include "vm/StringType.h"
#include "vm/Time.h"
#include "vm/Compartment-inl.h" // For js::UnwrapAndTypeCheckThis
#include "vm/GeckoProfiler-inl.h"
#include "vm/JSObject-inl.h"
using namespace js;
using mozilla::Atomic;
using mozilla::BitwiseCast;
using mozilla::IsAsciiAlpha;
using mozilla::IsAsciiDigit;
using mozilla::IsAsciiLowercaseAlpha;
using mozilla::NumbersAreIdentical;
using mozilla::Relaxed;
using JS::AutoCheckCannotGC;
using JS::ClippedTime;
using JS::GenericNaN;
using JS::GetBuiltinClass;
using JS::TimeClip;
using JS::ToInteger;
// When this value is non-zero, we'll round the time by this resolution.
static Atomic<uint32_t, Relaxed> sResolutionUsec;
// This is not implemented yet, but we will use this to know to jitter the time
// in the JS shell
static Atomic<bool, Relaxed> sJitter;
// The callback we will use for the Gecko implementation of Timer
// Clamping/Jittering
static Atomic<JS::ReduceMicrosecondTimePrecisionCallback, Relaxed>
sReduceMicrosecondTimePrecisionCallback;
namespace {
class DateTimeHelper {
private:
#if !JS_HAS_INTL_API
static int equivalentYearForDST(int year);
static bool isRepresentableAsTime32(int64_t t);
static int32_t daylightSavingTA(DateTimeInfo::ForceUTC forceUTC, int64_t t);
static int32_t adjustTime(DateTimeInfo::ForceUTC forceUTC, int64_t date);
static PRMJTime toPRMJTime(DateTimeInfo::ForceUTC forceUTC, int64_t localTime,
int64_t utcTime);
#endif
public:
static int32_t getTimeZoneOffset(DateTimeInfo::ForceUTC forceUTC,
int64_t epochMilliseconds,
DateTimeInfo::TimeZoneOffset offset);
static JSString* timeZoneComment(JSContext* cx,
DateTimeInfo::ForceUTC forceUTC,
const char* locale, int64_t utcTime,
int64_t localTime);
#if !JS_HAS_INTL_API
static size_t formatTime(DateTimeInfo::ForceUTC forceUTC, char* buf,
size_t buflen, const char* fmt, int64_t utcTime,
int64_t localTime);
#endif
};
} // namespace
static DateTimeInfo::ForceUTC ForceUTC(const Realm* realm) {
return realm->creationOptions().forceUTC() ? DateTimeInfo::ForceUTC::Yes
: DateTimeInfo::ForceUTC::No;
}
/**
* 5.2.5 Mathematical Operations
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static inline double PositiveModulo(double dividend, double divisor) {
MOZ_ASSERT(divisor > 0);
MOZ_ASSERT(std::isfinite(divisor));
double result = fmod(dividend, divisor);
if (result < 0) {
result += divisor;
}
return result + (+0.0);
}
template <typename T>
static inline std::enable_if_t<std::is_integral_v<T>, int32_t> PositiveModulo(
T dividend, int32_t divisor) {
MOZ_ASSERT(divisor > 0);
int32_t result = dividend % divisor;
if (result < 0) {
result += divisor;
}
return result;
}
template <typename T>
static constexpr T FloorDiv(T dividend, int32_t divisor) {
MOZ_ASSERT(divisor > 0);
T quotient = dividend / divisor;
T remainder = dividend % divisor;
if (remainder < 0) {
quotient -= 1;
}
return quotient;
}
#ifdef DEBUG
/**
* 21.4.1.1 Time Values and Time Range
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static inline bool IsTimeValue(double t) {
if (std::isnan(t)) {
return true;
}
return IsInteger(t) && StartOfTime <= t && t <= EndOfTime;
}
#endif
/**
* Time value with local time zone offset applied.
*/
static inline bool IsLocalTimeValue(double t) {
if (std::isnan(t)) {
return true;
}
return IsInteger(t) && (StartOfTime - msPerDay) < t &&
t < (EndOfTime + msPerDay);
}
/**
* 21.4.1.3 Day ( t )
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static inline int32_t Day(int64_t t) {
MOZ_ASSERT(IsLocalTimeValue(t));
return int32_t(FloorDiv(t, msPerDay));
}
/**
* 21.4.1.4 TimeWithinDay ( t )
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static int32_t TimeWithinDay(int64_t t) {
MOZ_ASSERT(IsLocalTimeValue(t));
return PositiveModulo(t, msPerDay);
}
/**
* 21.4.1.5 DaysInYear ( y )
* 21.4.1.10 InLeapYear ( t )
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static inline bool IsLeapYear(double year) {
MOZ_ASSERT(IsInteger(year));
return fmod(year, 4) == 0 && (fmod(year, 100) != 0 || fmod(year, 400) == 0);
}
static constexpr bool IsLeapYear(int32_t year) {
MOZ_ASSERT(mozilla::Abs(year) <= 2'000'000);
int32_t d = (year % 100 != 0) ? 4 : 16;
return (year & (d - 1)) == 0;
}
/**
* 21.4.1.6 DayFromYear ( y )
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static inline double DayFromYear(double y) {
// Steps 1-7.
return 365 * (y - 1970) + floor((y - 1969) / 4.0) -
floor((y - 1901) / 100.0) + floor((y - 1601) / 400.0);
}
static constexpr int32_t DayFromYear(int32_t y) {
MOZ_ASSERT(mozilla::Abs(y) <= 2'000'000);
// Steps 1-7.
return 365 * (y - 1970) + FloorDiv((y - 1969), 4) -
FloorDiv((y - 1901), 100) + FloorDiv((y - 1601), 400);
}
/**
* 21.4.1.7 TimeFromYear ( y )
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static inline double TimeFromYear(double y) {
return ::DayFromYear(y) * msPerDay;
}
static inline int64_t TimeFromYear(int32_t y) {
return ::DayFromYear(y) * int64_t(msPerDay);
}
/*
* This function returns the year, month and day corresponding to a given
* time value. The implementation closely follows (w.r.t. types and variable
* names) the algorithm shown in Figure 12 of [1].
*
* A key point of the algorithm is that it works on the so called
* Computational calendar where years run from March to February -- this
* largely avoids complications with leap years. The algorithm finds the
* date in the Computational calendar and then maps it to the Gregorian
* calendar.
*
* [1] Neri C, Schneider L., "Euclidean affine functions and their
* application to calendar algorithms."
* Softw Pract Exper. 2023;53(4):937-970. doi: 10.1002/spe.3172
*/
YearMonthDay js::ToYearMonthDay(int64_t time) {
// Calendar cycles repeat every 400 years in the Gregorian calendar: a
// leap day is added every 4 years, removed every 100 years and added
// every 400 years. The number of days in 400 years is cycleInDays.
constexpr uint32_t cycleInYears = 400;
constexpr uint32_t cycleInDays = cycleInYears * 365 + (cycleInYears / 4) -
(cycleInYears / 100) + (cycleInYears / 400);
static_assert(cycleInDays == 146097, "Wrong calculation of cycleInDays.");
// The natural epoch for the Computational calendar is 0000/Mar/01 and
// there are rataDie1970Jan1 = 719468 days from this date to 1970/Jan/01,
// the epoch used by ES2024, 21.4.1.1.
constexpr uint32_t rataDie1970Jan1 = 719468;
constexpr uint32_t maxU32 = std::numeric_limits<uint32_t>::max();
// Let N_U be the number of days since the 1970/Jan/01. This function sets
// N = N_U + K, where K = rataDie1970Jan1 + s * cycleInDays and s is an
// integer number (to be chosen). Then, it evaluates 4 * N + 3 on uint32_t
// operands so that N must be positive and, to prevent overflow,
// 4 * N + 3 <= maxU32 <=> N <= (maxU32 - 3) / 4.
// Therefore, we must have 0 <= N_U + K <= (maxU32 - 3) / 4 or, in other
// words, N_U must be in [minDays, maxDays] = [-K, (maxU32 - 3) / 4 - K].
// Notice that this interval moves cycleInDays positions to the left when
// s is incremented. We chose s to get the interval's mid-point as close
// as possible to 0. For this, we wish to have:
// K ~= (maxU32 - 3) / 4 - K <=> 2 * K ~= (maxU32 - 3) / 4 <=>
// K ~= (maxU32 - 3) / 8 <=>
// rataDie1970Jan1 + s * cycleInDays ~= (maxU32 - 3) / 8 <=>
// s ~= ((maxU32 - 3) / 8 - rataDie1970Jan1) / cycleInDays ~= 3669.8.
// Therefore, we chose s = 3670. The shift and correction constants
// (see [1]) are then:
constexpr uint32_t s = 3670;
constexpr uint32_t K = rataDie1970Jan1 + s * cycleInDays;
constexpr uint32_t L = s * cycleInYears;
// [minDays, maxDays] correspond to a date range from -1'468'000/Mar/01 to
// 1'471'805/Jun/05.
constexpr int32_t minDays = -int32_t(K);
constexpr int32_t maxDays = (maxU32 - 3) / 4 - K;
static_assert(minDays == -536'895'458, "Wrong calculation of minDays or K.");
static_assert(maxDays == 536'846'365, "Wrong calculation of maxDays or K.");
// These are hard limits for the algorithm and far greater than the
// range [-8.64e15, 8.64e15] required by ES2024 21.4.1.1. Callers must
// ensure this function is not called out of the hard limits and,
// preferably, not outside the ES2024 limits.
constexpr int64_t minTime = minDays * int64_t(msPerDay);
[[maybe_unused]] constexpr int64_t maxTime = maxDays * int64_t(msPerDay);
MOZ_ASSERT(minTime <= time && time <= maxTime);
// Since time is the number of milliseconds since the epoch, 1970/Jan/01,
// one might expect N_U = time / uint64_t(msPerDay) is the number of days
// since epoch. There's a catch tough. Consider, for instance, half day
// before the epoch, that is, t = -0.5 * msPerDay. This falls on
// 1969/Dec/31 and should correspond to N_U = -1 but the above gives
// N_U = 0. Indeed, t / msPerDay = -0.5 but integer division truncates
// towards 0 (C++ [expr.mul]/4) and not towards -infinity as needed, so
// that time / uint64_t(msPerDay) = 0. To workaround this issue we perform
// the division on positive operands so that truncations towards 0 and
// -infinity are equivalent. For this, set u = time - minTime, which is
// positive as asserted above. Then, perform the division u / msPerDay and
// to the result add minTime / msPerDay = minDays to cancel the
// subtraction of minTime.
const uint64_t u = uint64_t(time - minTime);
const int32_t N_U = int32_t(u / uint64_t(msPerDay)) + minDays;
MOZ_ASSERT(minDays <= N_U && N_U <= maxDays);
const uint32_t N = uint32_t(N_U) + K;
// Some magic numbers have been explained above but, unfortunately,
// others with no precise interpretation do appear. They mostly come
// from numerical approximations of Euclidean affine functions (see [1])
// which are faster for the CPU to calculate. Unfortunately, no compiler
// can do these optimizations.
// Century C and year of the century N_C:
const uint32_t N_1 = 4 * N + 3;
const uint32_t C = N_1 / 146097;
const uint32_t N_C = N_1 % 146097 / 4;
// Year of the century Z and day of the year N_Y:
const uint32_t N_2 = 4 * N_C + 3;
const uint64_t P_2 = uint64_t(2939745) * N_2;
const uint32_t Z = uint32_t(P_2 / 4294967296);
const uint32_t N_Y = uint32_t(P_2 % 4294967296) / 2939745 / 4;
// Year Y:
const uint32_t Y = 100 * C + Z;
// Month M and day D.
// The expression for N_3 has been adapted to account for the difference
// between month numbers in ES5 15.9.1.4 (from 0 to 11) and [1] (from 1
// to 12). This is done by subtracting 65536 from the original
// expression so that M decreases by 1 and so does M_G further down.
const uint32_t N_3 = 2141 * N_Y + 132377; // 132377 = 197913 - 65536
const uint32_t M = N_3 / 65536;
const uint32_t D = N_3 % 65536 / 2141;
// Map from Computational to Gregorian calendar. Notice also the year
// correction and the type change and that Jan/01 is day 306 of the
// Computational calendar, cf. Table 1. [1]
constexpr uint32_t daysFromMar01ToJan01 = 306;
const uint32_t J = N_Y >= daysFromMar01ToJan01;
const int32_t Y_G = int32_t((Y - L) + J);
const int32_t M_G = int32_t(J ? M - 12 : M);
const int32_t D_G = int32_t(D + 1);
return {Y_G, M_G, D_G};
}
/**
* 21.4.1.8 YearFromTime ( t )
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static int32_t YearFromTime(int64_t t) {
MOZ_ASSERT(IsLocalTimeValue(t));
return ToYearMonthDay(t).year;
}
/**
* 21.4.1.9 DayWithinYear ( t )
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static double DayWithinYear(int64_t t, double year) {
MOZ_ASSERT(::YearFromTime(t) == year);
return Day(t) - ::DayFromYear(year);
}
/**
* 21.4.1.11 MonthFromTime ( t )
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static int32_t MonthFromTime(int64_t t) {
MOZ_ASSERT(IsLocalTimeValue(t));
return ToYearMonthDay(t).month;
}
/**
* 21.4.1.12 DateFromTime ( t )
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static int32_t DateFromTime(int64_t t) {
MOZ_ASSERT(IsLocalTimeValue(t));
return ToYearMonthDay(t).day;
}
/**
* 21.4.1.13 WeekDay ( t )
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static int32_t WeekDay(int64_t t) {
MOZ_ASSERT(IsLocalTimeValue(t));
int32_t result = (Day(t) + 4) % 7;
if (result < 0) {
result += 7;
}
return result;
}
static inline int DayFromMonth(int month, bool isLeapYear) {
/*
* The following array contains the day of year for the first day of
* each month, where index 0 is January, and day 0 is January 1.
*/
static const int firstDayOfMonth[2][13] = {
{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}};
MOZ_ASSERT(0 <= month && month <= 12);
return firstDayOfMonth[isLeapYear][month];
}
template <typename T>
static inline int DayFromMonth(T month, bool isLeapYear) = delete;
/**
* 21.4.1.28 MakeDay ( year, month, date )
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static double MakeDay(double year, double month, double date) {
// Step 1.
if (!std::isfinite(year) || !std::isfinite(month) || !std::isfinite(date)) {
return GenericNaN();
}
// Steps 2-4.
double y = ToInteger(year);
double m = ToInteger(month);
double dt = ToInteger(date);
static constexpr int32_t maxYears = 1'000'000;
static constexpr int32_t maxMonths = 1'000'000 * 12;
static constexpr int32_t maxDate = 100'000'000;
// Use integer math if possible, because it avoids some notoriously slow
// functions like `fmod`.
if (MOZ_LIKELY(std::abs(y) <= maxYears && std::abs(m) <= maxMonths &&
std::abs(dt) <= maxDate)) {
int32_t year = mozilla::AssertedCast<int32_t>(y);
int32_t month = mozilla::AssertedCast<int32_t>(m);
int32_t date = mozilla::AssertedCast<int32_t>(dt);
static_assert(maxMonths % 12 == 0,
"maxYearMonths expects maxMonths is divisible by 12");
static constexpr int32_t maxYearMonths = maxYears + (maxMonths / 12);
static constexpr int32_t maxYearDay = DayFromYear(maxYearMonths);
static constexpr int32_t minYearDay = DayFromYear(-maxYearMonths);
static constexpr int32_t daysInLeapYear = 366;
static constexpr int32_t maxDay = maxYearDay + daysInLeapYear + maxDate;
static constexpr int32_t minDay = minYearDay + daysInLeapYear - maxDate;
static_assert(maxYearMonths == 2'000'000);
static_assert(maxYearDay == 729'765'472);
static_assert(minYearDay == -731'204'528);
static_assert(maxDay == maxYearDay + daysInLeapYear + maxDate);
static_assert(minDay == minYearDay + daysInLeapYear - maxDate);
// Step 5.
int32_t ym = year + FloorDiv(month, 12);
MOZ_ASSERT(std::abs(ym) <= maxYearMonths);
// Step 6. (Implicit)
// Step 7.
int32_t mn = PositiveModulo(month, 12);
// Step 8.
bool leap = IsLeapYear(ym);
int32_t yearday = ::DayFromYear(ym);
int32_t monthday = DayFromMonth(mn, leap);
MOZ_ASSERT(minYearDay <= yearday && yearday <= maxYearDay);
// Step 9.
int32_t day = yearday + monthday + date - 1;
MOZ_ASSERT(minDay <= day && day <= maxDay);
return day;
}
// Step 5.
double ym = y + floor(m / 12);
// Step 6. (Implicit)
// Step 7.
int mn = int(PositiveModulo(m, 12));
// Step 8.
bool leap = IsLeapYear(ym);
double yearday = floor(TimeFromYear(ym) / msPerDay);
double monthday = DayFromMonth(mn, leap);
// Step 9.
return yearday + monthday + dt - 1;
}
/**
* 21.4.1.29 MakeDate ( day, time )
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static inline double MakeDate(double day, double time) {
// Step 1.
if (!std::isfinite(day) || !std::isfinite(time)) {
return GenericNaN();
}
// Steps 2-4.
return day * msPerDay + time;
}
JS_PUBLIC_API double JS::MakeDate(double year, unsigned month, unsigned day) {
MOZ_ASSERT(month <= 11);
MOZ_ASSERT(day >= 1 && day <= 31);
return ::MakeDate(MakeDay(year, month, day), 0);
}
JS_PUBLIC_API double JS::MakeDate(double year, unsigned month, unsigned day,
double time) {
MOZ_ASSERT(month <= 11);
MOZ_ASSERT(day >= 1 && day <= 31);
return ::MakeDate(MakeDay(year, month, day), time);
}
JS_PUBLIC_API double JS::YearFromTime(double time) {
const auto clipped = TimeClip(time);
if (!clipped.isValid()) {
return GenericNaN();
}
int64_t tv;
MOZ_ALWAYS_TRUE(mozilla::NumberEqualsInt64(clipped.toDouble(), &tv));
return ::YearFromTime(tv);
}
JS_PUBLIC_API double JS::MonthFromTime(double time) {
const auto clipped = TimeClip(time);
if (!clipped.isValid()) {
return GenericNaN();
}
int64_t tv;
MOZ_ALWAYS_TRUE(mozilla::NumberEqualsInt64(clipped.toDouble(), &tv));
return ::MonthFromTime(tv);
}
JS_PUBLIC_API double JS::DayFromTime(double time) {
const auto clipped = TimeClip(time);
if (!clipped.isValid()) {
return GenericNaN();
}
int64_t tv;
MOZ_ALWAYS_TRUE(mozilla::NumberEqualsInt64(clipped.toDouble(), &tv));
return DateFromTime(tv);
}
JS_PUBLIC_API double JS::DayFromYear(double year) {
return ::DayFromYear(year);
}
JS_PUBLIC_API double JS::DayWithinYear(double time, double year) {
const auto clipped = TimeClip(time);
if (!clipped.isValid()) {
return GenericNaN();
}
int64_t tv;
MOZ_ALWAYS_TRUE(mozilla::NumberEqualsInt64(clipped.toDouble(), &tv));
return ::DayWithinYear(tv, year);
}
JS_PUBLIC_API void JS::SetReduceMicrosecondTimePrecisionCallback(
JS::ReduceMicrosecondTimePrecisionCallback callback) {
sReduceMicrosecondTimePrecisionCallback = callback;
}
JS_PUBLIC_API JS::ReduceMicrosecondTimePrecisionCallback
JS::GetReduceMicrosecondTimePrecisionCallback() {
return sReduceMicrosecondTimePrecisionCallback;
}
JS_PUBLIC_API void JS::SetTimeResolutionUsec(uint32_t resolution, bool jitter) {
sResolutionUsec = resolution;
sJitter = jitter;
}
#if JS_HAS_INTL_API
int32_t DateTimeHelper::getTimeZoneOffset(DateTimeInfo::ForceUTC forceUTC,
int64_t epochMilliseconds,
DateTimeInfo::TimeZoneOffset offset) {
MOZ_ASSERT_IF(offset == DateTimeInfo::TimeZoneOffset::UTC,
IsTimeValue(epochMilliseconds));
MOZ_ASSERT_IF(offset == DateTimeInfo::TimeZoneOffset::Local,
IsLocalTimeValue(epochMilliseconds));
return DateTimeInfo::getOffsetMilliseconds(forceUTC, epochMilliseconds,
offset);
}
#else
/*
* Find a year for which any given date will fall on the same weekday.
*
* This function should be used with caution when used other than
* for determining DST; it hasn't been proven not to produce an
* incorrect year for times near year boundaries.
*/
int DateTimeHelper::equivalentYearForDST(int year) {
/*
* Years and leap years on which Jan 1 is a Sunday, Monday, etc.
*
* yearStartingWith[0][i] is an example non-leap year where
* Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc.
*
* yearStartingWith[1][i] is an example leap year where
* Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc.
*
* Keep two different mappings, one for past years (< 1970), and a
* different one for future years (> 2037).
*/
static const int pastYearStartingWith[2][7] = {
{1978, 1973, 1974, 1975, 1981, 1971, 1977},
{1984, 1996, 1980, 1992, 1976, 1988, 1972}};
static const int futureYearStartingWith[2][7] = {
{2034, 2035, 2030, 2031, 2037, 2027, 2033},
{2012, 2024, 2036, 2020, 2032, 2016, 2028}};
int day = int(::DayFromYear(year) + 4) % 7;
if (day < 0) {
day += 7;
}
const auto& yearStartingWith =
year < 1970 ? pastYearStartingWith : futureYearStartingWith;
return yearStartingWith[IsLeapYear(year)][day];
}
// Return true if |t| is representable as a 32-bit time_t variable, that means
// the year is in [1970, 2038).
bool DateTimeHelper::isRepresentableAsTime32(int64_t t) {
return 0 <= t && t < 2145916800000;
}
/* ES5 15.9.1.8. */
int32_t DateTimeHelper::daylightSavingTA(DateTimeInfo::ForceUTC forceUTC,
int64_t t) {
/*
* If earlier than 1970 or after 2038, potentially beyond the ken of
* many OSes, map it to an equivalent year before asking.
*/
if (!isRepresentableAsTime32(t)) {
auto [year, month, day] = ToYearMonthDay(t);
int equivalentYear = equivalentYearForDST(year);
double equivalentDay = MakeDay(equivalentYear, month, day);
double equivalentDate = MakeDate(equivalentDay, TimeWithinDay(t));
MOZ_ALWAYS_TRUE(mozilla::NumberEqualsInt64(equivalentDate, &t));
}
return DateTimeInfo::getDSTOffsetMilliseconds(forceUTC, t);
}
int32_t DateTimeHelper::adjustTime(DateTimeInfo::ForceUTC forceUTC,
int64_t date) {
int32_t localTZA = DateTimeInfo::localTZA(forceUTC);
int32_t t = daylightSavingTA(forceUTC, date) + localTZA;
return (localTZA >= 0) ? (t % msPerDay) : -((msPerDay - t) % msPerDay);
}
int32_t DateTimeHelper::getTimeZoneOffset(DateTimeInfo::ForceUTC forceUTC,
int64_t epochMilliseconds,
DateTimeInfo::TimeZoneOffset offset) {
MOZ_ASSERT_IF(offset == DateTimeInfo::TimeZoneOffset::UTC,
IsTimeValue(epochMilliseconds));
MOZ_ASSERT_IF(offset == DateTimeInfo::TimeZoneOffset::Local,
IsLocalTimeValue(epochMilliseconds));
if (offset == DateTimeInfo::TimeZoneOffset::UTC) {
return adjustTime(forceUTC, epochMilliseconds);
}
// Following the ES2017 specification creates undesirable results at DST
// transitions. For example when transitioning from PST to PDT,
// |new Date(2016,2,13,2,0,0).toTimeString()| returns the string value
// "01:00:00 GMT-0800 (PST)" instead of "03:00:00 GMT-0700 (PDT)". Follow
// V8 and subtract one hour before computing the offset.
return adjustTime(forceUTC, epochMilliseconds -
int64_t(DateTimeInfo::localTZA(forceUTC)) -
int64_t(msPerHour));
}
#endif /* JS_HAS_INTL_API */
/**
* 21.4.1.25 LocalTime ( t )
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static int64_t LocalTime(DateTimeInfo::ForceUTC forceUTC, double t) {
MOZ_ASSERT(std::isfinite(t));
MOZ_ASSERT(IsTimeValue(t));
// Steps 1-4.
int32_t offsetMs = DateTimeHelper::getTimeZoneOffset(
forceUTC, static_cast<int64_t>(t), DateTimeInfo::TimeZoneOffset::UTC);
MOZ_ASSERT(std::abs(offsetMs) < msPerDay);
// Step 5.
return static_cast<int64_t>(t) + offsetMs;
}
/**
* 21.4.1.26 UTC ( t )
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static double UTC(DateTimeInfo::ForceUTC forceUTC, double t) {
// Step 1.
if (!std::isfinite(t)) {
return GenericNaN();
}
// Return early when |t| is outside the valid local time value limits.
if (!IsLocalTimeValue(t)) {
return GenericNaN();
}
// Steps 2-5.
int32_t offsetMs = DateTimeHelper::getTimeZoneOffset(
forceUTC, static_cast<int64_t>(t), DateTimeInfo::TimeZoneOffset::Local);
MOZ_ASSERT(std::abs(offsetMs) < msPerDay);
// Step 6.
return static_cast<double>(static_cast<int64_t>(t) - offsetMs);
}
/**
* 21.4.1.14 HourFromTime ( t )
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static int32_t HourFromTime(int64_t t) {
MOZ_ASSERT(IsLocalTimeValue(t));
return PositiveModulo(FloorDiv(t, msPerHour), HoursPerDay);
}
/**
* 21.4.1.15 MinFromTime ( t )
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static int32_t MinFromTime(int64_t t) {
MOZ_ASSERT(IsLocalTimeValue(t));
return PositiveModulo(FloorDiv(t, msPerMinute), MinutesPerHour);
}
/**
* 21.4.1.16 SecFromTime ( t )
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static int32_t SecFromTime(int64_t t) {
MOZ_ASSERT(IsLocalTimeValue(t));
return PositiveModulo(FloorDiv(t, msPerSecond), SecondsPerMinute);
}
/**
* 21.4.1.17 msFromTime ( t )
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static int32_t msFromTime(int64_t t) {
MOZ_ASSERT(IsLocalTimeValue(t));
return PositiveModulo(t, msPerSecond);
}
HourMinuteSecond js::ToHourMinuteSecond(int64_t epochMilliseconds) {
MOZ_ASSERT(IsLocalTimeValue(epochMilliseconds));
int32_t hour = HourFromTime(epochMilliseconds);
MOZ_ASSERT(0 <= hour && hour < HoursPerDay);
int32_t minute = MinFromTime(epochMilliseconds);
MOZ_ASSERT(0 <= minute && minute < MinutesPerHour);
int32_t second = SecFromTime(epochMilliseconds);
MOZ_ASSERT(0 <= minute && minute < SecondsPerMinute);
return {hour, minute, second};
}
/**
* 21.4.1.27 MakeTime ( hour, min, sec, ms )
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static double MakeTime(double hour, double min, double sec, double ms) {
// Step 1.
if (!std::isfinite(hour) || !std::isfinite(min) || !std::isfinite(sec) ||
!std::isfinite(ms)) {
return GenericNaN();
}
// Step 2.
double h = ToInteger(hour);
// Step 3.
double m = ToInteger(min);
// Step 4.
double s = ToInteger(sec);
// Step 5.
double milli = ToInteger(ms);
// Step 6.
return h * msPerHour + m * msPerMinute + s * msPerSecond + milli;
}
/**
* 21.4.1.30 MakeFullYear ( year )
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static double MakeFullYear(double year) {
// Step 1.
if (std::isnan(year)) {
return year;
}
// Step 2.
double truncated = ToInteger(year);
// Step 3.
if (0 <= truncated && truncated <= 99) {
return 1900 + truncated;
}
// Step 4.
return truncated;
}
/**
* end of ECMA 'support' functions
*/
/**
* 21.4.3.4 Date.UTC ( year [ , month [ , date [ , hours [ , minutes [ , seconds
* [ , ms ] ] ] ] ] ] )
*
* ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
*/
static bool date_UTC(JSContext* cx, unsigned argc, Value* vp) {
AutoJSMethodProfilerEntry pseudoFrame(cx, "Date", "UTC");
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
double y;
if (!ToNumber(cx, args.get(0), &y)) {
return false;
}
// Step 2.
double m;
if (args.length() >= 2) {
if (!ToNumber(cx, args[1], &m)) {
return false;
}
} else {
m = 0;
}
// Step 3.
double dt;
if (args.length() >= 3) {
if (!ToNumber(cx, args[2], &dt)) {
return false;
}
} else {
dt = 1;
}
// Step 4.
double h;
if (args.length() >= 4) {
if (!ToNumber(cx, args[3], &h)) {
return false;
}
} else {
h = 0;
}
// Step 5.
double min;
if (args.length() >= 5) {
if (!ToNumber(cx, args[4], &min)) {
return false;
}
} else {
min = 0;
}
// Step 6.
double s;
if (args.length() >= 6) {
if (!ToNumber(cx, args[5], &s)) {
return false;
}
} else {
s = 0;
}
// Step 7.
double milli;
if (args.length() >= 7) {
if (!ToNumber(cx, args[6], &milli)) {
return false;
}
} else {
milli = 0;
}
// Step 8.
double yr = MakeFullYear(y);
// Step 9.
ClippedTime time =
TimeClip(MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli)));
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
* as digits are consumed.
*/
template <typename CharT>
static bool ParseDigits(size_t* result, const CharT* s, size_t* i,
size_t limit) {
size_t init = *i;
*result = 0;
while (*i < limit && ('0' <= s[*i] && s[*i] <= '9')) {
*result *= 10;
*result += (s[*i] - '0');
++(*i);
}
return *i != init;
}
/*
* Read and convert decimal digits to the right of a decimal point,
* representing a fractional integer, from s[*i] into *result
* while *i < limit, up to 3 digits. Consumes any digits beyond 3
* without affecting the result.
*
* Succeed if any digits are converted. Advance *i only
* as digits are consumed.
*/
template <typename CharT>
static bool ParseFractional(int* result, const CharT* s, size_t* i,
size_t limit) {
int factor = 100;
size_t init = *i;
*result = 0;
for (; *i < limit && ('0' <= s[*i] && s[*i] <= '9'); ++(*i)) {
if (*i - init >= 3) {
// If we're past 3 digits, do nothing with it, but continue to
// consume the remainder of the digits
continue;
}
*result += (s[*i] - '0') * factor;
factor /= 10;
}
return *i != init;
}
/*
* Read and convert exactly n decimal digits from s[*i]
* to s[min(*i+n,limit)] into *result.
*
* Succeed if exactly n digits are converted. Advance *i only
* on success.
*/
template <typename CharT>
static bool ParseDigitsN(size_t n, size_t* result, const CharT* s, size_t* i,
size_t limit) {
size_t init = *i;
if (ParseDigits(result, s, i, std::min(limit, init + n))) {
return (*i - init) == n;
}
*i = init;
return false;
}
/*
* Read and convert n or less decimal digits from s[*i]
* to s[min(*i+n,limit)] into *result.
*
* Succeed only if greater than zero but less than or equal to n digits are
* converted. Advance *i only on success.
*/
template <typename CharT>
static bool ParseDigitsNOrLess(size_t n, size_t* result, const CharT* s,
size_t* i, size_t limit) {
size_t init = *i;
if (ParseDigits(result, s, i, std::min(limit, init + n))) {
return ((*i - init) > 0) && ((*i - init) <= n);
}
*i = init;
return false;
}
/*
* Parse a string according to the formats specified in the standard:
*
*
* These formats are based upon a simplification of the ISO 8601 Extended
* Format. As per the spec omitted month and day values are defaulted to '01',
* omitted HH:mm:ss values are defaulted to '00' and an omitted sss field is
* defaulted to '000'.
*
* For cross compatibility we allow the following extensions.
*
* These are:
*
* One or more decimal digits for milliseconds:
* The specification requires exactly three decimal digits for
* the fractional part but we allow for one or more digits.
*
* Time zone specifier without ':':
* We allow the time zone to be specified without a ':' character.
* E.g. "T19:00:00+0700" is equivalent to "T19:00:00+07:00".
*
* Date part:
*
* Year:
* YYYY (eg 1997)
*
* Year and month:
* YYYY-MM (eg 1997-07)
*
* Complete date:
* YYYY-MM-DD (eg 1997-07-16)
*
* Time part:
*
* Hours and minutes:
* Thh:mmTZD (eg T19:20+01:00)
*
* Hours, minutes and seconds:
* Thh:mm:ssTZD (eg T19:20:30+01:00)
*
* Hours, minutes, seconds and a decimal fraction of a second:
* Thh:mm:ss.sssTZD (eg T19:20:30.45+01:00)
*
* where:
*
* YYYY = four-digit year or six digit year as +YYYYYY or -YYYYYY
* MM = two-digit month (01=January, etc.)
* DD = two-digit day of month (01 through 31)
* hh = two digits of hour (00 through 24) (am/pm NOT allowed)
* mm = two digits of minute (00 through 59)
* ss = two digits of second (00 through 59)
* sss = 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 ParseISOStyleDate(DateTimeInfo::ForceUTC forceUTC, 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;
size_t min = 0;
size_t sec = 0;
int msec = 0;
bool isLocalTime = false;
size_t tzHour = 0;
size_t tzMin = 0;
#define PEEK(ch) (i < length && s[i] == ch)
#define NEED(ch) \
if (i >= length || s[i] != ch) { \
return false; \
} else { \
++i; \
}
#define DONE_DATE_UNLESS(ch) \
if (i >= length || s[i] != ch) { \
goto done_date; \
} else { \
++i; \
}
#define DONE_UNLESS(ch) \
if (i >= length || s[i] != ch) { \
goto done; \
} else { \
++i; \
}
#define NEED_NDIGITS(n, field) \
if (!ParseDigitsN(n, &field, s, &i, length)) { \
return false; \
}
if (PEEK('+') || PEEK('-')) {
if (PEEK('-')) {
dateMul = -1;
}
++i;
NEED_NDIGITS(6, year);
// -000000 is not a valid expanded year.
if (year == 0 && dateMul == -1) {
return false;
}
} else {
NEED_NDIGITS(4, year);
}
DONE_DATE_UNLESS('-');
NEED_NDIGITS(2, month);
DONE_DATE_UNLESS('-');
NEED_NDIGITS(2, day);
done_date:
if (PEEK('T')) {
++i;
} else {
goto done;
}
NEED_NDIGITS(2, hour);
NEED(':');
NEED_NDIGITS(2, min);
if (PEEK(':')) {
++i;
NEED_NDIGITS(2, sec);
if (PEEK('.')) {
++i;
if (!ParseFractional(&msec, s, &i, length)) {
return false;
}
}
}
if (PEEK('Z')) {
++i;
} else if (PEEK('+') || PEEK('-')) {
if (PEEK('-')) {
tzMul = -1;
}
++i;
NEED_NDIGITS(2, tzHour);
/*
* Non-standard extension to the ISO date format (permitted by ES5):
* allow "-0700" as a time zone offset, not just "-07:00".
*/
if (PEEK(':')) {
++i;
}
NEED_NDIGITS(2, tzMin);
} else {
isLocalTime = true;
}
done:
if (year > 275943 // ceil(1e8/365) + 1970
|| month == 0 || month > 12 || day == 0 || day > 31 || hour > 24 ||
(hour == 24 && (min > 0 || sec > 0 || msec > 0)) || min > 59 ||
sec > 59 || tzHour > 23 || tzMin > 59) {
return false;
}
if (i != length) {
return false;
}
month -= 1; /* convert month to 0-based */
double date = MakeDate(MakeDay(dateMul * int32_t(year), month, day),
MakeTime(hour, min, sec, msec));
if (isLocalTime) {
date = UTC(forceUTC, date);
} else {
date -=
tzMul * (int32_t(tzHour) * msPerHour + int32_t(tzMin) * msPerMinute);
}
*result = TimeClip(date);
return NumbersAreIdentical(date, result->toDouble());
#undef PEEK
#undef NEED
#undef DONE_UNLESS
#undef NEED_NDIGITS
}
/**
* Non-ISO years < 100 get fixed up, to allow 2-digit year formats.
* year < 50 becomes 2000-2049, 50-99 becomes 1950-1999.
*/
static int FixupYear(int year) {
if (year < 50) {
year += 2000;
} else if (year >= 50 && year < 100) {
year += 1900;
}
return year;
}
template <typename CharT>
static bool MatchesKeyword(const CharT* s, size_t len, const char* keyword) {