From 5c9465b24812cb167e062ab2de482efbc9e16cf3 Mon Sep 17 00:00:00 2001 From: Zibi Braniecki Date: Thu, 31 Dec 2015 19:07:45 -0800 Subject: [PATCH] Bug 1216150 - Implement ECMA 402 DateTimeFormat formatToParts --- js/src/builtin/Date.js | 6 +- js/src/builtin/Intl.cpp | 304 +++++++++++++++++- js/src/builtin/Intl.js | 32 +- js/src/jsapi.h | 22 +- js/src/shell/js.cpp | 5 + .../Intl/DateTimeFormat/formatToParts.js | 176 ++++++++++ js/src/vm/CommonPropertyNames.h | 14 + 7 files changed, 546 insertions(+), 13 deletions(-) create mode 100644 js/src/tests/Intl/DateTimeFormat/formatToParts.js diff --git a/js/src/builtin/Date.js b/js/src/builtin/Date.js index 8e938b30da9f..bd2d5854cbd0 100644 --- a/js/src/builtin/Date.js +++ b/js/src/builtin/Date.js @@ -101,7 +101,7 @@ function Date_toLocaleString() { } // Step 7. - return intl_FormatDateTime(dateTimeFormat, x); + return intl_FormatDateTime(dateTimeFormat, x, false); } @@ -134,7 +134,7 @@ function Date_toLocaleDateString() { } // Step 7. - return intl_FormatDateTime(dateTimeFormat, x); + return intl_FormatDateTime(dateTimeFormat, x, false); } @@ -167,5 +167,5 @@ function Date_toLocaleTimeString() { } // Step 7. - return intl_FormatDateTime(dateTimeFormat, x); + return intl_FormatDateTime(dateTimeFormat, x, false); } diff --git a/js/src/builtin/Intl.cpp b/js/src/builtin/Intl.cpp index 89fe2178688a..c780d1434745 100644 --- a/js/src/builtin/Intl.cpp +++ b/js/src/builtin/Intl.cpp @@ -12,6 +12,7 @@ #include "builtin/Intl.h" #include "mozilla/Range.h" +#include "mozilla/ScopeExit.h" #include @@ -45,6 +46,7 @@ using namespace js; using mozilla::IsFinite; using mozilla::IsNegativeZero; +using mozilla::MakeScopeExit; #if ENABLE_INTL_API using icu::Locale; @@ -176,6 +178,7 @@ ucol_getKeywordValuesForLocale(const char* key, const char* locale, UBool common struct UParseError; struct UFieldPosition; +struct UFieldPositionIterator; typedef void* UNumberFormat; enum UNumberFormatStyle { @@ -339,6 +342,46 @@ udatpg_close(UDateTimePatternGenerator* dtpg) typedef void* UCalendar; typedef void* UDateFormat; +enum UDateFormatField { + UDAT_ERA_FIELD = 0, + UDAT_YEAR_FIELD = 1, + UDAT_MONTH_FIELD = 2, + UDAT_DATE_FIELD = 3, + UDAT_HOUR_OF_DAY1_FIELD = 4, + UDAT_HOUR_OF_DAY0_FIELD = 5, + UDAT_MINUTE_FIELD = 6, + UDAT_SECOND_FIELD = 7, + UDAT_FRACTIONAL_SECOND_FIELD = 8, + UDAT_DAY_OF_WEEK_FIELD = 9, + UDAT_DAY_OF_YEAR_FIELD = 10, + UDAT_DAY_OF_WEEK_IN_MONTH_FIELD = 11, + UDAT_WEEK_OF_YEAR_FIELD = 12, + UDAT_WEEK_OF_MONTH_FIELD = 13, + UDAT_AM_PM_FIELD = 14, + UDAT_HOUR1_FIELD = 15, + UDAT_HOUR0_FIELD = 16, + UDAT_TIMEZONE_FIELD = 17, + UDAT_YEAR_WOY_FIELD = 18, + UDAT_DOW_LOCAL_FIELD = 19, + UDAT_EXTENDED_YEAR_FIELD = 20, + UDAT_JULIAN_DAY_FIELD = 21, + UDAT_MILLISECONDS_IN_DAY_FIELD = 22, + UDAT_TIMEZONE_RFC_FIELD = 23, + UDAT_TIMEZONE_GENERIC_FIELD = 24, + UDAT_STANDALONE_DAY_FIELD = 25, + UDAT_STANDALONE_MONTH_FIELD = 26, + UDAT_QUARTER_FIELD = 27, + UDAT_STANDALONE_QUARTER_FIELD = 28, + UDAT_TIMEZONE_SPECIAL_FIELD = 29, + UDAT_YEAR_NAME_FIELD = 30, + UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD = 31, + UDAT_TIMEZONE_ISO_FIELD = 32, + UDAT_TIMEZONE_ISO_LOCAL_FIELD = 33, + UDAT_RELATED_YEAR_FIELD = 34, + UDAT_TIME_SEPARATOR_FIELD = 35, + UDAT_FIELD_COUNT = 36 +}; + enum UDateFormatStyle { UDAT_PATTERN = -2, UDAT_IGNORE = UDAT_PATTERN @@ -383,6 +426,32 @@ udat_format(const UDateFormat* format, UDate dateToFormat, UChar* result, MOZ_CRASH("udat_format: Intl API disabled"); } +static int32_t +udat_formatForFields(const UDateFormat* format, UDate dateToFormat, + UChar* result, int32_t resultLength, UFieldPositionIterator* fpositer, + UErrorCode* status) +{ + MOZ_CRASH("udat_formatForFields: Intl API disabled"); +} + +static UFieldPositionIterator* +ufieldpositer_open(UErrorCode* status) +{ + MOZ_CRASH("ufieldpositer_open: Intl API disabled"); +} + +static void +ufieldpositer_close(UFieldPositionIterator* fpositer) +{ + MOZ_CRASH("ufieldpositer_close: Intl API disabled"); +} + +static int32_t +ufieldpositer_next(UFieldPositionIterator* fpositer, int32_t* beginIndex, int32_t* endIndex) +{ + MOZ_CRASH("ufieldpositer_next: Intl API disabled"); +} + static void udat_close(UDateFormat* format) { @@ -1669,11 +1738,9 @@ InitDateTimeFormatClass(JSContext* cx, HandleObject Intl, Handle if (!JS_DefineFunctions(cx, proto, dateTimeFormat_methods)) return nullptr; - /* - * Install the getter for DateTimeFormat.prototype.format, which returns a - * bound formatting function for the specified DateTimeFormat object - * (suitable for passing to methods like Array.prototype.map). - */ + // Install a getter for DateTimeFormat.prototype.format that returns a + // formatting function bound to a specified DateTimeFormat object (suitable + // for passing to methods like Array.prototype.map). RootedValue getter(cx); if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().DateTimeFormatFormatGet, &getter)) @@ -1687,6 +1754,22 @@ InitDateTimeFormatClass(JSContext* cx, HandleObject Intl, Handle return nullptr; } + // If the still-experimental DateTimeFormat.prototype.formatToParts method + // is enabled, also add its getter. + if (cx->compartment()->creationOptions().experimentalDateTimeFormatFormatToPartsEnabled()) { + if (!GlobalObject::getIntrinsicValue(cx, cx->global(), + cx->names().DateTimeFormatFormatToPartsGet, &getter)) + { + return nullptr; + } + if (!DefineProperty(cx, proto, cx->names().formatToParts, UndefinedHandleValue, + JS_DATA_TO_FUNC_PTR(JSGetterOp, &getter.toObject()), + nullptr, JSPROP_GETTER | JSPROP_SHARED)) + { + return nullptr; + } + } + RootedValue options(cx); if (!CreateDefaultOptions(cx, &options)) return nullptr; @@ -1986,6 +2069,210 @@ intl_FormatDateTime(JSContext* cx, UDateFormat* df, double x, MutableHandleValue return false; result.setString(str); + + return true; +} + +using FieldType = ImmutablePropertyNamePtr JSAtomState::*; + +static FieldType +GetFieldTypeForFormatField(UDateFormatField fieldName) +{ + // See intl/icu/source/i18n/unicode/udat.h for a detailed field list. This + // switch is deliberately exhaustive: cases might have to be added/removed + // if this code is compiled with a different ICU with more + // UDateFormatField enum initializers. Please guard such cases with + // appropriate ICU version-testing #ifdefs, should cross-version divergence + // occur. + switch (fieldName) { + case UDAT_ERA_FIELD: + return &JSAtomState::era; + case UDAT_YEAR_FIELD: + case UDAT_YEAR_WOY_FIELD: + case UDAT_EXTENDED_YEAR_FIELD: + case UDAT_YEAR_NAME_FIELD: + return &JSAtomState::year; + + case UDAT_MONTH_FIELD: + case UDAT_STANDALONE_MONTH_FIELD: + return &JSAtomState::month; + + case UDAT_DATE_FIELD: + case UDAT_JULIAN_DAY_FIELD: + return &JSAtomState::day; + + case UDAT_HOUR_OF_DAY1_FIELD: + case UDAT_HOUR_OF_DAY0_FIELD: + case UDAT_HOUR1_FIELD: + case UDAT_HOUR0_FIELD: + return &JSAtomState::hour; + + case UDAT_MINUTE_FIELD: + return &JSAtomState::minute; + + case UDAT_SECOND_FIELD: + return &JSAtomState::second; + + case UDAT_DAY_OF_WEEK_FIELD: + case UDAT_STANDALONE_DAY_FIELD: + case UDAT_DOW_LOCAL_FIELD: + case UDAT_DAY_OF_WEEK_IN_MONTH_FIELD: + return &JSAtomState::weekday; + + case UDAT_AM_PM_FIELD: + return &JSAtomState::dayperiod; + + case UDAT_TIMEZONE_FIELD: + return &JSAtomState::timeZoneName; + + case UDAT_FRACTIONAL_SECOND_FIELD: + case UDAT_DAY_OF_YEAR_FIELD: + case UDAT_WEEK_OF_YEAR_FIELD: + case UDAT_WEEK_OF_MONTH_FIELD: + case UDAT_MILLISECONDS_IN_DAY_FIELD: + case UDAT_TIMEZONE_RFC_FIELD: + case UDAT_TIMEZONE_GENERIC_FIELD: + case UDAT_QUARTER_FIELD: + case UDAT_STANDALONE_QUARTER_FIELD: + case UDAT_TIMEZONE_SPECIAL_FIELD: + case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD: + case UDAT_TIMEZONE_ISO_FIELD: + case UDAT_TIMEZONE_ISO_LOCAL_FIELD: +#ifndef U_HIDE_INTERNAL_API + case UDAT_RELATED_YEAR_FIELD: +#endif +#ifndef U_HIDE_DRAFT_API + case UDAT_TIME_SEPARATOR_FIELD: +#endif + // These fields are all unsupported. + return nullptr; + + case UDAT_FIELD_COUNT: + MOZ_ASSERT_UNREACHABLE("format field sentinel value returned by " + "iterator!"); + } + + MOZ_ASSERT_UNREACHABLE("unenumerated, undocumented format field returned " + "by iterator"); + return nullptr; +} + +static bool +intl_FormatToPartsDateTime(JSContext* cx, UDateFormat* df, double x, MutableHandleValue result) +{ + if (!IsFinite(x)) { + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DATE_NOT_FINITE); + return false; + } + + Vector chars(cx); + if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE)) + return false; + + UErrorCode status = U_ZERO_ERROR; + UFieldPositionIterator* fpositer = ufieldpositer_open(&status); + if (U_FAILURE(status)) { + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + auto closeFieldPosIter = MakeScopeExit([&]() { ufieldpositer_close(fpositer); }); + + int resultSize = + udat_formatForFields(df, x, Char16ToUChar(chars.begin()), INITIAL_CHAR_BUFFER_SIZE, + fpositer, &status); + if (status == U_BUFFER_OVERFLOW_ERROR) { + if (!chars.resize(resultSize)) + return false; + status = U_ZERO_ERROR; + udat_formatForFields(df, x, Char16ToUChar(chars.begin()), resultSize, fpositer, &status); + } + if (U_FAILURE(status)) { + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); + return false; + } + + RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx)); + if (!partsArray) + return false; + if (resultSize == 0) { + // An empty string contains no parts, so avoid extra work below. + result.setObject(*partsArray); + return true; + } + + RootedString overallResult(cx, NewStringCopyN(cx, chars.begin(), resultSize)); + if (!overallResult) + return false; + + size_t lastEndIndex = 0; + + uint32_t partIndex = 0; + RootedObject singlePart(cx); + RootedValue partType(cx); + RootedString partSubstr(cx); + RootedValue val(cx); + + auto AppendPart = [&](FieldType type, size_t beginIndex, size_t endIndex) { + singlePart = NewBuiltinClassInstance(cx); + if (!singlePart) + return false; + + partType = StringValue(cx->names().*type); + if (!DefineProperty(cx, singlePart, cx->names().type, partType)) + return false; + + partSubstr = SubstringKernel(cx, overallResult, beginIndex, endIndex - beginIndex); + if (!partSubstr) + return false; + + val = StringValue(partSubstr); + if (!DefineProperty(cx, singlePart, cx->names().value, val)) + return false; + + val = ObjectValue(*singlePart); + if (!DefineElement(cx, partsArray, partIndex, val)) + return false; + + lastEndIndex = endIndex; + partIndex++; + return true; + }; + + int32_t fieldInt, beginIndexInt, endIndexInt; + while ((fieldInt = ufieldpositer_next(fpositer, &beginIndexInt, &endIndexInt)) >= 0) { + MOZ_ASSERT(beginIndexInt >= 0); + MOZ_ASSERT(endIndexInt >= 0); + MOZ_ASSERT(beginIndexInt <= endIndexInt, + "field iterator returning invalid range"); + + size_t beginIndex(beginIndexInt); + size_t endIndex(endIndexInt); + + // Technically this isn't guaranteed. But it appears true in pratice, + // and http://bugs.icu-project.org/trac/ticket/12024 is expected to + // correct the documentation lapse. + MOZ_ASSERT(lastEndIndex <= beginIndex, + "field iteration didn't return fields in order start to " + "finish as expected"); + + if (FieldType type = GetFieldTypeForFormatField(static_cast(fieldInt))) { + if (lastEndIndex < beginIndex) { + if (!AppendPart(&JSAtomState::separator, lastEndIndex, beginIndex)) + return false; + } + + if (!AppendPart(type, beginIndex, endIndex)) + return false; + } + } + + // Append any final separator. + if (lastEndIndex < overallResult->length()) { + if (!AppendPart(&JSAtomState::separator, lastEndIndex, overallResult->length())) + return false; + } + + result.setObject(*partsArray); return true; } @@ -1993,9 +2280,10 @@ bool js::intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 2); + MOZ_ASSERT(args.length() == 3); MOZ_ASSERT(args[0].isObject()); MOZ_ASSERT(args[1].isNumber()); + MOZ_ASSERT(args[2].isBoolean()); RootedObject dateTimeFormat(cx, &args[0].toObject()); @@ -2024,7 +2312,9 @@ js::intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp) // Use the UDateFormat to actually format the time stamp. RootedValue result(cx); - bool success = intl_FormatDateTime(cx, df, args[1].toNumber(), &result); + bool success = args[2].toBoolean() + ? intl_FormatToPartsDateTime(cx, df, args[1].toNumber(), &result) + : intl_FormatDateTime(cx, df, args[1].toNumber(), &result); if (!isDateTimeFormatInstance) udat_close(df); diff --git a/js/src/builtin/Intl.js b/js/src/builtin/Intl.js index d69fa4599671..de7a1132265f 100644 --- a/js/src/builtin/Intl.js +++ b/js/src/builtin/Intl.js @@ -2712,10 +2712,9 @@ function dateTimeFormatFormatToBind() { var x = (date === undefined) ? std_Date_now() : ToNumber(date); // Step 1.a.iii. - return intl_FormatDateTime(this, x); + return intl_FormatDateTime(this, x, false); } - /** * Returns a function bound to this DateTimeFormat that returns a String value * representing the result of calling ToNumber(date) according to the @@ -2742,6 +2741,35 @@ function Intl_DateTimeFormat_format_get() { } +function dateTimeFormatFormatToPartsToBind() { + // Steps 1.a.i-ii + var date = arguments.length > 0 ? arguments[0] : undefined; + var x = (date === undefined) ? std_Date_now() : ToNumber(date); + + // Step 1.a.iii. + return intl_FormatDateTime(this, x, true); +} + + +function Intl_DateTimeFormat_formatToParts_get() { + // Check "this DateTimeFormat object" per introduction of section 12.3. + var internals = getDateTimeFormatInternals(this, "formatToParts"); + + // Step 1. + if (internals.boundFormatToParts === undefined) { + // Step 1.a. + var F = dateTimeFormatFormatToPartsToBind; + + // Step 1.b-d. + var bf = callFunction(std_Function_bind, F, this); + internals.boundFormatToParts = bf; + } + + // Step 2. + return internals.boundFormatToParts; +} + + /** * Returns the resolved options for a DateTimeFormat object. * diff --git a/js/src/jsapi.h b/js/src/jsapi.h index c4ac9cb69c75..07f9e34199c1 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -2180,7 +2180,8 @@ class JS_PUBLIC_API(CompartmentCreationOptions) invisibleToDebugger_(false), mergeable_(false), preserveJitCode_(false), - cloneSingletons_(false) + cloneSingletons_(false), + experimentalDateTimeFormatFormatToPartsEnabled_(false) { zone_.spec = JS::FreshZone; } @@ -2243,6 +2244,24 @@ class JS_PUBLIC_API(CompartmentCreationOptions) return *this; } + // ECMA-402 is considering adding a "formatToParts" DateTimeFormat method, + // that exposes not just a formatted string but its ordered subcomponents. + // The method, its semantics, and its name are all well short of being + // finalized, so for now it's exposed *only* if requested. + // + // Until "formatToParts" is included in a final specification edition, it's + // subject to change or removal at any time. Do *not* rely on it in + // mission-critical code that can't be changed if ECMA-402 decides not to + // accept the method in its current form. + bool experimentalDateTimeFormatFormatToPartsEnabled() const { + return experimentalDateTimeFormatFormatToPartsEnabled_; + } + CompartmentCreationOptions& setExperimentalDateTimeFormatFormatToPartsEnabled(bool flag) { + experimentalDateTimeFormatFormatToPartsEnabled_ = flag; + return *this; + } + + private: JSAddonId* addonId_; JSTraceOp traceGlobal_; @@ -2254,6 +2273,7 @@ class JS_PUBLIC_API(CompartmentCreationOptions) bool mergeable_; bool preserveJitCode_; bool cloneSingletons_; + bool experimentalDateTimeFormatFormatToPartsEnabled_; }; /** diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index b7e90943b8ef..d45948c06422 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -3977,6 +3977,11 @@ NewGlobal(JSContext* cx, unsigned argc, Value* vp) if (v.isBoolean()) creationOptions.setCloneSingletons(v.toBoolean()); + if (!JS_GetProperty(cx, opts, "experimentalDateTimeFormatFormatToPartsEnabled", &v)) + return true; + if (v.isBoolean()) + creationOptions.setExperimentalDateTimeFormatFormatToPartsEnabled(v.toBoolean()); + if (!JS_GetProperty(cx, opts, "sameZoneAs", &v)) return false; if (v.isObject()) diff --git a/js/src/tests/Intl/DateTimeFormat/formatToParts.js b/js/src/tests/Intl/DateTimeFormat/formatToParts.js new file mode 100644 index 000000000000..e88439cffdcd --- /dev/null +++ b/js/src/tests/Intl/DateTimeFormat/formatToParts.js @@ -0,0 +1,176 @@ +// |reftest| skip-if(!this.hasOwnProperty("Intl")||!this.newGlobal||!newGlobal({experimentalDateTimeFormatFormatToPartsEnabled:true}).Intl.DateTimeFormat().formatToParts) +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/licenses/publicdomain/ + +// Tests the format function with a diverse set of locales and options. +// Always use UTC to avoid dependencies on test environment. + +/* + * Return true if A is equal to B, where equality on arrays and objects + * means that they have the same set of enumerable properties, the values + * of each property are deep_equal, and their 'length' properties are + * equal. Equality on other types is ==. + */ +function deepEqual(a, b) { + if (typeof a !== typeof b) + return false; + + if (a === null) + return b === null; + + if (typeof a === 'object') { + // For every property of a, does b have that property with an equal value? + var props = {}; + for (var prop in a) { + if (!deepEqual(a[prop], b[prop])) + return false; + props[prop] = true; + } + + // Are all of b's properties present on a? + for (var prop in b) + if (!props[prop]) + return false; + + // length isn't enumerable, but we want to check it, too. + return a.length === b.length; + } + + return Object.is(a, b); +} + +function composeDate(parts) { + return parts.map(({value}) => value) + .reduce((string, part) => string + part); +} + +var format; +var date = Date.UTC(2012, 11, 17, 3, 0, 42); + +// The experimental formatToParts method is only exposed if specifically +// requested. Perform all tests using DateTimeFormat instances from a global +// object with this method enabled. +var DateTimeFormat = + newGlobal({experimentalDateTimeFormatFormatToPartsEnabled:true}).Intl.DateTimeFormat; + +// Locale en-US; default options. +format = new DateTimeFormat("en-us", {timeZone: "UTC"}); +assertEq(deepEqual(format.formatToParts(date), [ + { type: 'month', value: '12' }, + { type: 'separator', value: '/' }, + { type: 'day', value: '17' }, + { type: 'separator', value: '/' }, + { type: 'year', value: '2012' } +]), true); + +// Just date +format = new DateTimeFormat("en-us", { + year: 'numeric', + month: 'numeric', + day: 'numeric', + timeZone: "UTC"}); +assertEq(deepEqual(format.formatToParts(date), [ + { type: 'month', value: '12' }, + { type: 'separator', value: '/' }, + { type: 'day', value: '17' }, + { type: 'separator', value: '/' }, + { type: 'year', value: '2012' } +]), true); +assertEq(composeDate(format.formatToParts(date)), format.format(date)); + +// Just time in hour24 +format = new DateTimeFormat("en-us", { + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + hour12: false, + timeZone: "UTC"}); +assertEq(deepEqual(format.formatToParts(date), [ + { type: 'hour', value: '03' }, + { type: 'separator', value: ':' }, + { type: 'minute', value: '00' }, + { type: 'separator', value: ':' }, + { type: 'second', value: '42' } +]), true); +assertEq(composeDate(format.formatToParts(date)), format.format(date)); + +// Just time in hour12 +format = new DateTimeFormat("en-us", { + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + hour12: true, + timeZone: "UTC"}); +assertEq(deepEqual(format.formatToParts(date), [ + { type: 'hour', value: '3' }, + { type: 'separator', value: ':' }, + { type: 'minute', value: '00' }, + { type: 'separator', value: ':' }, + { type: 'second', value: '42' }, + { type: 'separator', value: ' ' }, + { type: 'dayperiod', value: 'AM' } +]), true); +assertEq(composeDate(format.formatToParts(date)), format.format(date)); + +// Just month. +format = new DateTimeFormat("en-us", { + month: "narrow", + timeZone: "UTC"}); +assertEq(deepEqual(format.formatToParts(date), [ + { type: 'month', value: 'D' } +]), true); +assertEq(composeDate(format.formatToParts(date)), format.format(date)); + +// Just weekday. +format = new DateTimeFormat("en-us", { + weekday: "narrow", + timeZone: "UTC"}); +assertEq(deepEqual(format.formatToParts(date), [ + { type: 'weekday', value: 'M' } +]), true); +assertEq(composeDate(format.formatToParts(date)), format.format(date)); + +// Year and era. +format = new DateTimeFormat("en-us", { + year: "numeric", + era: "short", + timeZone: "UTC"}); +assertEq(deepEqual(format.formatToParts(date), [ + { type: 'year', value: '2012' }, + { type: 'separator', value: ' ' }, + { type: 'era', value: 'AD' } +]), true); +assertEq(composeDate(format.formatToParts(date)), format.format(date)); + +// Time and date +format = new DateTimeFormat("en-us", { + weekday: 'long', + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + hour12: true, + timeZone: "UTC"}); +assertEq(deepEqual(format.formatToParts(date), [ + { type: 'weekday', value: 'Monday' }, + { type: 'separator', value: ', ' }, + { type: 'month', value: '12' }, + { type: 'separator', value: '/' }, + { type: 'day', value: '17' }, + { type: 'separator', value: '/' }, + { type: 'year', value: '2012' }, + { type: 'separator', value: ', ' }, + { type: 'hour', value: '3' }, + { type: 'separator', value: ':' }, + { type: 'minute', value: '00' }, + { type: 'separator', value: ':' }, + { type: 'second', value: '42' }, + { type: 'separator', value: ' ' }, + { type: 'dayperiod', value: 'AM' } +]), true); +assertEq(composeDate(format.formatToParts(date)), format.format(date)); + +if (typeof reportCompare === "function") + reportCompare(0, 0, 'ok'); diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 23aead116693..b6cf006254f7 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -59,6 +59,9 @@ macro(currencyDisplay, currencyDisplay, "currencyDisplay") \ macro(DateTimeFormat, DateTimeFormat, "DateTimeFormat") \ macro(DateTimeFormatFormatGet, DateTimeFormatFormatGet, "Intl_DateTimeFormat_format_get") \ + macro(DateTimeFormatFormatToPartsGet, DateTimeFormatFormatToPartsGet, "Intl_DateTimeFormat_formatToParts_get") \ + macro(day, day, "day") \ + macro(dayperiod, dayperiod, "dayperiod") \ macro(decodeURI, decodeURI, "decodeURI") \ macro(decodeURIComponent, decodeURIComponent, "decodeURIComponent") \ macro(default_, default_, "default") \ @@ -80,6 +83,7 @@ macro(endTimestamp, endTimestamp, "endTimestamp") \ macro(enumerable, enumerable, "enumerable") \ macro(enumerate, enumerate, "enumerate") \ + macro(era, era, "era") \ macro(escape, escape, "escape") \ macro(eval, eval, "eval") \ macro(false, false_, "false") \ @@ -95,6 +99,7 @@ macro(forceInterpreter, forceInterpreter, "forceInterpreter") \ macro(forEach, forEach, "forEach") \ macro(format, format, "format") \ + macro(formatToParts, formatToParts, "formatToParts") \ macro(frame, frame, "frame") \ macro(from, from, "from") \ macro(gcCycleNumber, gcCycleNumber, "gcCycleNumber") \ @@ -109,6 +114,7 @@ macro(has, has, "has") \ macro(hasOwn, hasOwn, "hasOwn") \ macro(hasOwnProperty, hasOwnProperty, "hasOwnProperty") \ + macro(hour, hour, "hour") \ macro(ignoreCase, ignoreCase, "ignoreCase") \ macro(ignorePunctuation, ignorePunctuation, "ignorePunctuation") \ macro(index, index, "index") \ @@ -152,8 +158,10 @@ macro(minimumFractionDigits, minimumFractionDigits, "minimumFractionDigits") \ macro(minimumIntegerDigits, minimumIntegerDigits, "minimumIntegerDigits") \ macro(minimumSignificantDigits, minimumSignificantDigits, "minimumSignificantDigits") \ + macro(minute, minute, "minute") \ macro(missingArguments, missingArguments, "missingArguments") \ macro(module, module, "module") \ + macro(month, month, "month") \ macro(multiline, multiline, "multiline") \ macro(name, name, "name") \ macro(NaN, NaN, "NaN") \ @@ -200,7 +208,9 @@ macro(revoke, revoke, "revoke") \ macro(script, script, "script") \ macro(scripts, scripts, "scripts") \ + macro(second, second, "second") \ macro(sensitivity, sensitivity, "sensitivity") \ + macro(separator, separator, "separator") \ macro(set, set, "set") \ macro(shape, shape, "shape") \ macro(size, size, "size") \ @@ -221,6 +231,7 @@ macro(throw, throw_, "throw") \ macro(timestamp, timestamp, "timestamp") \ macro(timeZone, timeZone, "timeZone") \ + macro(timeZoneName, timeZoneName, "timeZoneName") \ macro(toGMTString, toGMTString, "toGMTString") \ macro(toISOString, toISOString, "toISOString") \ macro(toJSON, toJSON, "toJSON") \ @@ -229,6 +240,7 @@ macro(toString, toString, "toString") \ macro(toUTCString, toUTCString, "toUTCString") \ macro(true, true_, "true") \ + macro(type, type, "type") \ macro(unescape, unescape, "unescape") \ macro(uneval, uneval, "uneval") \ macro(unicode, unicode, "unicode") \ @@ -255,7 +267,9 @@ macro(void0, void0, "(void 0)") \ macro(watch, watch, "watch") \ macro(WeakSet_add, WeakSet_add, "WeakSet_add") \ + macro(weekday, weekday, "weekday") \ macro(writable, writable, "writable") \ + macro(year, year, "year") \ macro(yield, yield, "yield") \ macro(raw, raw, "raw") \ /* Type names must be contiguous and ordered; see js::TypeName. */ \