From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- js/src/jsdate.cpp | 3339 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3339 insertions(+) create mode 100644 js/src/jsdate.cpp (limited to 'js/src/jsdate.cpp') diff --git a/js/src/jsdate.cpp b/js/src/jsdate.cpp new file mode 100644 index 000000000..d73fb93d2 --- /dev/null +++ b/js/src/jsdate.cpp @@ -0,0 +1,3339 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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/ArrayUtils.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/Sprintf.h" + +#include +#include +#include + +#include "jsapi.h" +#include "jscntxt.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsprf.h" +#include "jsstr.h" +#include "jstypes.h" +#include "jsutil.h" +#include "jswrapper.h" + +#include "js/Conversions.h" +#include "js/Date.h" +#include "vm/DateTime.h" +#include "vm/GlobalObject.h" +#include "vm/Interpreter.h" +#include "vm/String.h" +#include "vm/StringBuffer.h" +#include "vm/Time.h" + +#include "jsobjinlines.h" + +using namespace js; + +using mozilla::ArrayLength; +using mozilla::IsFinite; +using mozilla::IsNaN; +using mozilla::NumbersAreIdentical; + +using JS::AutoCheckCannotGC; +using JS::ClippedTime; +using JS::GenericNaN; +using JS::TimeClip; +using JS::ToInteger; + +/* + * The JS 'Date' object is patterned after the Java 'Date' object. + * Here is a script: + * + * today = new Date(); + * + * print(today.toLocaleString()); + * + * weekDay = today.getDay(); + * + * + * These Java (and ECMA-262) methods are supported: + * + * UTC + * getDate (getUTCDate) + * getDay (getUTCDay) + * getHours (getUTCHours) + * getMinutes (getUTCMinutes) + * getMonth (getUTCMonth) + * getSeconds (getUTCSeconds) + * getMilliseconds (getUTCMilliseconds) + * getTime + * getTimezoneOffset + * getYear + * getFullYear (getUTCFullYear) + * parse + * setDate (setUTCDate) + * setHours (setUTCHours) + * setMinutes (setUTCMinutes) + * setMonth (setUTCMonth) + * setSeconds (setUTCSeconds) + * setMilliseconds (setUTCMilliseconds) + * setTime + * setYear (setFullYear, setUTCFullYear) + * toGMTString (toUTCString) + * toLocaleString + * toString + * + * + * These Java methods are not supported + * + * setDay + * before + * after + * equals + * hashCode + */ + +static inline double +Day(double t) +{ + return floor(t / msPerDay); +} + +static double +TimeWithinDay(double t) +{ + double result = fmod(t, msPerDay); + if (result < 0) + result += msPerDay; + return result; +} + +/* ES5 15.9.1.3. */ +static inline bool +IsLeapYear(double year) +{ + MOZ_ASSERT(ToInteger(year) == year); + return fmod(year, 4) == 0 && (fmod(year, 100) != 0 || fmod(year, 400) == 0); +} + +static inline double +DaysInYear(double year) +{ + if (!IsFinite(year)) + return GenericNaN(); + return IsLeapYear(year) ? 366 : 365; +} + +static inline double +DayFromYear(double y) +{ + return 365 * (y - 1970) + + floor((y - 1969) / 4.0) - + floor((y - 1901) / 100.0) + + floor((y - 1601) / 400.0); +} + +static inline double +TimeFromYear(double y) +{ + return DayFromYear(y) * msPerDay; +} + +static double +YearFromTime(double t) +{ + if (!IsFinite(t)) + return GenericNaN(); + + MOZ_ASSERT(ToInteger(t) == t); + + double y = floor(t / (msPerDay * 365.2425)) + 1970; + double t2 = TimeFromYear(y); + + /* + * Adjust the year if the approximation was wrong. Since the year was + * computed using the average number of ms per year, it will usually + * be wrong for dates within several hours of a year transition. + */ + if (t2 > t) { + y--; + } else { + if (t2 + msPerDay * DaysInYear(y) <= t) + y++; + } + return y; +} + +static inline int +DaysInFebruary(double year) +{ + return IsLeapYear(year) ? 29 : 28; +} + +/* ES5 15.9.1.4. */ +static inline double +DayWithinYear(double t, double year) +{ + MOZ_ASSERT_IF(IsFinite(t), YearFromTime(t) == year); + return Day(t) - DayFromYear(year); +} + +static double +MonthFromTime(double t) +{ + if (!IsFinite(t)) + return GenericNaN(); + + double year = YearFromTime(t); + double d = DayWithinYear(t, year); + + int step; + if (d < (step = 31)) + return 0; + if (d < (step += DaysInFebruary(year))) + return 1; + if (d < (step += 31)) + return 2; + if (d < (step += 30)) + return 3; + if (d < (step += 31)) + return 4; + if (d < (step += 30)) + return 5; + if (d < (step += 31)) + return 6; + if (d < (step += 31)) + return 7; + if (d < (step += 30)) + return 8; + if (d < (step += 31)) + return 9; + if (d < (step += 30)) + return 10; + return 11; +} + +/* ES5 15.9.1.5. */ +static double +DateFromTime(double t) +{ + if (!IsFinite(t)) + return GenericNaN(); + + double year = YearFromTime(t); + double d = DayWithinYear(t, year); + + int next; + if (d <= (next = 30)) + return d + 1; + int step = next; + if (d <= (next += DaysInFebruary(year))) + return d - step; + step = next; + if (d <= (next += 31)) + return d - step; + step = next; + if (d <= (next += 30)) + return d - step; + step = next; + if (d <= (next += 31)) + return d - step; + step = next; + if (d <= (next += 30)) + return d - step; + step = next; + if (d <= (next += 31)) + return d - step; + step = next; + if (d <= (next += 31)) + return d - step; + step = next; + if (d <= (next += 30)) + return d - step; + step = next; + if (d <= (next += 31)) + return d - step; + step = next; + if (d <= (next += 30)) + return d - step; + step = next; + return d - step; +} + +/* ES5 15.9.1.6. */ +static int +WeekDay(double t) +{ + /* + * We can't assert TimeClip(t) == t because we call this function with + * local times, which can be offset outside TimeClip's permitted range. + */ + MOZ_ASSERT(ToInteger(t) == t); + int result = (int(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 +static inline int +DayFromMonth(T month, bool isLeapYear) = delete; + +/* ES5 15.9.1.12 (out of order to accommodate DaylightSavingTA). */ +static double +MakeDay(double year, double month, double date) +{ + /* Step 1. */ + if (!IsFinite(year) || !IsFinite(month) || !IsFinite(date)) + return GenericNaN(); + + /* Steps 2-4. */ + double y = ToInteger(year); + double m = ToInteger(month); + double dt = ToInteger(date); + + /* Step 5. */ + double ym = y + floor(m / 12); + + /* Step 6. */ + int mn = int(fmod(m, 12.0)); + if (mn < 0) + mn += 12; + + /* Steps 7-8. */ + bool leap = IsLeapYear(ym); + + double yearday = floor(TimeFromYear(ym) / msPerDay); + double monthday = DayFromMonth(mn, leap); + + return yearday + monthday + dt - 1; +} + +/* ES5 15.9.1.13 (out of order to accommodate DaylightSavingTA). */ +static inline double +MakeDate(double day, double time) +{ + /* Step 1. */ + if (!IsFinite(day) || !IsFinite(time)) + return GenericNaN(); + + /* Step 2. */ + return day * msPerDay + time; +} + +JS_PUBLIC_API(double) +JS::MakeDate(double year, unsigned month, unsigned day) +{ + return ::MakeDate(MakeDay(year, month, day), 0); +} + +JS_PUBLIC_API(double) +JS::YearFromTime(double time) +{ + return ::YearFromTime(time); +} + +JS_PUBLIC_API(double) +JS::MonthFromTime(double time) +{ + return ::MonthFromTime(time); +} + +JS_PUBLIC_API(double) +JS::DayFromTime(double time) +{ + return DateFromTime(time); +} + +JS_PUBLIC_API(double) +JS::DayFromYear(double year) +{ + return ::DayFromYear(year); +} + +JS_PUBLIC_API(double) +JS::DayWithinYear(double time, double year) +{ + return ::DayWithinYear(time, year); +} + +/* + * 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. + */ +static int +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. + */ + static const int yearStartingWith[2][7] = { + {1978, 1973, 1974, 1975, 1981, 1971, 1977}, + {1984, 1996, 1980, 1992, 1976, 1988, 1972} + }; + + int day = int(DayFromYear(year) + 4) % 7; + if (day < 0) + day += 7; + + return yearStartingWith[IsLeapYear(year)][day]; +} + +/* ES5 15.9.1.8. */ +static double +DaylightSavingTA(double t) +{ + if (!IsFinite(t)) + return GenericNaN(); + + /* + * If earlier than 1970 or after 2038, potentially beyond the ken of + * many OSes, map it to an equivalent year before asking. + */ + if (t < 0.0 || t > 2145916800000.0) { + int year = EquivalentYearForDST(int(YearFromTime(t))); + double day = MakeDay(year, MonthFromTime(t), DateFromTime(t)); + t = MakeDate(day, TimeWithinDay(t)); + } + + int64_t utcMilliseconds = static_cast(t); + int64_t offsetMilliseconds = DateTimeInfo::getDSTOffsetMilliseconds(utcMilliseconds); + return static_cast(offsetMilliseconds); +} + +static double +AdjustTime(double date) +{ + double localTZA = DateTimeInfo::localTZA(); + double t = DaylightSavingTA(date) + localTZA; + t = (localTZA >= 0) ? fmod(t, msPerDay) : -fmod(msPerDay - t, msPerDay); + return t; +} + +/* ES5 15.9.1.9. */ +static double +LocalTime(double t) +{ + return t + AdjustTime(t); +} + +static double +UTC(double t) +{ + return t - AdjustTime(t - DateTimeInfo::localTZA()); +} + +/* ES5 15.9.1.10. */ +static double +HourFromTime(double t) +{ + double result = fmod(floor(t/msPerHour), HoursPerDay); + if (result < 0) + result += HoursPerDay; + return result; +} + +static double +MinFromTime(double t) +{ + double result = fmod(floor(t / msPerMinute), MinutesPerHour); + if (result < 0) + result += MinutesPerHour; + return result; +} + +static double +SecFromTime(double t) +{ + double result = fmod(floor(t / msPerSecond), SecondsPerMinute); + if (result < 0) + result += SecondsPerMinute; + return result; +} + +static double +msFromTime(double t) +{ + double result = fmod(t, msPerSecond); + if (result < 0) + result += msPerSecond; + return result; +} + +/* ES5 15.9.1.11. */ +static double +MakeTime(double hour, double min, double sec, double ms) +{ + /* Step 1. */ + if (!IsFinite(hour) || + !IsFinite(min) || + !IsFinite(sec) || + !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); + + /* Steps 6-7. */ + return h * msPerHour + m * msPerMinute + s * msPerSecond + milli; +} + +/** + * end of ECMA 'support' functions + */ + +/* for use by date_parse */ + +static const char* const wtb[] = { + "am", "pm", + "monday", "tuesday", "wednesday", "thursday", "friday", + "saturday", "sunday", + "january", "february", "march", "april", "may", "june", + "july", "august", "september", "october", "november", "december", + "gmt", "ut", "utc", + "est", "edt", + "cst", "cdt", + "mst", "mdt", + "pst", "pdt" + /* time zone table needs to be expanded */ +}; + +static const int ttb[] = { + -1, -2, 0, 0, 0, 0, 0, 0, 0, /* AM/PM */ + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 10000 + 0, 10000 + 0, 10000 + 0, /* GMT/UT/UTC */ + 10000 + 5 * 60, 10000 + 4 * 60, /* EST/EDT */ + 10000 + 6 * 60, 10000 + 5 * 60, /* CST/CDT */ + 10000 + 7 * 60, 10000 + 6 * 60, /* MST/MDT */ + 10000 + 8 * 60, 10000 + 7 * 60 /* PST/PDT */ +}; + +template +static bool +RegionMatches(const char* s1, int s1off, const CharT* s2, int s2off, int count) +{ + while (count > 0 && s1[s1off] && s2[s2off]) { + if (unicode::ToLowerCase(s1[s1off]) != unicode::ToLowerCase(s2[s2off])) + break; + + s1off++; + s2off++; + count--; + } + + return count == 0; +} + +/* ES6 20.3.3.4. */ +static bool +date_UTC(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Steps 1-2. + double y; + if (!ToNumber(cx, args.get(0), &y)) + return false; + + // Steps 3-4. + double m; + if (!ToNumber(cx, args.get(1), &m)) + return false; + + // Steps 5-6. + double dt; + if (args.length() >= 3) { + if (!ToNumber(cx, args[2], &dt)) + return false; + } else { + dt = 1; + } + + // Steps 7-8. + double h; + if (args.length() >= 4) { + if (!ToNumber(cx, args[3], &h)) + return false; + } else { + h = 0; + } + + // Steps 9-10. + double min; + if (args.length() >= 5) { + if (!ToNumber(cx, args[4], &min)) + return false; + } else { + min = 0; + } + + // Steps 11-12. + double s; + if (args.length() >= 6) { + if (!ToNumber(cx, args[5], &s)) + return false; + } else { + s = 0; + } + + // Steps 13-14. + double milli; + if (args.length() >= 7) { + if (!ToNumber(cx, args[6], &milli)) + return false; + } else { + milli = 0; + } + + // Step 15. + double yr = y; + 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().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 +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. + * + * Succeed if any digits are converted. Advance *i only + * as digits are consumed. + */ +template +static bool +ParseFractional(double* result, const CharT* s, size_t* i, size_t limit) +{ + double factor = 0.1; + size_t init = *i; + *result = 0.0; + while (*i < limit && ('0' <= s[*i] && s[*i] <= '9')) { + *result += (s[*i] - '0') * factor; + factor *= 0.1; + ++(*i); + } + 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 +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, 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 +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, Min(limit, init + n))) + return ((*i - init) > 0) && ((*i - init) <= n); + + *i = init; + return false; +} + +static int +DaysInMonth(int year, int month) +{ + bool leap = IsLeapYear(year); + int result = int(DayFromMonth(month, leap) - DayFromMonth(month - 1, leap)); + return result; +} + +/* + * Parse a string in one of the date-time formats given by the W3C + * "NOTE-datetime" specification. These formats make up a restricted + * profile of the ISO 8601 format. Quoted here: + * + * Any combination of the date formats with the time formats is + * allowed, and also either the date or the time can be missing. + * + * The specification is silent on the meaning when fields are + * ommitted so the interpretations are a guess, but hopefully a + * reasonable one. We default the month to January, the day to the + * 1st, and hours minutes and seconds all to 0. If the date is + * missing entirely then we assume 1970-01-01 so that the time can + * be aded to a date later. If the time is missing then we assume + * 00:00 UTC. If the time is present but the time zone field is + * missing then we use local time. + * + * For the sake of cross compatibility with other implementations we + * make a few exceptions to the standard: months, days, hours, minutes + * and seconds may be either one or two digits long, and the 'T' from + * the time part may be replaced with a space. Given that, a date time + * like "1999-1-1 1:1:1" will parse successfully. + * + * 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.sTZD (eg T19:20:30.45+01:00) + * + * where: + * + * YYYY = four-digit year or six digit year as +YYYYYY or -YYYYYY + * MM = one or two-digit month (01=January, etc.) + * DD = one or two-digit day of month (01 through 31) + * hh = one or two digits of hour (00 through 23) (am/pm NOT allowed) + * mm = one or two digits of minute (00 through 59) + * ss = one or two digits of second (00 through 59) + * s = 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 +static bool +ParseISOStyleDate(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; + double frac = 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; } + +#define NEED_NDIGITS_OR_LESS(n, field) \ + if (!ParseDigitsNOrLess(n, &field, s, &i, length)) { return false; } + + if (PEEK('+') || PEEK('-')) { + if (PEEK('-')) + dateMul = -1; + ++i; + NEED_NDIGITS(6, year); + } else if (!PEEK('T')) { + NEED_NDIGITS(4, year); + } + DONE_DATE_UNLESS('-'); + NEED_NDIGITS_OR_LESS(2, month); + DONE_DATE_UNLESS('-'); + NEED_NDIGITS_OR_LESS(2, day); + + done_date: + if (PEEK('T') || PEEK(' ')) + i++; + else + goto done; + + NEED_NDIGITS_OR_LESS(2, hour); + NEED(':'); + NEED_NDIGITS_OR_LESS(2, min); + + if (PEEK(':')) { + ++i; + NEED_NDIGITS_OR_LESS(2, sec); + if (PEEK('.')) { + ++i; + if (!ParseFractional(&frac, 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 > size_t(DaysInMonth(year,month))) + || hour > 24 + || ((hour == 24) && (min > 0 || sec > 0 || frac > 0)) + || min > 59 + || sec > 59 + || tzHour > 23 + || tzMin > 59) + { + return false; + } + + if (i != length) + return false; + + month -= 1; /* convert month to 0-based */ + + double msec = MakeDate(MakeDay(dateMul * double(year), month, day), + MakeTime(hour, min, sec, frac * 1000.0)); + + if (isLocalTime) + msec = UTC(msec); + else + msec -= tzMul * (tzHour * msPerHour + tzMin * msPerMinute); + + *result = TimeClip(msec); + return NumbersAreIdentical(msec, result->toDouble()); + +#undef PEEK +#undef NEED +#undef DONE_UNLESS +#undef NEED_NDIGITS +} + +template +static bool +ParseDate(const CharT* s, size_t length, ClippedTime* result) +{ + if (ParseISOStyleDate(s, length, result)) + return true; + + if (length == 0) + return false; + + int year = -1; + int mon = -1; + int mday = -1; + int hour = -1; + int min = -1; + int sec = -1; + int tzOffset = -1; + + int prevc = 0; + + bool seenPlusMinus = false; + bool seenMonthName = false; + + size_t i = 0; + while (i < length) { + int c = s[i]; + i++; + if (c <= ' ' || c == ',' || c == '-') { + if (c == '-' && '0' <= s[i] && s[i] <= '9') + prevc = c; + continue; + } + if (c == '(') { /* comments) */ + int depth = 1; + while (i < length) { + c = s[i]; + i++; + if (c == '(') { + depth++; + } else if (c == ')') { + if (--depth <= 0) + break; + } + } + continue; + } + if ('0' <= c && c <= '9') { + int n = c - '0'; + while (i < length && '0' <= (c = s[i]) && c <= '9') { + n = n * 10 + c - '0'; + i++; + } + + /* + * 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 == '+' || prevc == '-')/* && year>=0 */) { + /* Make ':' case below change tzOffset. */ + seenPlusMinus = true; + + /* offset */ + if (n < 24) + 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; + + if (tzOffset != 0 && tzOffset != -1) + return false; + + tzOffset = n; + } else if (prevc == '/' && mon >= 0 && mday >= 0 && year < 0) { + if (c <= ' ' || c == ',' || c == '/' || i >= length) + year = n; + else + return false; + } else if (c == ':') { + if (hour < 0) + hour = /*byte*/ n; + else if (min < 0) + min = /*byte*/ n; + else + return false; + } else if (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; + else if (mday < 0) + mday = /*byte*/ n; + else + return false; + } else if (i < length && c != ',' && c > ' ' && c != '-' && c != '(') { + return false; + } else if (seenPlusMinus && n < 60) { /* handle GMT-3:30 */ + if (tzOffset < 0) + tzOffset -= n; + else + tzOffset += n; + } else if (hour >= 0 && min < 0) { + min = /*byte*/ n; + } else if (prevc == ':' && min >= 0 && sec < 0) { + sec = /*byte*/ n; + } else if (mon < 0) { + mon = /*byte*/n; + } else if (mon >= 0 && mday < 0) { + mday = /*byte*/ n; + } else if (mon >= 0 && mday >= 0 && year < 0) { + year = n; + } else { + return false; + } + prevc = 0; + } else if (c == '/' || c == ':' || c == '+' || c == '-') { + prevc = c; + } else { + size_t st = i - 1; + while (i < length) { + c = s[i]; + if (!(('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'))) + break; + i++; + } + + if (i <= st + 1) + return false; + + int k; + for (k = ArrayLength(wtb); --k >= 0;) { + if (RegionMatches(wtb[k], 0, s, st, i - st)) { + int action = ttb[k]; + if (action != 0) { + if (action < 0) { + /* + * AM/PM. Count 12:30 AM as 00:30, 12:30 PM as + * 12:30, instead of blindly adding 12 if PM. + */ + MOZ_ASSERT(action == -1 || action == -2); + if (hour > 12 || hour < 0) + return false; + + if (action == -1 && hour == 12) /* am */ + hour = 0; + else if (action == -2 && hour != 12) /* pm */ + hour += 12; + } else if (action <= 13) { /* month! */ + /* + * Adjust mon to be 1-based until the final values + * for mon, mday and year are adjusted below. + */ + if (seenMonthName) + return false; + + seenMonthName = true; + int temp = /*byte*/ (action - 2) + 1; + + if (mon < 0) { + mon = temp; + } else if (mday < 0) { + mday = mon; + mon = temp; + } else if (year < 0) { + year = mon; + mon = temp; + } else { + return false; + } + } else { + tzOffset = action - 10000; + } + } + break; + } + } + + if (k < 0) + return false; + + prevc = 0; + } + } + + if (year < 0 || mon < 0 || mday < 0) + return false; + + /* + * 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 the greater of the values f, l or + * whichever is set to zero. If the year is greater than or equal to + * 50 and less than 100, it is considered to be the number of years + * after 1900. If the year is less than 50 it is considered to be the + * number of years after 2000, otherwise it is considered to be the + * number of years after 0. + * + * 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. + * i. If year < 50, it is the number of years after 2000 + * ii. If year >= 50, it is the number of years after 1900. + * iii. If year >= 100, it is the number of years after 0. + * b. If 31 < f and 0 < m <= 12 and 0 < l <= 31 f/m/l is + * interpreted as year/month/day + * i. If year < 50, it is the number of years after 2000 + * ii. If year >= 50, it is the number of years after 1900. + * iii. If year >= 100, it is the number of years after 0. + */ + if (seenMonthName) { + if (mday >= 100 && mon >= 100) + return false; + + if (year > 0 && (mday == 0 || mday > year)) { + int temp = year; + year = mday; + mday = temp; + } + + if (mday <= 0 || mday > 31) + return false; + + } else if (0 < mon && mon <= 12 && 0 < mday && mday <= 31) { + /* (a) month/day/year */ + } else { + /* (b) year/month/day */ + if (mon > 31 && mday <= 12 && year <= 31) { + int temp = year; + year = mon; + mon = mday; + mday = temp; + } else { + return false; + } + } + + if (year < 50) + year += 2000; + else if (year >= 50 && year < 100) + year += 1900; + + mon -= 1; /* convert month to 0-based */ + if (sec < 0) + sec = 0; + if (min < 0) + min = 0; + if (hour < 0) + hour = 0; + + double msec = MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0)); + + if (tzOffset == -1) /* no time zone specified, have to use local */ + msec = UTC(msec); + else + msec += tzOffset * msPerMinute; + + *result = TimeClip(msec); + return true; +} + +static bool +ParseDate(JSLinearString* s, ClippedTime* result) +{ + AutoCheckCannotGC nogc; + return s->hasLatin1Chars() + ? ParseDate(s->latin1Chars(nogc), s->length(), result) + : ParseDate(s->twoByteChars(nogc), s->length(), result); +} + +static bool +date_parse(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() == 0) { + args.rval().setNaN(); + return true; + } + + JSString* str = ToString(cx, args[0]); + if (!str) + return false; + + JSLinearString* linearStr = str->ensureLinear(cx); + if (!linearStr) + return false; + + ClippedTime result; + if (!ParseDate(linearStr, &result)) { + args.rval().setNaN(); + return true; + } + + args.rval().set(TimeValue(result)); + return true; +} + +static ClippedTime +NowAsMillis() +{ + return TimeClip(static_cast(PRMJ_Now()) / PRMJ_USEC_PER_MSEC); +} + +bool +js::date_now(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + 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, TimeValue(t)); +} + +void +DateObject::setUTCTime(ClippedTime t, MutableHandleValue vp) +{ + setUTCTime(t); + vp.set(TimeValue(t)); +} + +void +DateObject::fillLocalTimeSlots() +{ + /* Check if the cache is already populated. */ + if (!getReservedSlot(LOCAL_TIME_SLOT).isUndefined() && + getReservedSlot(TZA_SLOT).toDouble() == DateTimeInfo::localTZA()) + { + return; + } + + /* Remember time zone used to generate the local cache. */ + setReservedSlot(TZA_SLOT, DoubleValue(DateTimeInfo::localTZA())); + + double utcTime = UTCTime().toNumber(); + + if (!IsFinite(utcTime)) { + for (size_t ind = COMPONENTS_START_SLOT; ind < RESERVED_SLOTS; ind++) + setReservedSlot(ind, DoubleValue(utcTime)); + return; + } + + double localTime = LocalTime(utcTime); + + setReservedSlot(LOCAL_TIME_SLOT, DoubleValue(localTime)); + + int year = (int) floor(localTime /(msPerDay * 365.2425)) + 1970; + double yearStartTime = TimeFromYear(year); + + /* Adjust the year in case the approximation was wrong, as in YearFromTime. */ + int yearDays; + if (yearStartTime > localTime) { + year--; + yearStartTime -= (msPerDay * DaysInYear(year)); + yearDays = DaysInYear(year); + } else { + yearDays = DaysInYear(year); + double nextStart = yearStartTime + (msPerDay * yearDays); + if (nextStart <= localTime) { + year++; + yearStartTime = nextStart; + yearDays = DaysInYear(year); + } + } + + setReservedSlot(LOCAL_YEAR_SLOT, Int32Value(year)); + + uint64_t yearTime = uint64_t(localTime - yearStartTime); + int yearSeconds = uint32_t(yearTime / 1000); + + int day = yearSeconds / int(SecondsPerDay); + + int step = -1, next = 30; + int month; + + do { + if (day <= next) { + month = 0; + break; + } + step = next; + next += ((yearDays == 366) ? 29 : 28); + if (day <= next) { + month = 1; + break; + } + step = next; + if (day <= (next += 31)) { + month = 2; + break; + } + step = next; + if (day <= (next += 30)) { + month = 3; + break; + } + step = next; + if (day <= (next += 31)) { + month = 4; + break; + } + step = next; + if (day <= (next += 30)) { + month = 5; + break; + } + step = next; + if (day <= (next += 31)) { + month = 6; + break; + } + step = next; + if (day <= (next += 31)) { + month = 7; + break; + } + step = next; + if (day <= (next += 30)) { + month = 8; + break; + } + step = next; + if (day <= (next += 31)) { + month = 9; + break; + } + step = next; + if (day <= (next += 30)) { + month = 10; + break; + } + step = next; + month = 11; + } while (0); + + setReservedSlot(LOCAL_MONTH_SLOT, Int32Value(month)); + setReservedSlot(LOCAL_DATE_SLOT, Int32Value(day - step)); + + int weekday = WeekDay(localTime); + setReservedSlot(LOCAL_DAY_SLOT, Int32Value(weekday)); + + setReservedSlot(LOCAL_SECONDS_INTO_YEAR_SLOT, Int32Value(yearSeconds)); +} + +inline double +DateObject::cachedLocalTime() +{ + fillLocalTimeSlots(); + return getReservedSlot(LOCAL_TIME_SLOT).toDouble(); +} + +MOZ_ALWAYS_INLINE bool +IsDate(HandleValue v) +{ + return v.isObject() && v.toObject().is(); +} + +/* + * See ECMA 15.9.5.4 thru 15.9.5.23 + */ +/* static */ MOZ_ALWAYS_INLINE bool +DateObject::getTime_impl(JSContext* cx, const CallArgs& args) +{ + args.rval().set(args.thisv().toObject().as().UTCTime()); + return true; +} + +static bool +date_getTime(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* static */ MOZ_ALWAYS_INLINE bool +DateObject::getYear_impl(JSContext* cx, const CallArgs& args) +{ + DateObject* dateObj = &args.thisv().toObject().as(); + dateObj->fillLocalTimeSlots(); + + Value yearVal = dateObj->getReservedSlot(LOCAL_YEAR_SLOT); + if (yearVal.isInt32()) { + /* Follow ECMA-262 to the letter, contrary to IE JScript. */ + int year = yearVal.toInt32() - 1900; + args.rval().setInt32(year); + } else { + args.rval().set(yearVal); + } + + return true; +} + +static bool +date_getYear(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* static */ MOZ_ALWAYS_INLINE bool +DateObject::getFullYear_impl(JSContext* cx, const CallArgs& args) +{ + DateObject* dateObj = &args.thisv().toObject().as(); + dateObj->fillLocalTimeSlots(); + + args.rval().set(dateObj->getReservedSlot(LOCAL_YEAR_SLOT)); + return true; +} + +static bool +date_getFullYear(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* static */ MOZ_ALWAYS_INLINE bool +DateObject::getUTCFullYear_impl(JSContext* cx, const CallArgs& args) +{ + double result = args.thisv().toObject().as().UTCTime().toNumber(); + if (IsFinite(result)) + result = YearFromTime(result); + + args.rval().setNumber(result); + return true; +} + +static bool +date_getUTCFullYear(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* static */ MOZ_ALWAYS_INLINE bool +DateObject::getMonth_impl(JSContext* cx, const CallArgs& args) +{ + DateObject* dateObj = &args.thisv().toObject().as(); + dateObj->fillLocalTimeSlots(); + + args.rval().set(dateObj->getReservedSlot(LOCAL_MONTH_SLOT)); + return true; +} + +static bool +date_getMonth(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* static */ MOZ_ALWAYS_INLINE bool +DateObject::getUTCMonth_impl(JSContext* cx, const CallArgs& args) +{ + double d = args.thisv().toObject().as().UTCTime().toNumber(); + args.rval().setNumber(MonthFromTime(d)); + return true; +} + +static bool +date_getUTCMonth(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* static */ MOZ_ALWAYS_INLINE bool +DateObject::getDate_impl(JSContext* cx, const CallArgs& args) +{ + DateObject* dateObj = &args.thisv().toObject().as(); + dateObj->fillLocalTimeSlots(); + + args.rval().set(dateObj->getReservedSlot(LOCAL_DATE_SLOT)); + return true; +} + +static bool +date_getDate(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* static */ MOZ_ALWAYS_INLINE bool +DateObject::getUTCDate_impl(JSContext* cx, const CallArgs& args) +{ + double result = args.thisv().toObject().as().UTCTime().toNumber(); + if (IsFinite(result)) + result = DateFromTime(result); + + args.rval().setNumber(result); + return true; +} + +static bool +date_getUTCDate(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* static */ MOZ_ALWAYS_INLINE bool +DateObject::getDay_impl(JSContext* cx, const CallArgs& args) +{ + DateObject* dateObj = &args.thisv().toObject().as(); + dateObj->fillLocalTimeSlots(); + + args.rval().set(dateObj->getReservedSlot(LOCAL_DAY_SLOT)); + return true; +} + +static bool +date_getDay(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* static */ MOZ_ALWAYS_INLINE bool +DateObject::getUTCDay_impl(JSContext* cx, const CallArgs& args) +{ + double result = args.thisv().toObject().as().UTCTime().toNumber(); + if (IsFinite(result)) + result = WeekDay(result); + + args.rval().setNumber(result); + return true; +} + +static bool +date_getUTCDay(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* static */ MOZ_ALWAYS_INLINE bool +DateObject::getHours_impl(JSContext* cx, const CallArgs& args) +{ + DateObject* dateObj = &args.thisv().toObject().as(); + dateObj->fillLocalTimeSlots(); + + // Note: LOCAL_SECONDS_INTO_YEAR_SLOT is guaranteed to contain an + // int32 or NaN after the call to fillLocalTimeSlots. + Value yearSeconds = dateObj->getReservedSlot(LOCAL_SECONDS_INTO_YEAR_SLOT); + if (yearSeconds.isDouble()) { + MOZ_ASSERT(IsNaN(yearSeconds.toDouble())); + args.rval().set(yearSeconds); + } else { + args.rval().setInt32((yearSeconds.toInt32() / int(SecondsPerHour)) % int(HoursPerDay)); + } + return true; +} + +static bool +date_getHours(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* static */ MOZ_ALWAYS_INLINE bool +DateObject::getUTCHours_impl(JSContext* cx, const CallArgs& args) +{ + double result = args.thisv().toObject().as().UTCTime().toNumber(); + if (IsFinite(result)) + result = HourFromTime(result); + + args.rval().setNumber(result); + return true; +} + +static bool +date_getUTCHours(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* static */ MOZ_ALWAYS_INLINE bool +DateObject::getMinutes_impl(JSContext* cx, const CallArgs& args) +{ + DateObject* dateObj = &args.thisv().toObject().as(); + dateObj->fillLocalTimeSlots(); + + // Note: LOCAL_SECONDS_INTO_YEAR_SLOT is guaranteed to contain an + // int32 or NaN after the call to fillLocalTimeSlots. + Value yearSeconds = dateObj->getReservedSlot(LOCAL_SECONDS_INTO_YEAR_SLOT); + if (yearSeconds.isDouble()) { + MOZ_ASSERT(IsNaN(yearSeconds.toDouble())); + args.rval().set(yearSeconds); + } else { + args.rval().setInt32((yearSeconds.toInt32() / int(SecondsPerMinute)) % int(MinutesPerHour)); + } + return true; +} + +static bool +date_getMinutes(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* static */ MOZ_ALWAYS_INLINE bool +DateObject::getUTCMinutes_impl(JSContext* cx, const CallArgs& args) +{ + double result = args.thisv().toObject().as().UTCTime().toNumber(); + if (IsFinite(result)) + result = MinFromTime(result); + + args.rval().setNumber(result); + return true; +} + +static bool +date_getUTCMinutes(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* + * Date.getSeconds is mapped to getUTCSeconds. As long as no supported time + * zone has a fractional-minute component, the differences in their + * specifications aren't observable. + * + * We'll have to split the implementations if a new time zone with a + * fractional-minute component is introduced or once we implement ES6's + * 20.3.1.7 Local Time Zone Adjustment: time zones with adjustments like that + * did historically exist, e.g. + * https://en.wikipedia.org/wiki/UTC%E2%88%9200:25:21 + */ + +/* static */ MOZ_ALWAYS_INLINE bool +DateObject::getUTCSeconds_impl(JSContext* cx, const CallArgs& args) +{ + DateObject* dateObj = &args.thisv().toObject().as(); + dateObj->fillLocalTimeSlots(); + + // Note: LOCAL_SECONDS_INTO_YEAR_SLOT is guaranteed to contain an + // int32 or NaN after the call to fillLocalTimeSlots. + Value yearSeconds = dateObj->getReservedSlot(LOCAL_SECONDS_INTO_YEAR_SLOT); + if (yearSeconds.isDouble()) { + MOZ_ASSERT(IsNaN(yearSeconds.toDouble())); + args.rval().set(yearSeconds); + } else { + args.rval().setInt32(yearSeconds.toInt32() % int(SecondsPerMinute)); + } + return true; +} + +static bool +date_getUTCSeconds(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} +/* + * Date.getMilliseconds is mapped to getUTCMilliseconds for the same reasons + * that getSeconds is mapped to getUTCSeconds (see above). No known LocalTZA + * has *ever* included a fractional-second component, however, so we can keep + * this simplification even if we stop implementing ES5 local-time computation + * semantics. + */ + +/* static */ MOZ_ALWAYS_INLINE bool +DateObject::getUTCMilliseconds_impl(JSContext* cx, const CallArgs& args) +{ + double result = args.thisv().toObject().as().UTCTime().toNumber(); + if (IsFinite(result)) + result = msFromTime(result); + + args.rval().setNumber(result); + return true; +} + +static bool +date_getUTCMilliseconds(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* static */ MOZ_ALWAYS_INLINE bool +DateObject::getTimezoneOffset_impl(JSContext* cx, const CallArgs& args) +{ + DateObject* dateObj = &args.thisv().toObject().as(); + double utctime = dateObj->UTCTime().toNumber(); + double localtime = dateObj->cachedLocalTime(); + + /* + * Return the time zone offset in minutes for the current locale that is + * appropriate for this time. This value would be a constant except for + * daylight savings time. + */ + double result = (utctime - localtime) / msPerMinute; + args.rval().setNumber(result); + return true; +} + +static bool +date_getTimezoneOffset(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +MOZ_ALWAYS_INLINE bool +date_setTime_impl(JSContext* cx, const CallArgs& args) +{ + Rooted dateObj(cx, &args.thisv().toObject().as()); + if (args.length() == 0) { + dateObj->setUTCTime(ClippedTime::invalid(), args.rval()); + return true; + } + + double result; + if (!ToNumber(cx, args[0], &result)) + return false; + + dateObj->setUTCTime(TimeClip(result), args.rval()); + return true; +} + +static bool +date_setTime(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +static bool +GetMsecsOrDefault(JSContext* cx, const CallArgs& args, unsigned i, double t, double* millis) +{ + if (args.length() <= i) { + *millis = msFromTime(t); + return true; + } + return ToNumber(cx, args[i], millis); +} + +static bool +GetSecsOrDefault(JSContext* cx, const CallArgs& args, unsigned i, double t, double* sec) +{ + if (args.length() <= i) { + *sec = SecFromTime(t); + return true; + } + return ToNumber(cx, args[i], sec); +} + +static bool +GetMinsOrDefault(JSContext* cx, const CallArgs& args, unsigned i, double t, double* mins) +{ + if (args.length() <= i) { + *mins = MinFromTime(t); + return true; + } + return ToNumber(cx, args[i], mins); +} + +/* ES6 20.3.4.23. */ +MOZ_ALWAYS_INLINE bool +date_setMilliseconds_impl(JSContext* cx, const CallArgs& args) +{ + Rooted dateObj(cx, &args.thisv().toObject().as()); + + // Steps 1-2. + double t = LocalTime(dateObj->UTCTime().toNumber()); + + // Steps 3-4. + double ms; + if (!ToNumber(cx, args.get(0), &ms)) + return false; + + // Step 5. + double time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms); + + // Step 6. + ClippedTime u = TimeClip(UTC(MakeDate(Day(t), time))); + + // Steps 7-8. + dateObj->setUTCTime(u, args.rval()); + return true; +} + +static bool +date_setMilliseconds(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* ES5 15.9.5.29. */ +MOZ_ALWAYS_INLINE bool +date_setUTCMilliseconds_impl(JSContext* cx, const CallArgs& args) +{ + Rooted dateObj(cx, &args.thisv().toObject().as()); + + /* Step 1. */ + double t = dateObj->UTCTime().toNumber(); + + /* Step 2. */ + double milli; + if (!ToNumber(cx, args.get(0), &milli)) + return false; + double time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), milli); + + /* Step 3. */ + ClippedTime v = TimeClip(MakeDate(Day(t), time)); + + /* Steps 4-5. */ + dateObj->setUTCTime(v, args.rval()); + return true; +} + +static bool +date_setUTCMilliseconds(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* ES5 15.9.5.30. */ +MOZ_ALWAYS_INLINE bool +date_setSeconds_impl(JSContext* cx, const CallArgs& args) +{ + Rooted dateObj(cx, &args.thisv().toObject().as()); + + // Steps 1-2. + double t = LocalTime(dateObj->UTCTime().toNumber()); + + // Steps 3-4. + double s; + if (!ToNumber(cx, args.get(0), &s)) + return false; + + // Steps 5-6. + double milli; + if (!GetMsecsOrDefault(cx, args, 1, t, &milli)) + return false; + + // Step 7. + double date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli)); + + // Step 8. + ClippedTime u = TimeClip(UTC(date)); + + // Step 9. + dateObj->setUTCTime(u, args.rval()); + return true; +} + +/* ES6 20.3.4.26. */ +static bool +date_setSeconds(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +MOZ_ALWAYS_INLINE bool +date_setUTCSeconds_impl(JSContext* cx, const CallArgs& args) +{ + Rooted dateObj(cx, &args.thisv().toObject().as()); + + /* Step 1. */ + double t = dateObj->UTCTime().toNumber(); + + /* Step 2. */ + double s; + if (!ToNumber(cx, args.get(0), &s)) + return false; + + /* Step 3. */ + double milli; + if (!GetMsecsOrDefault(cx, args, 1, t, &milli)) + return false; + + /* Step 4. */ + double date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli)); + + /* Step 5. */ + ClippedTime v = TimeClip(date); + + /* Steps 6-7. */ + dateObj->setUTCTime(v, args.rval()); + return true; +} + +/* ES5 15.9.5.32. */ +static bool +date_setUTCSeconds(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +MOZ_ALWAYS_INLINE bool +date_setMinutes_impl(JSContext* cx, const CallArgs& args) +{ + Rooted dateObj(cx, &args.thisv().toObject().as()); + + // Steps 1-2. + double t = LocalTime(dateObj->UTCTime().toNumber()); + + // Steps 3-4. + double m; + if (!ToNumber(cx, args.get(0), &m)) + return false; + + // Steps 5-6. + double s; + if (!GetSecsOrDefault(cx, args, 1, t, &s)) + return false; + + // Steps 7-8. + double milli; + if (!GetMsecsOrDefault(cx, args, 2, t, &milli)) + return false; + + // Step 9. + double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli)); + + // Step 10. + ClippedTime u = TimeClip(UTC(date)); + + // Steps 11-12. + dateObj->setUTCTime(u, args.rval()); + return true; +} + +/* ES6 20.3.4.24. */ +static bool +date_setMinutes(JSContext* cx, unsigned argc, Value* vp) +{ + // Steps 1-2 (the effectful parts). + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +MOZ_ALWAYS_INLINE bool +date_setUTCMinutes_impl(JSContext* cx, const CallArgs& args) +{ + Rooted dateObj(cx, &args.thisv().toObject().as()); + + /* Step 1. */ + double t = dateObj->UTCTime().toNumber(); + + /* Step 2. */ + double m; + if (!ToNumber(cx, args.get(0), &m)) + return false; + + /* Step 3. */ + double s; + if (!GetSecsOrDefault(cx, args, 1, t, &s)) + return false; + + /* Step 4. */ + double milli; + if (!GetMsecsOrDefault(cx, args, 2, t, &milli)) + return false; + + /* Step 5. */ + double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli)); + + /* Step 6. */ + ClippedTime v = TimeClip(date); + + /* Steps 7-8. */ + dateObj->setUTCTime(v, args.rval()); + return true; +} + +/* ES5 15.9.5.34. */ +static bool +date_setUTCMinutes(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +MOZ_ALWAYS_INLINE bool +date_setHours_impl(JSContext* cx, const CallArgs& args) +{ + Rooted dateObj(cx, &args.thisv().toObject().as()); + + // Steps 1-2. + double t = LocalTime(dateObj->UTCTime().toNumber()); + + // Steps 3-4. + double h; + if (!ToNumber(cx, args.get(0), &h)) + return false; + + // Steps 5-6. + double m; + if (!GetMinsOrDefault(cx, args, 1, t, &m)) + return false; + + // Steps 7-8. + double s; + if (!GetSecsOrDefault(cx, args, 2, t, &s)) + return false; + + // Steps 9-10. + double milli; + if (!GetMsecsOrDefault(cx, args, 3, t, &milli)) + return false; + + // Step 11. + double date = MakeDate(Day(t), MakeTime(h, m, s, milli)); + + // Step 12. + ClippedTime u = TimeClip(UTC(date)); + + // Steps 13-14. + dateObj->setUTCTime(u, args.rval()); + return true; +} + +/* ES5 15.9.5.35. */ +static bool +date_setHours(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +MOZ_ALWAYS_INLINE bool +date_setUTCHours_impl(JSContext* cx, const CallArgs& args) +{ + Rooted dateObj(cx, &args.thisv().toObject().as()); + + /* Step 1. */ + double t = dateObj->UTCTime().toNumber(); + + /* Step 2. */ + double h; + if (!ToNumber(cx, args.get(0), &h)) + return false; + + /* Step 3. */ + double m; + if (!GetMinsOrDefault(cx, args, 1, t, &m)) + return false; + + /* Step 4. */ + double s; + if (!GetSecsOrDefault(cx, args, 2, t, &s)) + return false; + + /* Step 5. */ + double milli; + if (!GetMsecsOrDefault(cx, args, 3, t, &milli)) + return false; + + /* Step 6. */ + double newDate = MakeDate(Day(t), MakeTime(h, m, s, milli)); + + /* Step 7. */ + ClippedTime v = TimeClip(newDate); + + /* Steps 8-9. */ + dateObj->setUTCTime(v, args.rval()); + return true; +} + +/* ES5 15.9.5.36. */ +static bool +date_setUTCHours(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +MOZ_ALWAYS_INLINE bool +date_setDate_impl(JSContext* cx, const CallArgs& args) +{ + Rooted dateObj(cx, &args.thisv().toObject().as()); + + /* Step 1. */ + double t = LocalTime(dateObj->UTCTime().toNumber()); + + /* Step 2. */ + double date; + if (!ToNumber(cx, args.get(0), &date)) + return false; + + /* Step 3. */ + double newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date), TimeWithinDay(t)); + + /* Step 4. */ + ClippedTime u = TimeClip(UTC(newDate)); + + /* Steps 5-6. */ + dateObj->setUTCTime(u, args.rval()); + return true; +} + +/* ES5 15.9.5.37. */ +static bool +date_setDate(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +MOZ_ALWAYS_INLINE bool +date_setUTCDate_impl(JSContext* cx, const CallArgs& args) +{ + Rooted dateObj(cx, &args.thisv().toObject().as()); + + /* Step 1. */ + double t = dateObj->UTCTime().toNumber(); + + /* Step 2. */ + double date; + if (!ToNumber(cx, args.get(0), &date)) + return false; + + /* Step 3. */ + double newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date), TimeWithinDay(t)); + + /* Step 4. */ + ClippedTime v = TimeClip(newDate); + + /* Steps 5-6. */ + dateObj->setUTCTime(v, args.rval()); + return true; +} + +static bool +date_setUTCDate(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +static bool +GetDateOrDefault(JSContext* cx, const CallArgs& args, unsigned i, double t, double* date) +{ + if (args.length() <= i) { + *date = DateFromTime(t); + return true; + } + return ToNumber(cx, args[i], date); +} + +static bool +GetMonthOrDefault(JSContext* cx, const CallArgs& args, unsigned i, double t, double* month) +{ + if (args.length() <= i) { + *month = MonthFromTime(t); + return true; + } + return ToNumber(cx, args[i], month); +} + +/* ES5 15.9.5.38. */ +MOZ_ALWAYS_INLINE bool +date_setMonth_impl(JSContext* cx, const CallArgs& args) +{ + Rooted dateObj(cx, &args.thisv().toObject().as()); + + /* Step 1. */ + double t = LocalTime(dateObj->UTCTime().toNumber()); + + /* Step 2. */ + double m; + if (!ToNumber(cx, args.get(0), &m)) + return false; + + /* Step 3. */ + double date; + if (!GetDateOrDefault(cx, args, 1, t, &date)) + return false; + + /* Step 4. */ + double newDate = MakeDate(MakeDay(YearFromTime(t), m, date), TimeWithinDay(t)); + + /* Step 5. */ + ClippedTime u = TimeClip(UTC(newDate)); + + /* Steps 6-7. */ + dateObj->setUTCTime(u, args.rval()); + return true; +} + +static bool +date_setMonth(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* ES5 15.9.5.39. */ +MOZ_ALWAYS_INLINE bool +date_setUTCMonth_impl(JSContext* cx, const CallArgs& args) +{ + Rooted dateObj(cx, &args.thisv().toObject().as()); + + /* Step 1. */ + double t = dateObj->UTCTime().toNumber(); + + /* Step 2. */ + double m; + if (!ToNumber(cx, args.get(0), &m)) + return false; + + /* Step 3. */ + double date; + if (!GetDateOrDefault(cx, args, 1, t, &date)) + return false; + + /* Step 4. */ + double newDate = MakeDate(MakeDay(YearFromTime(t), m, date), TimeWithinDay(t)); + + /* Step 5. */ + ClippedTime v = TimeClip(newDate); + + /* Steps 6-7. */ + dateObj->setUTCTime(v, args.rval()); + return true; +} + +static bool +date_setUTCMonth(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +static double +ThisLocalTimeOrZero(Handle dateObj) +{ + double t = dateObj->UTCTime().toNumber(); + if (IsNaN(t)) + return +0; + return LocalTime(t); +} + +static double +ThisUTCTimeOrZero(Handle dateObj) +{ + double t = dateObj->as().UTCTime().toNumber(); + return IsNaN(t) ? +0 : t; +} + +/* ES5 15.9.5.40. */ +MOZ_ALWAYS_INLINE bool +date_setFullYear_impl(JSContext* cx, const CallArgs& args) +{ + Rooted dateObj(cx, &args.thisv().toObject().as()); + + /* Step 1. */ + double t = ThisLocalTimeOrZero(dateObj); + + /* Step 2. */ + double y; + if (!ToNumber(cx, args.get(0), &y)) + return false; + + /* Step 3. */ + double m; + if (!GetMonthOrDefault(cx, args, 1, t, &m)) + return false; + + /* Step 4. */ + double date; + if (!GetDateOrDefault(cx, args, 2, t, &date)) + return false; + + /* Step 5. */ + double newDate = MakeDate(MakeDay(y, m, date), TimeWithinDay(t)); + + /* Step 6. */ + ClippedTime u = TimeClip(UTC(newDate)); + + /* Steps 7-8. */ + dateObj->setUTCTime(u, args.rval()); + return true; +} + +static bool +date_setFullYear(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* ES5 15.9.5.41. */ +MOZ_ALWAYS_INLINE bool +date_setUTCFullYear_impl(JSContext* cx, const CallArgs& args) +{ + Rooted dateObj(cx, &args.thisv().toObject().as()); + + /* Step 1. */ + double t = ThisUTCTimeOrZero(dateObj); + + /* Step 2. */ + double y; + if (!ToNumber(cx, args.get(0), &y)) + return false; + + /* Step 3. */ + double m; + if (!GetMonthOrDefault(cx, args, 1, t, &m)) + return false; + + /* Step 4. */ + double date; + if (!GetDateOrDefault(cx, args, 2, t, &date)) + return false; + + /* Step 5. */ + double newDate = MakeDate(MakeDay(y, m, date), TimeWithinDay(t)); + + /* Step 6. */ + ClippedTime v = TimeClip(newDate); + + /* Steps 7-8. */ + dateObj->setUTCTime(v, args.rval()); + return true; +} + +static bool +date_setUTCFullYear(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* ES5 Annex B.2.5. */ +MOZ_ALWAYS_INLINE bool +date_setYear_impl(JSContext* cx, const CallArgs& args) +{ + Rooted dateObj(cx, &args.thisv().toObject().as()); + + /* Step 1. */ + double t = ThisLocalTimeOrZero(dateObj); + + /* Step 2. */ + double y; + if (!ToNumber(cx, args.get(0), &y)) + return false; + + /* Step 3. */ + if (IsNaN(y)) { + dateObj->setUTCTime(ClippedTime::invalid(), args.rval()); + return true; + } + + /* Step 4. */ + double yint = ToInteger(y); + if (0 <= yint && yint <= 99) + yint += 1900; + + /* Step 5. */ + double day = MakeDay(yint, MonthFromTime(t), DateFromTime(t)); + + /* Step 6. */ + double u = UTC(MakeDate(day, TimeWithinDay(t))); + + /* Steps 7-8. */ + dateObj->setUTCTime(TimeClip(u), args.rval()); + return true; +} + +static bool +date_setYear(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* constants for toString, toUTCString */ +static const char js_NaN_date_str[] = "Invalid Date"; +static const char * const days[] = +{ + "Sun","Mon","Tue","Wed","Thu","Fri","Sat" +}; +static const char * const months[] = +{ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +/* ES5 B.2.6. */ +MOZ_ALWAYS_INLINE bool +date_toGMTString_impl(JSContext* cx, const CallArgs& args) +{ + double utctime = args.thisv().toObject().as().UTCTime().toNumber(); + + JSString* str; + if (!IsFinite(utctime)) { + str = NewStringCopyZ(cx, js_NaN_date_str); + } else { + char buf[100]; + SprintfLiteral(buf, "%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))); + + str = NewStringCopyZ(cx, buf); + } + + if (!str) + return false; + args.rval().setString(str); + return true; +} + +static bool +date_toGMTString(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* ES6 draft 2015-01-15 20.3.4.36. */ +MOZ_ALWAYS_INLINE bool +date_toISOString_impl(JSContext* cx, const CallArgs& args) +{ + double utctime = args.thisv().toObject().as().UTCTime().toNumber(); + if (!IsFinite(utctime)) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_INVALID_DATE); + return false; + } + + char buf[100]; + int year = int(YearFromTime(utctime)); + if (year < 0 || year > 9999) { + SprintfLiteral(buf, "%+.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))); + } else { + SprintfLiteral(buf, "%.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))); + } + + JSString* str = NewStringCopyZ(cx, buf); + if (!str) + return false; + args.rval().setString(str); + return true; + +} + +static bool +date_toISOString(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* ES5 15.9.5.44. */ +static bool +date_toJSON(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + /* Step 1. */ + RootedObject obj(cx, ToObject(cx, args.thisv())); + if (!obj) + return false; + + /* Step 2. */ + RootedValue tv(cx, ObjectValue(*obj)); + if (!ToPrimitive(cx, JSTYPE_NUMBER, &tv)) + return false; + + /* Step 3. */ + if (tv.isDouble() && !IsFinite(tv.toDouble())) { + args.rval().setNull(); + return true; + } + + /* Step 4. */ + RootedValue toISO(cx); + if (!GetProperty(cx, obj, obj, cx->names().toISOString, &toISO)) + return false; + + /* Step 5. */ + if (!IsCallable(toISO)) { + JS_ReportErrorFlagsAndNumberASCII(cx, JSREPORT_ERROR, js::GetErrorMessage, nullptr, + JSMSG_BAD_TOISOSTRING_PROP); + return false; + } + + /* Step 6. */ + return Call(cx, toISO, obj, args.rval()); +} + +/* Interface to PRMJTime date struct. */ +static PRMJTime +ToPRMJTime(double localTime) +{ + double year = YearFromTime(localTime); + + PRMJTime prtm; + prtm.tm_usec = int32_t(msFromTime(localTime)) * 1000; + prtm.tm_sec = int8_t(SecFromTime(localTime)); + prtm.tm_min = int8_t(MinFromTime(localTime)); + prtm.tm_hour = int8_t(HourFromTime(localTime)); + prtm.tm_mday = int8_t(DateFromTime(localTime)); + prtm.tm_mon = int8_t(MonthFromTime(localTime)); + prtm.tm_wday = int8_t(WeekDay(localTime)); + prtm.tm_year = year; + prtm.tm_yday = int16_t(DayWithinYear(localTime, year)); + + // XXX: DaylightSavingTA expects utc-time argument. + prtm.tm_isdst = (DaylightSavingTA(localTime) != 0); + + return prtm; +} + +enum class FormatSpec { + DateTime, + Date, + Time +}; + +static bool +FormatDate(JSContext* cx, double utcTime, FormatSpec format, MutableHandleValue rval) +{ + JSString* str; + if (!IsFinite(utcTime)) { + str = NewStringCopyZ(cx, js_NaN_date_str); + } else { + MOZ_ASSERT(NumbersAreIdentical(TimeClip(utcTime).toDouble(), utcTime)); + + double localTime = LocalTime(utcTime); + + int offset = 0; + char tzbuf[100]; + bool usetz = false; + if (format == FormatSpec::DateTime || format == FormatSpec::Time) { + /* + * Offset from GMT in minutes. The offset includes daylight + * savings, if it applies. + */ + int minutes = (int) floor((localTime - utcTime) / msPerMinute); + + /* Map 510 minutes to 0830 hours. */ + offset = (minutes / 60) * 100 + minutes % 60; + + /* + * Print as "Wed Nov 05 19:38:03 GMT-0800 (PST) 1997". + * + * The TZA is printed as 'GMT-0800' rather than as 'PST' to avoid + * operating-system dependence on strftime (which PRMJ_FormatTime + * calls, for %Z only.) win32 prints PST as + * 'Pacific Standard Time.' This way we always know what we're + * getting, and can parse it if we produce it. The OS time zone + * string is included as a comment. + */ + + /* get a time zone string from the OS to include as a comment. */ + PRMJTime prtm = ToPRMJTime(utcTime); + size_t tzlen = PRMJ_FormatTime(tzbuf, sizeof tzbuf, "(%Z)", &prtm); + if (tzlen != 0) { + /* + * Decide whether to use the resulting time zone string. + * + * Reject it if it contains any non-ASCII, non-alphanumeric + * characters. It's then likely in some other character + * encoding, and we probably won't display it correctly. + */ + usetz = true; + for (size_t i = 0; i < tzlen; i++) { + char16_t c = tzbuf[i]; + if (c > 127 || !(isalnum(c) || c == ' ' || c == '(' || c == ')' || c == '.')) { + usetz = false; + break; + } + } + + /* Also reject it if it's not parenthesized or if it's '()'. */ + if (tzbuf[0] != '(' || tzbuf[1] == ')') + usetz = false; + } + } + + char buf[100]; + switch (format) { + case FormatSpec::DateTime: + /* Tue Oct 31 2000 09:41:40 GMT-0800 (PST) */ + SprintfLiteral(buf, "%s %s %.2d %.4d %.2d:%.2d:%.2d GMT%+.4d%s%s", + days[int(WeekDay(localTime))], + months[int(MonthFromTime(localTime))], + int(DateFromTime(localTime)), + int(YearFromTime(localTime)), + int(HourFromTime(localTime)), + int(MinFromTime(localTime)), + int(SecFromTime(localTime)), + offset, + usetz ? " " : "", + usetz ? tzbuf : ""); + break; + case FormatSpec::Date: + /* Tue Oct 31 2000 */ + SprintfLiteral(buf, "%s %s %.2d %.4d", + days[int(WeekDay(localTime))], + months[int(MonthFromTime(localTime))], + int(DateFromTime(localTime)), + int(YearFromTime(localTime))); + break; + case FormatSpec::Time: + /* 09:41:40 GMT-0800 (PST) */ + SprintfLiteral(buf, "%.2d:%.2d:%.2d GMT%+.4d%s%s", + int(HourFromTime(localTime)), + int(MinFromTime(localTime)), + int(SecFromTime(localTime)), + offset, + usetz ? " " : "", + usetz ? tzbuf : ""); + break; + } + + str = NewStringCopyZ(cx, buf); + } + + if (!str) + return false; + rval.setString(str); + return true; +} + +static bool +ToLocaleFormatHelper(JSContext* cx, HandleObject obj, const char* format, MutableHandleValue rval) +{ + double utcTime = obj->as().UTCTime().toNumber(); + + char buf[100]; + if (!IsFinite(utcTime)) { + strcpy(buf, js_NaN_date_str); + } else { + double localTime = LocalTime(utcTime); + PRMJTime prtm = ToPRMJTime(localTime); + + /* Let PRMJTime format it. */ + size_t result_len = PRMJ_FormatTime(buf, sizeof buf, format, &prtm); + + /* If it failed, default to toString. */ + if (result_len == 0) + return FormatDate(cx, utcTime, FormatSpec::DateTime, rval); + + /* Hacked check against undesired 2-digit year 00/00/00 form. */ + if (strcmp(format, "%x") == 0 && result_len >= 6 && + /* Format %x means use OS settings, which may have 2-digit yr, so + hack end of 3/11/22 or 11.03.22 or 11Mar22 to use 4-digit yr...*/ + !isdigit(buf[result_len - 3]) && + isdigit(buf[result_len - 2]) && isdigit(buf[result_len - 1]) && + /* ...but not if starts with 4-digit year, like 2022/3/11. */ + !(isdigit(buf[0]) && isdigit(buf[1]) && + isdigit(buf[2]) && isdigit(buf[3]))) + { + int year = int(YearFromTime(localTime)); + snprintf(buf + (result_len - 2), (sizeof buf) - (result_len - 2), "%d", year); + } + + } + + if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeToUnicode) + return cx->runtime()->localeCallbacks->localeToUnicode(cx, buf, rval); + + JSString* str = NewStringCopyZ(cx, buf); + if (!str) + return false; + rval.setString(str); + return true; +} + +#if !EXPOSE_INTL_API +/* ES5 15.9.5.5. */ +MOZ_ALWAYS_INLINE bool +date_toLocaleString_impl(JSContext* cx, const CallArgs& args) +{ + /* + * Use '%#c' for windows, because '%c' is backward-compatible and non-y2k + * with msvc; '%#c' requests that a full year be used in the result string. + */ + static const char format[] = +#if defined(_WIN32) && !defined(__MWERKS__) + "%#c" +#else + "%c" +#endif + ; + + Rooted dateObj(cx, &args.thisv().toObject().as()); + return ToLocaleFormatHelper(cx, dateObj, format, args.rval()); +} + +static bool +date_toLocaleString(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* ES5 15.9.5.6. */ +MOZ_ALWAYS_INLINE bool +date_toLocaleDateString_impl(JSContext* cx, const CallArgs& args) +{ + /* + * Use '%#x' for windows, because '%x' is backward-compatible and non-y2k + * with msvc; '%#x' requests that a full year be used in the result string. + */ + static const char format[] = +#if defined(_WIN32) && !defined(__MWERKS__) + "%#x" +#else + "%x" +#endif + ; + + Rooted dateObj(cx, &args.thisv().toObject().as()); + return ToLocaleFormatHelper(cx, dateObj, format, args.rval()); +} + +static bool +date_toLocaleDateString(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* ES5 15.9.5.7. */ +MOZ_ALWAYS_INLINE bool +date_toLocaleTimeString_impl(JSContext* cx, const CallArgs& args) +{ + Rooted dateObj(cx, &args.thisv().toObject().as()); + return ToLocaleFormatHelper(cx, dateObj, "%X", args.rval()); +} + +static bool +date_toLocaleTimeString(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} +#endif /* !EXPOSE_INTL_API */ + +MOZ_ALWAYS_INLINE bool +date_toLocaleFormat_impl(JSContext* cx, const CallArgs& args) +{ + Rooted dateObj(cx, &args.thisv().toObject().as()); + + if (args.length() == 0) { + /* + * Use '%#c' for windows, because '%c' is backward-compatible and non-y2k + * with msvc; '%#c' requests that a full year be used in the result string. + */ + static const char format[] = +#if defined(_WIN32) && !defined(__MWERKS__) + "%#c" +#else + "%c" +#endif + ; + + return ToLocaleFormatHelper(cx, dateObj, format, args.rval()); + } + + RootedString fmt(cx, ToString(cx, args[0])); + if (!fmt) + return false; + + JSAutoByteString fmtbytes(cx, fmt); + if (!fmtbytes) + return false; + + return ToLocaleFormatHelper(cx, dateObj, fmtbytes.ptr(), args.rval()); +} + +static bool +date_toLocaleFormat(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* ES5 15.9.5.4. */ +MOZ_ALWAYS_INLINE bool +date_toTimeString_impl(JSContext* cx, const CallArgs& args) +{ + return FormatDate(cx, args.thisv().toObject().as().UTCTime().toNumber(), + FormatSpec::Time, args.rval()); +} + +static bool +date_toTimeString(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* ES5 15.9.5.3. */ +MOZ_ALWAYS_INLINE bool +date_toDateString_impl(JSContext* cx, const CallArgs& args) +{ + return FormatDate(cx, args.thisv().toObject().as().UTCTime().toNumber(), + FormatSpec::Date, args.rval()); +} + +static bool +date_toDateString(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +#if JS_HAS_TOSOURCE +MOZ_ALWAYS_INLINE bool +date_toSource_impl(JSContext* cx, const CallArgs& args) +{ + StringBuffer sb(cx); + if (!sb.append("(new Date(") || + !NumberValueToStringBuffer(cx, args.thisv().toObject().as().UTCTime(), sb) || + !sb.append("))")) + { + return false; + } + + JSString* str = sb.finishString(); + if (!str) + return false; + args.rval().setString(str); + return true; +} + +static bool +date_toSource(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} +#endif + +MOZ_ALWAYS_INLINE bool +IsObject(HandleValue v) +{ + return v.isObject(); +} + +// ES6 20.3.4.41. +MOZ_ALWAYS_INLINE bool +date_toString_impl(JSContext* cx, const CallArgs& args) +{ + // Step 1. + RootedObject obj(cx, &args.thisv().toObject()); + + // Step 2. + ESClass cls; + if (!GetBuiltinClass(cx, obj, &cls)) + return false; + + double tv; + if (cls != ESClass::Date) { + // Step 2. + tv = GenericNaN(); + } else { + // Step 3. + RootedValue unboxed(cx); + if (!Unbox(cx, obj, &unboxed)) + return false; + + tv = unboxed.toNumber(); + } + + // Step 4. + return FormatDate(cx, tv, FormatSpec::DateTime, args.rval()); +} + +bool +date_toString(JSContext* cx, unsigned argc, Value* vp) +{ + // Step 1. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +MOZ_ALWAYS_INLINE bool +date_valueOf_impl(JSContext* cx, const CallArgs& args) +{ + Rooted dateObj(cx, &args.thisv().toObject().as()); + args.rval().set(dateObj->UTCTime()); + return true; +} + +bool +js::date_valueOf(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +// ES6 20.3.4.45 Date.prototype[@@toPrimitive] +static bool +date_toPrimitive(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Steps 1-2. + if (!args.thisv().isObject()) { + ReportIncompatible(cx, args); + return false; + } + + // Steps 3-5. + JSType hint; + if (!GetFirstArgumentAsTypeHint(cx, args, &hint)) + return false; + if (hint == JSTYPE_VOID) + hint = JSTYPE_STRING; + + args.rval().set(args.thisv()); + RootedObject obj(cx, &args.thisv().toObject()); + return OrdinaryToPrimitive(cx, obj, hint, args.rval()); +} + +static const JSFunctionSpec date_static_methods[] = { + JS_FN("UTC", date_UTC, 7,0), + JS_FN("parse", date_parse, 1,0), + JS_FN("now", date_now, 0,0), + JS_FS_END +}; + +static const JSFunctionSpec date_methods[] = { + JS_FN("getTime", date_getTime, 0,0), + JS_FN("getTimezoneOffset", date_getTimezoneOffset, 0,0), + JS_FN("getYear", date_getYear, 0,0), + JS_FN("getFullYear", date_getFullYear, 0,0), + JS_FN("getUTCFullYear", date_getUTCFullYear, 0,0), + JS_FN("getMonth", date_getMonth, 0,0), + JS_FN("getUTCMonth", date_getUTCMonth, 0,0), + JS_FN("getDate", date_getDate, 0,0), + JS_FN("getUTCDate", date_getUTCDate, 0,0), + JS_FN("getDay", date_getDay, 0,0), + JS_FN("getUTCDay", date_getUTCDay, 0,0), + JS_FN("getHours", date_getHours, 0,0), + JS_FN("getUTCHours", date_getUTCHours, 0,0), + JS_FN("getMinutes", date_getMinutes, 0,0), + JS_FN("getUTCMinutes", date_getUTCMinutes, 0,0), + JS_FN("getSeconds", date_getUTCSeconds, 0,0), + JS_FN("getUTCSeconds", date_getUTCSeconds, 0,0), + JS_FN("getMilliseconds", date_getUTCMilliseconds, 0,0), + JS_FN("getUTCMilliseconds", date_getUTCMilliseconds, 0,0), + JS_FN("setTime", date_setTime, 1,0), + JS_FN("setYear", date_setYear, 1,0), + JS_FN("setFullYear", date_setFullYear, 3,0), + JS_FN("setUTCFullYear", date_setUTCFullYear, 3,0), + JS_FN("setMonth", date_setMonth, 2,0), + JS_FN("setUTCMonth", date_setUTCMonth, 2,0), + JS_FN("setDate", date_setDate, 1,0), + JS_FN("setUTCDate", date_setUTCDate, 1,0), + JS_FN("setHours", date_setHours, 4,0), + JS_FN("setUTCHours", date_setUTCHours, 4,0), + JS_FN("setMinutes", date_setMinutes, 3,0), + JS_FN("setUTCMinutes", date_setUTCMinutes, 3,0), + JS_FN("setSeconds", date_setSeconds, 2,0), + JS_FN("setUTCSeconds", date_setUTCSeconds, 2,0), + JS_FN("setMilliseconds", date_setMilliseconds, 1,0), + JS_FN("setUTCMilliseconds", date_setUTCMilliseconds, 1,0), + JS_FN("toUTCString", date_toGMTString, 0,0), + JS_FN("toLocaleFormat", date_toLocaleFormat, 0,0), +#if EXPOSE_INTL_API + JS_SELF_HOSTED_FN(js_toLocaleString_str, "Date_toLocaleString", 0,0), + JS_SELF_HOSTED_FN("toLocaleDateString", "Date_toLocaleDateString", 0,0), + JS_SELF_HOSTED_FN("toLocaleTimeString", "Date_toLocaleTimeString", 0,0), +#else + JS_FN(js_toLocaleString_str, date_toLocaleString, 0,0), + JS_FN("toLocaleDateString", date_toLocaleDateString, 0,0), + JS_FN("toLocaleTimeString", date_toLocaleTimeString, 0,0), +#endif + JS_FN("toDateString", date_toDateString, 0,0), + JS_FN("toTimeString", date_toTimeString, 0,0), + JS_FN("toISOString", date_toISOString, 0,0), + JS_FN(js_toJSON_str, date_toJSON, 1,0), +#if JS_HAS_TOSOURCE + JS_FN(js_toSource_str, date_toSource, 0,0), +#endif + JS_FN(js_toString_str, date_toString, 0,0), + JS_FN(js_valueOf_str, date_valueOf, 0,0), + JS_SYM_FN(toPrimitive, date_toPrimitive, 1,JSPROP_READONLY), + JS_FS_END +}; + +static bool +NewDateObject(JSContext* cx, const CallArgs& args, ClippedTime t) +{ + MOZ_ASSERT(args.isConstructing()); + + RootedObject proto(cx); + RootedObject newTarget(cx, &args.newTarget().toObject()); + if (!GetPrototypeFromConstructor(cx, newTarget, &proto)) + return false; + + JSObject* obj = NewDateObjectMsec(cx, t, proto); + if (!obj) + return false; + + args.rval().setObject(*obj); + return true; +} + +static bool +ToDateString(JSContext* cx, const CallArgs& args, ClippedTime t) +{ + return FormatDate(cx, t.toDouble(), FormatSpec::DateTime, args.rval()); +} + +static bool +DateNoArguments(JSContext* cx, const CallArgs& args) +{ + MOZ_ASSERT(args.length() == 0); + + ClippedTime now = NowAsMillis(); + + if (args.isConstructing()) + return NewDateObject(cx, args, now); + + return ToDateString(cx, args, now); +} + +static bool +DateOneArgument(JSContext* cx, const CallArgs& args) +{ + MOZ_ASSERT(args.length() == 1); + + if (args.isConstructing()) { + if (args[0].isObject()) { + RootedObject obj(cx, &args[0].toObject()); + + ESClass cls; + if (!GetBuiltinClass(cx, obj, &cls)) + return false; + + if (cls == ESClass::Date) { + RootedValue unboxed(cx); + if (!Unbox(cx, obj, &unboxed)) + return false; + + return NewDateObject(cx, args, TimeClip(unboxed.toNumber())); + } + } + + if (!ToPrimitive(cx, args[0])) + return false; + + ClippedTime t; + if (args[0].isString()) { + JSLinearString* linearStr = args[0].toString()->ensureLinear(cx); + if (!linearStr) + return false; + + if (!ParseDate(linearStr, &t)) + t = ClippedTime::invalid(); + } else { + double d; + if (!ToNumber(cx, args[0], &d)) + return false; + t = TimeClip(d); + } + + return NewDateObject(cx, args, t); + } + + return ToDateString(cx, args, NowAsMillis()); +} + +static bool +DateMultipleArguments(JSContext* cx, const CallArgs& args) +{ + MOZ_ASSERT(args.length() >= 2); + + // Step 3. + if (args.isConstructing()) { + // Steps 3a-b. + double y; + if (!ToNumber(cx, args[0], &y)) + return false; + + // Steps 3c-d. + double m; + if (!ToNumber(cx, args[1], &m)) + return false; + + // Steps 3e-f. + double dt; + if (args.length() >= 3) { + if (!ToNumber(cx, args[2], &dt)) + return false; + } else { + dt = 1; + } + + // Steps 3g-h. + double h; + if (args.length() >= 4) { + if (!ToNumber(cx, args[3], &h)) + return false; + } else { + h = 0; + } + + // Steps 3i-j. + double min; + if (args.length() >= 5) { + if (!ToNumber(cx, args[4], &min)) + return false; + } else { + min = 0; + } + + // Steps 3k-l. + double s; + if (args.length() >= 6) { + if (!ToNumber(cx, args[5], &s)) + return false; + } else { + s = 0; + } + + // Steps 3m-n. + double milli; + if (args.length() >= 7) { + if (!ToNumber(cx, args[6], &milli)) + return false; + } else { + milli = 0; + } + + // Step 3o. + double yr = y; + if (!IsNaN(y)) { + double yint = ToInteger(y); + if (0 <= yint && yint <= 99) + yr = 1900 + yint; + } + + // Step 3p. + double finalDate = MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli)); + + // Steps 3q-t. + return NewDateObject(cx, args, TimeClip(UTC(finalDate))); + } + + return ToDateString(cx, args, NowAsMillis()); +} + +bool +js::DateConstructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() == 0) + return DateNoArguments(cx, args); + + if (args.length() == 1) + return DateOneArgument(cx, args); + + return DateMultipleArguments(cx, args); +} + +// ES6 final draft 20.3.4. +static JSObject* +CreateDatePrototype(JSContext* cx, JSProtoKey key) +{ + return cx->global()->createBlankPrototype(cx, &DateObject::protoClass_); +} + +static bool +FinishDateClassInit(JSContext* cx, HandleObject ctor, HandleObject proto) +{ + /* + * Date.prototype.toGMTString has the same initial value as + * Date.prototype.toUTCString. + */ + RootedValue toUTCStringFun(cx); + RootedId toUTCStringId(cx, NameToId(cx->names().toUTCString)); + RootedId toGMTStringId(cx, NameToId(cx->names().toGMTString)); + return NativeGetProperty(cx, proto.as(), toUTCStringId, &toUTCStringFun) && + NativeDefineProperty(cx, proto.as(), toGMTStringId, toUTCStringFun, + nullptr, nullptr, 0); +} + +static const ClassSpec DateObjectClassSpec = { + GenericCreateConstructor, + CreateDatePrototype, + date_static_methods, + nullptr, + date_methods, + nullptr, + FinishDateClassInit +}; + +const Class DateObject::class_ = { + js_Date_str, + JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | + JSCLASS_HAS_CACHED_PROTO(JSProto_Date), + JS_NULL_CLASS_OPS, + &DateObjectClassSpec +}; + +static const ClassSpec DateObjectProtoClassSpec = { + DELEGATED_CLASSSPEC(DateObject::class_.spec), + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + ClassSpec::IsDelegated +}; + +const Class DateObject::protoClass_ = { + js_Object_str, + JSCLASS_HAS_CACHED_PROTO(JSProto_Date), + JS_NULL_CLASS_OPS, + &DateObjectProtoClassSpec +}; + +JSObject* +js::NewDateObjectMsec(JSContext* cx, ClippedTime t, HandleObject proto /* = nullptr */) +{ + JSObject* obj = NewObjectWithClassProto(cx, &DateObject::class_, proto); + if (!obj) + return nullptr; + obj->as().setUTCTime(t); + return obj; +} + +JS_FRIEND_API(JSObject*) +js::NewDateObject(JSContext* cx, int year, int mon, int mday, + int hour, int min, int sec) +{ + MOZ_ASSERT(mon < 12); + double msec_time = MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0.0)); + return NewDateObjectMsec(cx, TimeClip(UTC(msec_time))); +} + +JS_FRIEND_API(bool) +js::DateIsValid(JSContext* cx, HandleObject obj, bool* isValid) +{ + ESClass cls; + if (!GetBuiltinClass(cx, obj, &cls)) + return false; + + if (cls != ESClass::Date) { + *isValid = false; + return true; + } + + RootedValue unboxed(cx); + if (!Unbox(cx, obj, &unboxed)) + return false; + + *isValid = !IsNaN(unboxed.toNumber()); + return true; +} + +JS_FRIEND_API(bool) +js::DateGetMsecSinceEpoch(JSContext* cx, HandleObject obj, double* msecsSinceEpoch) +{ + ESClass cls; + if (!GetBuiltinClass(cx, obj, &cls)) + return false; + + if (cls != ESClass::Date) { + *msecsSinceEpoch = 0; + return true; + } + + RootedValue unboxed(cx); + if (!Unbox(cx, obj, &unboxed)) + return false; + + *msecsSinceEpoch = unboxed.toNumber(); + return true; +} -- cgit v1.2.3