[Intl] Implement Intl.DateTimeFormat.prototype.formatRangeToParts
Design Doc: https://goo.gl/PGUQ1d
Use template to share code between formatRange and formatRangeToParts
Lazy crate DateIntervalFormat inside formatRange/formatRangeToParts to
reduce performance impact.
Bug: v8:7729
Change-Id: I130748a5ff7ca11235e6608195d365e58d440580
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1556573
Commit-Queue: Frank Tang <ftang@chromium.org>
Reviewed-by: Sathya Gunasekaran <gsathya@chromium.org>
Cr-Commit-Position: refs/heads/master@{#60930}
diff --git a/src/heap-symbols.h b/src/heap-symbols.h
index ccb3ac8..478c896 100644
--- a/src/heap-symbols.h
+++ b/src/heap-symbols.h
@@ -19,6 +19,7 @@
V(_, day_string, "day") \
V(_, dayPeriod_string, "dayPeriod") \
V(_, decimal_string, "decimal") \
+ V(_, endRange_string, "endRange") \
V(_, era_string, "era") \
V(_, first_string, "first") \
V(_, format_string, "format") \
@@ -72,6 +73,8 @@
V(_, SegmentIterator_string, "Segment Iterator") \
V(_, sensitivity_string, "sensitivity") \
V(_, sep_string, "sep") \
+ V(_, shared_string, "shared") \
+ V(_, startRange_string, "startRange") \
V(_, strict_string, "strict") \
V(_, style_string, "style") \
V(_, term_string, "term") \
diff --git a/src/objects/intl-objects.cc b/src/objects/intl-objects.cc
index 8a43f36..895dc4c 100644
--- a/src/objects/intl-objects.cc
+++ b/src/objects/intl-objects.cc
@@ -31,6 +31,7 @@
#include "unicode/coll.h"
#include "unicode/datefmt.h"
#include "unicode/decimfmt.h"
+#include "unicode/formattedvalue.h"
#include "unicode/locid.h"
#include "unicode/normalizer2.h"
#include "unicode/numfmt.h"
@@ -1934,5 +1935,16 @@
}
}
+// A helper function to convert the FormattedValue for several Intl objects.
+MaybeHandle<String> Intl::FormattedToString(
+ Isolate* isolate, const icu::FormattedValue& formatted) {
+ UErrorCode status = U_ZERO_ERROR;
+ icu::UnicodeString result = formatted.toString(status);
+ if (U_FAILURE(status)) {
+ THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), String);
+ }
+ return Intl::ToString(isolate, result);
+}
+
} // namespace internal
} // namespace v8
diff --git a/src/objects/intl-objects.h b/src/objects/intl-objects.h
index 5adb6fa..275c0be 100644
--- a/src/objects/intl-objects.h
+++ b/src/objects/intl-objects.h
@@ -26,6 +26,7 @@
class BreakIterator;
class Collator;
class DecimalFormat;
+class FormattedValue;
class SimpleDateFormat;
class UnicodeString;
}
@@ -186,6 +187,10 @@
Isolate* isolate, const icu::UnicodeString& string, int32_t begin,
int32_t end);
+ // Helper function to convert a FormattedValue to String
+ V8_WARN_UNUSED_RESULT static MaybeHandle<String> FormattedToString(
+ Isolate* isolate, const icu::FormattedValue& formatted);
+
// Helper function to convert number field id to type string.
static Handle<String> NumberFieldToType(Isolate* isolate,
Handle<Object> numeric_obj,
diff --git a/src/objects/js-date-time-format.cc b/src/objects/js-date-time-format.cc
index eda95f8..5b117d5 100644
--- a/src/objects/js-date-time-format.cc
+++ b/src/objects/js-date-time-format.cc
@@ -959,14 +959,41 @@
cache.Pointer()->Create(icu_locale, skeleton, generator));
}
-std::unique_ptr<icu::DateIntervalFormat> CreateICUDateIntervalFormat(
- const icu::Locale& icu_locale, const icu::UnicodeString& skeleton) {
+icu::UnicodeString SkeletonFromDateFormat(
+ const icu::SimpleDateFormat& icu_date_format) {
+ icu::UnicodeString pattern;
+ pattern = icu_date_format.toPattern(pattern);
+
+ UErrorCode status = U_ZERO_ERROR;
+ icu::UnicodeString skeleton =
+ icu::DateTimePatternGenerator::staticGetSkeleton(pattern, status);
+ CHECK(U_SUCCESS(status));
+ return skeleton;
+}
+
+icu::DateIntervalFormat* LazyCreateDateIntervalFormat(
+ Isolate* isolate, Handle<JSDateTimeFormat> date_time_format) {
+ Managed<icu::DateIntervalFormat> managed_format =
+ date_time_format->icu_date_interval_format();
+ if (managed_format->get()) {
+ return managed_format->raw();
+ }
+ icu::SimpleDateFormat* icu_simple_date_format =
+ date_time_format->icu_simple_date_format()->raw();
UErrorCode status = U_ZERO_ERROR;
std::unique_ptr<icu::DateIntervalFormat> date_interval_format(
- icu::DateIntervalFormat::createInstance(skeleton, icu_locale, status));
- if (U_FAILURE(status)) return std::unique_ptr<icu::DateIntervalFormat>();
- CHECK_NOT_NULL(date_interval_format.get());
- return date_interval_format;
+ icu::DateIntervalFormat::createInstance(
+ SkeletonFromDateFormat(*icu_simple_date_format),
+ *(date_time_format->icu_locale()->raw()), status));
+ if (U_FAILURE(status)) {
+ return nullptr;
+ }
+ date_interval_format->setTimeZone(icu_simple_date_format->getTimeZone());
+ Handle<Managed<icu::DateIntervalFormat>> managed_interval_format =
+ Managed<icu::DateIntervalFormat>::FromUniquePtr(
+ isolate, 0, std::move(date_interval_format));
+ date_time_format->set_icu_date_interval_format(*managed_interval_format);
+ return (*managed_interval_format)->raw();
}
Intl::HourCycle HourCycleFromPattern(const icu::UnicodeString pattern) {
@@ -1103,18 +1130,6 @@
generator);
}
-icu::UnicodeString SkeletonFromDateFormat(
- const icu::SimpleDateFormat& icu_date_format) {
- icu::UnicodeString pattern;
- pattern = icu_date_format.toPattern(pattern);
-
- UErrorCode status = U_ZERO_ERROR;
- icu::UnicodeString skeleton =
- icu::DateTimePatternGenerator::staticGetSkeleton(pattern, status);
- CHECK(U_SUCCESS(status));
- return skeleton;
-}
-
class DateTimePatternGeneratorCache {
public:
// Return a clone copy that the caller have to free.
@@ -1297,7 +1312,6 @@
DateTimeStyle date_style = DateTimeStyle::kUndefined;
DateTimeStyle time_style = DateTimeStyle::kUndefined;
std::unique_ptr<icu::SimpleDateFormat> icu_date_format;
- std::unique_ptr<icu::DateIntervalFormat> icu_date_interval_format;
if (FLAG_harmony_intl_datetime_style) {
// 28. Let dateStyle be ? GetOption(options, "dateStyle", "string", «
@@ -1340,10 +1354,6 @@
time_style != DateTimeStyle::kUndefined) {
icu_date_format = DateTimeStylePattern(date_style, time_style, icu_locale,
hc, *generator);
- if (FLAG_harmony_intl_date_format_range) {
- icu_date_interval_format = CreateICUDateIntervalFormat(
- icu_locale, SkeletonFromDateFormat(*icu_date_format));
- }
}
}
// 33. Else,
@@ -1397,10 +1407,6 @@
FATAL("Failed to create ICU date format, are ICU data files missing?");
}
}
- if (FLAG_harmony_intl_date_format_range) {
- icu_date_interval_format =
- CreateICUDateIntervalFormat(icu_locale, skeleton_ustr);
- }
// g. If dateTimeFormat.[[Hour]] is not undefined, then
if (!has_hour_option) {
@@ -1449,12 +1455,10 @@
Managed<icu::SimpleDateFormat>::FromUniquePtr(isolate, 0,
std::move(icu_date_format));
date_time_format->set_icu_simple_date_format(*managed_format);
- if (FLAG_harmony_intl_date_format_range) {
- Handle<Managed<icu::DateIntervalFormat>> managed_interval_format =
- Managed<icu::DateIntervalFormat>::FromUniquePtr(
- isolate, 0, std::move(icu_date_interval_format));
- date_time_format->set_icu_date_interval_format(*managed_interval_format);
- }
+
+ Handle<Managed<icu::DateIntervalFormat>> managed_interval_format =
+ Managed<icu::DateIntervalFormat>::FromRawPtr(isolate, 0, nullptr);
+ date_time_format->set_icu_date_interval_format(*managed_interval_format);
return date_time_format;
}
@@ -1591,75 +1595,176 @@
}
}
-MaybeHandle<String> JSDateTimeFormat::FormatRange(
- Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, double x,
- double y) {
- // TODO(ftang): Merge the following with FormatRangeToParts after
- // the landing of ICU64 to make it cleaner.
+enum Source { kShared, kStartRange, kEndRange };
+namespace {
+
+class SourceTracker {
+ public:
+ SourceTracker() { start_[0] = start_[1] = limit_[0] = limit_[1] = 0; }
+ void Add(int32_t field, int32_t start, int32_t limit) {
+ CHECK_LT(field, 2);
+ start_[field] = start;
+ limit_[field] = limit;
+ }
+
+ Source GetSource(int32_t start, int32_t limit) const {
+ Source source = Source::kShared;
+ if (FieldContains(0, start, limit)) {
+ source = Source::kStartRange;
+ } else if (FieldContains(1, start, limit)) {
+ source = Source::kEndRange;
+ }
+ return source;
+ }
+
+ private:
+ int32_t start_[2];
+ int32_t limit_[2];
+
+ bool FieldContains(int32_t field, int32_t start, int32_t limit) const {
+ CHECK_LT(field, 2);
+ return (start_[field] <= start) && (start <= limit_[field]) &&
+ (start_[field] <= limit) && (limit <= limit_[field]);
+ }
+};
+
+Handle<String> SourceString(Isolate* isolate, Source source) {
+ switch (source) {
+ case Source::kShared:
+ return ReadOnlyRoots(isolate).shared_string_handle();
+ case Source::kStartRange:
+ return ReadOnlyRoots(isolate).startRange_string_handle();
+ case Source::kEndRange:
+ return ReadOnlyRoots(isolate).endRange_string_handle();
+ UNREACHABLE();
+ }
+}
+
+Maybe<bool> AddPartForFormatRange(Isolate* isolate, Handle<JSArray> array,
+ const icu::UnicodeString& string,
+ int32_t index, int32_t field, int32_t start,
+ int32_t end, const SourceTracker& tracker) {
+ Handle<String> substring;
+ ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, substring,
+ Intl::ToString(isolate, string, start, end),
+ Nothing<bool>());
+ Intl::AddElement(isolate, array, index,
+ IcuDateFieldIdToDateType(field, isolate), substring,
+ isolate->factory()->source_string(),
+ SourceString(isolate, tracker.GetSource(start, end)));
+ return Just(true);
+}
+
+// A helper function to convert the FormattedDateInterval to a
+// MaybeHandle<JSArray> for the implementation of formatRangeToParts.
+MaybeHandle<JSArray> FormattedDateIntervalToJSArray(
+ Isolate* isolate, const icu::FormattedValue& formatted) {
+ UErrorCode status = U_ZERO_ERROR;
+ icu::UnicodeString result = formatted.toString(status);
+
+ Factory* factory = isolate->factory();
+ Handle<JSArray> array = factory->NewJSArray(0);
+ icu::ConstrainedFieldPosition cfpos;
+ int index = 0;
+ int32_t previous_end_pos = 0;
+ SourceTracker tracker;
+ while (formatted.nextPosition(cfpos, status)) {
+ int32_t category = cfpos.getCategory();
+ int32_t field = cfpos.getField();
+ int32_t start = cfpos.getStart();
+ int32_t limit = cfpos.getLimit();
+
+ if (category == UFIELD_CATEGORY_DATE_INTERVAL_SPAN) {
+ CHECK_LE(field, 2);
+ tracker.Add(field, start, limit);
+ } else {
+ CHECK(category == UFIELD_CATEGORY_DATE);
+ if (start > previous_end_pos) {
+ // Add "literal" from the previous end position to the start if
+ // necessary.
+ Maybe<bool> maybe_added =
+ AddPartForFormatRange(isolate, array, result, index, -1,
+ previous_end_pos, start, tracker);
+ MAYBE_RETURN(maybe_added, Handle<JSArray>());
+ previous_end_pos = start;
+ index++;
+ }
+ Maybe<bool> maybe_added = AddPartForFormatRange(
+ isolate, array, result, index, field, start, limit, tracker);
+ MAYBE_RETURN(maybe_added, Handle<JSArray>());
+ previous_end_pos = limit;
+ ++index;
+ }
+ }
+ int32_t end = result.length();
+ // Add "literal" in the end if necessary.
+ if (end > previous_end_pos) {
+ Maybe<bool> maybe_added = AddPartForFormatRange(
+ isolate, array, result, index, -1, previous_end_pos, end, tracker);
+ MAYBE_RETURN(maybe_added, Handle<JSArray>());
+ }
+
+ if (U_FAILURE(status)) {
+ THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), JSArray);
+ }
+
+ JSObject::ValidateElements(*array);
+ return array;
+}
+
+// The shared code between formatRange and formatRangeToParts
+template <typename T>
+MaybeHandle<T> FormatRangeCommon(
+ Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, double x,
+ double y,
+ MaybeHandle<T> (*formatToResult)(Isolate*, const icu::FormattedValue&)) {
// #sec-partitiondatetimerangepattern
// 1. Let x be TimeClip(x).
x = DateCache::TimeClip(x);
// 2. If x is NaN, throw a RangeError exception.
if (std::isnan(x)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue),
- String);
+ T);
}
// 3. Let y be TimeClip(y).
y = DateCache::TimeClip(y);
// 4. If y is NaN, throw a RangeError exception.
if (std::isnan(y)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue),
- String);
+ T);
}
-
- icu::DateIntervalFormat* date_interval_format =
- date_time_format->icu_date_interval_format()->raw();
- CHECK_NOT_NULL(date_interval_format);
icu::DateInterval interval(x, y);
- icu::UnicodeString result;
- icu::FieldPosition fpos;
- UErrorCode status = U_ZERO_ERROR;
- date_interval_format->format(&interval, result, fpos, status);
- CHECK(U_SUCCESS(status));
+ icu::DateIntervalFormat* format =
+ LazyCreateDateIntervalFormat(isolate, date_time_format);
+ if (format == nullptr) {
+ THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), T);
+ }
- return Intl::ToString(isolate, result);
+ UErrorCode status = U_ZERO_ERROR;
+ icu::FormattedDateInterval formatted =
+ format->formatToValue(interval, status);
+ if (U_FAILURE(status)) {
+ THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), T);
+ }
+ return formatToResult(isolate, formatted);
+}
+
+} // namespace
+
+MaybeHandle<String> JSDateTimeFormat::FormatRange(
+ Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, double x,
+ double y) {
+ return FormatRangeCommon<String>(isolate, date_time_format, x, y,
+ Intl::FormattedToString);
}
MaybeHandle<JSArray> JSDateTimeFormat::FormatRangeToParts(
Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, double x,
double y) {
- // TODO(ftang): Merge the following with FormatRangeToParts after
- // the landing of ICU64 to make it cleaner.
-
- // #sec-partitiondatetimerangepattern
- // 1. Let x be TimeClip(x).
- x = DateCache::TimeClip(x);
- // 2. If x is NaN, throw a RangeError exception.
- if (std::isnan(x)) {
- THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue),
- JSArray);
- }
- // 3. Let y be TimeClip(y).
- y = DateCache::TimeClip(y);
- // 4. If y is NaN, throw a RangeError exception.
- if (std::isnan(y)) {
- THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue),
- JSArray);
- }
-
- icu::DateIntervalFormat* date_interval_format =
- date_time_format->icu_date_interval_format()->raw();
- CHECK_NOT_NULL(date_interval_format);
- Factory* factory = isolate->factory();
- Handle<JSArray> result = factory->NewJSArray(0);
-
- // TODO(ftang) To be implemented after ICU64 landed that support
- // DateIntervalFormat::formatToValue() and FormattedDateInterval.
-
- JSObject::ValidateElements(*result);
- return result;
+ return FormatRangeCommon<JSArray>(isolate, date_time_format, x, y,
+ FormattedDateIntervalToJSArray);
}
} // namespace internal
diff --git a/src/objects/js-list-format.cc b/src/objects/js-list-format.cc
index c432940..ad5ee9b 100644
--- a/src/objects/js-list-format.cc
+++ b/src/objects/js-list-format.cc
@@ -296,7 +296,7 @@
template <typename T>
MaybeHandle<T> FormatListCommon(
Isolate* isolate, Handle<JSListFormat> format, Handle<JSArray> list,
- MaybeHandle<T> (*formatToResult)(Isolate*, const icu::FormattedList&)) {
+ MaybeHandle<T> (*formatToResult)(Isolate*, const icu::FormattedValue&)) {
DCHECK(!list->IsUndefined());
// ecma402 #sec-createpartsfromlist
// 2. If list contains any element value such that Type(value) is not String,
@@ -318,18 +318,6 @@
return formatToResult(isolate, formatted);
}
-// A helper function to convert the FormattedList to a
-// MaybeHandle<String> for the implementation of format.
-MaybeHandle<String> FormattedToString(Isolate* isolate,
- const icu::FormattedList& formatted) {
- UErrorCode status = U_ZERO_ERROR;
- icu::UnicodeString result = formatted.toString(status);
- if (U_FAILURE(status)) {
- THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), String);
- }
- return Intl::ToString(isolate, result);
-}
-
Handle<String> IcuFieldIdToType(Isolate* isolate, int32_t field_id) {
switch (field_id) {
case ULISTFMT_LITERAL_FIELD:
@@ -345,8 +333,8 @@
// A helper function to convert the FormattedList to a
// MaybeHandle<JSArray> for the implementation of formatToParts.
-MaybeHandle<JSArray> FormattedToJSArray(Isolate* isolate,
- const icu::FormattedList& formatted) {
+MaybeHandle<JSArray> FormattedListToJSArray(
+ Isolate* isolate, const icu::FormattedValue& formatted) {
Handle<JSArray> array = isolate->factory()->NewJSArray(0);
icu::ConstrainedFieldPosition cfpos;
cfpos.constrainCategory(UFIELD_CATEGORY_LIST);
@@ -375,13 +363,15 @@
MaybeHandle<String> JSListFormat::FormatList(Isolate* isolate,
Handle<JSListFormat> format,
Handle<JSArray> list) {
- return FormatListCommon<String>(isolate, format, list, FormattedToString);
+ return FormatListCommon<String>(isolate, format, list,
+ Intl::FormattedToString);
}
// ecma42 #sec-formatlisttoparts
MaybeHandle<JSArray> JSListFormat::FormatListToParts(
Isolate* isolate, Handle<JSListFormat> format, Handle<JSArray> list) {
- return FormatListCommon<JSArray>(isolate, format, list, FormattedToJSArray);
+ return FormatListCommon<JSArray>(isolate, format, list,
+ FormattedListToJSArray);
}
const std::set<std::string>& JSListFormat::GetAvailableLocales() {
diff --git a/test/intl/date-format/en-format-range-to-parts.js b/test/intl/date-format/en-format-range-to-parts.js
new file mode 100644
index 0000000..c242181
--- /dev/null
+++ b/test/intl/date-format/en-format-range-to-parts.js
@@ -0,0 +1,49 @@
+// Copyright 2019 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: --harmony-intl-date-format-range
+
+const date1 = new Date("2019-01-03T03:20");
+const date2 = new Date("2019-01-05T19:33");
+const date3 = new Date("2019-01-05T22:57");
+
+// value: "Jan 3 – 5, 2019"
+// source: hhhhShhhEhhhhhh
+// type: mmmldllldllyyyy
+// h: Shared, S: startRange, E: endRange
+// m: month, l: literal, d: day, y: year
+const expected1 = [
+ {type: "month", value: "Jan", source: "shared"},
+ {type: "literal", value: " ", source: "shared"},
+ {type: "day", value: "3", source: "startRange"},
+ {type: "literal", value: " – ", source: "shared"},
+ {type: "day", value: "5", source: "endRange"},
+ {type: "literal", value: ", ", source: "shared"},
+ {type: "year", value: "2019", source: "shared"}
+];
+
+var dtf = new Intl.DateTimeFormat(["en"], {year: "numeric", month: "short", day: "numeric"});
+const ret1 = dtf.formatRangeToParts(date1, date2);
+assertEquals(expected1, ret1);
+
+// value: "Jan 5, 7 – 10 PM"
+// source: hhhhhhhShhhEEhhh
+// type: mmmldlldlllhhlpp
+// h: Shared, S: startRange, E: endRange
+// m: month, l: literal, d: day, h: hour, p: dayPeriod
+
+const expected2 = [
+ {type: "month", value: "Jan", source: "shared"},
+ {type: "literal", value: " ", source: "shared"},
+ {type: "day", value: "5", source: "shared"},
+ {type: "literal", value: ", ", source: "shared"},
+ {type: "hour", value: "7", source: "startRange"},
+ {type: "literal", value: " – ", source: "shared"},
+ {type: "hour", value: "10", source: "endRange"},
+ {type: "literal", value: " ", source: "shared"},
+ {type: "dayPeriod", value: "PM", source: "shared"}
+];
+dtf = new Intl.DateTimeFormat(["en"], {month: "short", day: "numeric", hour: "numeric"});
+const ret2 = dtf.formatRangeToParts(date2, date3);
+assertEquals(expected2, ret2);
diff --git a/test/intl/date-format/format-range-to-parts.js b/test/intl/date-format/format-range-to-parts.js
index 472ec27..b2eac17 100644
--- a/test/intl/date-format/format-range-to-parts.js
+++ b/test/intl/date-format/format-range-to-parts.js
@@ -11,8 +11,10 @@
assertTrue(descriptor.configurable);
const date1 = new Date("2019-1-3");
-const date2 = new Date("2019-3-4");
-const dtf = new Intl.DateTimeFormat();
+const date2 = new Date("2019-1-5");
+const date3 = new Date("2019-3-4");
+const date4 = new Date("2020-3-4");
+let dtf = new Intl.DateTimeFormat();
assertThrows(() => dtf.formatRangeToParts(), RangeError);
assertThrows(() => dtf.formatRangeToParts(date1), RangeError);
assertThrows(() => dtf.formatRangeToParts(undefined, date2), RangeError);
@@ -22,3 +24,60 @@
assertThrows(() => dtf.formatRangeToParts(date2, date1), RangeError);
assertDoesNotThrow(() =>dtf.formatRangeToParts(date1, date2));
+
+function partsToString(parts) {
+ return parts.map(x => x.value).join("");
+}
+
+const validSources = ["startRange", "endRange", "shared"];
+const validTypes = ["literal", "year", "month", "day", "hour", "minute", "second",
+ "weekday", "dayPeriod", "timeZoneName", "era"];
+
+function assertParts(parts) {
+ const str = partsToString(parts);
+ parts.forEach(function(part) {
+ // Check the range of part.source
+ assertTrue(validSources.includes(part.source),
+ "Invalid source '" + part.source + "' in '" + str + "' for '" + part.value + "'");
+ // Check the range of part.type
+ assertTrue(validTypes.includes(part.type),
+ "Invalid type '" + part.type + "' in '" + str + "' for '" + part.value + "'");
+ // Check the part.value is a string
+ assertEquals("string", typeof part.value, "Invalid value for '" + str + "'");
+ });
+}
+
+function verifyFormatRangeToParts(a, b, dtf) {
+ var parts = dtf.formatRangeToParts(a, b);
+ // Check each parts fulfill basic property of the parts.
+ assertParts(parts);
+ // ensure the 'value' in the parts is the same as the output of
+ // the formatRange.
+ assertEquals(dtf.formatRange(a, b), partsToString(parts));
+}
+
+verifyFormatRangeToParts(date1, date2, dtf);
+verifyFormatRangeToParts(date1, date3, dtf);
+verifyFormatRangeToParts(date1, date4, dtf);
+verifyFormatRangeToParts(date2, date3, dtf);
+verifyFormatRangeToParts(date2, date4, dtf);
+verifyFormatRangeToParts(date3, date4, dtf);
+
+dtf = new Intl.DateTimeFormat(["en"], {year: "numeric", month: "short", day: "numeric"});
+
+verifyFormatRangeToParts(date1, date2, dtf);
+verifyFormatRangeToParts(date1, date3, dtf);
+verifyFormatRangeToParts(date1, date4, dtf);
+verifyFormatRangeToParts(date2, date3, dtf);
+verifyFormatRangeToParts(date2, date4, dtf);
+verifyFormatRangeToParts(date3, date4, dtf);
+
+// Test the sequence of ToNumber and TimeClip
+var secondDateAccessed = false;
+assertThrows(
+ () =>
+ dtf.formatRangeToParts(
+ new Date(864000000*10000000 + 1), // a date will cause TimeClip return NaN
+ { get [Symbol.toPrimitive]() { secondDateAccessed = true; return {}} }),
+ TypeError);
+assertTrue(secondDateAccessed);
diff --git a/tools/v8heapconst.py b/tools/v8heapconst.py
index b891154..fc8c5eb 100644
--- a/tools/v8heapconst.py
+++ b/tools/v8heapconst.py
@@ -304,47 +304,47 @@
("read_only_space", 0x026e1): (98, "EnumCacheMap"),
("read_only_space", 0x02781): (114, "ArrayBoilerplateDescriptionMap"),
("read_only_space", 0x02ad1): (101, "InterceptorInfoMap"),
- ("read_only_space", 0x050b9): (89, "AccessCheckInfoMap"),
- ("read_only_space", 0x05109): (90, "AccessorInfoMap"),
- ("read_only_space", 0x05159): (91, "AccessorPairMap"),
- ("read_only_space", 0x051a9): (92, "AliasedArgumentsEntryMap"),
- ("read_only_space", 0x051f9): (93, "AllocationMementoMap"),
- ("read_only_space", 0x05249): (94, "AsmWasmDataMap"),
- ("read_only_space", 0x05299): (95, "AsyncGeneratorRequestMap"),
- ("read_only_space", 0x052e9): (96, "ClassPositionsMap"),
- ("read_only_space", 0x05339): (97, "DebugInfoMap"),
- ("read_only_space", 0x05389): (99, "FunctionTemplateInfoMap"),
- ("read_only_space", 0x053d9): (100, "FunctionTemplateRareDataMap"),
- ("read_only_space", 0x05429): (102, "InterpreterDataMap"),
- ("read_only_space", 0x05479): (103, "ModuleInfoEntryMap"),
- ("read_only_space", 0x054c9): (104, "ModuleMap"),
- ("read_only_space", 0x05519): (105, "ObjectTemplateInfoMap"),
- ("read_only_space", 0x05569): (106, "PromiseCapabilityMap"),
- ("read_only_space", 0x055b9): (107, "PromiseReactionMap"),
- ("read_only_space", 0x05609): (108, "PrototypeInfoMap"),
- ("read_only_space", 0x05659): (109, "ScriptMap"),
- ("read_only_space", 0x056a9): (110, "StackFrameInfoMap"),
- ("read_only_space", 0x056f9): (111, "StackTraceFrameMap"),
- ("read_only_space", 0x05749): (112, "Tuple2Map"),
- ("read_only_space", 0x05799): (113, "Tuple3Map"),
- ("read_only_space", 0x057e9): (115, "WasmDebugInfoMap"),
- ("read_only_space", 0x05839): (116, "WasmExceptionTagMap"),
- ("read_only_space", 0x05889): (117, "WasmExportedFunctionDataMap"),
- ("read_only_space", 0x058d9): (118, "CallableTaskMap"),
- ("read_only_space", 0x05929): (119, "CallbackTaskMap"),
- ("read_only_space", 0x05979): (120, "PromiseFulfillReactionJobTaskMap"),
- ("read_only_space", 0x059c9): (121, "PromiseRejectReactionJobTaskMap"),
- ("read_only_space", 0x05a19): (122, "PromiseResolveThenableJobTaskMap"),
- ("read_only_space", 0x05a69): (123, "FinalizationGroupCleanupJobTaskMap"),
- ("read_only_space", 0x05ab9): (124, "AllocationSiteWithWeakNextMap"),
- ("read_only_space", 0x05b09): (124, "AllocationSiteWithoutWeakNextMap"),
- ("read_only_space", 0x05b59): (159, "LoadHandler1Map"),
- ("read_only_space", 0x05ba9): (159, "LoadHandler2Map"),
- ("read_only_space", 0x05bf9): (159, "LoadHandler3Map"),
- ("read_only_space", 0x05c49): (167, "StoreHandler0Map"),
- ("read_only_space", 0x05c99): (167, "StoreHandler1Map"),
- ("read_only_space", 0x05ce9): (167, "StoreHandler2Map"),
- ("read_only_space", 0x05d39): (167, "StoreHandler3Map"),
+ ("read_only_space", 0x05109): (89, "AccessCheckInfoMap"),
+ ("read_only_space", 0x05159): (90, "AccessorInfoMap"),
+ ("read_only_space", 0x051a9): (91, "AccessorPairMap"),
+ ("read_only_space", 0x051f9): (92, "AliasedArgumentsEntryMap"),
+ ("read_only_space", 0x05249): (93, "AllocationMementoMap"),
+ ("read_only_space", 0x05299): (94, "AsmWasmDataMap"),
+ ("read_only_space", 0x052e9): (95, "AsyncGeneratorRequestMap"),
+ ("read_only_space", 0x05339): (96, "ClassPositionsMap"),
+ ("read_only_space", 0x05389): (97, "DebugInfoMap"),
+ ("read_only_space", 0x053d9): (99, "FunctionTemplateInfoMap"),
+ ("read_only_space", 0x05429): (100, "FunctionTemplateRareDataMap"),
+ ("read_only_space", 0x05479): (102, "InterpreterDataMap"),
+ ("read_only_space", 0x054c9): (103, "ModuleInfoEntryMap"),
+ ("read_only_space", 0x05519): (104, "ModuleMap"),
+ ("read_only_space", 0x05569): (105, "ObjectTemplateInfoMap"),
+ ("read_only_space", 0x055b9): (106, "PromiseCapabilityMap"),
+ ("read_only_space", 0x05609): (107, "PromiseReactionMap"),
+ ("read_only_space", 0x05659): (108, "PrototypeInfoMap"),
+ ("read_only_space", 0x056a9): (109, "ScriptMap"),
+ ("read_only_space", 0x056f9): (110, "StackFrameInfoMap"),
+ ("read_only_space", 0x05749): (111, "StackTraceFrameMap"),
+ ("read_only_space", 0x05799): (112, "Tuple2Map"),
+ ("read_only_space", 0x057e9): (113, "Tuple3Map"),
+ ("read_only_space", 0x05839): (115, "WasmDebugInfoMap"),
+ ("read_only_space", 0x05889): (116, "WasmExceptionTagMap"),
+ ("read_only_space", 0x058d9): (117, "WasmExportedFunctionDataMap"),
+ ("read_only_space", 0x05929): (118, "CallableTaskMap"),
+ ("read_only_space", 0x05979): (119, "CallbackTaskMap"),
+ ("read_only_space", 0x059c9): (120, "PromiseFulfillReactionJobTaskMap"),
+ ("read_only_space", 0x05a19): (121, "PromiseRejectReactionJobTaskMap"),
+ ("read_only_space", 0x05a69): (122, "PromiseResolveThenableJobTaskMap"),
+ ("read_only_space", 0x05ab9): (123, "FinalizationGroupCleanupJobTaskMap"),
+ ("read_only_space", 0x05b09): (124, "AllocationSiteWithWeakNextMap"),
+ ("read_only_space", 0x05b59): (124, "AllocationSiteWithoutWeakNextMap"),
+ ("read_only_space", 0x05ba9): (159, "LoadHandler1Map"),
+ ("read_only_space", 0x05bf9): (159, "LoadHandler2Map"),
+ ("read_only_space", 0x05c49): (159, "LoadHandler3Map"),
+ ("read_only_space", 0x05c99): (167, "StoreHandler0Map"),
+ ("read_only_space", 0x05ce9): (167, "StoreHandler1Map"),
+ ("read_only_space", 0x05d39): (167, "StoreHandler2Map"),
+ ("read_only_space", 0x05d89): (167, "StoreHandler3Map"),
("map_space", 0x00139): (1057, "ExternalMap"),
("map_space", 0x00189): (1073, "JSMessageObjectMap"),
}