mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-20 00:20:37 +00:00
Bug 1216150 - Implement ECMA 402 DateTimeFormat formatToParts
This commit is contained in:
parent
1ee2112c8f
commit
5c9465b248
@ -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);
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "builtin/Intl.h"
|
||||
|
||||
#include "mozilla/Range.h"
|
||||
#include "mozilla/ScopeExit.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
@ -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<GlobalObject*>
|
||||
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<GlobalObject*>
|
||||
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<char16_t, INITIAL_CHAR_BUFFER_SIZE> 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<CanGC>(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<PlainObject>(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<UDateFormatField>(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);
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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())
|
||||
|
176
js/src/tests/Intl/DateTimeFormat/formatToParts.js
Normal file
176
js/src/tests/Intl/DateTimeFormat/formatToParts.js
Normal file
@ -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');
|
@ -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. */ \
|
||||
|
Loading…
x
Reference in New Issue
Block a user