--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -213,22 +213,30 @@ YearFromTime(jsdouble t)
#define InLeapYear(t) (JSBool) (DaysInYear(YearFromTime(t)) == 366)
#define DayWithinYear(t, year) ((intN) (Day(t) - DayFromYear(year)))
/*
* The following array contains the day of year for the first day of
* each month, where index 0 is January, and day 0 is January 1.
*/
-static jsdouble firstDayOfMonth[2][12] = {
- {0.0, 31.0, 59.0, 90.0, 120.0, 151.0, 181.0, 212.0, 243.0, 273.0, 304.0, 334.0},
- {0.0, 31.0, 60.0, 91.0, 121.0, 152.0, 182.0, 213.0, 244.0, 274.0, 305.0, 335.0}
+static jsdouble firstDayOfMonth[2][13] = {
+ {0.0, 31.0, 59.0, 90.0, 120.0, 151.0, 181.0, 212.0, 243.0, 273.0, 304.0, 334.0, 365.0},
+ {0.0, 31.0, 60.0, 91.0, 121.0, 152.0, 182.0, 213.0, 244.0, 274.0, 305.0, 335.0, 366.0}
};
-#define DayFromMonth(m, leap) firstDayOfMonth[leap][(intN)m];
+#define DayFromMonth(m, leap) firstDayOfMonth[leap][(intN)m]
+
+static intN
+DaysInMonth(jsint year, jsint month)
+{
+ JSBool leap = (DaysInYear(year) == 366);
+ intN result = DayFromMonth(month, leap) - DayFromMonth(month-1, leap);
+ return result;
+}
static intN
MonthFromTime(jsdouble t)
{
intN d, step;
jsint year = YearFromTime(t);
d = DayWithinYear(t, year);
@@ -621,16 +629,268 @@ date_UTC(JSContext *cx, uintN argc, jsva
if (!date_msecFromArgs(cx, argc, vp + 2, &msec_time))
return JS_FALSE;
msec_time = TIMECLIP(msec_time);
return js_NewNumberInRootedValue(cx, msec_time, vp);
}
+/*
+ * Read and convert decimal digits from s[*i] into *result
+ * while *i < limit.
+ *
+ * Succeed if any digits are converted. Advance *i only
+ * as digits are consumed.
+ */
+static JSBool
+digits(size_t *result, const jschar *s, size_t *i, size_t limit)
+{
+ size_t init = *i;
+ *result = 0;
+ while (*i < limit &&
+ ('0' <= s[*i] && s[*i] <= '9')) {
+ *result *= 10;
+ *result += (s[*i] - '0');
+ ++(*i);
+ }
+ return (*i != init);
+}
+
+/*
+ * Read and convert decimal digits to the right of a decimal point,
+ * representing a fractional integer, from s[*i] into *result
+ * while *i < limit.
+ *
+ * Succeed if any digits are converted. Advance *i only
+ * as digits are consumed.
+ */
+static JSBool
+fractional(jsdouble *result, const jschar *s, size_t *i, size_t limit)
+{
+ jsdouble factor = 0.1;
+ size_t init = *i;
+ *result = 0.0;
+ while (*i < limit &&
+ ('0' <= s[*i] && s[*i] <= '9')) {
+ *result += (s[*i] - '0') * factor;
+ factor *= 0.1;
+ ++(*i);
+ }
+ return (*i != init);
+}
+
+/*
+ * Read and convert exactly n decimal digits from s[*i]
+ * to s[min(*i+n,limit)] into *result.
+ *
+ * Succeed if exactly n digits are converted. Advance *i only
+ * on success.
+ */
+static JSBool
+ndigits(size_t n, size_t *result, const jschar *s, size_t* i, size_t limit)
+{
+ size_t init = *i;
+
+ if (digits(result, s, i, JS_MIN(limit, init+n)))
+ return ((*i - init) == n);
+
+ *i = init;
+ return JS_FALSE;
+}
+
+/*
+ * Parse a string in one of the date-time formats given by the W3C
+ * "NOTE-datetime" specification. These formats make up a restricted
+ * profile of the ISO 8601 format. Quoted here:
+ *
+ * The formats are as follows. Exactly the components shown here
+ * must be present, with exactly this punctuation. Note that the "T"
+ * appears literally in the string, to indicate the beginning of the
+ * time element, as specified in ISO 8601.
+ *
+ * Any combination of the date formats with the time formats is
+ * allowed, and also either the date or the time can be missing.
+ *
+ * The specification is silent on the meaning when fields are
+ * ommitted so the interpretations are a guess, but hopefully a
+ * reasonable one. We default the month to January, the day to the
+ * 1st, and hours minutes and seconds all to 0. If the date is
+ * missing entirely then we assume 1970-01-01 so that the time can
+ * be aded to a date later. If the time is missing then we assume
+ * 00:00 UTC. If the time is present but the time zone field is
+ * missing then we use local time.
+ *
+ * Date part:
+ *
+ * Year:
+ * YYYY (eg 1997)
+ *
+ * Year and month:
+ * YYYY-MM (eg 1997-07)
+ *
+ * Complete date:
+ * YYYY-MM-DD (eg 1997-07-16)
+ *
+ * Time part:
+ *
+ * Hours and minutes:
+ * Thh:mmTZD (eg T19:20+01:00)
+ *
+ * Hours, minutes and seconds:
+ * Thh:mm:ssTZD (eg T19:20:30+01:00)
+ *
+ * Hours, minutes, seconds and a decimal fraction of a second:
+ * Thh:mm:ss.sTZD (eg T19:20:30.45+01:00)
+ *
+ * where:
+ *
+ * YYYY = four-digit year or six digit year as +YYYYYY or -YYYYYY
+ * MM = two-digit month (01=January, etc.)
+ * DD = two-digit day of month (01 through 31)
+ * hh = two digits of hour (00 through 23) (am/pm NOT allowed)
+ * mm = two digits of minute (00 through 59)
+ * ss = two digits of second (00 through 59)
+ * s = one or more digits representing a decimal fraction of a second
+ * TZD = time zone designator (Z or +hh:mm or -hh:mm or missing for local)
+ */
+
+static JSBool
+date_parseISOString(JSString *str, jsdouble *result)
+{
+ jsdouble msec;
+
+ const jschar *s;
+ size_t limit;
+ size_t i = 0;
+ int tzMul = 1;
+ int dateMul = 1;
+ size_t year = 1970;
+ size_t month = 1;
+ size_t day = 1;
+ size_t hour = 0;
+ size_t min = 0;
+ size_t sec = 0;
+ jsdouble frac = 0;
+ bool isLocalTime = JS_FALSE;
+ size_t tzHour = 0;
+ size_t tzMin = 0;
+
+#define PEEK(ch) (i < limit && s[i] == ch)
+
+#define NEED(ch) \
+ JS_BEGIN_MACRO \
+ if (i >= limit || s[i] != ch) { goto syntax; } else { ++i; } \
+ JS_END_MACRO
+
+#define DONE_DATE_UNLESS(ch) \
+ JS_BEGIN_MACRO \
+ if (i >= limit || s[i] != ch) { goto done_date; } else { ++i; } \
+ JS_END_MACRO
+
+#define DONE_UNLESS(ch) \
+ JS_BEGIN_MACRO \
+ if (i >= limit || s[i] != ch) { goto done; } else { ++i; } \
+ JS_END_MACRO
+
+#define NEED_NDIGITS(n, field) \
+ JS_BEGIN_MACRO \
+ if (!ndigits(n, &field, s, &i, limit)) { goto syntax; } \
+ JS_END_MACRO
+
+ str->getCharsAndLength(s, limit);
+
+ if (PEEK('+') || PEEK('-')) {
+ if (PEEK('-'))
+ dateMul = -1;
+ ++i;
+ NEED_NDIGITS(6, year);
+ } else if (!PEEK('T')) {
+ NEED_NDIGITS(4, year);
+ }
+ DONE_DATE_UNLESS('-');
+ NEED_NDIGITS(2, month);
+ DONE_DATE_UNLESS('-');
+ NEED_NDIGITS(2, day);
+
+ done_date:
+ DONE_UNLESS('T');
+ NEED_NDIGITS(2, hour);
+ NEED(':');
+ NEED_NDIGITS(2, min);
+
+ if (PEEK(':')) {
+ ++i;
+ NEED_NDIGITS(2, sec);
+ if (PEEK('.')) {
+ ++i;
+ if (!fractional(&frac, s, &i, limit))
+ goto syntax;
+ }
+ }
+
+ if (PEEK('Z')) {
+ ++i;
+ } else if (PEEK('+') || PEEK('-')) {
+ if (PEEK('-'))
+ tzMul = -1;
+ ++i;
+ NEED_NDIGITS(2, tzHour);
+ NEED(':');
+ NEED_NDIGITS(2, tzMin);
+ } else {
+ isLocalTime = JS_TRUE;
+ }
+
+ done:
+ if (year > 275943 // ceil(1e8/365) + 1970
+ || (month == 0 || month > 12)
+ || (day == 0 || day > DaysInMonth(year,month))
+ || hour > 24
+ || ((hour == 24) && (min > 0 || sec > 0))
+ || min > 59
+ || sec > 59
+ || tzHour > 23
+ || tzMin > 59)
+ goto syntax;
+
+ if (i != limit)
+ goto syntax;
+
+ month -= 1; /* convert month to 0-based */
+
+ msec = date_msecFromDate(dateMul * (jsdouble)year, month, day,
+ hour, min, sec,
+ frac * 1000.0);;
+
+ if (isLocalTime) {
+ msec = UTC(msec);
+ } else {
+ msec -= ((tzMul) * ((tzHour * msPerHour)
+ + (tzMin * msPerMinute)));
+ }
+
+ if (msec < -8.64e15 || msec > 8.64e15)
+ goto syntax;
+
+ *result = msec;
+
+ return JS_TRUE;
+
+ syntax:
+ /* syntax error */
+ *result = 0;
+ return JS_FALSE;
+
+#undef PEEK
+#undef NEED
+#undef DONE_UNLESS
+#undef NEED_NDIGITS
+}
+
static JSBool
date_parseString(JSString *str, jsdouble *result)
{
jsdouble msec;
const jschar *s;
size_t limit;
size_t i = 0;
@@ -643,16 +903,19 @@ date_parseString(JSString *str, jsdouble
int c = -1;
int n = -1;
int tzoffset = -1;
int prevc = 0;
JSBool seenplusminus = JS_FALSE;
int temp;
JSBool seenmonthname = JS_FALSE;
+ if (date_parseISOString(str, result))
+ return JS_TRUE;
+
str->getCharsAndLength(s, limit);
if (limit == 0)
goto syntax;
while (i < limit) {
c = s[i];
i++;
if (c <= ' ' || c == ',' || c == '-') {
if (c == '-' && '0' <= s[i] && s[i] <= '9') {
@@ -871,26 +1134,25 @@ date_parseString(JSString *str, jsdouble
}
mon -= 1; /* convert month to 0-based */
if (sec < 0)
sec = 0;
if (min < 0)
min = 0;
if (hour < 0)
hour = 0;
- if (tzoffset == -1) { /* no time zone specified, have to use local */
- jsdouble msec_time;
- msec_time = date_msecFromDate(year, mon, mday, hour, min, sec, 0);
-
- *result = UTC(msec_time);
- return JS_TRUE;
- }
msec = date_msecFromDate(year, mon, mday, hour, min, sec, 0);
- msec += tzoffset * msPerMinute;
+
+ if (tzoffset == -1) { /* no time zone specified, have to use local */
+ msec = UTC(msec);
+ } else {
+ msec += tzoffset * msPerMinute;
+ }
+
*result = msec;
return JS_TRUE;
syntax:
/* syntax error */
*result = 0;
return JS_FALSE;
}
new file mode 100644
--- /dev/null
+++ b/js/tests/ecma_5/Date/15.9.4.2.js
@@ -0,0 +1,158 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is JavaScript Engine testing utilities.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s): Bruce Hoult
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+var gTestfile = '15.9.4.2.js';
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 430930;
+
+
+//-----------------------------------------------------------------------------
+test();
+//-----------------------------------------------------------------------------
+
+function iso(d){
+ return new Date(d).toISOString();
+}
+
+function check(s, millis){
+ description = "Date.parse('"+s+"') == '"+iso(millis)+"'";
+ expected = millis;
+ actual = Date.parse(s);
+ reportCompare(expected, actual, description);
+}
+
+function dd(year, month, day, hour, minute, second, millis){
+ return Date.UTC(year, month-1, day, hour, minute, second, millis);
+}
+
+function TZAtDate(d){
+ return d.getTimezoneOffset() * 60000;
+}
+
+function TZInMonth(month){
+ return TZAtDate(new Date(dd(2009,month,1,0,0,0,0)));
+}
+
+function test()
+{
+ enterFunc ('test');
+ printBugNumber(BUGNUMBER);
+
+ JanTZ = TZInMonth(1);
+ JulTZ = TZInMonth(7);
+ CurrTZ = TZAtDate(new Date());
+
+ // formats with explicit timezone
+ check("2009-07-23T19:53:21.001+12:00", dd(2009,7,23,7,53,21,1));
+ check("2009-07-23T19:53:21+12:00", dd(2009,7,23,7,53,21,0));
+ check("2009-07-23T19:53+12:00", dd(2009,7,23,7,53,0,0));
+
+ check("2009-07T19:53:21.001+12:00", dd(2009,7,1,7,53,21,1));
+ check("2009-07T19:53:21+12:00", dd(2009,7,1,7,53,21,0));
+ check("2009-07T19:53+12:00", dd(2009,7,1,7,53,0,0));
+
+ check("2009T19:53:21.001+12:00", dd(2009,1,1,7,53,21,1));
+ check("2009T19:53:21+12:00", dd(2009,1,1,7,53,21,0));
+ check("2009T19:53+12:00", dd(2009,1,1,7,53,0,0));
+
+ check("T19:53:21.001+12:00", dd(1970,1,1,7,53,21,1));
+ check("T19:53:21+12:00", dd(1970,1,1,7,53,21,0));
+ check("T19:53+12:00", dd(1970,1,1,7,53,0,0));
+
+ // formats without timezone uses the timezone as at that date
+ check("2009-07-23T19:53:21.001", dd(2009,7,23,19,53,21,1)+JulTZ);
+ check("2009-07-23T19:53:21", dd(2009,7,23,19,53,21,0)+JulTZ);
+ check("2009-07-23T19:53", dd(2009,7,23,19,53,0,0)+JulTZ);
+
+ check("2009-07T19:53:21.001", dd(2009,7,1,19,53,21,1)+JulTZ);
+ check("2009-07T19:53:21", dd(2009,7,1,19,53,21,0)+JulTZ);
+ check("2009-07T19:53", dd(2009,7,1,19,53,0,0)+JulTZ);
+
+ check("2009T19:53:21.001", dd(2009,1,1,19,53,21,1)+JanTZ);
+ check("2009T19:53:21", dd(2009,1,1,19,53,21,0)+JanTZ);
+ check("2009T19:53", dd(2009,1,1,19,53,0,0)+JanTZ);
+
+ check("T19:53:21.001", dd(1970,1,1,19,53,21,1)+CurrTZ);
+ check("T19:53:21", dd(1970,1,1,19,53,21,0)+CurrTZ);
+ check("T19:53", dd(1970,1,1,19,53,0,0)+CurrTZ);
+
+ // with no time at all assume UTC
+ check("2009-07-23", dd(2009,7,23,0,0,0,0));
+ check("2009-07", dd(2009,7,1,0,0,0,0));
+ check("2009", dd(2009,1,1,0,0,0,0));
+ check("", NaN);
+
+ // one field too big
+ check("2009-13-23T19:53:21.001+12:00", NaN);
+ check("2009-07-32T19:53:21.001+12:00", NaN);
+ check("2009-07-23T25:53:21.001+12:00", NaN);
+ check("2009-07-23T19:60:21.001+12:00", NaN);
+ check("2009-07-23T19:53:60.001+12:00", NaN);
+ check("2009-07-23T19:53:21.001+24:00", NaN);
+ check("2009-07-23T19:53:21.001+12:60", NaN);
+
+ // other month ends
+ check("2009-06-30T19:53:21.001+12:00", dd(2009,6,30,7,53,21,1));
+ check("2009-06-31T19:53:21.001+12:00", NaN);
+ check("2009-02-28T19:53:21.001+12:00", dd(2009,2,28,7,53,21,1));
+ check("2009-02-29T19:53:21.001+12:00", NaN);
+ check("2008-02-29T19:53:21.001+12:00", dd(2008,2,29,7,53,21,1));
+ check("2008-02-30T19:53:21.001+12:00", NaN);
+
+ // limits of representation
+ check("-271821-04-19T23:59:59.999Z", NaN);
+ check("-271821-04-20", -8.64e15);
+ check("+275760-09-13", 8.64e15);
+ check("+275760-09-13T00:00:00.001Z", NaN);
+
+ check("-269845-07-23T19:53:21.001+12:00", dd(-269845,7,23,7,53,21,1));
+ check("+273785-07-23T19:53:21.001+12:00", dd(273785,7,23,7,53,21,1));
+
+ // explicit UTC
+ check("2009-07-23T19:53:21.001Z", dd(2009,7,23,19,53,21,1));
+ check("+002009-07-23T19:53:21.001Z", dd(2009,7,23,19,53,21,1));
+
+ // different timezones
+ check("2009-07-23T19:53:21.001+12:00", dd(2009,7,23,7,53,21,1));
+ check("2009-07-23T00:53:21.001-07:00", dd(2009,7,23,7,53,21,1));
+
+ // 00:00 and 24:00
+ check("2009-07-23T00:00:00.000-07:00", dd(2009,7,23,7,0,0,0));
+ check("2009-07-23T24:00:00.000-07:00", dd(2009,7,24,7,0,0,0));
+
+ exitFunc ('test');
+}