Bug 769871 - Reimplement Date.toLocaleString per ECMA-402. r=jwalden

--HG--
extra : rebase_source : 77b0023432502bc4e110143d06c10825fb020190
This commit is contained in:
Norbert Lindenberg 2013-03-21 16:38:25 -07:00
parent 66f19f7626
commit 4c42f1c4ef
10 changed files with 241 additions and 35 deletions

View File

@ -961,6 +961,7 @@ selfhosting:: selfhosted.out.h
selfhosting_srcs := \
$(srcdir)/builtin/Utilities.js \
$(srcdir)/builtin/Array.js \
$(srcdir)/builtin/Date.js \
$(srcdir)/builtin/Intl.js \
$(srcdir)/builtin/IntlData.js \
$(srcdir)/builtin/Number.js \

122
js/src/builtin/Date.js Normal file
View File

@ -0,0 +1,122 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*global date_CheckThisDate: false, intl_DateTimeFormat: false, */
var dateTimeFormatCache = new Record();
/**
* Format this Date object into a date and time string, using the locale and
* formatting options provided.
*
* Spec: ECMAScript Language Specification, 5.1 edition, 15.9.5.5.
* Spec: ECMAScript Internationalization API Specification, 13.3.1.
*/
function Date_toLocaleString() {
// Steps 1-2.
callFunction(date_CheckThisDate, this);
var x = callFunction(std_Date_valueOf, this);
if (std_isNaN(x))
return "Invalid Date";
// Steps 3-4.
var locales = arguments.length > 0 ? arguments[0] : undefined;
var options = arguments.length > 1 ? arguments[1] : undefined;
// Step 5-6.
var dateTimeFormat;
if (locales === undefined && options === undefined) {
// This cache only optimizes for the old ES5 toLocaleString without
// locales and options.
if (dateTimeFormatCache.dateTimeFormat === undefined) {
options = ToDateTimeOptions(options, "any", "all");
dateTimeFormatCache.dateTimeFormat = intl_DateTimeFormat(locales, options);
}
dateTimeFormat = dateTimeFormatCache.dateTimeFormat;
} else {
options = ToDateTimeOptions(options, "any", "all");
dateTimeFormat = intl_DateTimeFormat(locales, options);
}
// Step 7.
return intl_FormatDateTime(dateTimeFormat, x);
}
/**
* Format this Date object into a date string, using the locale and formatting
* options provided.
*
* Spec: ECMAScript Language Specification, 5.1 edition, 15.9.5.6.
* Spec: ECMAScript Internationalization API Specification, 13.3.2.
*/
function Date_toLocaleDateString() {
// Steps 1-2.
callFunction(date_CheckThisDate, this);
var x = callFunction(std_Date_valueOf, this);
if (std_isNaN(x))
return "Invalid Date";
// Steps 3-4.
var locales = arguments.length > 0 ? arguments[0] : undefined;
var options = arguments.length > 1 ? arguments[1] : undefined;
// Step 5-6.
var dateTimeFormat;
if (locales === undefined && options === undefined) {
// This cache only optimizes for the old ES5 toLocaleDateString without
// locales and options.
if (dateTimeFormatCache.dateFormat === undefined) {
options = ToDateTimeOptions(options, "date", "date");
dateTimeFormatCache.dateFormat = intl_DateTimeFormat(locales, options);
}
dateTimeFormat = dateTimeFormatCache.dateFormat;
} else {
options = ToDateTimeOptions(options, "date", "date");
dateTimeFormat = intl_DateTimeFormat(locales, options);
}
// Step 7.
return intl_FormatDateTime(dateTimeFormat, x);
}
/**
* Format this Date object into a time string, using the locale and formatting
* options provided.
*
* Spec: ECMAScript Language Specification, 5.1 edition, 15.9.5.7.
* Spec: ECMAScript Internationalization API Specification, 13.3.3.
*/
function Date_toLocaleTimeString() {
// Steps 1-2.
callFunction(date_CheckThisDate, this);
var x = callFunction(std_Date_valueOf, this);
if (std_isNaN(x))
return "Invalid Date";
// Steps 3-4.
var locales = arguments.length > 0 ? arguments[0] : undefined;
var options = arguments.length > 1 ? arguments[1] : undefined;
// Step 5-6.
var dateTimeFormat;
if (locales === undefined && options === undefined) {
// This cache only optimizes for the old ES5 toLocaleTimeString without
// locales and options.
if (dateTimeFormatCache.timeFormat === undefined) {
options = ToDateTimeOptions(options, "time", "time");
dateTimeFormatCache.timeFormat = intl_DateTimeFormat(locales, options);
}
dateTimeFormat = dateTimeFormatCache.timeFormat;
} else {
options = ToDateTimeOptions(options, "time", "time");
dateTimeFormat = intl_DateTimeFormat(locales, options);
}
// Step 7.
return intl_FormatDateTime(dateTimeFormat, x);
}

