summaryrefslogtreecommitdiffstats
path: root/js/src/jsdate.cpp
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /js/src/jsdate.cpp
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'js/src/jsdate.cpp')
-rw-r--r--js/src/jsdate.cpp3339
1 files changed, 3339 insertions, 0 deletions
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 <ctype.h>
+#include <math.h>
+#include <string.h>
+
+#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<typename T>
+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<int64_t>(t);
+ int64_t offsetMilliseconds = DateTimeInfo::getDSTOffsetMilliseconds(utcMilliseconds);
+ return static_cast<double>(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 <typename CharT>
+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 <typename CharT>
+static bool
+ParseDigits(size_t* result, const CharT* s, size_t* i, size_t limit)
+{
+ size_t init = *i;
+ *result = 0;
+ while (*i < limit && ('0' <= s[*i] && s[*i] <= '9')) {
+ *result *= 10;
+ *result += (s[*i] - '0');
+ ++(*i);
+ }
+ return *i != init;
+}
+
+/*
+ * Read and convert decimal digits to the right of a decimal point,
+ * representing a fractional integer, from s[*i] into *result
+ * while *i < limit.
+ *
+ * Succeed if any digits are converted. Advance *i only
+ * as digits are consumed.
+ */
+template <typename CharT>
+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 <typename CharT>
+static bool
+ParseDigitsN(size_t n, size_t* result, const CharT* s, size_t* i, size_t limit)
+{
+ size_t init = *i;
+
+ if (ParseDigits(result, s, i, Min(limit, init + n)))
+ return (*i - init) == n;
+
+ *i = init;
+ return false;
+}
+
+/*
+ * Read and convert n or less decimal digits from s[*i]
+ * to s[min(*i+n,limit)] into *result.
+ *
+ * Succeed only if greater than zero but less than or equal to n digits are
+ * converted. Advance *i only on success.
+ */
+template <typename CharT>
+static bool
+ParseDigitsNOrLess(size_t n, size_t* result, const CharT* s, size_t* i, size_t limit)
+{
+ size_t init = *i;
+
+ if (ParseDigits(result, s, i, 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 <typename CharT>
+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 <typename CharT>
+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<CanGC>(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<double>(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<DateObject>();
+}
+
+/*
+ * 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<DateObject>().UTCTime());
+ return true;
+}
+
+static bool
+date_getTime(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDate, DateObject::getTime_impl>(cx, args);
+}
+
+/* static */ MOZ_ALWAYS_INLINE bool
+DateObject::getYear_impl(JSContext* cx, const CallArgs& args)
+{
+ DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
+ 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<IsDate, DateObject::getYear_impl>(cx, args);
+}
+
+/* static */ MOZ_ALWAYS_INLINE bool
+DateObject::getFullYear_impl(JSContext* cx, const CallArgs& args)
+{
+ DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
+ 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<IsDate, DateObject::getFullYear_impl>(cx, args);
+}
+
+/* static */ MOZ_ALWAYS_INLINE bool
+DateObject::getUTCFullYear_impl(JSContext* cx, const CallArgs& args)
+{
+ double result = args.thisv().toObject().as<DateObject>().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<IsDate, DateObject::getUTCFullYear_impl>(cx, args);
+}
+
+/* static */ MOZ_ALWAYS_INLINE bool
+DateObject::getMonth_impl(JSContext* cx, const CallArgs& args)
+{
+ DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
+ 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<IsDate, DateObject::getMonth_impl>(cx, args);
+}
+
+/* static */ MOZ_ALWAYS_INLINE bool
+DateObject::getUTCMonth_impl(JSContext* cx, const CallArgs& args)
+{
+ double d = args.thisv().toObject().as<DateObject>().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<IsDate, DateObject::getUTCMonth_impl>(cx, args);
+}
+
+/* static */ MOZ_ALWAYS_INLINE bool
+DateObject::getDate_impl(JSContext* cx, const CallArgs& args)
+{
+ DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
+ 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<IsDate, DateObject::getDate_impl>(cx, args);
+}
+
+/* static */ MOZ_ALWAYS_INLINE bool
+DateObject::getUTCDate_impl(JSContext* cx, const CallArgs& args)
+{
+ double result = args.thisv().toObject().as<DateObject>().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<IsDate, DateObject::getUTCDate_impl>(cx, args);
+}
+
+/* static */ MOZ_ALWAYS_INLINE bool
+DateObject::getDay_impl(JSContext* cx, const CallArgs& args)
+{
+ DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
+ 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<IsDate, DateObject::getDay_impl>(cx, args);
+}
+
+/* static */ MOZ_ALWAYS_INLINE bool
+DateObject::getUTCDay_impl(JSContext* cx, const CallArgs& args)
+{
+ double result = args.thisv().toObject().as<DateObject>().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<IsDate, DateObject::getUTCDay_impl>(cx, args);
+}
+
+/* static */ MOZ_ALWAYS_INLINE bool
+DateObject::getHours_impl(JSContext* cx, const CallArgs& args)
+{
+ DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
+ 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<IsDate, DateObject::getHours_impl>(cx, args);
+}
+
+/* static */ MOZ_ALWAYS_INLINE bool
+DateObject::getUTCHours_impl(JSContext* cx, const CallArgs& args)
+{
+ double result = args.thisv().toObject().as<DateObject>().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<IsDate, DateObject::getUTCHours_impl>(cx, args);
+}
+
+/* static */ MOZ_ALWAYS_INLINE bool
+DateObject::getMinutes_impl(JSContext* cx, const CallArgs& args)
+{
+ DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
+ 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<IsDate, DateObject::getMinutes_impl>(cx, args);
+}
+
+/* static */ MOZ_ALWAYS_INLINE bool
+DateObject::getUTCMinutes_impl(JSContext* cx, const CallArgs& args)
+{
+ double result = args.thisv().toObject().as<DateObject>().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<IsDate, DateObject::getUTCMinutes_impl>(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<DateObject>();
+ 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<IsDate, DateObject::getUTCSeconds_impl>(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<DateObject>().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<IsDate, DateObject::getUTCMilliseconds_impl>(cx, args);
+}
+
+/* static */ MOZ_ALWAYS_INLINE bool
+DateObject::getTimezoneOffset_impl(JSContext* cx, const CallArgs& args)
+{
+ DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
+ 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<IsDate, DateObject::getTimezoneOffset_impl>(cx, args);
+}
+
+MOZ_ALWAYS_INLINE bool
+date_setTime_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
+ 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<IsDate, date_setTime_impl>(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<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
+
+ // 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<IsDate, date_setMilliseconds_impl>(cx, args);
+}
+
+/* ES5 15.9.5.29. */
+MOZ_ALWAYS_INLINE bool
+date_setUTCMilliseconds_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
+
+ /* 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<IsDate, date_setUTCMilliseconds_impl>(cx, args);
+}
+
+/* ES5 15.9.5.30. */
+MOZ_ALWAYS_INLINE bool
+date_setSeconds_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
+
+ // 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<IsDate, date_setSeconds_impl>(cx, args);
+}
+
+MOZ_ALWAYS_INLINE bool
+date_setUTCSeconds_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
+
+ /* 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<IsDate, date_setUTCSeconds_impl>(cx, args);
+}
+
+MOZ_ALWAYS_INLINE bool
+date_setMinutes_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
+
+ // 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<IsDate, date_setMinutes_impl>(cx, args);
+}
+
+MOZ_ALWAYS_INLINE bool
+date_setUTCMinutes_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
+
+ /* 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<IsDate, date_setUTCMinutes_impl>(cx, args);
+}
+
+MOZ_ALWAYS_INLINE bool
+date_setHours_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
+
+ // 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<IsDate, date_setHours_impl>(cx, args);
+}
+
+MOZ_ALWAYS_INLINE bool
+date_setUTCHours_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
+
+ /* 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<IsDate, date_setUTCHours_impl>(cx, args);
+}
+
+MOZ_ALWAYS_INLINE bool
+date_setDate_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
+
+ /* 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<IsDate, date_setDate_impl>(cx, args);
+}
+
+MOZ_ALWAYS_INLINE bool
+date_setUTCDate_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
+
+ /* 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<IsDate, date_setUTCDate_impl>(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<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
+
+ /* 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<IsDate, date_setMonth_impl>(cx, args);
+}
+
+/* ES5 15.9.5.39. */
+MOZ_ALWAYS_INLINE bool
+date_setUTCMonth_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
+
+ /* 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<IsDate, date_setUTCMonth_impl>(cx, args);
+}
+
+static double
+ThisLocalTimeOrZero(Handle<DateObject*> dateObj)
+{
+ double t = dateObj->UTCTime().toNumber();
+ if (IsNaN(t))
+ return +0;
+ return LocalTime(t);
+}
+
+static double
+ThisUTCTimeOrZero(Handle<DateObject*> dateObj)
+{
+ double t = dateObj->as<DateObject>().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<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
+
+ /* 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<IsDate, date_setFullYear_impl>(cx, args);
+}
+
+/* ES5 15.9.5.41. */
+MOZ_ALWAYS_INLINE bool
+date_setUTCFullYear_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
+
+ /* 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<IsDate, date_setUTCFullYear_impl>(cx, args);
+}
+
+/* ES5 Annex B.2.5. */
+MOZ_ALWAYS_INLINE bool
+date_setYear_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
+
+ /* 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<IsDate, date_setYear_impl>(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<DateObject>().UTCTime().toNumber();
+
+ JSString* str;
+ if (!IsFinite(utctime)) {
+ str = NewStringCopyZ<CanGC>(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<CanGC>(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<IsDate, date_toGMTString_impl>(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<DateObject>().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<CanGC>(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<IsDate, date_toISOString_impl>(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<CanGC>(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<CanGC>(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<DateObject>().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<CanGC>(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<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
+ return ToLocaleFormatHelper(cx, dateObj, format, args.rval());
+}
+
+static bool
+date_toLocaleString(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDate, date_toLocaleString_impl>(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<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
+ return ToLocaleFormatHelper(cx, dateObj, format, args.rval());
+}
+
+static bool
+date_toLocaleDateString(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDate, date_toLocaleDateString_impl>(cx, args);
+}
+
+/* ES5 15.9.5.7. */
+MOZ_ALWAYS_INLINE bool
+date_toLocaleTimeString_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
+ return ToLocaleFormatHelper(cx, dateObj, "%X", args.rval());
+}
+
+static bool
+date_toLocaleTimeString(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDate, date_toLocaleTimeString_impl>(cx, args);
+}
+#endif /* !EXPOSE_INTL_API */
+
+MOZ_ALWAYS_INLINE bool
+date_toLocaleFormat_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
+
+ 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<CanGC>(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<IsDate, date_toLocaleFormat_impl>(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<DateObject>().UTCTime().toNumber(),
+ FormatSpec::Time, args.rval());
+}
+
+static bool
+date_toTimeString(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDate, date_toTimeString_impl>(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<DateObject>().UTCTime().toNumber(),
+ FormatSpec::Date, args.rval());
+}
+
+static bool
+date_toDateString(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDate, date_toDateString_impl>(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<DateObject>().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<IsDate, date_toSource_impl>(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<IsObject, date_toString_impl>(cx, args);
+}
+
+MOZ_ALWAYS_INLINE bool
+date_valueOf_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
+ args.rval().set(dateObj->UTCTime());
+ return true;
+}
+
+bool
+js::date_valueOf(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDate, date_valueOf_impl>(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<NativeObject>(), toUTCStringId, &toUTCStringFun) &&
+ NativeDefineProperty(cx, proto.as<NativeObject>(), toGMTStringId, toUTCStringFun,
+ nullptr, nullptr, 0);
+}
+
+static const ClassSpec DateObjectClassSpec = {
+ GenericCreateConstructor<DateConstructor, 7, gc::AllocKind::FUNCTION>,
+ 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<DateObject>().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;
+}