Add support for DateTimeFormat.formatToParts

Spec discussion:  https://github.com/tc39/ecma402/issues/30

It's in stage 4 and Firefox has already implemented it.

For now, it's added to HARMONY_IN_PROGRESS bucket behind
'--datetime-format-to-parts' flag.

BUG=v8:5244
TEST=intl/date-format/date-format-to-parts.js
TEST=test262/intl402/DateTimeFormat/prototype/formatToParts/*

Review-Url: https://codereview.chromium.org/2273953003
Cr-Commit-Position: refs/heads/master@{#39225}
This commit is contained in:
jshin 2016-09-06 15:56:52 -07:00 committed by Commit bot
parent 01cc19fa6f
commit a3db819c9e
13 changed files with 256 additions and 13 deletions

View File

@ -439,6 +439,7 @@ action("js2c_experimental") {
if (v8_enable_i18n_support) {
sources += [
"src/js/datetime-format-to-parts.js",
"src/js/icu-case-mapping.js",
"src/js/intl-extra.js",
]

View File

@ -210,7 +210,9 @@ class Genesis BASE_EMBEDDED {
HARMONY_INPROGRESS(DECLARE_FEATURE_INITIALIZATION)
HARMONY_STAGED(DECLARE_FEATURE_INITIALIZATION)
HARMONY_SHIPPING(DECLARE_FEATURE_INITIALIZATION)
#ifdef V8_I18N_SUPPORT
DECLARE_FEATURE_INITIALIZATION(intl_extra, "")
#endif
#undef DECLARE_FEATURE_INITIALIZATION
Handle<JSFunction> InstallArrayBuffer(Handle<JSObject> target,
@ -2209,7 +2211,9 @@ void Genesis::InitializeExperimentalGlobal() {
HARMONY_INPROGRESS(FEATURE_INITIALIZE_GLOBAL)
HARMONY_STAGED(FEATURE_INITIALIZE_GLOBAL)
HARMONY_SHIPPING(FEATURE_INITIALIZE_GLOBAL)
#ifdef V8_I18N_SUPPORT
FEATURE_INITIALIZE_GLOBAL(intl_extra, "")
#endif
#undef FEATURE_INITIALIZE_GLOBAL
}
@ -2771,6 +2775,7 @@ void Bootstrapper::ExportExperimentalFromRuntime(Isolate* isolate,
Handle<JSObject> container) {
HandleScope scope(isolate);
#ifdef V8_I18N_SUPPORT
#define INITIALIZE_FLAG(FLAG) \
{ \
Handle<String> name = \
@ -2782,6 +2787,7 @@ void Bootstrapper::ExportExperimentalFromRuntime(Isolate* isolate,
INITIALIZE_FLAG(FLAG_intl_extra)
#undef INITIALIZE_FLAG
#endif
}
@ -2794,13 +2800,14 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_regexp_lookbehind)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_regexp_named_captures)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_regexp_property)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_function_sent)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(intl_extra)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_explicit_tailcalls)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_tailcalls)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_restrictive_declarations)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_string_padding)
#ifdef V8_I18N_SUPPORT
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(datetime_format_to_parts)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(icu_case_mapping)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(intl_extra)
#endif
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_async_await)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_restrictive_generators)
@ -3381,7 +3388,6 @@ bool Genesis::InstallExperimentalNatives() {
static const char* harmony_regexp_named_captures_natives[] = {nullptr};
static const char* harmony_regexp_property_natives[] = {nullptr};
static const char* harmony_function_sent_natives[] = {nullptr};
static const char* intl_extra_natives[] = {"native intl-extra.js", nullptr};
static const char* harmony_object_values_entries_natives[] = {nullptr};
static const char* harmony_object_own_property_descriptors_natives[] = {
nullptr};
@ -3391,6 +3397,9 @@ bool Genesis::InstallExperimentalNatives() {
#ifdef V8_I18N_SUPPORT
static const char* icu_case_mapping_natives[] = {"native icu-case-mapping.js",
nullptr};
static const char* intl_extra_natives[] = {"native intl-extra.js", nullptr};
static const char* datetime_format_to_parts_natives[] = {
"native datetime-format-to-parts.js", nullptr};
#endif
static const char* harmony_async_await_natives[] = {
"native harmony-async-await.js", nullptr};
@ -3414,7 +3423,9 @@ bool Genesis::InstallExperimentalNatives() {
HARMONY_INPROGRESS(INSTALL_EXPERIMENTAL_NATIVES);
HARMONY_STAGED(INSTALL_EXPERIMENTAL_NATIVES);
HARMONY_SHIPPING(INSTALL_EXPERIMENTAL_NATIVES);
#ifdef V8_I18N_SUPPORT
INSTALL_EXPERIMENTAL_NATIVES(intl_extra, "");
#endif
#undef INSTALL_EXPERIMENTAL_NATIVES
}

View File

@ -180,16 +180,18 @@ DEFINE_BOOL(harmony, false, "enable all completed harmony features")
DEFINE_BOOL(harmony_shipping, true, "enable all shipped harmony features")
DEFINE_IMPLICATION(es_staging, harmony)
#ifdef V8_I18N_SUPPORT
DEFINE_BOOL(intl_extra, false, "additional V8 Intl functions")
// Removing extra Intl functions is shipped
DEFINE_NEG_VALUE_IMPLICATION(harmony_shipping, intl_extra, true)
#endif
// Activate on ClusterFuzz.
DEFINE_IMPLICATION(es_staging, harmony_regexp_lookbehind)
DEFINE_IMPLICATION(es_staging, move_object_start)
// Features that are still work in progress (behind individual flags).
#define HARMONY_INPROGRESS(V) \
#define HARMONY_INPROGRESS_BASE(V) \
V(harmony_array_prototype_values, "harmony Array.prototype.values") \
V(harmony_function_sent, "harmony function.sent") \
V(harmony_sharedarraybuffer, "harmony sharedarraybuffer") \
@ -204,6 +206,14 @@ DEFINE_IMPLICATION(es_staging, move_object_start)
V(harmony_trailing_commas, \
"harmony trailing commas in function parameter lists")
#ifdef V8_I18N_SUPPORT
#define HARMONY_INPROGRESS(V) \
HARMONY_INPROGRESS_BASE(V) \
V(datetime_format_to_parts, "Intl.DateTimeFormat.formatToParts")
#else
#define HARMONY_INPROGRESS(V) HARMONY_INPROGRESS_BASE(V)
#endif
// Features that are complete (but still behind --harmony/es-staging flag).
#define HARMONY_STAGED_BASE(V) \
V(harmony_regexp_lookbehind, "harmony regexp lookbehind") \

View File

@ -8,11 +8,11 @@
#define INTERNALIZED_STRING_LIST(V) \
V(anonymous_string, "anonymous") \
V(apply_string, "apply") \
V(assign_string, "assign") \
V(arguments_string, "arguments") \
V(Arguments_string, "Arguments") \
V(Array_string, "Array") \
V(arguments_to_string, "[object Arguments]") \
V(Array_string, "Array") \
V(assign_string, "assign") \
V(array_to_string, "[object Array]") \
V(boolean_to_string, "[object Boolean]") \
V(date_to_string, "[object Date]") \
@ -48,6 +48,8 @@
V(construct_string, "construct") \
V(create_string, "create") \
V(Date_string, "Date") \
V(dayperiod_string, "dayperiod") \
V(day_string, "day") \
V(default_string, "default") \
V(defineProperty_string, "defineProperty") \
V(deleteProperty_string, "deleteProperty") \
@ -57,6 +59,7 @@
V(dot_string, ".") \
V(entries_string, "entries") \
V(enumerable_string, "enumerable") \
V(era_string, "era") \
V(Error_string, "Error") \
V(eval_string, "eval") \
V(EvalError_string, "EvalError") \
@ -74,6 +77,8 @@
V(get_string, "get") \
V(global_string, "global") \
V(has_string, "has") \
V(hour_string, "hour") \
V(ignoreCase_string, "ignoreCase") \
V(illegal_access_string, "illegal access") \
V(illegal_argument_string, "illegal argument") \
V(index_string, "index") \
@ -92,10 +97,14 @@
V(last_index_string, "lastIndex") \
V(length_string, "length") \
V(line_string, "line") \
V(literal_string, "literal") \
V(Map_string, "Map") \
V(message_string, "message") \
V(minus_infinity_string, "-Infinity") \
V(minus_zero_string, "-0") \
V(minute_string, "minute") \
V(month_string, "month") \
V(multiline_string, "multiline") \
V(name_string, "name") \
V(nan_string, "NaN") \
V(next_string, "next") \
@ -120,6 +129,7 @@
V(ReferenceError_string, "ReferenceError") \
V(RegExp_string, "RegExp") \
V(script_string, "script") \
V(second_string, "second") \
V(setPrototypeOf_string, "setPrototypeOf") \
V(set_string, "set") \
V(Set_string, "Set") \
@ -138,10 +148,12 @@
V(this_string, "this") \
V(throw_string, "throw") \
V(timed_out, "timed-out") \
V(timeZoneName_string, "timeZoneName") \
V(toJSON_string, "toJSON") \
V(toString_string, "toString") \
V(true_string, "true") \
V(TypeError_string, "TypeError") \
V(type_string, "type") \
V(uint16x8_string, "uint16x8") \
V(Uint16x8_string, "Uint16x8") \
V(uint32x4_string, "uint32x4") \
@ -156,7 +168,9 @@
V(value_string, "value") \
V(WeakMap_string, "WeakMap") \
V(WeakSet_string, "WeakSet") \
V(writable_string, "writable")
V(weekday_string, "weekday") \
V(writable_string, "writable") \
V(year_string, "year")
#define PRIVATE_SYMBOL_LIST(V) \
V(array_iteration_kind_symbol) \

View File

@ -5,6 +5,8 @@
#include "src/i18n.h"
#include <memory>
#include "src/api.h"
#include "src/factory.h"
#include "src/isolate.h"
@ -115,13 +117,11 @@ icu::SimpleDateFormat* CreateICUDateFormat(
icu::SimpleDateFormat* date_format = NULL;
icu::UnicodeString skeleton;
if (ExtractStringSetting(isolate, options, "skeleton", &skeleton)) {
icu::DateTimePatternGenerator* generator =
icu::DateTimePatternGenerator::createInstance(icu_locale, status);
std::unique_ptr<icu::DateTimePatternGenerator> generator(
icu::DateTimePatternGenerator::createInstance(icu_locale, status));
icu::UnicodeString pattern;
if (U_SUCCESS(status)) {
if (U_SUCCESS(status))
pattern = generator->getBestPattern(skeleton, status);
delete generator;
}
date_format = new icu::SimpleDateFormat(pattern, icu_locale, status);
if (U_SUCCESS(status)) {
@ -132,7 +132,7 @@ icu::SimpleDateFormat* CreateICUDateFormat(
if (U_FAILURE(status)) {
delete calendar;
delete date_format;
date_format = NULL;
date_format = nullptr;
}
return date_format;

View File

@ -0,0 +1,16 @@
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
(function(global, utils) {
"use strict";
%CheckIsBootstrapping();
var GlobalIntl = global.Intl;
var FormatDateToParts = utils.ImportNow("FormatDateToParts");
utils.InstallFunctions(GlobalIntl.DateTimeFormat.prototype, DONT_ENUM, [
'formatToParts', FormatDateToParts
]);
})

View File

@ -1797,6 +1797,29 @@ function formatDate(formatter, dateValue) {
new GlobalDate(dateMs));
}
function FormatDateToParts(dateValue) {
if (!IS_UNDEFINED(new.target)) {
throw %make_type_error(kOrdinaryFunctionCalledAsConstructor);
}
CHECK_OBJECT_COERCIBLE(this, "Intl.DateTimeFormat.prototype.formatToParts");
if (!IS_OBJECT(this)) {
throw %make_type_error(kCalledOnNonObject, this);
}
var dateMs;
if (IS_UNDEFINED(dateValue)) {
dateMs = %DateCurrentTime();
} else {
dateMs = TO_NUMBER(dateValue);
}
if (!NUMBER_IS_FINITE(dateMs)) throw %make_range_error(kDateRange);
return %InternalDateFormatToParts(
%GetImplFromInitializedIntlObject(this), new GlobalDate(dateMs));
}
%FunctionSetLength(FormatDateToParts, 0);
/**
* Returns a Date object representing the result of calling ToString(value)
@ -2291,8 +2314,11 @@ OverrideFunction(GlobalDate.prototype, 'toLocaleTimeString', function() {
}
);
%FunctionRemovePrototype(FormatDateToParts);
utils.Export(function(to) {
to.AddBoundMethod = AddBoundMethod;
to.FormatDateToParts = FormatDateToParts;
to.IntlParseDate = IntlParseDate;
to.IntlParseNumber = IntlParseNumber;
});

View File

@ -185,6 +185,7 @@ function PostNatives(utils) {
"ArrayToString",
"AsyncFunctionNext",
"AsyncFunctionThrow",
"FormatDateToParts",
"GetIterator",
"GetMethod",
"GlobalPromise",

View File

@ -25,6 +25,8 @@
#include "unicode/decimfmt.h"
#include "unicode/dtfmtsym.h"
#include "unicode/dtptngen.h"
#include "unicode/fieldpos.h"
#include "unicode/fpositer.h"
#include "unicode/locid.h"
#include "unicode/normalizer2.h"
#include "unicode/numfmt.h"
@ -322,7 +324,7 @@ RUNTIME_FUNCTION(Runtime_GetImplFromInitializedIntlObject) {
Handle<Symbol> marker = isolate->factory()->intl_impl_object_symbol();
Handle<Object> impl = JSReceiver::GetDataProperty(obj, marker);
if (impl->IsTheHole(isolate)) {
if (!impl->IsJSObject()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kNotIntlObject, obj));
}
@ -393,6 +395,138 @@ RUNTIME_FUNCTION(Runtime_InternalDateFormat) {
result.length())));
}
namespace {
// The list comes from third_party/icu/source/i18n/unicode/udat.h.
// They're mapped to DateTimeFormat components listed at
// https://tc39.github.io/ecma402/#sec-datetimeformat-abstracts .
Handle<String> IcuDateFieldIdToDateType(int32_t field_id, Isolate* isolate) {
switch (field_id) {
case -1:
return isolate->factory()->literal_string();
case UDAT_YEAR_FIELD:
case UDAT_EXTENDED_YEAR_FIELD:
case UDAT_YEAR_NAME_FIELD:
return isolate->factory()->year_string();
case UDAT_MONTH_FIELD:
case UDAT_STANDALONE_MONTH_FIELD:
return isolate->factory()->month_string();
case UDAT_DATE_FIELD:
return isolate->factory()->day_string();
case UDAT_HOUR_OF_DAY1_FIELD:
case UDAT_HOUR_OF_DAY0_FIELD:
case UDAT_HOUR1_FIELD:
case UDAT_HOUR0_FIELD:
return isolate->factory()->hour_string();
case UDAT_MINUTE_FIELD:
return isolate->factory()->minute_string();
case UDAT_SECOND_FIELD:
return isolate->factory()->second_string();
case UDAT_DAY_OF_WEEK_FIELD:
case UDAT_DOW_LOCAL_FIELD:
case UDAT_STANDALONE_DAY_FIELD:
return isolate->factory()->weekday_string();
case UDAT_AM_PM_FIELD:
return isolate->factory()->dayperiod_string();
case UDAT_TIMEZONE_FIELD:
case UDAT_TIMEZONE_RFC_FIELD:
case UDAT_TIMEZONE_GENERIC_FIELD:
case UDAT_TIMEZONE_SPECIAL_FIELD:
case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD:
case UDAT_TIMEZONE_ISO_FIELD:
case UDAT_TIMEZONE_ISO_LOCAL_FIELD:
return isolate->factory()->timeZoneName_string();
case UDAT_ERA_FIELD:
return isolate->factory()->era_string();
default:
// Other UDAT_*_FIELD's cannot show up because there is no way to specify
// them via options of Intl.DateTimeFormat.
UNREACHABLE();
// To prevent MSVC from issuing C4715 warning.
return Handle<String>();
}
}
bool AddElement(Handle<JSArray> array, int index, int32_t field_id,
const icu::UnicodeString& formatted, int32_t begin, int32_t end,
Isolate* isolate) {
HandleScope scope(isolate);
Factory* factory = isolate->factory();
Handle<JSObject> element = factory->NewJSObject(isolate->object_function());
Handle<String> value = IcuDateFieldIdToDateType(field_id, isolate);
JSObject::AddProperty(element, factory->type_string(), value, NONE);
icu::UnicodeString field(formatted.tempSubStringBetween(begin, end));
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, value, factory->NewStringFromTwoByte(Vector<const uint16_t>(
reinterpret_cast<const uint16_t*>(field.getBuffer()),
field.length())),
false);
JSObject::AddProperty(element, factory->value_string(), value, NONE);
RETURN_ON_EXCEPTION_VALUE(
isolate, JSObject::AddDataElement(array, index, element, NONE), false);
return true;
}
} // namespace
RUNTIME_FUNCTION(Runtime_InternalDateFormatToParts) {
HandleScope scope(isolate);
Factory* factory = isolate->factory();
DCHECK(args.length() == 2);
CONVERT_ARG_HANDLE_CHECKED(JSObject, date_format_holder, 0);
CONVERT_ARG_HANDLE_CHECKED(JSDate, date, 1);
Handle<Object> value;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, value, Object::ToNumber(date));
icu::SimpleDateFormat* date_format =
DateFormat::UnpackDateFormat(isolate, date_format_holder);
if (!date_format) return isolate->ThrowIllegalOperation();
icu::UnicodeString formatted;
icu::FieldPositionIterator fp_iter;
icu::FieldPosition fp;
UErrorCode status = U_ZERO_ERROR;
date_format->format(value->Number(), formatted, &fp_iter, status);
if (U_FAILURE(status)) return isolate->heap()->undefined_value();
Handle<JSArray> result = factory->NewJSArray(0);
int32_t length = formatted.length();
if (length == 0) return *result;
int index = 0;
int32_t previous_end_pos = 0;
while (fp_iter.next(fp)) {
int32_t begin_pos = fp.getBeginIndex();
int32_t end_pos = fp.getEndIndex();
if (previous_end_pos < begin_pos) {
if (!AddElement(result, index, -1, formatted, previous_end_pos, begin_pos,
isolate)) {
return isolate->heap()->undefined_value();
}
++index;
}
if (!AddElement(result, index, fp.getField(), formatted, begin_pos, end_pos,
isolate)) {
return isolate->heap()->undefined_value();
}
previous_end_pos = end_pos;
++index;
}
if (previous_end_pos < length) {
if (!AddElement(result, index, -1, formatted, previous_end_pos, length,
isolate)) {
return isolate->heap()->undefined_value();
}
}
JSObject::ValidateElements(result);
return *result;
}
RUNTIME_FUNCTION(Runtime_InternalDateParse) {
HandleScope scope(isolate);

View File

@ -260,6 +260,7 @@ namespace internal {
F(GetImplFromInitializedIntlObject, 1, 1) \
F(CreateDateTimeFormat, 3, 1) \
F(InternalDateFormat, 2, 1) \
F(InternalDateFormatToParts, 2, 1) \
F(InternalDateParse, 2, 1) \
F(CreateNumberFormat, 3, 1) \
F(InternalNumberFormat, 2, 1) \

View File

@ -2213,6 +2213,7 @@
['v8_enable_i18n_support==1', {
'library_files': ['js/i18n.js'],
'experimental_library_files': [
'js/datetime-format-to-parts.js',
'js/icu-case-mapping.js',
'js/intl-extra.js',
],

View File

@ -27,6 +27,14 @@
// Some methods are taken from v8/test/mjsunit/mjsunit.js
function classOf(object) {
// Argument must not be null or undefined.
var string = Object.prototype.toString.call(object);
// String has format [object <ClassName>].
return string.substring(8, string.length - 1);
}
/**
* Compares two objects for key/value equality.
* Returns true if they are equal, false otherwise.

View File

@ -0,0 +1,20 @@
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --datetime-format-to-parts
var d = new Date(2016, 11, 15, 14, 10, 34);
var df = Intl.DateTimeFormat("ja",
{hour: 'numeric', minute: 'numeric', second: 'numeric', year: 'numeric',
month: 'numeric', day: 'numeric', timeZoneName: 'short', era: 'short'});
var formattedParts = df.formatToParts(d);
var formattedReconstructedFromParts = formattedParts.map((part) => part.value)
.reduce((accumulated, part) => accumulated + part);
assertEquals(df.format(d), formattedReconstructedFromParts);
// 西暦2016年11月15日 14:10:34 GMT-7
assertEquals(["era", "year", "literal", "month", "literal", "day", "literal",
"hour", "literal", "minute", "literal", "second", "literal",
"timeZoneName"], formattedParts.map((part) => part.type));