View File

@ -1546,14 +1546,11 @@ static JSFunctionSpec dateTimeFormat_methods[] = {
* DateTimeFormat constructor.
* Spec: ECMAScript Internationalization API Specification, 12.1
*/
static JSBool
DateTimeFormat(JSContext *cx, unsigned argc, Value *vp)
static bool
DateTimeFormat(JSContext *cx, CallArgs args, bool construct)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject obj(cx);
bool construct = IsConstructing(args);
if (!construct) {
// 12.1.2.1 step 3
JSObject *intl = cx->global()->getOrCreateIntlObject(cx);
@ -1599,6 +1596,21 @@ DateTimeFormat(JSContext *cx, unsigned argc, Value *vp)
return true;
}
static JSBool
DateTimeFormat(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return DateTimeFormat(cx, args, IsConstructing(args));
}
JSBool
js::intl_DateTimeFormat(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JS_ASSERT(args.length() == 2);
return DateTimeFormat(cx, args, true);
}
static void
dateTimeFormat_finalize(FreeOp *fop, JSObject *obj)
{

View File

@ -127,6 +127,16 @@ intl_FormatNumber(JSContext *cx, unsigned argc, Value *vp);
/******************** DateTimeFormat ********************/
/**
* Returns a new instance of the standard built-in DateTimeFormat constructor.
* Self-hosted code cannot cache this constructor (as it does for others in
* Utilities.js) because it is initialized after self-hosted code is compiled.
*
* Usage: dateTimeFormat = intl_DateTimeFormat(locales, options)
*/
extern JSBool
intl_DateTimeFormat(JSContext *cx, unsigned argc, Value *vp);
/**
* Returns an object indicating the supported locales for date and time
* formatting by having a true-valued property for each such locale with the

View File

@ -39,6 +39,7 @@ var std_Array_unshift = Array.prototype.unshift;
var std_Boolean_toString = Boolean.prototype.toString;
var Std_Date = Date;
var std_Date_now = Date.now;
var std_Date_valueOf = Date.prototype.valueOf;
var std_Function_bind = Function.prototype.bind;
var std_Function_apply = Function.prototype.apply;
var std_Math_floor = Math.floor;

View File

@ -59,7 +59,7 @@ using mozilla::ArrayLength;
/*
* The JS 'Date' object is patterned after the Java 'Date' object.
* Here is an script:
* Here is a script:
*
* today = new Date();
*
@ -1392,6 +1392,26 @@ IsDate(const Value &v)
return v.isObject() && v.toObject().hasClass(&DateClass);
}
JS_ALWAYS_INLINE bool
date_nop(JSContext *cx, CallArgs args)
{
JS_ASSERT(IsDate(args.thisv()));
args.rval().setUndefined();
return true;
}
JSBool
date_CheckThisDate(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// CallNonGenericMethod will handle proxies correctly and throw exceptions
// in the right circumstances, but will report date_CheckThisDate as the
// function name in the message. We need a better solution:
// https://bugzilla.mozilla.org/show_bug.cgi?id=844677
return CallNonGenericMethod<IsDate, date_nop>(cx, args);
}
/*
* See ECMA 15.9.5.4 thru 15.9.5.23
*/
@ -2584,7 +2604,7 @@ date_toJSON(JSContext *cx, unsigned argc, Value *vp)
return true;
}
/* for Date.toLocaleString; interface to PRMJTime date struct.
/* for Date.toLocaleFormat; interface to PRMJTime date struct.
*/
static void
new_explode(double timeval, PRMJTime *split, DateTimeInfo *dtInfo)
@ -2726,7 +2746,7 @@ date_format(JSContext *cx, double date, formatspec format, MutableHandleValue rv
}
static bool
ToLocaleHelper(JSContext *cx, HandleObject obj, const char *format, MutableHandleValue rval)
ToLocaleFormatHelper(JSContext *cx, HandleObject obj, const char *format, MutableHandleValue rval)
{
double utctime = obj->getDateUTCTime().toNumber();
@ -2771,6 +2791,7 @@ ToLocaleHelper(JSContext *cx, HandleObject obj, const char *format, MutableHandl
return true;
}
#if !ENABLE_INTL_API
static bool
ToLocaleStringHelper(JSContext *cx, HandleObject thisObj, MutableHandleValue rval)
{
@ -2778,7 +2799,7 @@ ToLocaleStringHelper(JSContext *cx, HandleObject thisObj, MutableHandleValue rva
* Use '%#c' for windows, because '%c' is backward-compatible and non-y2k
* with msvc; '%#c' requests that a full year be used in the result string.
*/
return ToLocaleHelper(cx, thisObj,
return ToLocaleFormatHelper(cx, thisObj,
#if defined(_WIN32) && !defined(__MWERKS__)
"%#c"
#else
@ -2823,7 +2844,7 @@ date_toLocaleDateString_impl(JSContext *cx, CallArgs args)
;
RootedObject thisObj(cx, &args.thisv().toObject());
return ToLocaleHelper(cx, thisObj, format, args.rval());
return ToLocaleFormatHelper(cx, thisObj, format, args.rval());
}
static JSBool
@ -2840,7 +2861,7 @@ date_toLocaleTimeString_impl(JSContext *cx, CallArgs args)
JS_ASSERT(IsDate(args.thisv()));
RootedObject thisObj(cx, &args.thisv().toObject());
return ToLocaleHelper(cx, thisObj, "%X", args.rval());
return ToLocaleFormatHelper(cx, thisObj, "%X", args.rval());
}
static JSBool
@ -2849,6 +2870,7 @@ date_toLocaleTimeString(JSContext *cx, unsigned argc, Value *vp)
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsDate, date_toLocaleTimeString_impl>(cx, args);
}
#endif
JS_ALWAYS_INLINE bool
date_toLocaleFormat_impl(JSContext *cx, CallArgs args)
@ -2857,8 +2879,19 @@ date_toLocaleFormat_impl(JSContext *cx, CallArgs args)
RootedObject thisObj(cx, &args.thisv().toObject());
if (args.length() == 0)
return ToLocaleStringHelper(cx, thisObj, args.rval());
if (args.length() == 0) {
/*
* Use '%#c' for windows, because '%c' is backward-compatible and non-y2k
* with msvc; '%#c' requests that a full year be used in the result string.
*/
return ToLocaleFormatHelper(cx, thisObj,
#if defined(_WIN32) && !defined(__MWERKS__)
"%#c"
#else
"%c"
#endif
, args.rval());
}
RootedString fmt(cx, ToString<CanGC>(cx, args[0]));
if (!fmt)
@ -2868,7 +2901,7 @@ date_toLocaleFormat_impl(JSContext *cx, CallArgs args)
if (!fmtbytes)
return false;
return ToLocaleHelper(cx, thisObj, fmtbytes.ptr(), args.rval());
return ToLocaleFormatHelper(cx, thisObj, fmtbytes.ptr(), args.rval());
}
static JSBool
@ -3018,9 +3051,6 @@ static JSFunctionSpec date_methods[] = {
JS_FN("setMilliseconds", date_setMilliseconds, 1,0),
JS_FN("setUTCMilliseconds", date_setUTCMilliseconds, 1,0),
JS_FN("toUTCString", date_toGMTString, 0,0),
JS_FN(js_toLocaleString_str, date_toLocaleString, 0,0),
JS_FN("toLocaleDateString", date_toLocaleDateString, 0,0),
JS_FN("toLocaleTimeString", date_toLocaleTimeString, 0,0),
JS_FN("toLocaleFormat", date_toLocaleFormat, 0,0),
JS_FN("toDateString", date_toDateString, 0,0),
JS_FN("toTimeString", date_toTimeString, 0,0),
@ -3031,6 +3061,19 @@ static JSFunctionSpec date_methods[] = {
#endif
JS_FN(js_toString_str, date_toString, 0,0),
JS_FN(js_valueOf_str, date_valueOf, 0,0),
// This must be at the end because of bug 853075: functions listed after
// self-hosted methods aren't available in self-hosted code.
#if ENABLE_INTL_API
{js_toLocaleString_str, {NULL, NULL}, 0,0, "Date_toLocaleString"},
{"toLocaleDateString", {NULL, NULL}, 0,0, "Date_toLocaleDateString"},
{"toLocaleTimeString", {NULL, NULL}, 0,0, "Date_toLocaleTimeString"},
#else
JS_FN(js_toLocaleString_str, date_toLocaleString, 0,0),
JS_FN("toLocaleDateString", date_toLocaleDateString, 0,0),
JS_FN("toLocaleTimeString", date_toLocaleTimeString, 0,0),
#endif
JS_FS_END
};
@ -3261,9 +3304,6 @@ static const NativeImpl sReadOnlyDateMethods[] = {
date_getTimezoneOffset_impl,
date_toGMTString_impl,
date_toISOString_impl,
date_toLocaleString_impl,
date_toLocaleDateString_impl,
date_toLocaleTimeString_impl,
date_toLocaleFormat_impl,
date_toTimeString_impl,
date_toDateString_impl,

View File

@ -65,6 +65,15 @@ js_DateGetMinutes(JSContext *cx, JSRawObject obj);
extern JS_FRIEND_API(int)
js_DateGetSeconds(JSRawObject obj);
/**
* Checks that the this value provided meets the requirements for "this Date
* object" in ES5.1, 15.9.5, and throws a TypeError if not.
*
* Usage: callFunction(date_CheckThisDate, this)
*/
extern JSBool
date_CheckThisDate(JSContext *cx, unsigned argc, js::Value *vp);
/* Date constructor native. Exposed only so the JIT can know its address. */
JSBool
js_Date(JSContext *cx, unsigned argc, js::Value *vp);

View File

@ -27,15 +27,26 @@ function test()
d = new Date(-maxms );
y = d.getFullYear();
l = d.toLocaleString();
print(l);
actual = y;
expect = -271821;
reportCompare(expect, actual, summary + ': check year');
actual = l.match(new RegExp(y)) + '';
expect = y + '';
l = d.toLocaleString();
print(l);
if (this.hasOwnProperty("Intl")) {
// ECMA-402 specifies that toLocaleString uses a proleptic Gregorian
// calender without year 0.
// Also, localized strings usually use era indicators such as "BC"
// instead of minus signs.
expect = Math.abs(y - 1) + '';
} else {
// ECMA-262 up to edition 5.1 didn't specify toLocaleString;
// the previous implementation assumed a calendar with year 0 and used
// minus sign.
expect = y + '';
}
actual = l.match(/-?[0-9]{3,}/) + '';
reportCompare(expect, actual, summary + ': check toLocaleString');
d = new Date(maxms );

View File

@ -20,7 +20,7 @@ var temp;
enterFunc ('test');
printBugNumber(BUGNUMBER);
printStatus (summary);
var date = new Date("06/05/2005 00:00:00 GMT-0000");
expect = date.getTimezoneOffset() > 0 ? 'Sat' : 'Sun';
@ -86,11 +86,6 @@ expect = '22';
actual = date.toLocaleFormat('%W');
reportCompare(expect, actual, 'Date.toLocaleFormat("%W")');
expect = date.toLocaleTimeString();
actual = date.toLocaleFormat('%X');
reportCompare(expect, actual, 'Date.toLocaleTimeString() == ' +
'Date.toLocaleFormat("%X")');
expect = '05';
actual = date.toLocaleFormat('%y');
reportCompare(expect, actual, 'Date.toLocaleFormat("%y")');

View File

@ -7,6 +7,7 @@
#include "jscntxt.h"
#include "jscompartment.h"
#include "jsdate.h"
#include "jsinterp.h"
#include "jsnum.h"
#include "jsobj.h"
@ -470,19 +471,23 @@ JSFunctionSpec intrinsic_functions[] = {
JS_FN("ShouldForceSequential", intrinsic_ShouldForceSequential, 0,0),
JS_FN("ParallelTestsShouldPass", intrinsic_ParallelTestsShouldPass, 0,0),
// See jsdate.h for descriptions of the date_* functions.
JS_FN("date_CheckThisDate", date_CheckThisDate, 2,0),
// See builtin/Intl.h for descriptions of the intl_* functions.
JS_FN("intl_availableCalendars", intl_availableCalendars, 1,0),
JS_FN("intl_availableCollations", intl_availableCollations, 1,0),
JS_FN("intl_Collator", intl_Collator, 2,0),
JS_FN("intl_Collator_availableLocales", intl_Collator_availableLocales, 0,0),
JS_FN("intl_availableCollations", intl_availableCollations, 1,0),
JS_FN("intl_CompareStrings", intl_CompareStrings, 3,0),
JS_FN("intl_DateTimeFormat", intl_DateTimeFormat, 2,0),
JS_FN("intl_DateTimeFormat_availableLocales", intl_DateTimeFormat_availableLocales, 0,0),
JS_FN("intl_FormatDateTime", intl_FormatDateTime, 2,0),
JS_FN("intl_FormatNumber", intl_FormatNumber, 2,0),
JS_FN("intl_NumberFormat", intl_NumberFormat, 2,0),
JS_FN("intl_NumberFormat_availableLocales", intl_NumberFormat_availableLocales, 0,0),
JS_FN("intl_numberingSystem", intl_numberingSystem, 1,0),
JS_FN("intl_FormatNumber", intl_FormatNumber, 2,0),
JS_FN("intl_DateTimeFormat_availableLocales", intl_DateTimeFormat_availableLocales, 0,0),
JS_FN("intl_availableCalendars", intl_availableCalendars, 1,0),
JS_FN("intl_patternForSkeleton", intl_patternForSkeleton, 2,0),
JS_FN("intl_FormatDateTime", intl_FormatDateTime, 2,0),
// See jsnum.h for descriptions of the num_* functions.
JS_FN("num_CheckThisNumber", num_CheckThisNumber, 2,0),