michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * JS date methods. michael@0: * michael@0: * "For example, OS/360 devotes 26 bytes of the permanently michael@0: * resident date-turnover routine to the proper handling of michael@0: * December 31 on leap years (when it is Day 366). That michael@0: * might have been left to the operator." michael@0: * michael@0: * Frederick Brooks, 'The Second-System Effect'. michael@0: */ michael@0: michael@0: #include "jsdate.h" michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/FloatingPoint.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "jsapi.h" michael@0: #include "jscntxt.h" michael@0: #include "jsnum.h" michael@0: #include "jsobj.h" michael@0: #include "jsprf.h" michael@0: #include "jsstr.h" michael@0: #include "jstypes.h" michael@0: #include "jsutil.h" michael@0: #include "prmjtime.h" michael@0: michael@0: #include "js/Date.h" michael@0: #include "vm/DateTime.h" michael@0: #include "vm/GlobalObject.h" michael@0: #include "vm/Interpreter.h" michael@0: #include "vm/NumericConversions.h" michael@0: #include "vm/String.h" michael@0: #include "vm/StringBuffer.h" michael@0: michael@0: #include "jsobjinlines.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::types; michael@0: michael@0: using mozilla::ArrayLength; michael@0: using mozilla::IsFinite; michael@0: using mozilla::IsNaN; michael@0: using JS::GenericNaN; michael@0: michael@0: /* michael@0: * The JS 'Date' object is patterned after the Java 'Date' object. michael@0: * Here is a script: michael@0: * michael@0: * today = new Date(); michael@0: * michael@0: * print(today.toLocaleString()); michael@0: * michael@0: * weekDay = today.getDay(); michael@0: * michael@0: * michael@0: * These Java (and ECMA-262) methods are supported: michael@0: * michael@0: * UTC michael@0: * getDate (getUTCDate) michael@0: * getDay (getUTCDay) michael@0: * getHours (getUTCHours) michael@0: * getMinutes (getUTCMinutes) michael@0: * getMonth (getUTCMonth) michael@0: * getSeconds (getUTCSeconds) michael@0: * getMilliseconds (getUTCMilliseconds) michael@0: * getTime michael@0: * getTimezoneOffset michael@0: * getYear michael@0: * getFullYear (getUTCFullYear) michael@0: * parse michael@0: * setDate (setUTCDate) michael@0: * setHours (setUTCHours) michael@0: * setMinutes (setUTCMinutes) michael@0: * setMonth (setUTCMonth) michael@0: * setSeconds (setUTCSeconds) michael@0: * setMilliseconds (setUTCMilliseconds) michael@0: * setTime michael@0: * setYear (setFullYear, setUTCFullYear) michael@0: * toGMTString (toUTCString) michael@0: * toLocaleString michael@0: * toString michael@0: * michael@0: * michael@0: * These Java methods are not supported michael@0: * michael@0: * setDay michael@0: * before michael@0: * after michael@0: * equals michael@0: * hashCode michael@0: */ michael@0: michael@0: static inline double michael@0: Day(double t) michael@0: { michael@0: return floor(t / msPerDay); michael@0: } michael@0: michael@0: static double michael@0: TimeWithinDay(double t) michael@0: { michael@0: double result = fmod(t, msPerDay); michael@0: if (result < 0) michael@0: result += msPerDay; michael@0: return result; michael@0: } michael@0: michael@0: /* ES5 15.9.1.3. */ michael@0: static inline bool michael@0: IsLeapYear(double year) michael@0: { michael@0: JS_ASSERT(ToInteger(year) == year); michael@0: return fmod(year, 4) == 0 && (fmod(year, 100) != 0 || fmod(year, 400) == 0); michael@0: } michael@0: michael@0: static inline double michael@0: DaysInYear(double year) michael@0: { michael@0: if (!IsFinite(year)) michael@0: return GenericNaN(); michael@0: return IsLeapYear(year) ? 366 : 365; michael@0: } michael@0: michael@0: static inline double michael@0: DayFromYear(double y) michael@0: { michael@0: return 365 * (y - 1970) + michael@0: floor((y - 1969) / 4.0) - michael@0: floor((y - 1901) / 100.0) + michael@0: floor((y - 1601) / 400.0); michael@0: } michael@0: michael@0: static inline double michael@0: TimeFromYear(double y) michael@0: { michael@0: return DayFromYear(y) * msPerDay; michael@0: } michael@0: michael@0: static double michael@0: YearFromTime(double t) michael@0: { michael@0: if (!IsFinite(t)) michael@0: return GenericNaN(); michael@0: michael@0: JS_ASSERT(ToInteger(t) == t); michael@0: michael@0: double y = floor(t / (msPerDay * 365.2425)) + 1970; michael@0: double t2 = TimeFromYear(y); michael@0: michael@0: /* michael@0: * Adjust the year if the approximation was wrong. Since the year was michael@0: * computed using the average number of ms per year, it will usually michael@0: * be wrong for dates within several hours of a year transition. michael@0: */ michael@0: if (t2 > t) { michael@0: y--; michael@0: } else { michael@0: if (t2 + msPerDay * DaysInYear(y) <= t) michael@0: y++; michael@0: } michael@0: return y; michael@0: } michael@0: michael@0: static inline int michael@0: DaysInFebruary(double year) michael@0: { michael@0: return IsLeapYear(year) ? 29 : 28; michael@0: } michael@0: michael@0: /* ES5 15.9.1.4. */ michael@0: static inline double michael@0: DayWithinYear(double t, double year) michael@0: { michael@0: JS_ASSERT_IF(IsFinite(t), YearFromTime(t) == year); michael@0: return Day(t) - DayFromYear(year); michael@0: } michael@0: michael@0: static double michael@0: MonthFromTime(double t) michael@0: { michael@0: if (!IsFinite(t)) michael@0: return GenericNaN(); michael@0: michael@0: double year = YearFromTime(t); michael@0: double d = DayWithinYear(t, year); michael@0: michael@0: int step; michael@0: if (d < (step = 31)) michael@0: return 0; michael@0: if (d < (step += DaysInFebruary(year))) michael@0: return 1; michael@0: if (d < (step += 31)) michael@0: return 2; michael@0: if (d < (step += 30)) michael@0: return 3; michael@0: if (d < (step += 31)) michael@0: return 4; michael@0: if (d < (step += 30)) michael@0: return 5; michael@0: if (d < (step += 31)) michael@0: return 6; michael@0: if (d < (step += 31)) michael@0: return 7; michael@0: if (d < (step += 30)) michael@0: return 8; michael@0: if (d < (step += 31)) michael@0: return 9; michael@0: if (d < (step += 30)) michael@0: return 10; michael@0: return 11; michael@0: } michael@0: michael@0: /* ES5 15.9.1.5. */ michael@0: static double michael@0: DateFromTime(double t) michael@0: { michael@0: if (!IsFinite(t)) michael@0: return GenericNaN(); michael@0: michael@0: double year = YearFromTime(t); michael@0: double d = DayWithinYear(t, year); michael@0: michael@0: int next; michael@0: if (d <= (next = 30)) michael@0: return d + 1; michael@0: int step = next; michael@0: if (d <= (next += DaysInFebruary(year))) michael@0: return d - step; michael@0: step = next; michael@0: if (d <= (next += 31)) michael@0: return d - step; michael@0: step = next; michael@0: if (d <= (next += 30)) michael@0: return d - step; michael@0: step = next; michael@0: if (d <= (next += 31)) michael@0: return d - step; michael@0: step = next; michael@0: if (d <= (next += 30)) michael@0: return d - step; michael@0: step = next; michael@0: if (d <= (next += 31)) michael@0: return d - step; michael@0: step = next; michael@0: if (d <= (next += 31)) michael@0: return d - step; michael@0: step = next; michael@0: if (d <= (next += 30)) michael@0: return d - step; michael@0: step = next; michael@0: if (d <= (next += 31)) michael@0: return d - step; michael@0: step = next; michael@0: if (d <= (next += 30)) michael@0: return d - step; michael@0: step = next; michael@0: return d - step; michael@0: } michael@0: michael@0: /* ES5 15.9.1.6. */ michael@0: static int michael@0: WeekDay(double t) michael@0: { michael@0: /* michael@0: * We can't assert TimeClip(t) == t because we call this function with michael@0: * local times, which can be offset outside TimeClip's permitted range. michael@0: */ michael@0: JS_ASSERT(ToInteger(t) == t); michael@0: int result = (int(Day(t)) + 4) % 7; michael@0: if (result < 0) michael@0: result += 7; michael@0: return result; michael@0: } michael@0: michael@0: static inline int michael@0: DayFromMonth(int month, bool isLeapYear) michael@0: { michael@0: /* michael@0: * The following array contains the day of year for the first day of michael@0: * each month, where index 0 is January, and day 0 is January 1. michael@0: */ michael@0: static const int firstDayOfMonth[2][13] = { michael@0: {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}, michael@0: {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366} michael@0: }; michael@0: michael@0: JS_ASSERT(0 <= month && month <= 12); michael@0: return firstDayOfMonth[isLeapYear][month]; michael@0: } michael@0: michael@0: template michael@0: static inline int michael@0: DayFromMonth(T month, bool isLeapYear) MOZ_DELETE; michael@0: michael@0: /* ES5 15.9.1.12 (out of order to accommodate DaylightSavingTA). */ michael@0: static double michael@0: MakeDay(double year, double month, double date) michael@0: { michael@0: /* Step 1. */ michael@0: if (!IsFinite(year) || !IsFinite(month) || !IsFinite(date)) michael@0: return GenericNaN(); michael@0: michael@0: /* Steps 2-4. */ michael@0: double y = ToInteger(year); michael@0: double m = ToInteger(month); michael@0: double dt = ToInteger(date); michael@0: michael@0: /* Step 5. */ michael@0: double ym = y + floor(m / 12); michael@0: michael@0: /* Step 6. */ michael@0: int mn = int(fmod(m, 12.0)); michael@0: if (mn < 0) michael@0: mn += 12; michael@0: michael@0: /* Steps 7-8. */ michael@0: bool leap = IsLeapYear(ym); michael@0: michael@0: double yearday = floor(TimeFromYear(ym) / msPerDay); michael@0: double monthday = DayFromMonth(mn, leap); michael@0: michael@0: return yearday + monthday + dt - 1; michael@0: } michael@0: michael@0: /* ES5 15.9.1.13 (out of order to accommodate DaylightSavingTA). */ michael@0: static inline double michael@0: MakeDate(double day, double time) michael@0: { michael@0: /* Step 1. */ michael@0: if (!IsFinite(day) || !IsFinite(time)) michael@0: return GenericNaN(); michael@0: michael@0: /* Step 2. */ michael@0: return day * msPerDay + time; michael@0: } michael@0: michael@0: JS_PUBLIC_API(double) michael@0: JS::MakeDate(double year, unsigned month, unsigned day) michael@0: { michael@0: return TimeClip(::MakeDate(MakeDay(year, month, day), 0)); michael@0: } michael@0: michael@0: JS_PUBLIC_API(double) michael@0: JS::YearFromTime(double time) michael@0: { michael@0: return ::YearFromTime(time); michael@0: } michael@0: michael@0: JS_PUBLIC_API(double) michael@0: JS::MonthFromTime(double time) michael@0: { michael@0: return ::MonthFromTime(time); michael@0: } michael@0: michael@0: JS_PUBLIC_API(double) michael@0: JS::DayFromTime(double time) michael@0: { michael@0: return DateFromTime(time); michael@0: } michael@0: michael@0: /* michael@0: * Find a year for which any given date will fall on the same weekday. michael@0: * michael@0: * This function should be used with caution when used other than michael@0: * for determining DST; it hasn't been proven not to produce an michael@0: * incorrect year for times near year boundaries. michael@0: */ michael@0: static int michael@0: EquivalentYearForDST(int year) michael@0: { michael@0: /* michael@0: * Years and leap years on which Jan 1 is a Sunday, Monday, etc. michael@0: * michael@0: * yearStartingWith[0][i] is an example non-leap year where michael@0: * Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc. michael@0: * michael@0: * yearStartingWith[1][i] is an example leap year where michael@0: * Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc. michael@0: */ michael@0: static const int yearStartingWith[2][7] = { michael@0: {1978, 1973, 1974, 1975, 1981, 1971, 1977}, michael@0: {1984, 1996, 1980, 1992, 1976, 1988, 1972} michael@0: }; michael@0: michael@0: int day = int(DayFromYear(year) + 4) % 7; michael@0: if (day < 0) michael@0: day += 7; michael@0: michael@0: return yearStartingWith[IsLeapYear(year)][day]; michael@0: } michael@0: michael@0: /* ES5 15.9.1.8. */ michael@0: static double michael@0: DaylightSavingTA(double t, DateTimeInfo *dtInfo) michael@0: { michael@0: if (!IsFinite(t)) michael@0: return GenericNaN(); michael@0: michael@0: /* michael@0: * If earlier than 1970 or after 2038, potentially beyond the ken of michael@0: * many OSes, map it to an equivalent year before asking. michael@0: */ michael@0: if (t < 0.0 || t > 2145916800000.0) { michael@0: int year = EquivalentYearForDST(int(YearFromTime(t))); michael@0: double day = MakeDay(year, MonthFromTime(t), DateFromTime(t)); michael@0: t = MakeDate(day, TimeWithinDay(t)); michael@0: } michael@0: michael@0: int64_t utcMilliseconds = static_cast(t); michael@0: int64_t offsetMilliseconds = dtInfo->getDSTOffsetMilliseconds(utcMilliseconds); michael@0: return static_cast(offsetMilliseconds); michael@0: } michael@0: michael@0: static double michael@0: AdjustTime(double date, DateTimeInfo *dtInfo) michael@0: { michael@0: double t = DaylightSavingTA(date, dtInfo) + dtInfo->localTZA(); michael@0: t = (dtInfo->localTZA() >= 0) ? fmod(t, msPerDay) : -fmod(msPerDay - t, msPerDay); michael@0: return t; michael@0: } michael@0: michael@0: /* ES5 15.9.1.9. */ michael@0: static double michael@0: LocalTime(double t, DateTimeInfo *dtInfo) michael@0: { michael@0: return t + AdjustTime(t, dtInfo); michael@0: } michael@0: michael@0: static double michael@0: UTC(double t, DateTimeInfo *dtInfo) michael@0: { michael@0: return t - AdjustTime(t - dtInfo->localTZA(), dtInfo); michael@0: } michael@0: michael@0: /* ES5 15.9.1.10. */ michael@0: static double michael@0: HourFromTime(double t) michael@0: { michael@0: double result = fmod(floor(t/msPerHour), HoursPerDay); michael@0: if (result < 0) michael@0: result += HoursPerDay; michael@0: return result; michael@0: } michael@0: michael@0: static double michael@0: MinFromTime(double t) michael@0: { michael@0: double result = fmod(floor(t / msPerMinute), MinutesPerHour); michael@0: if (result < 0) michael@0: result += MinutesPerHour; michael@0: return result; michael@0: } michael@0: michael@0: static double michael@0: SecFromTime(double t) michael@0: { michael@0: double result = fmod(floor(t / msPerSecond), SecondsPerMinute); michael@0: if (result < 0) michael@0: result += SecondsPerMinute; michael@0: return result; michael@0: } michael@0: michael@0: static double michael@0: msFromTime(double t) michael@0: { michael@0: double result = fmod(t, msPerSecond); michael@0: if (result < 0) michael@0: result += msPerSecond; michael@0: return result; michael@0: } michael@0: michael@0: /* ES5 15.9.1.11. */ michael@0: static double michael@0: MakeTime(double hour, double min, double sec, double ms) michael@0: { michael@0: /* Step 1. */ michael@0: if (!IsFinite(hour) || michael@0: !IsFinite(min) || michael@0: !IsFinite(sec) || michael@0: !IsFinite(ms)) michael@0: { michael@0: return GenericNaN(); michael@0: } michael@0: michael@0: /* Step 2. */ michael@0: double h = ToInteger(hour); michael@0: michael@0: /* Step 3. */ michael@0: double m = ToInteger(min); michael@0: michael@0: /* Step 4. */ michael@0: double s = ToInteger(sec); michael@0: michael@0: /* Step 5. */ michael@0: double milli = ToInteger(ms); michael@0: michael@0: /* Steps 6-7. */ michael@0: return h * msPerHour + m * msPerMinute + s * msPerSecond + milli; michael@0: } michael@0: michael@0: /** michael@0: * end of ECMA 'support' functions michael@0: */ michael@0: michael@0: static bool michael@0: date_convert(JSContext *cx, HandleObject obj, JSType hint, MutableHandleValue vp) michael@0: { michael@0: JS_ASSERT(hint == JSTYPE_NUMBER || hint == JSTYPE_STRING || hint == JSTYPE_VOID); michael@0: JS_ASSERT(obj->is()); michael@0: michael@0: return DefaultValue(cx, obj, (hint == JSTYPE_VOID) ? JSTYPE_STRING : hint, vp); michael@0: } michael@0: michael@0: /* for use by date_parse */ michael@0: michael@0: static const char* const wtb[] = { michael@0: "am", "pm", michael@0: "monday", "tuesday", "wednesday", "thursday", "friday", michael@0: "saturday", "sunday", michael@0: "january", "february", "march", "april", "may", "june", michael@0: "july", "august", "september", "october", "november", "december", michael@0: "gmt", "ut", "utc", michael@0: "est", "edt", michael@0: "cst", "cdt", michael@0: "mst", "mdt", michael@0: "pst", "pdt" michael@0: /* time zone table needs to be expanded */ michael@0: }; michael@0: michael@0: static const int ttb[] = { michael@0: -1, -2, 0, 0, 0, 0, 0, 0, 0, /* AM/PM */ michael@0: 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, michael@0: 10000 + 0, 10000 + 0, 10000 + 0, /* GMT/UT/UTC */ michael@0: 10000 + 5 * 60, 10000 + 4 * 60, /* EST/EDT */ michael@0: 10000 + 6 * 60, 10000 + 5 * 60, /* CST/CDT */ michael@0: 10000 + 7 * 60, 10000 + 6 * 60, /* MST/MDT */ michael@0: 10000 + 8 * 60, 10000 + 7 * 60 /* PST/PDT */ michael@0: }; michael@0: michael@0: /* helper for date_parse */ michael@0: static bool michael@0: date_regionMatches(const char* s1, int s1off, const jschar* s2, int s2off, michael@0: int count, int ignoreCase) michael@0: { michael@0: bool result = false; michael@0: /* return true if matches, otherwise, false */ michael@0: michael@0: while (count > 0 && s1[s1off] && s2[s2off]) { michael@0: if (ignoreCase) { michael@0: if (unicode::ToLowerCase(s1[s1off]) != unicode::ToLowerCase(s2[s2off])) michael@0: break; michael@0: } else { michael@0: if ((jschar)s1[s1off] != s2[s2off]) { michael@0: break; michael@0: } michael@0: } michael@0: s1off++; michael@0: s2off++; michael@0: count--; michael@0: } michael@0: michael@0: if (count == 0) { michael@0: result = true; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: /* find UTC time from given date... no 1900 correction! */ michael@0: static double michael@0: date_msecFromDate(double year, double mon, double mday, double hour, michael@0: double min, double sec, double msec) michael@0: { michael@0: return MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, msec)); michael@0: } michael@0: michael@0: /* compute the time in msec (unclipped) from the given args */ michael@0: #define MAXARGS 7 michael@0: michael@0: static bool michael@0: date_msecFromArgs(JSContext *cx, CallArgs args, double *rval) michael@0: { michael@0: unsigned loop; michael@0: double array[MAXARGS]; michael@0: double msec_time; michael@0: michael@0: for (loop = 0; loop < MAXARGS; loop++) { michael@0: if (loop < args.length()) { michael@0: double d; michael@0: if (!ToNumber(cx, args[loop], &d)) michael@0: return false; michael@0: /* return NaN if any arg is not finite */ michael@0: if (!IsFinite(d)) { michael@0: *rval = GenericNaN(); michael@0: return true; michael@0: } michael@0: array[loop] = ToInteger(d); michael@0: } else { michael@0: if (loop == 2) { michael@0: array[loop] = 1; /* Default the date argument to 1. */ michael@0: } else { michael@0: array[loop] = 0; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* adjust 2-digit years into the 20th century */ michael@0: if (array[0] >= 0 && array[0] <= 99) michael@0: array[0] += 1900; michael@0: michael@0: msec_time = date_msecFromDate(array[0], array[1], array[2], michael@0: array[3], array[4], array[5], array[6]); michael@0: *rval = msec_time; michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * See ECMA 15.9.4.[3-10]; michael@0: */ michael@0: static bool michael@0: date_UTC(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: double msec_time; michael@0: if (!date_msecFromArgs(cx, args, &msec_time)) michael@0: return false; michael@0: michael@0: msec_time = TimeClip(msec_time); michael@0: michael@0: args.rval().setNumber(msec_time); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Read and convert decimal digits from s[*i] into *result michael@0: * while *i < limit. michael@0: * michael@0: * Succeed if any digits are converted. Advance *i only michael@0: * as digits are consumed. michael@0: */ michael@0: static bool michael@0: digits(size_t *result, const jschar *s, size_t *i, size_t limit) michael@0: { michael@0: size_t init = *i; michael@0: *result = 0; michael@0: while (*i < limit && michael@0: ('0' <= s[*i] && s[*i] <= '9')) { michael@0: *result *= 10; michael@0: *result += (s[*i] - '0'); michael@0: ++(*i); michael@0: } michael@0: return *i != init; michael@0: } michael@0: michael@0: /* michael@0: * Read and convert decimal digits to the right of a decimal point, michael@0: * representing a fractional integer, from s[*i] into *result michael@0: * while *i < limit. michael@0: * michael@0: * Succeed if any digits are converted. Advance *i only michael@0: * as digits are consumed. michael@0: */ michael@0: static bool michael@0: fractional(double *result, const jschar *s, size_t *i, size_t limit) michael@0: { michael@0: double factor = 0.1; michael@0: size_t init = *i; michael@0: *result = 0.0; michael@0: while (*i < limit && michael@0: ('0' <= s[*i] && s[*i] <= '9')) { michael@0: *result += (s[*i] - '0') * factor; michael@0: factor *= 0.1; michael@0: ++(*i); michael@0: } michael@0: return *i != init; michael@0: } michael@0: michael@0: /* michael@0: * Read and convert exactly n decimal digits from s[*i] michael@0: * to s[min(*i+n,limit)] into *result. michael@0: * michael@0: * Succeed if exactly n digits are converted. Advance *i only michael@0: * on success. michael@0: */ michael@0: static bool michael@0: ndigits(size_t n, size_t *result, const jschar *s, size_t* i, size_t limit) michael@0: { michael@0: size_t init = *i; michael@0: michael@0: if (digits(result, s, i, Min(limit, init+n))) michael@0: return (*i - init) == n; michael@0: michael@0: *i = init; michael@0: return false; michael@0: } michael@0: michael@0: static int michael@0: DaysInMonth(int year, int month) michael@0: { michael@0: bool leap = IsLeapYear(year); michael@0: int result = int(DayFromMonth(month, leap) - DayFromMonth(month - 1, leap)); michael@0: return result; michael@0: } michael@0: michael@0: /* michael@0: * Parse a string in one of the date-time formats given by the W3C michael@0: * "NOTE-datetime" specification. These formats make up a restricted michael@0: * profile of the ISO 8601 format. Quoted here: michael@0: * michael@0: * The formats are as follows. Exactly the components shown here michael@0: * must be present, with exactly this punctuation. Note that the "T" michael@0: * appears literally in the string, to indicate the beginning of the michael@0: * time element, as specified in ISO 8601. michael@0: * michael@0: * Any combination of the date formats with the time formats is michael@0: * allowed, and also either the date or the time can be missing. michael@0: * michael@0: * The specification is silent on the meaning when fields are michael@0: * ommitted so the interpretations are a guess, but hopefully a michael@0: * reasonable one. We default the month to January, the day to the michael@0: * 1st, and hours minutes and seconds all to 0. If the date is michael@0: * missing entirely then we assume 1970-01-01 so that the time can michael@0: * be aded to a date later. If the time is missing then we assume michael@0: * 00:00 UTC. If the time is present but the time zone field is michael@0: * missing then we use local time. michael@0: * michael@0: * Date part: michael@0: * michael@0: * Year: michael@0: * YYYY (eg 1997) michael@0: * michael@0: * Year and month: michael@0: * YYYY-MM (eg 1997-07) michael@0: * michael@0: * Complete date: michael@0: * YYYY-MM-DD (eg 1997-07-16) michael@0: * michael@0: * Time part: michael@0: * michael@0: * Hours and minutes: michael@0: * Thh:mmTZD (eg T19:20+01:00) michael@0: * michael@0: * Hours, minutes and seconds: michael@0: * Thh:mm:ssTZD (eg T19:20:30+01:00) michael@0: * michael@0: * Hours, minutes, seconds and a decimal fraction of a second: michael@0: * Thh:mm:ss.sTZD (eg T19:20:30.45+01:00) michael@0: * michael@0: * where: michael@0: * michael@0: * YYYY = four-digit year or six digit year as +YYYYYY or -YYYYYY michael@0: * MM = two-digit month (01=January, etc.) michael@0: * DD = two-digit day of month (01 through 31) michael@0: * hh = two digits of hour (00 through 23) (am/pm NOT allowed) michael@0: * mm = two digits of minute (00 through 59) michael@0: * ss = two digits of second (00 through 59) michael@0: * s = one or more digits representing a decimal fraction of a second michael@0: * TZD = time zone designator (Z or +hh:mm or -hh:mm or missing for local) michael@0: */ michael@0: michael@0: static bool michael@0: date_parseISOString(JSLinearString *str, double *result, DateTimeInfo *dtInfo) michael@0: { michael@0: double msec; michael@0: michael@0: const jschar *s; michael@0: size_t limit; michael@0: size_t i = 0; michael@0: int tzMul = 1; michael@0: int dateMul = 1; michael@0: size_t year = 1970; michael@0: size_t month = 1; michael@0: size_t day = 1; michael@0: size_t hour = 0; michael@0: size_t min = 0; michael@0: size_t sec = 0; michael@0: double frac = 0; michael@0: bool isLocalTime = false; michael@0: size_t tzHour = 0; michael@0: size_t tzMin = 0; michael@0: michael@0: #define PEEK(ch) (i < limit && s[i] == ch) michael@0: michael@0: #define NEED(ch) \ michael@0: JS_BEGIN_MACRO \ michael@0: if (i >= limit || s[i] != ch) { goto syntax; } else { ++i; } \ michael@0: JS_END_MACRO michael@0: michael@0: #define DONE_DATE_UNLESS(ch) \ michael@0: JS_BEGIN_MACRO \ michael@0: if (i >= limit || s[i] != ch) { goto done_date; } else { ++i; } \ michael@0: JS_END_MACRO michael@0: michael@0: #define DONE_UNLESS(ch) \ michael@0: JS_BEGIN_MACRO \ michael@0: if (i >= limit || s[i] != ch) { goto done; } else { ++i; } \ michael@0: JS_END_MACRO michael@0: michael@0: #define NEED_NDIGITS(n, field) \ michael@0: JS_BEGIN_MACRO \ michael@0: if (!ndigits(n, &field, s, &i, limit)) { goto syntax; } \ michael@0: JS_END_MACRO michael@0: michael@0: s = str->chars(); michael@0: limit = str->length(); michael@0: michael@0: if (PEEK('+') || PEEK('-')) { michael@0: if (PEEK('-')) michael@0: dateMul = -1; michael@0: ++i; michael@0: NEED_NDIGITS(6, year); michael@0: } else if (!PEEK('T')) { michael@0: NEED_NDIGITS(4, year); michael@0: } michael@0: DONE_DATE_UNLESS('-'); michael@0: NEED_NDIGITS(2, month); michael@0: DONE_DATE_UNLESS('-'); michael@0: NEED_NDIGITS(2, day); michael@0: michael@0: done_date: michael@0: DONE_UNLESS('T'); michael@0: NEED_NDIGITS(2, hour); michael@0: NEED(':'); michael@0: NEED_NDIGITS(2, min); michael@0: michael@0: if (PEEK(':')) { michael@0: ++i; michael@0: NEED_NDIGITS(2, sec); michael@0: if (PEEK('.')) { michael@0: ++i; michael@0: if (!fractional(&frac, s, &i, limit)) michael@0: goto syntax; michael@0: } michael@0: } michael@0: michael@0: if (PEEK('Z')) { michael@0: ++i; michael@0: } else if (PEEK('+') || PEEK('-')) { michael@0: if (PEEK('-')) michael@0: tzMul = -1; michael@0: ++i; michael@0: NEED_NDIGITS(2, tzHour); michael@0: /* michael@0: * Non-standard extension to the ISO date format (permitted by ES5): michael@0: * allow "-0700" as a time zone offset, not just "-07:00". michael@0: */ michael@0: if (PEEK(':')) michael@0: ++i; michael@0: NEED_NDIGITS(2, tzMin); michael@0: } else { michael@0: isLocalTime = true; michael@0: } michael@0: michael@0: done: michael@0: if (year > 275943 // ceil(1e8/365) + 1970 michael@0: || (month == 0 || month > 12) michael@0: || (day == 0 || day > size_t(DaysInMonth(year,month))) michael@0: || hour > 24 michael@0: || ((hour == 24) && (min > 0 || sec > 0)) michael@0: || min > 59 michael@0: || sec > 59 michael@0: || tzHour > 23 michael@0: || tzMin > 59) michael@0: goto syntax; michael@0: michael@0: if (i != limit) michael@0: goto syntax; michael@0: michael@0: month -= 1; /* convert month to 0-based */ michael@0: michael@0: msec = date_msecFromDate(dateMul * (double)year, month, day, michael@0: hour, min, sec, michael@0: frac * 1000.0);; michael@0: michael@0: if (isLocalTime) { michael@0: msec = UTC(msec, dtInfo); michael@0: } else { michael@0: msec -= ((tzMul) * ((tzHour * msPerHour) michael@0: + (tzMin * msPerMinute))); michael@0: } michael@0: michael@0: if (msec < -8.64e15 || msec > 8.64e15) michael@0: goto syntax; michael@0: michael@0: *result = msec; michael@0: michael@0: return true; michael@0: michael@0: syntax: michael@0: /* syntax error */ michael@0: *result = 0; michael@0: return false; michael@0: michael@0: #undef PEEK michael@0: #undef NEED michael@0: #undef DONE_UNLESS michael@0: #undef NEED_NDIGITS michael@0: } michael@0: michael@0: static bool michael@0: date_parseString(JSLinearString *str, double *result, DateTimeInfo *dtInfo) michael@0: { michael@0: double msec; michael@0: michael@0: const jschar *s; michael@0: size_t limit; michael@0: size_t i = 0; michael@0: int year = -1; michael@0: int mon = -1; michael@0: int mday = -1; michael@0: int hour = -1; michael@0: int min = -1; michael@0: int sec = -1; michael@0: int c = -1; michael@0: int n = -1; michael@0: int tzoffset = -1; michael@0: int prevc = 0; michael@0: bool seenplusminus = false; michael@0: int temp; michael@0: bool seenmonthname = false; michael@0: michael@0: if (date_parseISOString(str, result, dtInfo)) michael@0: return true; michael@0: michael@0: s = str->chars(); michael@0: limit = str->length(); michael@0: if (limit == 0) michael@0: goto syntax; michael@0: while (i < limit) { michael@0: c = s[i]; michael@0: i++; michael@0: if (c <= ' ' || c == ',' || c == '-') { michael@0: if (c == '-' && '0' <= s[i] && s[i] <= '9') { michael@0: prevc = c; michael@0: } michael@0: continue; michael@0: } michael@0: if (c == '(') { /* comments) */ michael@0: int depth = 1; michael@0: while (i < limit) { michael@0: c = s[i]; michael@0: i++; michael@0: if (c == '(') depth++; michael@0: else if (c == ')') michael@0: if (--depth <= 0) michael@0: break; michael@0: } michael@0: continue; michael@0: } michael@0: if ('0' <= c && c <= '9') { michael@0: n = c - '0'; michael@0: while (i < limit && '0' <= (c = s[i]) && c <= '9') { michael@0: n = n * 10 + c - '0'; michael@0: i++; michael@0: } michael@0: michael@0: /* allow TZA before the year, so michael@0: * 'Wed Nov 05 21:49:11 GMT-0800 1997' michael@0: * works */ michael@0: michael@0: /* uses of seenplusminus allow : in TZA, so Java michael@0: * no-timezone style of GMT+4:30 works michael@0: */ michael@0: michael@0: if ((prevc == '+' || prevc == '-')/* && year>=0 */) { michael@0: /* make ':' case below change tzoffset */ michael@0: seenplusminus = true; michael@0: michael@0: /* offset */ michael@0: if (n < 24) michael@0: n = n * 60; /* EG. "GMT-3" */ michael@0: else michael@0: n = n % 100 + n / 100 * 60; /* eg "GMT-0430" */ michael@0: if (prevc == '+') /* plus means east of GMT */ michael@0: n = -n; michael@0: if (tzoffset != 0 && tzoffset != -1) michael@0: goto syntax; michael@0: tzoffset = n; michael@0: } else if (prevc == '/' && mon >= 0 && mday >= 0 && year < 0) { michael@0: if (c <= ' ' || c == ',' || c == '/' || i >= limit) michael@0: year = n; michael@0: else michael@0: goto syntax; michael@0: } else if (c == ':') { michael@0: if (hour < 0) michael@0: hour = /*byte*/ n; michael@0: else if (min < 0) michael@0: min = /*byte*/ n; michael@0: else michael@0: goto syntax; michael@0: } else if (c == '/') { michael@0: /* until it is determined that mon is the actual michael@0: month, keep it as 1-based rather than 0-based */ michael@0: if (mon < 0) michael@0: mon = /*byte*/ n; michael@0: else if (mday < 0) michael@0: mday = /*byte*/ n; michael@0: else michael@0: goto syntax; michael@0: } else if (i < limit && c != ',' && c > ' ' && c != '-' && c != '(') { michael@0: goto syntax; michael@0: } else if (seenplusminus && n < 60) { /* handle GMT-3:30 */ michael@0: if (tzoffset < 0) michael@0: tzoffset -= n; michael@0: else michael@0: tzoffset += n; michael@0: } else if (hour >= 0 && min < 0) { michael@0: min = /*byte*/ n; michael@0: } else if (prevc == ':' && min >= 0 && sec < 0) { michael@0: sec = /*byte*/ n; michael@0: } else if (mon < 0) { michael@0: mon = /*byte*/n; michael@0: } else if (mon >= 0 && mday < 0) { michael@0: mday = /*byte*/ n; michael@0: } else if (mon >= 0 && mday >= 0 && year < 0) { michael@0: year = n; michael@0: } else { michael@0: goto syntax; michael@0: } michael@0: prevc = 0; michael@0: } else if (c == '/' || c == ':' || c == '+' || c == '-') { michael@0: prevc = c; michael@0: } else { michael@0: size_t st = i - 1; michael@0: int k; michael@0: while (i < limit) { michael@0: c = s[i]; michael@0: if (!(('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'))) michael@0: break; michael@0: i++; michael@0: } michael@0: if (i <= st + 1) michael@0: goto syntax; michael@0: for (k = ArrayLength(wtb); --k >= 0;) michael@0: if (date_regionMatches(wtb[k], 0, s, st, i-st, 1)) { michael@0: int action = ttb[k]; michael@0: if (action != 0) { michael@0: if (action < 0) { michael@0: /* michael@0: * AM/PM. Count 12:30 AM as 00:30, 12:30 PM as michael@0: * 12:30, instead of blindly adding 12 if PM. michael@0: */ michael@0: JS_ASSERT(action == -1 || action == -2); michael@0: if (hour > 12 || hour < 0) { michael@0: goto syntax; michael@0: } else { michael@0: if (action == -1 && hour == 12) { /* am */ michael@0: hour = 0; michael@0: } else if (action == -2 && hour != 12) { /* pm */ michael@0: hour += 12; michael@0: } michael@0: } michael@0: } else if (action <= 13) { /* month! */ michael@0: /* Adjust mon to be 1-based until the final values michael@0: for mon, mday and year are adjusted below */ michael@0: if (seenmonthname) { michael@0: goto syntax; michael@0: } michael@0: seenmonthname = true; michael@0: temp = /*byte*/ (action - 2) + 1; michael@0: michael@0: if (mon < 0) { michael@0: mon = temp; michael@0: } else if (mday < 0) { michael@0: mday = mon; michael@0: mon = temp; michael@0: } else if (year < 0) { michael@0: year = mon; michael@0: mon = temp; michael@0: } else { michael@0: goto syntax; michael@0: } michael@0: } else { michael@0: tzoffset = action - 10000; michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: if (k < 0) michael@0: goto syntax; michael@0: prevc = 0; michael@0: } michael@0: } michael@0: if (year < 0 || mon < 0 || mday < 0) michael@0: goto syntax; michael@0: /* michael@0: Case 1. The input string contains an English month name. michael@0: The form of the string can be month f l, or f month l, or michael@0: f l month which each evaluate to the same date. michael@0: If f and l are both greater than or equal to 70, or michael@0: both less than 70, the date is invalid. michael@0: The year is taken to be the greater of the values f, l. michael@0: If the year is greater than or equal to 70 and less than 100, michael@0: it is considered to be the number of years after 1900. michael@0: Case 2. The input string is of the form "f/m/l" where f, m and l are michael@0: integers, e.g. 7/16/45. michael@0: Adjust the mon, mday and year values to achieve 100% MSIE michael@0: compatibility. michael@0: a. If 0 <= f < 70, f/m/l is interpreted as month/day/year. michael@0: i. If year < 100, it is the number of years after 1900 michael@0: ii. If year >= 100, it is the number of years after 0. michael@0: b. If 70 <= f < 100 michael@0: i. If m < 70, f/m/l is interpreted as michael@0: year/month/day where year is the number of years after michael@0: 1900. michael@0: ii. If m >= 70, the date is invalid. michael@0: c. If f >= 100 michael@0: i. If m < 70, f/m/l is interpreted as michael@0: year/month/day where year is the number of years after 0. michael@0: ii. If m >= 70, the date is invalid. michael@0: */ michael@0: if (seenmonthname) { michael@0: if ((mday >= 70 && year >= 70) || (mday < 70 && year < 70)) { michael@0: goto syntax; michael@0: } michael@0: if (mday > year) { michael@0: temp = year; michael@0: year = mday; michael@0: mday = temp; michael@0: } michael@0: if (year >= 70 && year < 100) { michael@0: year += 1900; michael@0: } michael@0: } else if (mon < 70) { /* (a) month/day/year */ michael@0: if (year < 100) { michael@0: year += 1900; michael@0: } michael@0: } else if (mon < 100) { /* (b) year/month/day */ michael@0: if (mday < 70) { michael@0: temp = year; michael@0: year = mon + 1900; michael@0: mon = mday; michael@0: mday = temp; michael@0: } else { michael@0: goto syntax; michael@0: } michael@0: } else { /* (c) year/month/day */ michael@0: if (mday < 70) { michael@0: temp = year; michael@0: year = mon; michael@0: mon = mday; michael@0: mday = temp; michael@0: } else { michael@0: goto syntax; michael@0: } michael@0: } michael@0: mon -= 1; /* convert month to 0-based */ michael@0: if (sec < 0) michael@0: sec = 0; michael@0: if (min < 0) michael@0: min = 0; michael@0: if (hour < 0) michael@0: hour = 0; michael@0: michael@0: msec = date_msecFromDate(year, mon, mday, hour, min, sec, 0); michael@0: michael@0: if (tzoffset == -1) { /* no time zone specified, have to use local */ michael@0: msec = UTC(msec, dtInfo); michael@0: } else { michael@0: msec += tzoffset * msPerMinute; michael@0: } michael@0: michael@0: *result = msec; michael@0: return true; michael@0: michael@0: syntax: michael@0: /* syntax error */ michael@0: *result = 0; michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: date_parse(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() == 0) { michael@0: args.rval().setNaN(); michael@0: return true; michael@0: } michael@0: michael@0: JSString *str = ToString(cx, args[0]); michael@0: if (!str) michael@0: return false; michael@0: michael@0: JSLinearString *linearStr = str->ensureLinear(cx); michael@0: if (!linearStr) michael@0: return false; michael@0: michael@0: double result; michael@0: if (!date_parseString(linearStr, &result, &cx->runtime()->dateTimeInfo)) { michael@0: args.rval().setNaN(); michael@0: return true; michael@0: } michael@0: michael@0: result = TimeClip(result); michael@0: args.rval().setNumber(result); michael@0: return true; michael@0: } michael@0: michael@0: static inline double michael@0: NowAsMillis() michael@0: { michael@0: return (double) (PRMJ_Now() / PRMJ_USEC_PER_MSEC); michael@0: } michael@0: michael@0: static bool michael@0: date_now(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: args.rval().setDouble(NowAsMillis()); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: DateObject::setUTCTime(double t, Value *vp) michael@0: { michael@0: for (size_t ind = COMPONENTS_START_SLOT; ind < RESERVED_SLOTS; ind++) michael@0: setReservedSlot(ind, UndefinedValue()); michael@0: michael@0: setFixedSlot(UTC_TIME_SLOT, DoubleValue(t)); michael@0: if (vp) michael@0: vp->setDouble(t); michael@0: } michael@0: michael@0: void michael@0: DateObject::fillLocalTimeSlots(DateTimeInfo *dtInfo) michael@0: { michael@0: /* Check if the cache is already populated. */ michael@0: if (!getReservedSlot(LOCAL_TIME_SLOT).isUndefined() && michael@0: getReservedSlot(TZA_SLOT).toDouble() == dtInfo->localTZA()) michael@0: { michael@0: return; michael@0: } michael@0: michael@0: /* Remember timezone used to generate the local cache. */ michael@0: setReservedSlot(TZA_SLOT, DoubleValue(dtInfo->localTZA())); michael@0: michael@0: double utcTime = UTCTime().toNumber(); michael@0: michael@0: if (!IsFinite(utcTime)) { michael@0: for (size_t ind = COMPONENTS_START_SLOT; ind < RESERVED_SLOTS; ind++) michael@0: setReservedSlot(ind, DoubleValue(utcTime)); michael@0: return; michael@0: } michael@0: michael@0: double localTime = LocalTime(utcTime, dtInfo); michael@0: michael@0: setReservedSlot(LOCAL_TIME_SLOT, DoubleValue(localTime)); michael@0: michael@0: int year = (int) floor(localTime /(msPerDay * 365.2425)) + 1970; michael@0: double yearStartTime = TimeFromYear(year); michael@0: michael@0: /* Adjust the year in case the approximation was wrong, as in YearFromTime. */ michael@0: int yearDays; michael@0: if (yearStartTime > localTime) { michael@0: year--; michael@0: yearStartTime -= (msPerDay * DaysInYear(year)); michael@0: yearDays = DaysInYear(year); michael@0: } else { michael@0: yearDays = DaysInYear(year); michael@0: double nextStart = yearStartTime + (msPerDay * yearDays); michael@0: if (nextStart <= localTime) { michael@0: year++; michael@0: yearStartTime = nextStart; michael@0: yearDays = DaysInYear(year); michael@0: } michael@0: } michael@0: michael@0: setReservedSlot(LOCAL_YEAR_SLOT, Int32Value(year)); michael@0: michael@0: uint64_t yearTime = uint64_t(localTime - yearStartTime); michael@0: int yearSeconds = uint32_t(yearTime / 1000); michael@0: michael@0: int day = yearSeconds / int(SecondsPerDay); michael@0: michael@0: int step = -1, next = 30; michael@0: int month; michael@0: michael@0: do { michael@0: if (day <= next) { michael@0: month = 0; michael@0: break; michael@0: } michael@0: step = next; michael@0: next += ((yearDays == 366) ? 29 : 28); michael@0: if (day <= next) { michael@0: month = 1; michael@0: break; michael@0: } michael@0: step = next; michael@0: if (day <= (next += 31)) { michael@0: month = 2; michael@0: break; michael@0: } michael@0: step = next; michael@0: if (day <= (next += 30)) { michael@0: month = 3; michael@0: break; michael@0: } michael@0: step = next; michael@0: if (day <= (next += 31)) { michael@0: month = 4; michael@0: break; michael@0: } michael@0: step = next; michael@0: if (day <= (next += 30)) { michael@0: month = 5; michael@0: break; michael@0: } michael@0: step = next; michael@0: if (day <= (next += 31)) { michael@0: month = 6; michael@0: break; michael@0: } michael@0: step = next; michael@0: if (day <= (next += 31)) { michael@0: month = 7; michael@0: break; michael@0: } michael@0: step = next; michael@0: if (day <= (next += 30)) { michael@0: month = 8; michael@0: break; michael@0: } michael@0: step = next; michael@0: if (day <= (next += 31)) { michael@0: month = 9; michael@0: break; michael@0: } michael@0: step = next; michael@0: if (day <= (next += 30)) { michael@0: month = 10; michael@0: break; michael@0: } michael@0: step = next; michael@0: month = 11; michael@0: } while (0); michael@0: michael@0: setReservedSlot(LOCAL_MONTH_SLOT, Int32Value(month)); michael@0: setReservedSlot(LOCAL_DATE_SLOT, Int32Value(day - step)); michael@0: michael@0: int weekday = WeekDay(localTime); michael@0: setReservedSlot(LOCAL_DAY_SLOT, Int32Value(weekday)); michael@0: michael@0: int seconds = yearSeconds % 60; michael@0: setReservedSlot(LOCAL_SECONDS_SLOT, Int32Value(seconds)); michael@0: michael@0: int minutes = (yearSeconds / 60) % 60; michael@0: setReservedSlot(LOCAL_MINUTES_SLOT, Int32Value(minutes)); michael@0: michael@0: int hours = (yearSeconds / (60 * 60)) % 24; michael@0: setReservedSlot(LOCAL_HOURS_SLOT, Int32Value(hours)); michael@0: } michael@0: michael@0: inline double michael@0: DateObject::cachedLocalTime(DateTimeInfo *dtInfo) michael@0: { michael@0: fillLocalTimeSlots(dtInfo); michael@0: return getReservedSlot(LOCAL_TIME_SLOT).toDouble(); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: IsDate(HandleValue v) michael@0: { michael@0: return v.isObject() && v.toObject().is(); michael@0: } michael@0: michael@0: /* michael@0: * See ECMA 15.9.5.4 thru 15.9.5.23 michael@0: */ michael@0: /* static */ MOZ_ALWAYS_INLINE bool michael@0: DateObject::getTime_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: args.rval().set(args.thisv().toObject().as().UTCTime()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_getTime(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* static */ MOZ_ALWAYS_INLINE bool michael@0: DateObject::getYear_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: DateObject *dateObj = &args.thisv().toObject().as(); michael@0: dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); michael@0: michael@0: Value yearVal = dateObj->getReservedSlot(LOCAL_YEAR_SLOT); michael@0: if (yearVal.isInt32()) { michael@0: /* Follow ECMA-262 to the letter, contrary to IE JScript. */ michael@0: int year = yearVal.toInt32() - 1900; michael@0: args.rval().setInt32(year); michael@0: } else { michael@0: args.rval().set(yearVal); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_getYear(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* static */ MOZ_ALWAYS_INLINE bool michael@0: DateObject::getFullYear_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: DateObject *dateObj = &args.thisv().toObject().as(); michael@0: dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); michael@0: michael@0: args.rval().set(dateObj->getReservedSlot(LOCAL_YEAR_SLOT)); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_getFullYear(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* static */ MOZ_ALWAYS_INLINE bool michael@0: DateObject::getUTCFullYear_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: double result = args.thisv().toObject().as().UTCTime().toNumber(); michael@0: if (IsFinite(result)) michael@0: result = YearFromTime(result); michael@0: michael@0: args.rval().setNumber(result); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_getUTCFullYear(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* static */ MOZ_ALWAYS_INLINE bool michael@0: DateObject::getMonth_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: DateObject *dateObj = &args.thisv().toObject().as(); michael@0: dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); michael@0: michael@0: args.rval().set(dateObj->getReservedSlot(LOCAL_MONTH_SLOT)); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_getMonth(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* static */ MOZ_ALWAYS_INLINE bool michael@0: DateObject::getUTCMonth_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: double d = args.thisv().toObject().as().UTCTime().toNumber(); michael@0: args.rval().setNumber(MonthFromTime(d)); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_getUTCMonth(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* static */ MOZ_ALWAYS_INLINE bool michael@0: DateObject::getDate_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: DateObject *dateObj = &args.thisv().toObject().as(); michael@0: dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); michael@0: michael@0: args.rval().set(dateObj->getReservedSlot(LOCAL_DATE_SLOT)); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_getDate(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* static */ MOZ_ALWAYS_INLINE bool michael@0: DateObject::getUTCDate_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: double result = args.thisv().toObject().as().UTCTime().toNumber(); michael@0: if (IsFinite(result)) michael@0: result = DateFromTime(result); michael@0: michael@0: args.rval().setNumber(result); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_getUTCDate(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* static */ MOZ_ALWAYS_INLINE bool michael@0: DateObject::getDay_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: DateObject *dateObj = &args.thisv().toObject().as(); michael@0: dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); michael@0: michael@0: args.rval().set(dateObj->getReservedSlot(LOCAL_DAY_SLOT)); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_getDay(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* static */ MOZ_ALWAYS_INLINE bool michael@0: DateObject::getUTCDay_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: double result = args.thisv().toObject().as().UTCTime().toNumber(); michael@0: if (IsFinite(result)) michael@0: result = WeekDay(result); michael@0: michael@0: args.rval().setNumber(result); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_getUTCDay(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* static */ MOZ_ALWAYS_INLINE bool michael@0: DateObject::getHours_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: DateObject *dateObj = &args.thisv().toObject().as(); michael@0: dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); michael@0: michael@0: args.rval().set(dateObj->getReservedSlot(LOCAL_HOURS_SLOT)); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_getHours(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* static */ MOZ_ALWAYS_INLINE bool michael@0: DateObject::getUTCHours_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: double result = args.thisv().toObject().as().UTCTime().toNumber(); michael@0: if (IsFinite(result)) michael@0: result = HourFromTime(result); michael@0: michael@0: args.rval().setNumber(result); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_getUTCHours(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* static */ MOZ_ALWAYS_INLINE bool michael@0: DateObject::getMinutes_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: DateObject *dateObj = &args.thisv().toObject().as(); michael@0: dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); michael@0: michael@0: args.rval().set(dateObj->getReservedSlot(LOCAL_MINUTES_SLOT)); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_getMinutes(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* static */ MOZ_ALWAYS_INLINE bool michael@0: DateObject::getUTCMinutes_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: double result = args.thisv().toObject().as().UTCTime().toNumber(); michael@0: if (IsFinite(result)) michael@0: result = MinFromTime(result); michael@0: michael@0: args.rval().setNumber(result); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_getUTCMinutes(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* Date.getSeconds is mapped to getUTCSeconds */ michael@0: michael@0: /* static */ MOZ_ALWAYS_INLINE bool michael@0: DateObject::getUTCSeconds_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: DateObject *dateObj = &args.thisv().toObject().as(); michael@0: dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); michael@0: michael@0: args.rval().set(dateObj->getReservedSlot(LOCAL_SECONDS_SLOT)); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_getUTCSeconds(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* Date.getMilliseconds is mapped to getUTCMilliseconds */ michael@0: michael@0: /* static */ MOZ_ALWAYS_INLINE bool michael@0: DateObject::getUTCMilliseconds_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: double result = args.thisv().toObject().as().UTCTime().toNumber(); michael@0: if (IsFinite(result)) michael@0: result = msFromTime(result); michael@0: michael@0: args.rval().setNumber(result); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_getUTCMilliseconds(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* static */ MOZ_ALWAYS_INLINE bool michael@0: DateObject::getTimezoneOffset_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: DateObject *dateObj = &args.thisv().toObject().as(); michael@0: double utctime = dateObj->UTCTime().toNumber(); michael@0: double localtime = dateObj->cachedLocalTime(&cx->runtime()->dateTimeInfo); michael@0: michael@0: /* michael@0: * Return the time zone offset in minutes for the current locale that is michael@0: * appropriate for this time. This value would be a constant except for michael@0: * daylight savings time. michael@0: */ michael@0: double result = (utctime - localtime) / msPerMinute; michael@0: args.rval().setNumber(result); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_getTimezoneOffset(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_setTime_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: Rooted dateObj(cx, &args.thisv().toObject().as()); michael@0: if (args.length() == 0) { michael@0: dateObj->setUTCTime(GenericNaN(), args.rval().address()); michael@0: return true; michael@0: } michael@0: michael@0: double result; michael@0: if (!ToNumber(cx, args[0], &result)) michael@0: return false; michael@0: michael@0: dateObj->setUTCTime(TimeClip(result), args.rval().address()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_setTime(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: static bool michael@0: GetMsecsOrDefault(JSContext *cx, const CallArgs &args, unsigned i, double t, double *millis) michael@0: { michael@0: if (args.length() <= i) { michael@0: *millis = msFromTime(t); michael@0: return true; michael@0: } michael@0: return ToNumber(cx, args[i], millis); michael@0: } michael@0: michael@0: static bool michael@0: GetSecsOrDefault(JSContext *cx, const CallArgs &args, unsigned i, double t, double *sec) michael@0: { michael@0: if (args.length() <= i) { michael@0: *sec = SecFromTime(t); michael@0: return true; michael@0: } michael@0: return ToNumber(cx, args[i], sec); michael@0: } michael@0: michael@0: static bool michael@0: GetMinsOrDefault(JSContext *cx, const CallArgs &args, unsigned i, double t, double *mins) michael@0: { michael@0: if (args.length() <= i) { michael@0: *mins = MinFromTime(t); michael@0: return true; michael@0: } michael@0: return ToNumber(cx, args[i], mins); michael@0: } michael@0: michael@0: /* ES5 15.9.5.28. */ michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_setMilliseconds_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: Rooted dateObj(cx, &args.thisv().toObject().as()); michael@0: michael@0: /* Step 1. */ michael@0: double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo); michael@0: michael@0: /* Step 2. */ michael@0: double milli; michael@0: if (!ToNumber(cx, args.get(0), &milli)) michael@0: return false; michael@0: double time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), milli); michael@0: michael@0: /* Step 3. */ michael@0: double u = TimeClip(UTC(MakeDate(Day(t), time), &cx->runtime()->dateTimeInfo)); michael@0: michael@0: /* Steps 4-5. */ michael@0: dateObj->setUTCTime(u, args.rval().address()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_setMilliseconds(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* ES5 15.9.5.29. */ michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_setUTCMilliseconds_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: Rooted dateObj(cx, &args.thisv().toObject().as()); michael@0: michael@0: /* Step 1. */ michael@0: double t = dateObj->UTCTime().toNumber(); michael@0: michael@0: /* Step 2. */ michael@0: double milli; michael@0: if (!ToNumber(cx, args.get(0), &milli)) michael@0: return false; michael@0: double time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), milli); michael@0: michael@0: /* Step 3. */ michael@0: double v = TimeClip(MakeDate(Day(t), time)); michael@0: michael@0: /* Steps 4-5. */ michael@0: dateObj->setUTCTime(v, args.rval().address()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_setUTCMilliseconds(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* ES5 15.9.5.30. */ michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_setSeconds_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: Rooted dateObj(cx, &args.thisv().toObject().as()); michael@0: michael@0: /* Step 1. */ michael@0: double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo); michael@0: michael@0: /* Step 2. */ michael@0: double s; michael@0: if (!ToNumber(cx, args.get(0), &s)) michael@0: return false; michael@0: michael@0: /* Step 3. */ michael@0: double milli; michael@0: if (!GetMsecsOrDefault(cx, args, 1, t, &milli)) michael@0: return false; michael@0: michael@0: /* Step 4. */ michael@0: double date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli)); michael@0: michael@0: /* Step 5. */ michael@0: double u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo)); michael@0: michael@0: /* Steps 6-7. */ michael@0: dateObj->setUTCTime(u, args.rval().address()); michael@0: return true; michael@0: } michael@0: michael@0: /* ES5 15.9.5.31. */ michael@0: static bool michael@0: date_setSeconds(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_setUTCSeconds_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: Rooted dateObj(cx, &args.thisv().toObject().as()); michael@0: michael@0: /* Step 1. */ michael@0: double t = dateObj->UTCTime().toNumber(); michael@0: michael@0: /* Step 2. */ michael@0: double s; michael@0: if (!ToNumber(cx, args.get(0), &s)) michael@0: return false; michael@0: michael@0: /* Step 3. */ michael@0: double milli; michael@0: if (!GetMsecsOrDefault(cx, args, 1, t, &milli)) michael@0: return false; michael@0: michael@0: /* Step 4. */ michael@0: double date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli)); michael@0: michael@0: /* Step 5. */ michael@0: double v = TimeClip(date); michael@0: michael@0: /* Steps 6-7. */ michael@0: dateObj->setUTCTime(v, args.rval().address()); michael@0: return true; michael@0: } michael@0: michael@0: /* ES5 15.9.5.32. */ michael@0: static bool michael@0: date_setUTCSeconds(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_setMinutes_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: Rooted dateObj(cx, &args.thisv().toObject().as()); michael@0: michael@0: /* Step 1. */ michael@0: double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo); michael@0: michael@0: /* Step 2. */ michael@0: double m; michael@0: if (!ToNumber(cx, args.get(0), &m)) michael@0: return false; michael@0: michael@0: /* Step 3. */ michael@0: double s; michael@0: if (!GetSecsOrDefault(cx, args, 1, t, &s)) michael@0: return false; michael@0: michael@0: /* Step 4. */ michael@0: double milli; michael@0: if (!GetMsecsOrDefault(cx, args, 2, t, &milli)) michael@0: return false; michael@0: michael@0: /* Step 5. */ michael@0: double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli)); michael@0: michael@0: /* Step 6. */ michael@0: double u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo)); michael@0: michael@0: /* Steps 7-8. */ michael@0: dateObj->setUTCTime(u, args.rval().address()); michael@0: return true; michael@0: } michael@0: michael@0: /* ES5 15.9.5.33. */ michael@0: static bool michael@0: date_setMinutes(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_setUTCMinutes_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: Rooted dateObj(cx, &args.thisv().toObject().as()); michael@0: michael@0: /* Step 1. */ michael@0: double t = dateObj->UTCTime().toNumber(); michael@0: michael@0: /* Step 2. */ michael@0: double m; michael@0: if (!ToNumber(cx, args.get(0), &m)) michael@0: return false; michael@0: michael@0: /* Step 3. */ michael@0: double s; michael@0: if (!GetSecsOrDefault(cx, args, 1, t, &s)) michael@0: return false; michael@0: michael@0: /* Step 4. */ michael@0: double milli; michael@0: if (!GetMsecsOrDefault(cx, args, 2, t, &milli)) michael@0: return false; michael@0: michael@0: /* Step 5. */ michael@0: double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli)); michael@0: michael@0: /* Step 6. */ michael@0: double v = TimeClip(date); michael@0: michael@0: /* Steps 7-8. */ michael@0: dateObj->setUTCTime(v, args.rval().address()); michael@0: return true; michael@0: } michael@0: michael@0: /* ES5 15.9.5.34. */ michael@0: static bool michael@0: date_setUTCMinutes(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_setHours_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: Rooted dateObj(cx, &args.thisv().toObject().as()); michael@0: michael@0: /* Step 1. */ michael@0: double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo); michael@0: michael@0: /* Step 2. */ michael@0: double h; michael@0: if (!ToNumber(cx, args.get(0), &h)) michael@0: return false; michael@0: michael@0: /* Step 3. */ michael@0: double m; michael@0: if (!GetMinsOrDefault(cx, args, 1, t, &m)) michael@0: return false; michael@0: michael@0: /* Step 4. */ michael@0: double s; michael@0: if (!GetSecsOrDefault(cx, args, 2, t, &s)) michael@0: return false; michael@0: michael@0: /* Step 5. */ michael@0: double milli; michael@0: if (!GetMsecsOrDefault(cx, args, 3, t, &milli)) michael@0: return false; michael@0: michael@0: /* Step 6. */ michael@0: double date = MakeDate(Day(t), MakeTime(h, m, s, milli)); michael@0: michael@0: /* Step 6. */ michael@0: double u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo)); michael@0: michael@0: /* Steps 7-8. */ michael@0: dateObj->setUTCTime(u, args.rval().address()); michael@0: return true; michael@0: } michael@0: michael@0: /* ES5 15.9.5.35. */ michael@0: static bool michael@0: date_setHours(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_setUTCHours_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: Rooted dateObj(cx, &args.thisv().toObject().as()); michael@0: michael@0: /* Step 1. */ michael@0: double t = dateObj->UTCTime().toNumber(); michael@0: michael@0: /* Step 2. */ michael@0: double h; michael@0: if (!ToNumber(cx, args.get(0), &h)) michael@0: return false; michael@0: michael@0: /* Step 3. */ michael@0: double m; michael@0: if (!GetMinsOrDefault(cx, args, 1, t, &m)) michael@0: return false; michael@0: michael@0: /* Step 4. */ michael@0: double s; michael@0: if (!GetSecsOrDefault(cx, args, 2, t, &s)) michael@0: return false; michael@0: michael@0: /* Step 5. */ michael@0: double milli; michael@0: if (!GetMsecsOrDefault(cx, args, 3, t, &milli)) michael@0: return false; michael@0: michael@0: /* Step 6. */ michael@0: double newDate = MakeDate(Day(t), MakeTime(h, m, s, milli)); michael@0: michael@0: /* Step 7. */ michael@0: double v = TimeClip(newDate); michael@0: michael@0: /* Steps 8-9. */ michael@0: dateObj->setUTCTime(v, args.rval().address()); michael@0: return true; michael@0: } michael@0: michael@0: /* ES5 15.9.5.36. */ michael@0: static bool michael@0: date_setUTCHours(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_setDate_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: Rooted dateObj(cx, &args.thisv().toObject().as()); michael@0: michael@0: /* Step 1. */ michael@0: double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo); michael@0: michael@0: /* Step 2. */ michael@0: double date; michael@0: if (!ToNumber(cx, args.get(0), &date)) michael@0: return false; michael@0: michael@0: /* Step 3. */ michael@0: double newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date), TimeWithinDay(t)); michael@0: michael@0: /* Step 4. */ michael@0: double u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo)); michael@0: michael@0: /* Steps 5-6. */ michael@0: dateObj->setUTCTime(u, args.rval().address()); michael@0: return true; michael@0: } michael@0: michael@0: /* ES5 15.9.5.37. */ michael@0: static bool michael@0: date_setDate(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_setUTCDate_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: Rooted dateObj(cx, &args.thisv().toObject().as()); michael@0: michael@0: /* Step 1. */ michael@0: double t = dateObj->UTCTime().toNumber(); michael@0: michael@0: /* Step 2. */ michael@0: double date; michael@0: if (!ToNumber(cx, args.get(0), &date)) michael@0: return false; michael@0: michael@0: /* Step 3. */ michael@0: double newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date), TimeWithinDay(t)); michael@0: michael@0: /* Step 4. */ michael@0: double v = TimeClip(newDate); michael@0: michael@0: /* Steps 5-6. */ michael@0: dateObj->setUTCTime(v, args.rval().address()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_setUTCDate(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: static bool michael@0: GetDateOrDefault(JSContext *cx, const CallArgs &args, unsigned i, double t, double *date) michael@0: { michael@0: if (args.length() <= i) { michael@0: *date = DateFromTime(t); michael@0: return true; michael@0: } michael@0: return ToNumber(cx, args[i], date); michael@0: } michael@0: michael@0: static bool michael@0: GetMonthOrDefault(JSContext *cx, const CallArgs &args, unsigned i, double t, double *month) michael@0: { michael@0: if (args.length() <= i) { michael@0: *month = MonthFromTime(t); michael@0: return true; michael@0: } michael@0: return ToNumber(cx, args[i], month); michael@0: } michael@0: michael@0: /* ES5 15.9.5.38. */ michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_setMonth_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: Rooted dateObj(cx, &args.thisv().toObject().as()); michael@0: michael@0: /* Step 1. */ michael@0: double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo); michael@0: michael@0: /* Step 2. */ michael@0: double m; michael@0: if (!ToNumber(cx, args.get(0), &m)) michael@0: return false; michael@0: michael@0: /* Step 3. */ michael@0: double date; michael@0: if (!GetDateOrDefault(cx, args, 1, t, &date)) michael@0: return false; michael@0: michael@0: /* Step 4. */ michael@0: double newDate = MakeDate(MakeDay(YearFromTime(t), m, date), TimeWithinDay(t)); michael@0: michael@0: /* Step 5. */ michael@0: double u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo)); michael@0: michael@0: /* Steps 6-7. */ michael@0: dateObj->setUTCTime(u, args.rval().address()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_setMonth(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* ES5 15.9.5.39. */ michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_setUTCMonth_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: Rooted dateObj(cx, &args.thisv().toObject().as()); michael@0: michael@0: /* Step 1. */ michael@0: double t = dateObj->UTCTime().toNumber(); michael@0: michael@0: /* Step 2. */ michael@0: double m; michael@0: if (!ToNumber(cx, args.get(0), &m)) michael@0: return false; michael@0: michael@0: /* Step 3. */ michael@0: double date; michael@0: if (!GetDateOrDefault(cx, args, 1, t, &date)) michael@0: return false; michael@0: michael@0: /* Step 4. */ michael@0: double newDate = MakeDate(MakeDay(YearFromTime(t), m, date), TimeWithinDay(t)); michael@0: michael@0: /* Step 5. */ michael@0: double v = TimeClip(newDate); michael@0: michael@0: /* Steps 6-7. */ michael@0: dateObj->setUTCTime(v, args.rval().address()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_setUTCMonth(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: static double michael@0: ThisLocalTimeOrZero(Handle dateObj, DateTimeInfo *dtInfo) michael@0: { michael@0: double t = dateObj->UTCTime().toNumber(); michael@0: if (IsNaN(t)) michael@0: return +0; michael@0: return LocalTime(t, dtInfo); michael@0: } michael@0: michael@0: static double michael@0: ThisUTCTimeOrZero(Handle dateObj) michael@0: { michael@0: double t = dateObj->as().UTCTime().toNumber(); michael@0: return IsNaN(t) ? +0 : t; michael@0: } michael@0: michael@0: /* ES5 15.9.5.40. */ michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_setFullYear_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: Rooted dateObj(cx, &args.thisv().toObject().as()); michael@0: michael@0: /* Step 1. */ michael@0: double t = ThisLocalTimeOrZero(dateObj, &cx->runtime()->dateTimeInfo); michael@0: michael@0: /* Step 2. */ michael@0: double y; michael@0: if (!ToNumber(cx, args.get(0), &y)) michael@0: return false; michael@0: michael@0: /* Step 3. */ michael@0: double m; michael@0: if (!GetMonthOrDefault(cx, args, 1, t, &m)) michael@0: return false; michael@0: michael@0: /* Step 4. */ michael@0: double date; michael@0: if (!GetDateOrDefault(cx, args, 2, t, &date)) michael@0: return false; michael@0: michael@0: /* Step 5. */ michael@0: double newDate = MakeDate(MakeDay(y, m, date), TimeWithinDay(t)); michael@0: michael@0: /* Step 6. */ michael@0: double u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo)); michael@0: michael@0: /* Steps 7-8. */ michael@0: dateObj->setUTCTime(u, args.rval().address()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_setFullYear(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* ES5 15.9.5.41. */ michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_setUTCFullYear_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: Rooted dateObj(cx, &args.thisv().toObject().as()); michael@0: michael@0: /* Step 1. */ michael@0: double t = ThisUTCTimeOrZero(dateObj); michael@0: michael@0: /* Step 2. */ michael@0: double y; michael@0: if (!ToNumber(cx, args.get(0), &y)) michael@0: return false; michael@0: michael@0: /* Step 3. */ michael@0: double m; michael@0: if (!GetMonthOrDefault(cx, args, 1, t, &m)) michael@0: return false; michael@0: michael@0: /* Step 4. */ michael@0: double date; michael@0: if (!GetDateOrDefault(cx, args, 2, t, &date)) michael@0: return false; michael@0: michael@0: /* Step 5. */ michael@0: double newDate = MakeDate(MakeDay(y, m, date), TimeWithinDay(t)); michael@0: michael@0: /* Step 6. */ michael@0: double v = TimeClip(newDate); michael@0: michael@0: /* Steps 7-8. */ michael@0: dateObj->setUTCTime(v, args.rval().address()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_setUTCFullYear(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* ES5 Annex B.2.5. */ michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_setYear_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: Rooted dateObj(cx, &args.thisv().toObject().as()); michael@0: michael@0: /* Step 1. */ michael@0: double t = ThisLocalTimeOrZero(dateObj, &cx->runtime()->dateTimeInfo); michael@0: michael@0: /* Step 2. */ michael@0: double y; michael@0: if (!ToNumber(cx, args.get(0), &y)) michael@0: return false; michael@0: michael@0: /* Step 3. */ michael@0: if (IsNaN(y)) { michael@0: dateObj->setUTCTime(GenericNaN(), args.rval().address()); michael@0: return true; michael@0: } michael@0: michael@0: /* Step 4. */ michael@0: double yint = ToInteger(y); michael@0: if (0 <= yint && yint <= 99) michael@0: yint += 1900; michael@0: michael@0: /* Step 5. */ michael@0: double day = MakeDay(yint, MonthFromTime(t), DateFromTime(t)); michael@0: michael@0: /* Step 6. */ michael@0: double u = UTC(MakeDate(day, TimeWithinDay(t)), &cx->runtime()->dateTimeInfo); michael@0: michael@0: /* Steps 7-8. */ michael@0: dateObj->setUTCTime(TimeClip(u), args.rval().address()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_setYear(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* constants for toString, toUTCString */ michael@0: static const char js_NaN_date_str[] = "Invalid Date"; michael@0: static const char * const days[] = michael@0: { michael@0: "Sun","Mon","Tue","Wed","Thu","Fri","Sat" michael@0: }; michael@0: static const char * const months[] = michael@0: { michael@0: "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" michael@0: }; michael@0: michael@0: michael@0: // Avoid dependence on PRMJ_FormatTimeUSEnglish, because it michael@0: // requires a PRMJTime... which only has 16-bit years. Sub-ECMA. michael@0: static void michael@0: print_gmt_string(char* buf, size_t size, double utctime) michael@0: { michael@0: JS_ASSERT(TimeClip(utctime) == utctime); michael@0: JS_snprintf(buf, size, "%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT", michael@0: days[int(WeekDay(utctime))], michael@0: int(DateFromTime(utctime)), michael@0: months[int(MonthFromTime(utctime))], michael@0: int(YearFromTime(utctime)), michael@0: int(HourFromTime(utctime)), michael@0: int(MinFromTime(utctime)), michael@0: int(SecFromTime(utctime))); michael@0: } michael@0: michael@0: static void michael@0: print_iso_string(char* buf, size_t size, double utctime) michael@0: { michael@0: JS_ASSERT(TimeClip(utctime) == utctime); michael@0: JS_snprintf(buf, size, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ", michael@0: int(YearFromTime(utctime)), michael@0: int(MonthFromTime(utctime)) + 1, michael@0: int(DateFromTime(utctime)), michael@0: int(HourFromTime(utctime)), michael@0: int(MinFromTime(utctime)), michael@0: int(SecFromTime(utctime)), michael@0: int(msFromTime(utctime))); michael@0: } michael@0: michael@0: /* ES5 B.2.6. */ michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_toGMTString_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: double utctime = args.thisv().toObject().as().UTCTime().toNumber(); michael@0: michael@0: char buf[100]; michael@0: if (!IsFinite(utctime)) michael@0: JS_snprintf(buf, sizeof buf, js_NaN_date_str); michael@0: else michael@0: print_gmt_string(buf, sizeof buf, utctime); michael@0: michael@0: JSString *str = JS_NewStringCopyZ(cx, buf); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: /* ES5 15.9.5.43. */ michael@0: static bool michael@0: date_toGMTString(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_toISOString_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: double utctime = args.thisv().toObject().as().UTCTime().toNumber(); michael@0: if (!IsFinite(utctime)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INVALID_DATE); michael@0: return false; michael@0: } michael@0: michael@0: char buf[100]; michael@0: print_iso_string(buf, sizeof buf, utctime); michael@0: michael@0: JSString *str = JS_NewStringCopyZ(cx, buf); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: return true; michael@0: michael@0: } michael@0: michael@0: static bool michael@0: date_toISOString(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* ES5 15.9.5.44. */ michael@0: static bool michael@0: date_toJSON(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: /* Step 1. */ michael@0: RootedObject obj(cx, ToObject(cx, args.thisv())); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: /* Step 2. */ michael@0: RootedValue tv(cx, ObjectValue(*obj)); michael@0: if (!ToPrimitive(cx, JSTYPE_NUMBER, &tv)) michael@0: return false; michael@0: michael@0: /* Step 3. */ michael@0: if (tv.isDouble() && !IsFinite(tv.toDouble())) { michael@0: args.rval().setNull(); michael@0: return true; michael@0: } michael@0: michael@0: /* Step 4. */ michael@0: RootedValue toISO(cx); michael@0: if (!JSObject::getProperty(cx, obj, obj, cx->names().toISOString, &toISO)) michael@0: return false; michael@0: michael@0: /* Step 5. */ michael@0: if (!js_IsCallable(toISO)) { michael@0: JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage, nullptr, michael@0: JSMSG_BAD_TOISOSTRING_PROP); michael@0: return false; michael@0: } michael@0: michael@0: /* Step 6. */ michael@0: InvokeArgs args2(cx); michael@0: if (!args2.init(0)) michael@0: return false; michael@0: michael@0: args2.setCallee(toISO); michael@0: args2.setThis(ObjectValue(*obj)); michael@0: michael@0: if (!Invoke(cx, args2)) michael@0: return false; michael@0: args.rval().set(args2.rval()); michael@0: return true; michael@0: } michael@0: michael@0: /* for Date.toLocaleFormat; interface to PRMJTime date struct. michael@0: */ michael@0: static void michael@0: new_explode(double timeval, PRMJTime *split, DateTimeInfo *dtInfo) michael@0: { michael@0: double year = YearFromTime(timeval); michael@0: michael@0: split->tm_usec = int32_t(msFromTime(timeval)) * 1000; michael@0: split->tm_sec = int8_t(SecFromTime(timeval)); michael@0: split->tm_min = int8_t(MinFromTime(timeval)); michael@0: split->tm_hour = int8_t(HourFromTime(timeval)); michael@0: split->tm_mday = int8_t(DateFromTime(timeval)); michael@0: split->tm_mon = int8_t(MonthFromTime(timeval)); michael@0: split->tm_wday = int8_t(WeekDay(timeval)); michael@0: split->tm_year = year; michael@0: split->tm_yday = int16_t(DayWithinYear(timeval, year)); michael@0: michael@0: /* not sure how this affects things, but it doesn't seem michael@0: to matter. */ michael@0: split->tm_isdst = (DaylightSavingTA(timeval, dtInfo) != 0); michael@0: } michael@0: michael@0: typedef enum formatspec { michael@0: FORMATSPEC_FULL, FORMATSPEC_DATE, FORMATSPEC_TIME michael@0: } formatspec; michael@0: michael@0: /* helper function */ michael@0: static bool michael@0: date_format(JSContext *cx, double date, formatspec format, MutableHandleValue rval) michael@0: { michael@0: char buf[100]; michael@0: char tzbuf[100]; michael@0: bool usetz; michael@0: size_t i, tzlen; michael@0: PRMJTime split; michael@0: michael@0: if (!IsFinite(date)) { michael@0: JS_snprintf(buf, sizeof buf, js_NaN_date_str); michael@0: } else { michael@0: JS_ASSERT(TimeClip(date) == date); michael@0: michael@0: double local = LocalTime(date, &cx->runtime()->dateTimeInfo); michael@0: michael@0: /* offset from GMT in minutes. The offset includes daylight savings, michael@0: if it applies. */ michael@0: int minutes = (int) floor(AdjustTime(date, &cx->runtime()->dateTimeInfo) / msPerMinute); michael@0: michael@0: /* map 510 minutes to 0830 hours */ michael@0: int offset = (minutes / 60) * 100 + minutes % 60; michael@0: michael@0: /* print as "Wed Nov 05 19:38:03 GMT-0800 (PST) 1997" The TZA is michael@0: * printed as 'GMT-0800' rather than as 'PST' to avoid michael@0: * operating-system dependence on strftime (which michael@0: * PRMJ_FormatTimeUSEnglish calls, for %Z only.) win32 prints michael@0: * PST as 'Pacific Standard Time.' This way we always know michael@0: * what we're getting, and can parse it if we produce it. michael@0: * The OS TZA string is included as a comment. michael@0: */ michael@0: michael@0: /* get a timezone string from the OS to include as a michael@0: comment. */ michael@0: new_explode(date, &split, &cx->runtime()->dateTimeInfo); michael@0: if (PRMJ_FormatTime(tzbuf, sizeof tzbuf, "(%Z)", &split) != 0) { michael@0: michael@0: /* Decide whether to use the resulting timezone string. michael@0: * michael@0: * Reject it if it contains any non-ASCII, non-alphanumeric michael@0: * characters. It's then likely in some other character michael@0: * encoding, and we probably won't display it correctly. michael@0: */ michael@0: usetz = true; michael@0: tzlen = strlen(tzbuf); michael@0: if (tzlen > 100) { michael@0: usetz = false; michael@0: } else { michael@0: for (i = 0; i < tzlen; i++) { michael@0: jschar c = tzbuf[i]; michael@0: if (c > 127 || michael@0: !(isalpha(c) || isdigit(c) || michael@0: c == ' ' || c == '(' || c == ')')) { michael@0: usetz = false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* Also reject it if it's not parenthesized or if it's '()'. */ michael@0: if (tzbuf[0] != '(' || tzbuf[1] == ')') michael@0: usetz = false; michael@0: } else michael@0: usetz = false; michael@0: michael@0: switch (format) { michael@0: case FORMATSPEC_FULL: michael@0: /* michael@0: * Avoid dependence on PRMJ_FormatTimeUSEnglish, because it michael@0: * requires a PRMJTime... which only has 16-bit years. Sub-ECMA. michael@0: */ michael@0: /* Tue Oct 31 2000 09:41:40 GMT-0800 (PST) */ michael@0: JS_snprintf(buf, sizeof buf, michael@0: "%s %s %.2d %.4d %.2d:%.2d:%.2d GMT%+.4d%s%s", michael@0: days[int(WeekDay(local))], michael@0: months[int(MonthFromTime(local))], michael@0: int(DateFromTime(local)), michael@0: int(YearFromTime(local)), michael@0: int(HourFromTime(local)), michael@0: int(MinFromTime(local)), michael@0: int(SecFromTime(local)), michael@0: offset, michael@0: usetz ? " " : "", michael@0: usetz ? tzbuf : ""); michael@0: break; michael@0: case FORMATSPEC_DATE: michael@0: /* Tue Oct 31 2000 */ michael@0: JS_snprintf(buf, sizeof buf, michael@0: "%s %s %.2d %.4d", michael@0: days[int(WeekDay(local))], michael@0: months[int(MonthFromTime(local))], michael@0: int(DateFromTime(local)), michael@0: int(YearFromTime(local))); michael@0: break; michael@0: case FORMATSPEC_TIME: michael@0: /* 09:41:40 GMT-0800 (PST) */ michael@0: JS_snprintf(buf, sizeof buf, michael@0: "%.2d:%.2d:%.2d GMT%+.4d%s%s", michael@0: int(HourFromTime(local)), michael@0: int(MinFromTime(local)), michael@0: int(SecFromTime(local)), michael@0: offset, michael@0: usetz ? " " : "", michael@0: usetz ? tzbuf : ""); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: JSString *str = JS_NewStringCopyZ(cx, buf); michael@0: if (!str) michael@0: return false; michael@0: rval.setString(str); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: ToLocaleFormatHelper(JSContext *cx, HandleObject obj, const char *format, MutableHandleValue rval) michael@0: { michael@0: double utctime = obj->as().UTCTime().toNumber(); michael@0: michael@0: char buf[100]; michael@0: if (!IsFinite(utctime)) { michael@0: JS_snprintf(buf, sizeof buf, js_NaN_date_str); michael@0: } else { michael@0: int result_len; michael@0: double local = LocalTime(utctime, &cx->runtime()->dateTimeInfo); michael@0: PRMJTime split; michael@0: new_explode(local, &split, &cx->runtime()->dateTimeInfo); michael@0: michael@0: /* Let PRMJTime format it. */ michael@0: result_len = PRMJ_FormatTime(buf, sizeof buf, format, &split); michael@0: michael@0: /* If it failed, default to toString. */ michael@0: if (result_len == 0) michael@0: return date_format(cx, utctime, FORMATSPEC_FULL, rval); michael@0: michael@0: /* Hacked check against undesired 2-digit year 00/00/00 form. */ michael@0: if (strcmp(format, "%x") == 0 && result_len >= 6 && michael@0: /* Format %x means use OS settings, which may have 2-digit yr, so michael@0: hack end of 3/11/22 or 11.03.22 or 11Mar22 to use 4-digit yr...*/ michael@0: !isdigit(buf[result_len - 3]) && michael@0: isdigit(buf[result_len - 2]) && isdigit(buf[result_len - 1]) && michael@0: /* ...but not if starts with 4-digit year, like 2022/3/11. */ michael@0: !(isdigit(buf[0]) && isdigit(buf[1]) && michael@0: isdigit(buf[2]) && isdigit(buf[3]))) { michael@0: JS_snprintf(buf + (result_len - 2), (sizeof buf) - (result_len - 2), michael@0: "%d", js_DateGetYear(cx, obj)); michael@0: } michael@0: michael@0: } michael@0: michael@0: if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeToUnicode) michael@0: return cx->runtime()->localeCallbacks->localeToUnicode(cx, buf, rval); michael@0: michael@0: JSString *str = JS_NewStringCopyZ(cx, buf); michael@0: if (!str) michael@0: return false; michael@0: rval.setString(str); michael@0: return true; michael@0: } michael@0: michael@0: #if !EXPOSE_INTL_API michael@0: static bool michael@0: ToLocaleStringHelper(JSContext *cx, Handle dateObj, MutableHandleValue rval) michael@0: { michael@0: /* michael@0: * Use '%#c' for windows, because '%c' is backward-compatible and non-y2k michael@0: * with msvc; '%#c' requests that a full year be used in the result string. michael@0: */ michael@0: return ToLocaleFormatHelper(cx, dateObj, michael@0: #if defined(_WIN32) && !defined(__MWERKS__) michael@0: "%#c" michael@0: #else michael@0: "%c" michael@0: #endif michael@0: , rval); michael@0: } michael@0: michael@0: /* ES5 15.9.5.5. */ michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_toLocaleString_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: Rooted dateObj(cx, &args.thisv().toObject().as()); michael@0: return ToLocaleStringHelper(cx, dateObj, args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: date_toLocaleString(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* ES5 15.9.5.6. */ michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_toLocaleDateString_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: /* michael@0: * Use '%#x' for windows, because '%x' is backward-compatible and non-y2k michael@0: * with msvc; '%#x' requests that a full year be used in the result string. michael@0: */ michael@0: static const char format[] = michael@0: #if defined(_WIN32) && !defined(__MWERKS__) michael@0: "%#x" michael@0: #else michael@0: "%x" michael@0: #endif michael@0: ; michael@0: michael@0: Rooted dateObj(cx, &args.thisv().toObject().as()); michael@0: return ToLocaleFormatHelper(cx, dateObj, format, args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: date_toLocaleDateString(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* ES5 15.9.5.7. */ michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_toLocaleTimeString_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: Rooted dateObj(cx, &args.thisv().toObject().as()); michael@0: return ToLocaleFormatHelper(cx, dateObj, "%X", args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: date_toLocaleTimeString(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: #endif /* !EXPOSE_INTL_API */ michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_toLocaleFormat_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: Rooted dateObj(cx, &args.thisv().toObject().as()); michael@0: michael@0: if (args.length() == 0) { michael@0: /* michael@0: * Use '%#c' for windows, because '%c' is backward-compatible and non-y2k michael@0: * with msvc; '%#c' requests that a full year be used in the result string. michael@0: */ michael@0: return ToLocaleFormatHelper(cx, dateObj, michael@0: #if defined(_WIN32) && !defined(__MWERKS__) michael@0: "%#c" michael@0: #else michael@0: "%c" michael@0: #endif michael@0: , args.rval()); michael@0: } michael@0: michael@0: RootedString fmt(cx, ToString(cx, args[0])); michael@0: if (!fmt) michael@0: return false; michael@0: michael@0: JSAutoByteString fmtbytes(cx, fmt); michael@0: if (!fmtbytes) michael@0: return false; michael@0: michael@0: return ToLocaleFormatHelper(cx, dateObj, fmtbytes.ptr(), args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: date_toLocaleFormat(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* ES5 15.9.5.4. */ michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_toTimeString_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: return date_format(cx, args.thisv().toObject().as().UTCTime().toNumber(), michael@0: FORMATSPEC_TIME, args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: date_toTimeString(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: /* ES5 15.9.5.3. */ michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_toDateString_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: return date_format(cx, args.thisv().toObject().as().UTCTime().toNumber(), michael@0: FORMATSPEC_DATE, args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: date_toDateString(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: #if JS_HAS_TOSOURCE michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_toSource_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: StringBuffer sb(cx); michael@0: if (!sb.append("(new Date(") || michael@0: !NumberValueToStringBuffer(cx, args.thisv().toObject().as().UTCTime(), sb) || michael@0: !sb.append("))")) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: JSString *str = sb.finishString(); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_toSource(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: #endif michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_toString_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: return date_format(cx, args.thisv().toObject().as().UTCTime().toNumber(), michael@0: FORMATSPEC_FULL, args.rval()); michael@0: } michael@0: michael@0: static bool michael@0: date_toString(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: date_valueOf_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: Rooted dateObj(cx, &args.thisv().toObject().as()); michael@0: args.rval().set(dateObj->UTCTime()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: date_valueOf(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: static const JSFunctionSpec date_static_methods[] = { michael@0: JS_FN("UTC", date_UTC, MAXARGS,0), michael@0: JS_FN("parse", date_parse, 1,0), michael@0: JS_FN("now", date_now, 0,0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: static const JSFunctionSpec date_methods[] = { michael@0: JS_FN("getTime", date_getTime, 0,0), michael@0: JS_FN("getTimezoneOffset", date_getTimezoneOffset, 0,0), michael@0: JS_FN("getYear", date_getYear, 0,0), michael@0: JS_FN("getFullYear", date_getFullYear, 0,0), michael@0: JS_FN("getUTCFullYear", date_getUTCFullYear, 0,0), michael@0: JS_FN("getMonth", date_getMonth, 0,0), michael@0: JS_FN("getUTCMonth", date_getUTCMonth, 0,0), michael@0: JS_FN("getDate", date_getDate, 0,0), michael@0: JS_FN("getUTCDate", date_getUTCDate, 0,0), michael@0: JS_FN("getDay", date_getDay, 0,0), michael@0: JS_FN("getUTCDay", date_getUTCDay, 0,0), michael@0: JS_FN("getHours", date_getHours, 0,0), michael@0: JS_FN("getUTCHours", date_getUTCHours, 0,0), michael@0: JS_FN("getMinutes", date_getMinutes, 0,0), michael@0: JS_FN("getUTCMinutes", date_getUTCMinutes, 0,0), michael@0: JS_FN("getSeconds", date_getUTCSeconds, 0,0), michael@0: JS_FN("getUTCSeconds", date_getUTCSeconds, 0,0), michael@0: JS_FN("getMilliseconds", date_getUTCMilliseconds, 0,0), michael@0: JS_FN("getUTCMilliseconds", date_getUTCMilliseconds, 0,0), michael@0: JS_FN("setTime", date_setTime, 1,0), michael@0: JS_FN("setYear", date_setYear, 1,0), michael@0: JS_FN("setFullYear", date_setFullYear, 3,0), michael@0: JS_FN("setUTCFullYear", date_setUTCFullYear, 3,0), michael@0: JS_FN("setMonth", date_setMonth, 2,0), michael@0: JS_FN("setUTCMonth", date_setUTCMonth, 2,0), michael@0: JS_FN("setDate", date_setDate, 1,0), michael@0: JS_FN("setUTCDate", date_setUTCDate, 1,0), michael@0: JS_FN("setHours", date_setHours, 4,0), michael@0: JS_FN("setUTCHours", date_setUTCHours, 4,0), michael@0: JS_FN("setMinutes", date_setMinutes, 3,0), michael@0: JS_FN("setUTCMinutes", date_setUTCMinutes, 3,0), michael@0: JS_FN("setSeconds", date_setSeconds, 2,0), michael@0: JS_FN("setUTCSeconds", date_setUTCSeconds, 2,0), michael@0: JS_FN("setMilliseconds", date_setMilliseconds, 1,0), michael@0: JS_FN("setUTCMilliseconds", date_setUTCMilliseconds, 1,0), michael@0: JS_FN("toUTCString", date_toGMTString, 0,0), michael@0: JS_FN("toLocaleFormat", date_toLocaleFormat, 0,0), michael@0: #if EXPOSE_INTL_API michael@0: JS_SELF_HOSTED_FN(js_toLocaleString_str, "Date_toLocaleString", 0,0), michael@0: JS_SELF_HOSTED_FN("toLocaleDateString", "Date_toLocaleDateString", 0,0), michael@0: JS_SELF_HOSTED_FN("toLocaleTimeString", "Date_toLocaleTimeString", 0,0), michael@0: #else michael@0: JS_FN(js_toLocaleString_str, date_toLocaleString, 0,0), michael@0: JS_FN("toLocaleDateString", date_toLocaleDateString, 0,0), michael@0: JS_FN("toLocaleTimeString", date_toLocaleTimeString, 0,0), michael@0: #endif michael@0: JS_FN("toDateString", date_toDateString, 0,0), michael@0: JS_FN("toTimeString", date_toTimeString, 0,0), michael@0: JS_FN("toISOString", date_toISOString, 0,0), michael@0: JS_FN(js_toJSON_str, date_toJSON, 1,0), michael@0: #if JS_HAS_TOSOURCE michael@0: JS_FN(js_toSource_str, date_toSource, 0,0), michael@0: #endif michael@0: JS_FN(js_toString_str, date_toString, 0,0), michael@0: JS_FN(js_valueOf_str, date_valueOf, 0,0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: bool michael@0: js_Date(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: /* Date called as function. */ michael@0: if (!args.isConstructing()) michael@0: return date_format(cx, NowAsMillis(), FORMATSPEC_FULL, args.rval()); michael@0: michael@0: /* Date called as constructor. */ michael@0: double d; michael@0: if (args.length() == 0) { michael@0: /* ES5 15.9.3.3. */ michael@0: d = NowAsMillis(); michael@0: } else if (args.length() == 1) { michael@0: /* ES5 15.9.3.2. */ michael@0: michael@0: /* Step 1. */ michael@0: if (!ToPrimitive(cx, args[0])) michael@0: return false; michael@0: michael@0: if (args[0].isString()) { michael@0: /* Step 2. */ michael@0: JSString *str = args[0].toString(); michael@0: if (!str) michael@0: return false; michael@0: michael@0: JSLinearString *linearStr = str->ensureLinear(cx); michael@0: if (!linearStr) michael@0: return false; michael@0: michael@0: if (!date_parseString(linearStr, &d, &cx->runtime()->dateTimeInfo)) michael@0: d = GenericNaN(); michael@0: else michael@0: d = TimeClip(d); michael@0: } else { michael@0: /* Step 3. */ michael@0: if (!ToNumber(cx, args[0], &d)) michael@0: return false; michael@0: d = TimeClip(d); michael@0: } michael@0: } else { michael@0: double msec_time; michael@0: if (!date_msecFromArgs(cx, args, &msec_time)) michael@0: return false; michael@0: michael@0: if (IsFinite(msec_time)) { michael@0: msec_time = UTC(msec_time, &cx->runtime()->dateTimeInfo); michael@0: msec_time = TimeClip(msec_time); michael@0: } michael@0: d = msec_time; michael@0: } michael@0: michael@0: JSObject *obj = js_NewDateObjectMsec(cx, d); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: args.rval().setObject(*obj); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: FinishDateClassInit(JSContext *cx, HandleObject ctor, HandleObject proto) michael@0: { michael@0: proto->as().setUTCTime(GenericNaN()); michael@0: michael@0: /* michael@0: * Date.prototype.toGMTString has the same initial value as michael@0: * Date.prototype.toUTCString. michael@0: */ michael@0: RootedValue toUTCStringFun(cx); michael@0: RootedId toUTCStringId(cx, NameToId(cx->names().toUTCString)); michael@0: RootedId toGMTStringId(cx, NameToId(cx->names().toGMTString)); michael@0: return baseops::GetProperty(cx, proto, toUTCStringId, &toUTCStringFun) && michael@0: baseops::DefineGeneric(cx, proto, toGMTStringId, toUTCStringFun, michael@0: JS_PropertyStub, JS_StrictPropertyStub, 0); michael@0: } michael@0: michael@0: const Class DateObject::class_ = { michael@0: js_Date_str, michael@0: JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | michael@0: JSCLASS_HAS_CACHED_PROTO(JSProto_Date), michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: date_convert, michael@0: nullptr, /* finalize */ michael@0: nullptr, /* call */ michael@0: nullptr, /* hasInstance */ michael@0: nullptr, /* construct */ michael@0: nullptr, /* trace */ michael@0: { michael@0: GenericCreateConstructor, michael@0: GenericCreatePrototype<&DateObject::class_>, michael@0: date_static_methods, michael@0: date_methods, michael@0: FinishDateClassInit michael@0: } michael@0: }; michael@0: michael@0: JS_FRIEND_API(JSObject *) michael@0: js_NewDateObjectMsec(JSContext *cx, double msec_time) michael@0: { michael@0: JSObject *obj = NewBuiltinClassInstance(cx, &DateObject::class_); michael@0: if (!obj) michael@0: return nullptr; michael@0: obj->as().setUTCTime(msec_time); michael@0: return obj; michael@0: } michael@0: michael@0: JS_FRIEND_API(JSObject *) michael@0: js_NewDateObject(JSContext *cx, int year, int mon, int mday, michael@0: int hour, int min, int sec) michael@0: { michael@0: JS_ASSERT(mon < 12); michael@0: double msec_time = date_msecFromDate(year, mon, mday, hour, min, sec, 0); michael@0: return js_NewDateObjectMsec(cx, UTC(msec_time, &cx->runtime()->dateTimeInfo)); michael@0: } michael@0: michael@0: JS_FRIEND_API(bool) michael@0: js_DateIsValid(JSObject *obj) michael@0: { michael@0: return obj->is() && !IsNaN(obj->as().UTCTime().toNumber()); michael@0: } michael@0: michael@0: JS_FRIEND_API(int) michael@0: js_DateGetYear(JSContext *cx, JSObject *obj) michael@0: { michael@0: /* Preserve legacy API behavior of returning 0 for invalid dates. */ michael@0: JS_ASSERT(obj); michael@0: double localtime = obj->as().cachedLocalTime(&cx->runtime()->dateTimeInfo); michael@0: if (IsNaN(localtime)) michael@0: return 0; michael@0: michael@0: return (int) YearFromTime(localtime); michael@0: } michael@0: michael@0: JS_FRIEND_API(int) michael@0: js_DateGetMonth(JSContext *cx, JSObject *obj) michael@0: { michael@0: JS_ASSERT(obj); michael@0: double localtime = obj->as().cachedLocalTime(&cx->runtime()->dateTimeInfo); michael@0: if (IsNaN(localtime)) michael@0: return 0; michael@0: michael@0: return (int) MonthFromTime(localtime); michael@0: } michael@0: michael@0: JS_FRIEND_API(int) michael@0: js_DateGetDate(JSContext *cx, JSObject *obj) michael@0: { michael@0: JS_ASSERT(obj); michael@0: double localtime = obj->as().cachedLocalTime(&cx->runtime()->dateTimeInfo); michael@0: if (IsNaN(localtime)) michael@0: return 0; michael@0: michael@0: return (int) DateFromTime(localtime); michael@0: } michael@0: michael@0: JS_FRIEND_API(int) michael@0: js_DateGetHours(JSContext *cx, JSObject *obj) michael@0: { michael@0: JS_ASSERT(obj); michael@0: double localtime = obj->as().cachedLocalTime(&cx->runtime()->dateTimeInfo); michael@0: if (IsNaN(localtime)) michael@0: return 0; michael@0: michael@0: return (int) HourFromTime(localtime); michael@0: } michael@0: michael@0: JS_FRIEND_API(int) michael@0: js_DateGetMinutes(JSContext *cx, JSObject *obj) michael@0: { michael@0: JS_ASSERT(obj); michael@0: double localtime = obj->as().cachedLocalTime(&cx->runtime()->dateTimeInfo); michael@0: if (IsNaN(localtime)) michael@0: return 0; michael@0: michael@0: return (int) MinFromTime(localtime); michael@0: } michael@0: michael@0: JS_FRIEND_API(int) michael@0: js_DateGetSeconds(JSObject *obj) michael@0: { michael@0: if (!obj->is()) michael@0: return 0; michael@0: michael@0: double utctime = obj->as().UTCTime().toNumber(); michael@0: if (IsNaN(utctime)) michael@0: return 0; michael@0: return (int) SecFromTime(utctime); michael@0: } michael@0: michael@0: JS_FRIEND_API(double) michael@0: js_DateGetMsecSinceEpoch(JSObject *obj) michael@0: { michael@0: return obj->is() ? obj->as().UTCTime().toNumber() : 0; michael@0: } michael@0: michael@0: michael@0: static const NativeImpl sReadOnlyDateMethods[] = { michael@0: DateObject::getTime_impl, michael@0: DateObject::getYear_impl, michael@0: DateObject::getFullYear_impl, michael@0: DateObject::getUTCFullYear_impl, michael@0: DateObject::getMonth_impl, michael@0: DateObject::getUTCMonth_impl, michael@0: DateObject::getDate_impl, michael@0: DateObject::getUTCDate_impl, michael@0: DateObject::getDay_impl, michael@0: DateObject::getUTCDay_impl, michael@0: DateObject::getHours_impl, michael@0: DateObject::getUTCHours_impl, michael@0: DateObject::getMinutes_impl, michael@0: DateObject::getUTCMinutes_impl, michael@0: DateObject::getUTCSeconds_impl, michael@0: DateObject::getUTCMilliseconds_impl, michael@0: DateObject::getTimezoneOffset_impl, michael@0: date_toGMTString_impl, michael@0: date_toISOString_impl, michael@0: date_toLocaleFormat_impl, michael@0: date_toTimeString_impl, michael@0: date_toDateString_impl, michael@0: date_toSource_impl, michael@0: date_toString_impl, michael@0: date_valueOf_impl michael@0: };