/* -*- 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() { const double maxResolutionMs = 2; double timestamp = static_cast<double>(PRMJ_Now()) / PRMJ_USEC_PER_MSEC; timestamp = floor(timestamp / maxResolutionMs) * maxResolutionMs; return TimeClip(timestamp); } 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; }