blob: 6c7859b426d831a24743042fc65306c3e05499dc [file] [log] [blame]
/* -*- 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 <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);
}
/*
* 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;
}
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:
*
* The formats are as follows. Exactly the components shown here
* must be present, with exactly this punctuation. Note that the "T"
* appears literally in the string, to indicate the beginning of the
* time element, as specified in ISO 8601.
*
* 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.
*
* 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 = two-digit month (01=January, etc.)
* DD = two-digit day of month (01 through 31)
* hh = two digits of hour (00 through 23) (am/pm NOT allowed)
* mm = two digits of minute (00 through 59)
* ss = 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
ParseISODate(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; }
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(2, month);
DONE_DATE_UNLESS('-');
NEED_NDIGITS(2, day);
done_date:
DONE_UNLESS('T');
NEED_NDIGITS(2, hour);
NEED(':');
NEED_NDIGITS(2, min);
if (PEEK(':')) {
++i;
NEED_NDIGITS(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))
|| 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 (ParseISODate(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;
int k;
while (i < length) {
c = s[i];
if (!(('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')))
break;
i++;
}
if (i <= st + 1)
return false;
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 70, or
* both less than 70, the date is invalid.
* The year is taken to be the greater of the values f, l.
* If the year is greater than or equal to 70 and less than 100,
* it is considered to be the number of years after 1900.
* Case 2. The input string is of the form "f/m/l" where f, m and l are
* integers, e.g. 7/16/45.
* Adjust the mon, mday and year values to achieve 100% MSIE
* compatibility.
* a. If 0 <= f < 70, f/m/l is interpreted as month/day/year.
* i. If year < 100, it is the number of years after 1900
* ii. If year >= 100, it is the number of years after 0.
* b. If 70 <= f < 100
* i. If m < 70, f/m/l is interpreted as
* year/month/day where year is the number of years after
* 1900.
* ii. If m >= 70, the date is invalid.
* c. If f >= 100
* i. If m < 70, f/m/l is interpreted as
* year/month/day where year is the number of years after 0.
* ii. If m >= 70, the date is invalid.
*/
if (seenMonthName) {
if ((mday >= 70 && year >= 70) || (mday < 70 && year < 70))
return false;
if (mday > year) {
int temp = year;
year = mday;
mday = temp;
}
if (year >= 70 && year < 100) {
year += 1900;
}
} else if (mon < 70) { /* (a) month/day/year */
if (year < 100) {
year += 1900;
}
} else if (mon < 100) { /* (b) year/month/day */
if (mday < 70) {
int temp = year;
year = mon + 1900;
mon = mday;
mday = temp;
} else {
return false;
}
} else { /* (c) year/month/day */
if (mday < 70) {
int temp = year;
year = mon;
mon = mday;
mday = temp;
} else {
return false;
}
}
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 timezone 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));
int seconds = yearSeconds % 60;
setReservedSlot(LOCAL_SECONDS_SLOT, Int32Value(seconds));
int minutes = (yearSeconds / 60) % 60;
setReservedSlot(LOCAL_MINUTES_SLOT, Int32Value(minutes));
int hours = (yearSeconds / (60 * 60)) % 24;
setReservedSlot(LOCAL_HOURS_SLOT, Int32Value(hours));
}
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();
args.rval().set(dateObj->getReservedSlot(LOCAL_HOURS_SLOT));
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();
args.rval().set(dateObj->getReservedSlot(LOCAL_MINUTES_SLOT));
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 */
/* static */ MOZ_ALWAYS_INLINE bool
DateObject::getUTCSeconds_impl(JSContext* cx, const CallArgs& args)
{
DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
dateObj->fillLocalTimeSlots();
args.rval().set(dateObj->getReservedSlot(LOCAL_SECONDS_SLOT));
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 */
/* 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"
};
// Avoid dependence on PRMJ_FormatTimeUSEnglish, because it
// requires a PRMJTime... which only has 16-bit years. Sub-ECMA.
static void
print_gmt_string(char* buf, size_t size, double utctime)
{
MOZ_ASSERT(NumbersAreIdentical(TimeClip(utctime).toDouble(), utctime));
JS_snprintf(buf, size, "%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)));
}
static void
print_iso_string(char* buf, size_t size, double utctime)
{
MOZ_ASSERT(NumbersAreIdentical(TimeClip(utctime).toDouble(), utctime));
JS_snprintf(buf, size, "%.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)));
}
static void
print_iso_extended_string(char* buf, size_t size, double utctime)
{
MOZ_ASSERT(NumbersAreIdentical(TimeClip(utctime).toDouble(), utctime));
JS_snprintf(buf, size, "%+.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)));
}
/* 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();
char buf[100];
if (!IsFinite(utctime))
JS_snprintf(buf, sizeof buf, js_NaN_date_str);
else
print_gmt_string(buf, sizeof buf, utctime);
JSString* str = JS_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<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_ReportErrorNumber(cx, js::GetErrorMessage, nullptr, JSMSG_INVALID_DATE);
return false;
}
char buf[100];
int year = int(YearFromTime(utctime));
if (year < 0 || year > 9999)
print_iso_extended_string(buf, sizeof buf, utctime);
else
print_iso_string(buf, sizeof buf, utctime);
JSString* str = JS_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<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_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js::GetErrorMessage, nullptr,
JSMSG_BAD_TOISOSTRING_PROP);
return false;
}
/* Step 6. */
InvokeArgs args2(cx);
if (!args2.init(0))
return false;
args2.setCallee(toISO);
args2.setThis(ObjectValue(*obj));
if (!Invoke(cx, args2))
return false;
args.rval().set(args2.rval());
return true;
}
/* for Date.toLocaleFormat; interface to PRMJTime date struct.
*/
static void
new_explode(double timeval, PRMJTime* split)
{
double year = YearFromTime(timeval);
split->tm_usec = int32_t(msFromTime(timeval)) * 1000;
split->tm_sec = int8_t(SecFromTime(timeval));
split->tm_min = int8_t(MinFromTime(timeval));
split->tm_hour = int8_t(HourFromTime(timeval));
split->tm_mday = int8_t(DateFromTime(timeval));
split->tm_mon = int8_t(MonthFromTime(timeval));
split->tm_wday = int8_t(WeekDay(timeval));
split->tm_year = year;
split->tm_yday = int16_t(DayWithinYear(timeval, year));
/* not sure how this affects things, but it doesn't seem
to matter. */
split->tm_isdst = (DaylightSavingTA(timeval) != 0);
}
typedef enum formatspec {
FORMATSPEC_FULL, FORMATSPEC_DATE, FORMATSPEC_TIME
} formatspec;
/* helper function */
static bool
date_format(JSContext* cx, double date, formatspec format, MutableHandleValue rval)
{
char buf[100];
char tzbuf[100];
bool usetz;
size_t i, tzlen;
PRMJTime split;
if (!IsFinite(date)) {
JS_snprintf(buf, sizeof buf, js_NaN_date_str);
} else {
MOZ_ASSERT(NumbersAreIdentical(TimeClip(date).toDouble(), date));
double local = LocalTime(date);
/* offset from GMT in minutes. The offset includes daylight savings,
if it applies. */
int minutes = (int) floor(AdjustTime(date) / msPerMinute);
/* map 510 minutes to 0830 hours */
int 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_FormatTimeUSEnglish 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 TZA string is included as a comment.
*/
/* get a timezone string from the OS to include as a
comment. */
new_explode(date, &split);
if (PRMJ_FormatTime(tzbuf, sizeof tzbuf, "(%Z)", &split) != 0) {
/* Decide whether to use the resulting timezone 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;
tzlen = strlen(tzbuf);
if (tzlen > 100) {
usetz = false;
} else {
for (i = 0; i < tzlen; i++) {
char16_t c = tzbuf[i];
if (c > 127 ||
!(isalpha(c) || isdigit(c) ||
c == ' ' || c == '(' || c == ')' || c == '.')) {
usetz = false;
}
}
}
/* Also reject it if it's not parenthesized or if it's '()'. */
if (tzbuf[0] != '(' || tzbuf[1] == ')')
usetz = false;
} else
usetz = false;
switch (format) {
case FORMATSPEC_FULL:
/*
* Avoid dependence on PRMJ_FormatTimeUSEnglish, because it
* requires a PRMJTime... which only has 16-bit years. Sub-ECMA.
*/
/* Tue Oct 31 2000 09:41:40 GMT-0800 (PST) */
JS_snprintf(buf, sizeof buf,
"%s %s %.2d %.4d %.2d:%.2d:%.2d GMT%+.4d%s%s",
days[int(WeekDay(local))],
months[int(MonthFromTime(local))],
int(DateFromTime(local)),
int(YearFromTime(local)),
int(HourFromTime(local)),
int(MinFromTime(local)),
int(SecFromTime(local)),
offset,
usetz ? " " : "",
usetz ? tzbuf : "");
break;
case FORMATSPEC_DATE:
/* Tue Oct 31 2000 */
JS_snprintf(buf, sizeof buf,
"%s %s %.2d %.4d",
days[int(WeekDay(local))],
months[int(MonthFromTime(local))],
int(DateFromTime(local)),
int(YearFromTime(local)));
break;
case FORMATSPEC_TIME:
/* 09:41:40 GMT-0800 (PST) */
JS_snprintf(buf, sizeof buf,
"%.2d:%.2d:%.2d GMT%+.4d%s%s",
int(HourFromTime(local)),
int(MinFromTime(local)),
int(SecFromTime(local)),
offset,
usetz ? " " : "",
usetz ? tzbuf : "");
break;
}
}
JSString* str = JS_NewStringCopyZ(cx, buf);
if (!str)
return false;
rval.setString(str);