/* -*- 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"vm/Compartment-inl.h"// For js::UnwrapAndTypeCheckThis #include"vm/GeckoProfiler-inl.h" #include"vm/JSObject-inl.h"
usingnamespace 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;
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
*/ staticinlinebool IsTimeValue(double t) { if (std::isnan(t)) { returntrue;
} return IsInteger(t) && StartOfTime <= t && t <= EndOfTime;
} #endif
/** * Time value with local time zone offset applied.
*/ staticinlinebool IsLocalTimeValue(double t) { if (std::isnan(t)) { returntrue;
} return IsInteger(t) && (StartOfTime - msPerDay) < t &&
t < (EndOfTime + 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 * https://onlinelibrary.wiley.com/doi/full/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;
// 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);
int32_t result = (Day(t) + 4) % 7; if (result < 0) {
result += 7;
} return result;
}
staticinlineint 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.
*/ staticconstint 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}};
// 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");
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).
*/ staticconstint pastYearStartingWith[2][7] = {
{1978, 1973, 1974, 1975, 1981, 1971, 1977},
{1984, 1996, 1980, 1992, 1976, 1988, 1972}}; staticconstint 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;
}
// 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);
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. // Spec bug: https://bugs.ecmascript.org/show_bug.cgi?id=4007
// Step 4. double h; if (args.length() >= 4) { if (!ToNumber(cx, args[3], &h)) { returnfalse;
}
} else {
h = 0;
}
// Step 5. double min; if (args.length() >= 5) { if (!ToNumber(cx, args[4], &min)) { returnfalse;
}
} else {
min = 0;
}
// Step 6. double s; if (args.length() >= 6) { if (!ToNumber(cx, args[5], &s)) { returnfalse;
}
} else {
s = 0;
}
// Step 7. double milli; if (args.length() >= 7) { if (!ToNumber(cx, args[6], &milli)) { returnfalse;
}
} 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)); returntrue;
}
/* * 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> staticbool 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> staticbool 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> staticbool 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; returnfalse;
}
/* * 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> staticbool ParseDigitsNOrLess(size_t n, size_t* result, const CharT* s,
size_t* i, size_t limit) {
size_t init = *i;
/* * Parse a string according to the formats specified in the standard: * * https://tc39.es/ecma262/#sec-date-time-string-format * https://tc39.es/ecma262/#sec-expanded-years * * 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> staticbool 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;
if (PEEK(':')) {
++i;
NEED_NDIGITS(2, sec); if (PEEK('.')) {
++i; if (!ParseFractional(&msec, s, &i, length)) { returnfalse;
}
}
}
if (PEEK('Z')) {
++i;
} elseif (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;
}
/** * Given a string s of length >= 3, checks if it begins, * case-insensitive, with the given lower case prefix.
*/ template <typename CharT> staticbool StartsWithMonthPrefix(const CharT* s, constchar* prefix) {
MOZ_ASSERT(strlen(prefix) == 3);
for (size_t i = 0; i < 3; ++i) {
MOZ_ASSERT(IsAsciiAlpha(*s));
MOZ_ASSERT(IsAsciiLowercaseAlpha(*prefix));
if (unicode::ToLowerCase(static_cast<Latin1Char>(*s)) != *prefix) { returnfalse;
}
++s, ++prefix;
}
returntrue;
}
template <typename CharT> staticbool IsMonthName(const CharT* s, size_t len, int* mon) { // Month abbreviations < 3 chars are not accepted. if (len < 3) { returnfalse;
}
for (size_t m = 0; m < std::size(month_prefixes); ++m) { if (StartsWithMonthPrefix(s, month_prefixes[m])) { // Use numeric value.
*mon = m + 1; returntrue;
}
}
returnfalse;
}
/* * Try to parse the following date formats: * dd-MMM-yyyy * dd-MMM-yy * MMM-dd-yyyy * MMM-dd-yy * yyyy-MMM-dd * yy-MMM-dd * * Returns true and fills all out parameters when successfully parsed * dashed-date. Otherwise returns false and leaves out parameters untouched.
*/ template <typename CharT> staticbool TryParseDashedDatePrefix(const CharT* s, size_t length,
size_t* indexOut, int* yearOut, int* monOut, int* mdayOut) {
size_t i = *indexOut;
size_t pre = i;
size_t mday; if (!ParseDigitsNOrLess(6, &mday, s, &i, length)) { returnfalse;
}
size_t mdayDigits = i - pre;
if (i >= length || s[i] != '-') { returnfalse;
}
++i;
int mon = 0; if (*monOut == -1) { // If month wasn't already set by ParseDate, it must be in the middle of // this format, let's look for it
size_t start = i; for (; i < length; i++) { if (!IsAsciiAlpha(s[i])) { break;
}
}
if (!IsMonthName(s + start, i - start, &mon)) { returnfalse;
}
if (i >= length || s[i] != '-') { returnfalse;
}
++i;
}
pre = i;
size_t year; if (!ParseDigitsNOrLess(6, &year, s, &i, length)) { returnfalse;
}
size_t yearDigits = i - pre;
if (i < length && IsAsciiDigit(s[i])) { returnfalse;
}
// Swap the mday and year if the year wasn't specified in full. if (mday > 31 && year <= 31 && yearDigits < 4) {
std::swap(mday, year);
std::swap(mdayDigits, yearDigits);
}
/* * Try to parse dates in the style of YYYY-MM-DD which do not conform to * the formal standard from ParseISOStyleDate. This includes cases such as * * - Year does not have 4 digits * - Month or mday has 1 digit * - Space in between date and time, rather than a 'T' * * Regarding the last case, this function only parses out the date, returning * to ParseDate to finish parsing the time and timezone, if present. * * Returns true and fills all out parameters when successfully parsed * dashed-date. Otherwise returns false and leaves out parameters untouched.
*/ template <typename CharT> staticbool TryParseDashedNumericDatePrefix(const CharT* s, size_t length,
size_t* indexOut, int* yearOut, int* monOut, int* mdayOut) {
size_t i = *indexOut;
// 1 or 2 digits for the first number is tricky; 1-12 means it's a month, 0 or // >31 means it's a year, and 13-31 is invalid due to ambiguity. if (first >= 1 && first <= 12) {
mon = first;
} elseif (first == 0 || first > 31) {
year = first;
} else { returnfalse;
}
if (mon < 0) { // If month hasn't been set yet, it's definitely the 2nd number
mon = second;
} else { // If it has, the next number is the mday
mday = second;
}
if (mday < 0) { // The third number is probably the mday...
mday = third;
} else { // But otherwise, it's the year
year = third;
}
if (ParseISOStyleDate(forceUTC, s, length, result)) { returntrue;
}
// Collect telemetry on how often Date.parse enters implementation defined // code. This can be removed in the future, see Bug 1944630.
cx->runtime()->setUseCounter(cx->global(), JSUseCounter::DATEPARSE_IMPL_DEF);
size_t index = 0; int mon = -1; bool seenMonthName = false;
// Before we begin, we need to scrub any words from the beginning of the // string up to the first number, recording the month if we encounter it for (; index < length; index++) { int c = s[index];
if (strchr(" ,.-/", c)) { continue;
} if (!IsAsciiAlpha(c)) { break;
}
size_t start = index;
index++; for (; index < length; index++) { if (!IsAsciiAlpha(s[index])) { break;
}
}
if (index >= length) { returnfalse;
}
if (IsMonthName(s + start, index - start, &mon)) {
seenMonthName = true; // If the next digit is a number, we need to break so it // gets parsed as mday if (IsAsciiDigit(s[index])) { break;
}
} elseif (!strchr(" ,.-/", s[index])) { // We're only allowing the above delimiters after the day of // week to prevent things such as "foo_1" from being parsed // as a date, which may break software which uses this function // to determine whether or not something is a date. returnfalse;
}
}
int year = -1; int mday = -1; int hour = -1; int min = -1; int sec = -1; int msec = 0; int tzOffset = -1;
// One of '+', '-', ':', '/', or 0 (the default value). int prevc = 0;
// Try parsing the leading dashed-date. // // If successfully parsed, index is updated to the end of the date part, // and year, mon, mday are set to the date. // Continue parsing optional time + tzOffset parts. // // Otherwise, this is no-op. bool isDashedDate =
TryParseDashedDatePrefix(s, length, &index, &year, &mon, &mday) ||
TryParseDashedNumericDatePrefix(s, length, &index, &year, &mon, &mday);
if (isDashedDate && index < length && strchr("T:+", s[index])) { returnfalse;
}
while (index < length) { int c = s[index];
index++;
// Normalize U+202F (NARROW NO-BREAK SPACE). This character appears between // the AM/PM markers for |date.toLocaleString("en")|. We have to normalize // it for backward compatibility reasons. if (c == 0x202F) {
c = ' ';
}
if ((c == '+' || c == '-') && // Reject + or - after timezone (still allowing for negative year)
((seenPlusMinus && year != -1) || // Reject timezones like "1995-09-26 -04:30" (if the - is right up // against the previous number, it will get parsed as a time, // see the other comment below)
(year != -1 && hour == -1 && !seenGmtAbbr &&
!IsAsciiDigit(s[index - 2])))) { returnfalse;
}
// Spaces, ASCII control characters, periods, and commas are simply ignored. if (c <= ' ' || c == '.' || c == ',') { continue;
}
// Parse delimiter characters. Save them to the side for future use. if (c == '/' || c == ':' || c == '+') {
prevc = c; continue;
}
// Dashes are delimiters if they're immediately followed by a number field. // If they're not followed by a number field, they're simply ignored. if (c == '-') { if (index < length && IsAsciiDigit(s[index])) {
prevc = c;
} continue;
}
// Skip over comments -- text inside matching parentheses. (Comments // themselves may contain comments as long as all the parentheses properly // match up. And apparently comments, including nested ones, may validly be // terminated by end of input...) if (c == '(') { int depth = 1; while (index < length) {
c = s[index];
index++; if (c == '(') {
depth++;
} elseif (c == ')') { if (--depth <= 0) { break;
}
}
} continue;
}
// Parse a number field. if (IsAsciiDigit(c)) {
size_t partStart = index - 1;
uint32_t u = c - '0'; while (index < length) {
c = s[index]; if (!IsAsciiDigit(c)) { break;
}
u = u * 10 + (c - '0');
index++;
}
size_t partLength = index - partStart;
// See above for why we have to normalize U+202F. if (c == 0x202F) {
c = ' ';
}
int n = int(u);
/* * Allow TZA before the year, so 'Wed Nov 05 21:49:11 GMT-0800 1997' * works. * * Uses of seenPlusMinus allow ':' in TZA, so Java no-timezone style * of GMT+4:30 works.
*/
if (prevc == '-' && (tzOffset != 0 || seenPlusMinus) && partLength >= 4 &&
year < 0) { // Parse as a negative, possibly zero-padded year if // 1. the preceding character is '-', // 2. the TZA is not 'GMT' (tested by |tzOffset != 0|), // 3. or a TZA was already parsed |seenPlusMinus == true|, // 4. the part length is at least 4 (to parse '-08' as a TZA), // 5. and we did not already parse a year |year < 0|.
year = n;
seenFullYear = true;
negativeYear = true;
} elseif ((prevc == '+' || prevc == '-') && // "1995-09-26-04:30" needs to be parsed as a time, // not a time zone
(seenGmtAbbr || hour != -1)) { /* Make ':' case below change tzOffset. */
seenPlusMinus = true;
/* offset */ if (n < 24 && partLength <= 2) {
n = n * 60; /* EG. "GMT-3" */
} else {
n = n % 100 + n / 100 * 60; /* eg "GMT-0430" */
}
if (prevc == '+') /* plus means east of GMT */
n = -n;
// Reject if not preceded by 'GMT' or if a time zone offset // was already parsed. if (tzOffset != 0 && tzOffset != -1) { returnfalse;
}
tzOffset = n;
} elseif (prevc == '/' && mon >= 0 && mday >= 0 && year < 0) { if (c <= ' ' || c == ',' || c == '/' || index >= length) {
year = n;
} else { returnfalse;
}
} elseif (c == ':') { if (hour < 0) {
hour = /*byte*/ n;
} elseif (min < 0) {
min = /*byte*/ n;
} else { returnfalse;
}
} elseif (c == '/') { /* * Until it is determined that mon is the actual month, keep * it as 1-based rather than 0-based.
*/ if (mon < 0) {
mon = /*byte*/ n;
} elseif (mday < 0) {
mday = /*byte*/ n;
} else { returnfalse;
}
} elseif (index < length && c != ',' && c > ' ' && c != '-' &&
c != '(' && // Allow '.' as a delimiter until seconds have been parsed // (this allows the decimal for milliseconds)
(c != '.' || sec != -1) && // Allow zulu time e.g. "09/26/1995 16:00Z", or // '+' directly after time e.g. 00:00+0500
!(hour != -1 && strchr("Zz+", c)) && // Allow month or AM/PM directly after a number
(!IsAsciiAlpha(c) ||
(mon != -1 && !(strchr("AaPp", c) && index < length - 1 &&
strchr("Mm", s[index + 1]))))) { returnfalse;
} elseif (seenPlusMinus && n < 60) { /* handle GMT-3:30 */ if (tzOffset < 0) {
tzOffset -= n;
} else {
tzOffset += n;
}
} elseif (hour >= 0 && min < 0) {
min = /*byte*/ n;
} elseif (prevc == ':' && min >= 0 && sec < 0) {
sec = /*byte*/ n; if (c == '.') {
index++; if (!ParseFractional(&msec, s, &index, length)) { returnfalse;
}
}
} elseif (mon < 0) {
mon = /*byte*/ n;
} elseif (mon >= 0 && mday < 0) {
mday = /*byte*/ n;
} elseif (mon >= 0 && mday >= 0 && year < 0) {
year = n;
seenFullYear = partLength >= 4;
} else { returnfalse;
}
prevc = 0; continue;
}
// Parse fields that are words: ASCII letters spelling out in English AM/PM, // day of week, month, or an extremely limited set of legacy time zone // abbreviations. if (IsAsciiAlpha(c)) {
size_t start = index - 1; while (index < length) {
c = s[index]; if (!IsAsciiAlpha(c)) { break;
}
index++;
}
// There must be at least as many letters as in the shortest keyword.
constexpr size_t MinLength = MinKeywordLength(keywords); if (index - start < MinLength) { returnfalse;
}
// Record a month if it is a month name. Note that some numbers are // initially treated as months; if a numeric field has already been // interpreted as a month, store that value to the actually appropriate // date component and set the month here. int tryMonth; if (IsMonthName(s + start, index - start, &tryMonth)) { if (seenMonthName) { // Overwrite the previous month name
mon = tryMonth;
prevc = 0; continue;
}
seenMonthName = true;
if (mon < 0) {
mon = tryMonth;
} elseif (mday < 0) {
mday = mon;
mon = tryMonth;
} elseif (year < 0) { if (mday > 0) { // If the date is of the form f l month, then when month is // reached we have f in mon and l in mday. In order to be // consistent with the f month l and month f l forms, we need to // swap so that f is in mday and l is in year.
year = mday;
mday = mon;
} else {
year = mon;
}
mon = tryMonth;
} else { returnfalse;
}
prevc = 0; continue;
}
size_t k = std::size(keywords); while (k-- > 0) { const CharsAndAction& keyword = keywords[k];
// If the field doesn't match the keyword, try the next one. if (!MatchesKeyword(s + start, index - start, keyword.chars)) { continue;
}
int action = keyword.action;
if (action == 10000) {
seenGmtAbbr = true;
}
// Perform action tests from smallest action values to largest.
// Adjust a previously-specified hour for AM/PM accordingly (taking care // to treat 12:xx AM as 00:xx, 12:xx PM as 12:xx). if (action < 0) {
MOZ_ASSERT(action == -1 || action == -2); if (hour > 12 || hour < 0) { returnfalse;
}
// Finally, record a time zone offset.
MOZ_ASSERT(action >= 10000);
tzOffset = action - 10000; break;
}
if (k == size_t(-1)) { returnfalse;
}
prevc = 0; continue;
}
// Any other character fails to parse. returnfalse;
}
// Handle cases where the input is a single number. Single numbers >= 1000 // are handled by the spec (ParseISOStyleDate), so we don't need to account // for that here. if (mon != -1 && year < 0 && mday < 0) { // Reject 13-31 for Chrome parity if (mon >= 13 && mon <= 31) { returnfalse;
}
mday = 1; if (mon >= 1 && mon <= 12) { // 1-12 is parsed as a month with the year defaulted to 2001 // (again, for Chrome parity)
year = 2001;
} else {
year = FixupYear(mon);
mon = 1;
}
}
if (!isDashedDate) { // NOTE: TryParseDashedDatePrefix already handles the following fixup.
/* * Case 1. The input string contains an English month name. * The form of the string can be month f l, or f month l, or * f l month which each evaluate to the same date. * If f and l are both greater than or equal to 100 the date * is invalid. * * The year is taken to be either l, f if f > 31, or whichever * is set to zero. * * Case 2. The input string is of the form "f/m/l" where f, m and l are * integers, e.g. 7/16/45. mon, mday and year values are adjusted * to achieve Chrome compatibility. * * a. If 0 < f <= 12 and 0 < l <= 31, f/m/l is interpreted as * month/day/year. * b. If 31 < f and 0 < m <= 12 and 0 < l <= 31 f/m/l is * interpreted as year/month/day
*/ if (seenMonthName) { if (mday >= 100 && mon >= 100) { returnfalse;
}
if (year > 0 && (mday == 0 || mday > 31) && !seenFullYear) { int temp = year;
year = mday;
mday = temp;
}
// Collect telemetry on how often Date.parse is being used. // This can be removed in the future, see Bug 1944630.
cx->runtime()->setUseCounter(cx->global(), JSUseCounter::DATEPARSE);
CallArgs args = CallArgsFromVp(argc, vp); if (args.length() == 0) {
args.rval().setNaN(); returntrue;
}
JSString* str = ToString<CanGC>(cx, args[0]); if (!str) { returnfalse;
}
JSLinearString* linearStr = str->ensureLinear(cx); if (!linearStr) { returnfalse;
}
if (sJitter) { // Calculate a random midpoint for jittering. In the browser, we are // adversarial: Web Content may try to calculate the midpoint themselves // and use that to bypass it's security. In the JS Shell, we are not // adversarial, we want to jitter the time to recreate the operating // environment, but we do not concern ourselves with trying to prevent an // attacker from calculating the midpoint themselves. So we use a very // simple, very fast CRC with a hardcoded seed.
if (now > clamped + midpoint) { // We're jittering up to the next step
now = clamped + sResolutionUsec;
} else { // We're staying at the clamped value
now = clamped;
}
} else { // No jitter, only clamping
now = clamped;
}
}
/* Check if the cache is already populated. */ if (!getReservedSlot(LOCAL_TIME_SLOT).isUndefined() &&
getReservedSlot(UTC_TIME_ZONE_OFFSET_SLOT).toInt32() == utcTZOffset) { return;
}
/* Remember time zone used to generate the local cache. */
setReservedSlot(UTC_TIME_ZONE_OFFSET_SLOT, Int32Value(utcTZOffset));
double utcTime = UTCTime().toDouble();
if (!std::isfinite(utcTime)) { for (size_t ind = COMPONENTS_START_SLOT; ind < RESERVED_SLOTS; ind++) {
setReservedSlot(ind, DoubleValue(utcTime));
} return;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.