[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"),
 }