Bug 1473229 - Part 3: Add support for "formatToParts" to Intl.RelativeTimeFormat. r=jwalden
authorAndré Bargull <andre.bargull@gmail.com>
Tue, 09 Jul 2019 10:04:45 +0000
changeset 481876 66cbc40a0aac22026b099d36339795c61dfc4837
parent 481875 e65dd4b9b049105459482abd82d166595a9d6ac1
child 481877 2e1e388eca6d6693cb914645a5cdf47d27a0d0bb
push id36266
push useraciure@mozilla.com
push dateWed, 10 Jul 2019 09:40:52 +0000
treeherdermozilla-central@823177295f4c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwalden
bugs1473229
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1473229 - Part 3: Add support for "formatToParts" to Intl.RelativeTimeFormat. r=jwalden - The new formatted-value API is still draft-only, so extra U_HIDE_DRAFT_API guards are currently needed. - Also moves steps 4-5 of PartitionRelativeTimePattern to native code to reduce code duplication. Differential Revision: https://phabricator.services.mozilla.com/D26719
config/check_spidermonkey_style.py
config/system-headers.mozbuild
js/src/builtin/intl/DateTimeFormat.cpp
js/src/builtin/intl/ICUStubs.h
js/src/builtin/intl/NumberFormat.cpp
js/src/builtin/intl/RelativeTimeFormat.cpp
js/src/builtin/intl/RelativeTimeFormat.h
js/src/builtin/intl/RelativeTimeFormat.js
js/src/js.msg
js/src/tests/jstests.list
js/src/vm/CommonPropertyNames.h
--- a/config/check_spidermonkey_style.py
+++ b/config/check_spidermonkey_style.py
@@ -92,16 +92,17 @@ included_inclnames_to_ignore = set([
     'unicode/ucal.h',           # ICU
     'unicode/uchar.h',          # ICU
     'unicode/uclean.h',         # ICU
     'unicode/ucol.h',           # ICU
     'unicode/udat.h',           # ICU
     'unicode/udatpg.h',         # ICU
     'unicode/udisplaycontext.h',  # ICU
     'unicode/uenum.h',          # ICU
+    'unicode/uformattedvalue.h',  # ICU
     'unicode/uloc.h',           # ICU
     'unicode/unistr.h',         # ICU
     'unicode/unorm2.h',         # ICU
     'unicode/unum.h',           # ICU
     'unicode/unumsys.h',        # ICU
     'unicode/upluralrules.h',   # ICU
     'unicode/ureldatefmt.h',    # ICU
     'unicode/ustring.h',        # ICU
--- a/config/system-headers.mozbuild
+++ b/config/system-headers.mozbuild
@@ -1325,16 +1325,17 @@ if CONFIG['MOZ_SYSTEM_ICU']:
         'unicode/ucal.h',
         'unicode/uchar.h',
         'unicode/uclean.h',
         'unicode/ucol.h',
         'unicode/udat.h',
         'unicode/udatpg.h',
         'unicode/udisplaycontext.h',
         'unicode/uenum.h',
+        'unicode/uformattedvalue.h',
         'unicode/unistr.h',
         'unicode/unorm.h',
         'unicode/unum.h',
         'unicode/upluralrules.h',
         'unicode/ureldatefmt.h',
         'unicode/ustring.h',
         'unicode/utypes.h',
     ]
--- a/js/src/builtin/intl/DateTimeFormat.cpp
+++ b/js/src/builtin/intl/DateTimeFormat.cpp
@@ -941,20 +941,23 @@ bool js::intl_FormatDateTime(JSContext* 
   MOZ_ASSERT(args.length() == 3);
   MOZ_ASSERT(args[0].isObject());
   MOZ_ASSERT(args[1].isNumber());
   MOZ_ASSERT(args[2].isBoolean());
 
   Rooted<DateTimeFormatObject*> dateTimeFormat(cx);
   dateTimeFormat = &args[0].toObject().as<DateTimeFormatObject>();
 
+  bool formatToParts = args[2].toBoolean();
+
   ClippedTime x = TimeClip(args[1].toNumber());
   if (!x.isValid()) {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
-                              JSMSG_DATE_NOT_FINITE, "DateTimeFormat");
+                              JSMSG_DATE_NOT_FINITE, "DateTimeFormat",
+                              formatToParts ? "formatToParts" : "format");
     return false;
   }
 
   // Obtain a cached UDateFormat object.
   void* priv =
       dateTimeFormat->getReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT)
           .toPrivate();
   UDateFormat* df = static_cast<UDateFormat*>(priv);
@@ -963,12 +966,11 @@ bool js::intl_FormatDateTime(JSContext* 
     if (!df) {
       return false;
     }
     dateTimeFormat->setReservedSlot(DateTimeFormatObject::UDATE_FORMAT_SLOT,
                                     PrivateValue(df));
   }
 
   // Use the UDateFormat to actually format the time stamp.
-  return args[2].toBoolean()
-             ? intl_FormatToPartsDateTime(cx, df, x, args.rval())
-             : intl_FormatDateTime(cx, df, x, args.rval());
+  return formatToParts ? intl_FormatToPartsDateTime(cx, df, x, args.rval())
+                       : intl_FormatDateTime(cx, df, x, args.rval());
 }
--- a/js/src/builtin/intl/ICUStubs.h
+++ b/js/src/builtin/intl/ICUStubs.h
@@ -23,16 +23,17 @@
 
 #if ENABLE_INTL_API
 #  include "unicode/ucal.h"
 #  include "unicode/ucol.h"
 #  include "unicode/udat.h"
 #  include "unicode/udatpg.h"
 #  include "unicode/udisplaycontext.h"
 #  include "unicode/uenum.h"
+#  include "unicode/uformattedvalue.h"
 #  include "unicode/uloc.h"
 #  include "unicode/unum.h"
 #  include "unicode/unumsys.h"
 #  include "unicode/upluralrules.h"
 #  include "unicode/ureldatefmt.h"
 #  include "unicode/ustring.h"
 #endif
 
@@ -667,11 +668,81 @@ inline int32_t ureldatefmt_format(const 
 
 inline int32_t ureldatefmt_formatNumeric(
     const URelativeDateTimeFormatter* reldatefmt, double offset,
     URelativeDateTimeUnit unit, UChar* result, int32_t resultCapacity,
     UErrorCode* status) {
   MOZ_CRASH("ureldatefmt_formatNumeric: Intl API disabled");
 }
 
+struct UFormattedRelativeDateTime;
+
+inline UFormattedRelativeDateTime* ureldatefmt_openResult(UErrorCode* status) {
+  MOZ_CRASH("ureldatefmt_openResult: Intl API disabled");
+}
+
+inline void ureldatefmt_closeResult(UFormattedRelativeDateTime* ufrdt) {
+  MOZ_CRASH("ureldatefmt_closeResult: Intl API disabled");
+}
+
+inline void ureldatefmt_formatToResult(
+    const URelativeDateTimeFormatter* reldatefmt, double offset,
+    URelativeDateTimeUnit unit, UFormattedRelativeDateTime* result,
+    UErrorCode* status) {
+  MOZ_CRASH("ureldatefmt_formatToResult: Intl API disabled");
+}
+
+inline void ureldatefmt_formatNumericToResult(
+    const URelativeDateTimeFormatter* reldatefmt, double offset,
+    URelativeDateTimeUnit unit, UFormattedRelativeDateTime* result,
+    UErrorCode* status) {
+  MOZ_CRASH("ureldatefmt_formatToResult: Intl API disabled");
+}
+
+struct UFormattedValue;
+
+inline const UFormattedValue* ureldatefmt_resultAsValue(
+    const UFormattedRelativeDateTime* ufrdt, UErrorCode* status) {
+  MOZ_CRASH("ureldatefmt_resultAsValue: Intl API disabled");
+}
+
+inline const UChar* ufmtval_getString(const UFormattedValue* ufmtval,
+                                      int32_t* pLength, UErrorCode* status) {
+  MOZ_CRASH("ufmtval_getString: Intl API disabled");
+}
+
+struct UConstrainedFieldPosition;
+
+inline UConstrainedFieldPosition* ucfpos_open(UErrorCode* status) {
+  MOZ_CRASH("ucfpos_open: Intl API disabled");
+}
+
+inline void ucfpos_close(UConstrainedFieldPosition* ucfpos) {
+  MOZ_CRASH("ucfpos_close: Intl API disabled");
+}
+
+typedef enum UFieldCategory { UFIELD_CATEGORY_NUMBER } UFieldCategory;
+
+inline void ucfpos_constrainCategory(UConstrainedFieldPosition* ucfpos,
+                                     int32_t category, UErrorCode* status) {
+  MOZ_CRASH("ucfpos_constrainCategory: Intl API disabled");
+}
+
+inline bool ufmtval_nextPosition(const UFormattedValue* ufmtval,
+                                 UConstrainedFieldPosition* ucfpos,
+                                 UErrorCode* status) {
+  MOZ_CRASH("ufmtval_nextPosition: Intl API disabled");
+}
+
+inline int32_t ucfpos_getField(const UConstrainedFieldPosition* ucfpos,
+                               UErrorCode* status) {
+  MOZ_CRASH("ucfpos_getField: Intl API disabled");
+}
+
+inline void ucfpos_getIndexes(const UConstrainedFieldPosition* ucfpos,
+                              int32_t* pStart, int32_t* pLimit,
+                              UErrorCode* status) {
+  MOZ_CRASH("ucfpos_getIndexes: Intl API disabled");
+}
+
 #endif  // !ENABLE_INTL_API
 
 #endif /* builtin_intl_ICUStubs_h */
--- a/js/src/builtin/intl/NumberFormat.cpp
+++ b/js/src/builtin/intl/NumberFormat.cpp
@@ -805,16 +805,23 @@ ArrayObject* js::intl::NumberFormatField
       return nullptr;
     }
 
     propVal.setString(partSubstr);
     if (!DefineDataProperty(cx, singlePart, cx->names().value, propVal)) {
       return nullptr;
     }
 
+    if (unitType != nullptr && type != &JSAtomState::literal) {
+      propVal.setString(cx->names().*unitType);
+      if (!DefineDataProperty(cx, singlePart, cx->names().unit, propVal)) {
+        return nullptr;
+      }
+    }
+
     propVal.setObject(*singlePart);
     if (!DefineDataElement(cx, partsArray, partIndex, propVal)) {
       return nullptr;
     }
 
     lastEndIndex = endIndex;
     partIndex++;
   } while (true);
--- a/js/src/builtin/intl/RelativeTimeFormat.cpp
+++ b/js/src/builtin/intl/RelativeTimeFormat.cpp
@@ -8,29 +8,29 @@
 
 #include "builtin/intl/RelativeTimeFormat.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/FloatingPoint.h"
 
 #include "builtin/intl/CommonFunctions.h"
 #include "builtin/intl/ICUStubs.h"
+#include "builtin/intl/NumberFormat.h"
 #include "builtin/intl/ScopedICUObject.h"
 #include "gc/FreeOp.h"
 #include "js/CharacterEncoding.h"
 #include "js/PropertySpec.h"
 #include "vm/GlobalObject.h"
 #include "vm/JSContext.h"
+#include "vm/StringType.h"
 
 #include "vm/NativeObject-inl.h"
 
 using namespace js;
 
-using mozilla::IsNegativeZero;
-
 using js::intl::CallICU;
 using js::intl::GetAvailableLocales;
 using js::intl::IcuLocale;
 
 /**************** RelativeTimeFormat *****************/
 
 const ClassOps RelativeTimeFormatObject::classOps_ = {
     nullptr, /* addProperty */
@@ -58,16 +58,20 @@ static const JSFunctionSpec relativeTime
     JS_SELF_HOSTED_FN("supportedLocalesOf",
                       "Intl_RelativeTimeFormat_supportedLocalesOf", 1, 0),
     JS_FS_END};
 
 static const JSFunctionSpec relativeTimeFormat_methods[] = {
     JS_SELF_HOSTED_FN("resolvedOptions",
                       "Intl_RelativeTimeFormat_resolvedOptions", 0, 0),
     JS_SELF_HOSTED_FN("format", "Intl_RelativeTimeFormat_format", 2, 0),
+#ifndef U_HIDE_DRAFT_API
+    JS_SELF_HOSTED_FN("formatToParts", "Intl_RelativeTimeFormat_formatToParts",
+                      2, 0),
+#endif
     JS_FN(js_toSource_str, relativeTimeFormat_toSource, 0, 0), JS_FS_END};
 
 static const JSPropertySpec relativeTimeFormat_properties[] = {
     JS_STRING_SYM_PS(toStringTag, "Intl.RelativeTimeFormat", JSPROP_READONLY),
     JS_PS_END};
 
 /**
  * RelativeTimeFormat constructor.
@@ -252,24 +256,186 @@ enum class RelativeTimeNumeric {
   Always,
   /**
    * Natural-language strings like `yesterday` when possible,
    * otherwise strings with numeric components as in `7 months ago`.
    */
   Auto,
 };
 
+static bool intl_FormatRelativeTime(JSContext* cx,
+                                    URelativeDateTimeFormatter* rtf, double t,
+                                    URelativeDateTimeUnit unit,
+                                    RelativeTimeNumeric numeric,
+                                    MutableHandleValue result) {
+  JSString* str = CallICU(
+      cx,
+      [rtf, t, unit, numeric](UChar* chars, int32_t size, UErrorCode* status) {
+        auto fmt = numeric == RelativeTimeNumeric::Auto
+                       ? ureldatefmt_format
+                       : ureldatefmt_formatNumeric;
+        return fmt(rtf, t, unit, chars, size, status);
+      });
+  if (!str) {
+    return false;
+  }
+
+  result.setString(str);
+  return true;
+}
+
+#ifndef U_HIDE_DRAFT_API
+static bool intl_FormatToPartsRelativeTime(JSContext* cx,
+                                           URelativeDateTimeFormatter* rtf,
+                                           double t, URelativeDateTimeUnit unit,
+                                           RelativeTimeNumeric numeric,
+                                           MutableHandleValue result) {
+  UErrorCode status = U_ZERO_ERROR;
+  UFormattedRelativeDateTime* formatted = ureldatefmt_openResult(&status);
+  if (U_FAILURE(status)) {
+    intl::ReportInternalError(cx);
+    return false;
+  }
+  ScopedICUObject<UFormattedRelativeDateTime, ureldatefmt_closeResult> toClose(
+      formatted);
+
+  if (numeric == RelativeTimeNumeric::Auto) {
+    ureldatefmt_formatToResult(rtf, t, unit, formatted, &status);
+  } else {
+    ureldatefmt_formatNumericToResult(rtf, t, unit, formatted, &status);
+  }
+  if (U_FAILURE(status)) {
+    intl::ReportInternalError(cx);
+    return false;
+  }
+
+  const UFormattedValue* formattedValue =
+      ureldatefmt_resultAsValue(formatted, &status);
+  if (U_FAILURE(status)) {
+    intl::ReportInternalError(cx);
+    return false;
+  }
+
+  int32_t strLength;
+  const char16_t* str = ufmtval_getString(formattedValue, &strLength, &status);
+  if (U_FAILURE(status)) {
+    intl::ReportInternalError(cx);
+    return false;
+  }
+  MOZ_ASSERT(strLength >= 0);
+
+  RootedString overallResult(cx,
+                             NewStringCopyN<CanGC>(cx, str, size_t(strLength)));
+  if (!overallResult) {
+    return false;
+  }
+
+  intl::FieldType unitType;
+  switch (unit) {
+    case UDAT_REL_UNIT_SECOND:
+      unitType = &JSAtomState::second;
+      break;
+    case UDAT_REL_UNIT_MINUTE:
+      unitType = &JSAtomState::minute;
+      break;
+    case UDAT_REL_UNIT_HOUR:
+      unitType = &JSAtomState::hour;
+      break;
+    case UDAT_REL_UNIT_DAY:
+      unitType = &JSAtomState::day;
+      break;
+    case UDAT_REL_UNIT_WEEK:
+      unitType = &JSAtomState::week;
+      break;
+    case UDAT_REL_UNIT_MONTH:
+      unitType = &JSAtomState::month;
+      break;
+    case UDAT_REL_UNIT_QUARTER:
+      unitType = &JSAtomState::quarter;
+      break;
+    case UDAT_REL_UNIT_YEAR:
+      unitType = &JSAtomState::year;
+      break;
+    default:
+      MOZ_CRASH("unexpected relative time unit");
+  }
+
+  UConstrainedFieldPosition* fpos = ucfpos_open(&status);
+  if (U_FAILURE(status)) {
+    intl::ReportInternalError(cx);
+    return false;
+  }
+  ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos);
+
+  // The possible field position categories are UFIELD_CATEGORY_NUMBER and
+  // UFIELD_CATEGORY_RELATIVE_DATETIME. For the parts array we only need to
+  // iterate over the number formatted fields.
+  ucfpos_constrainCategory(fpos, UFIELD_CATEGORY_NUMBER, &status);
+  if (U_FAILURE(status)) {
+    intl::ReportInternalError(cx);
+    return false;
+  }
+
+  intl::NumberFormatFields fields(cx, t);
+
+  while (true) {
+    bool hasMore = ufmtval_nextPosition(formattedValue, fpos, &status);
+    if (U_FAILURE(status)) {
+      intl::ReportInternalError(cx);
+      return false;
+    }
+    if (!hasMore) {
+      break;
+    }
+
+    int32_t field = ucfpos_getField(fpos, &status);
+    if (U_FAILURE(status)) {
+      intl::ReportInternalError(cx);
+      return false;
+    }
+
+    int32_t beginIndex, endIndex;
+    ucfpos_getIndexes(fpos, &beginIndex, &endIndex, &status);
+    if (U_FAILURE(status)) {
+      intl::ReportInternalError(cx);
+      return false;
+    }
+
+    if (!fields.append(field, beginIndex, endIndex)) {
+      return false;
+    }
+  }
+
+  ArrayObject* array = fields.toArray(cx, overallResult, unitType);
+  if (!array) {
+    return false;
+  }
+
+  result.setObject(*array);
+  return true;
+}
+#endif
+
 bool js::intl_FormatRelativeTime(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
-  MOZ_ASSERT(args.length() == 4);
+  MOZ_ASSERT(args.length() == 5);
 
   Rooted<RelativeTimeFormatObject*> relativeTimeFormat(cx);
   relativeTimeFormat = &args[0].toObject().as<RelativeTimeFormatObject>();
 
+  bool formatToParts = args[4].toBoolean();
+
+  // PartitionRelativeTimePattern, step 4.
   double t = args[1].toNumber();
+  if (!mozilla::IsFinite(t)) {
+    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+                              JSMSG_DATE_NOT_FINITE, "RelativeTimeFormat",
+                              formatToParts ? "formatToParts" : "format");
+    return false;
+  }
 
   // Obtain a cached URelativeDateTimeFormatter object.
   constexpr auto RT_FORMAT_SLOT =
       RelativeTimeFormatObject::URELATIVE_TIME_FORMAT_SLOT;
   void* priv = relativeTimeFormat->getReservedSlot(RT_FORMAT_SLOT).toPrivate();
   URelativeDateTimeFormatter* rtf =
       static_cast<URelativeDateTimeFormatter*>(priv);
   if (!rtf) {
@@ -282,16 +448,17 @@ bool js::intl_FormatRelativeTime(JSConte
 
   URelativeDateTimeUnit relDateTimeUnit;
   {
     JSLinearString* unit = args[2].toString()->ensureLinear(cx);
     if (!unit) {
       return false;
     }
 
+    // PartitionRelativeTimePattern, step 5.
     if (StringEqualsAscii(unit, "second") ||
         StringEqualsAscii(unit, "seconds")) {
       relDateTimeUnit = UDAT_REL_UNIT_SECOND;
     } else if (StringEqualsAscii(unit, "minute") ||
                StringEqualsAscii(unit, "minutes")) {
       relDateTimeUnit = UDAT_REL_UNIT_MINUTE;
     } else if (StringEqualsAscii(unit, "hour") ||
                StringEqualsAscii(unit, "hours")) {
@@ -303,20 +470,26 @@ bool js::intl_FormatRelativeTime(JSConte
                StringEqualsAscii(unit, "weeks")) {
       relDateTimeUnit = UDAT_REL_UNIT_WEEK;
     } else if (StringEqualsAscii(unit, "month") ||
                StringEqualsAscii(unit, "months")) {
       relDateTimeUnit = UDAT_REL_UNIT_MONTH;
     } else if (StringEqualsAscii(unit, "quarter") ||
                StringEqualsAscii(unit, "quarters")) {
       relDateTimeUnit = UDAT_REL_UNIT_QUARTER;
+    } else if (StringEqualsAscii(unit, "year") ||
+               StringEqualsAscii(unit, "years")) {
+      relDateTimeUnit = UDAT_REL_UNIT_YEAR;
     } else {
-      MOZ_ASSERT(StringEqualsAscii(unit, "year") ||
-                 StringEqualsAscii(unit, "years"));
-      relDateTimeUnit = UDAT_REL_UNIT_YEAR;
+      if (auto unitChars = StringToNewUTF8CharsZ(cx, *unit)) {
+        JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+                                 JSMSG_INVALID_OPTION_VALUE, "unit",
+                                 unitChars.get());
+      }
+      return false;
     }
   }
 
   RelativeTimeNumeric relDateTimeNumeric;
   {
     JSLinearString* numeric = args[3].toString()->ensureLinear(cx);
     if (!numeric) {
       return false;
@@ -325,23 +498,20 @@ bool js::intl_FormatRelativeTime(JSConte
     if (StringEqualsAscii(numeric, "auto")) {
       relDateTimeNumeric = RelativeTimeNumeric::Auto;
     } else {
       MOZ_ASSERT(StringEqualsAscii(numeric, "always"));
       relDateTimeNumeric = RelativeTimeNumeric::Always;
     }
   }
 
-  JSString* str =
-      CallICU(cx, [rtf, t, relDateTimeUnit, relDateTimeNumeric](
-                      UChar* chars, int32_t size, UErrorCode* status) {
-        auto fmt = relDateTimeNumeric == RelativeTimeNumeric::Auto
-                       ? ureldatefmt_format
-                       : ureldatefmt_formatNumeric;
-        return fmt(rtf, t, relDateTimeUnit, chars, size, status);
-      });
-  if (!str) {
-    return false;
-  }
-
-  args.rval().setString(str);
-  return true;
+#ifndef U_HIDE_DRAFT_API
+  return formatToParts
+             ? intl_FormatToPartsRelativeTime(cx, rtf, t, relDateTimeUnit,
+                                              relDateTimeNumeric, args.rval())
+             : intl_FormatRelativeTime(cx, rtf, t, relDateTimeUnit,
+                                       relDateTimeNumeric, args.rval());
+#else
+  MOZ_ASSERT(!formatToParts);
+  return intl_FormatRelativeTime(cx, rtf, t, relDateTimeUnit,
+                                 relDateTimeNumeric, args.rval());
+#endif
 }
--- a/js/src/builtin/intl/RelativeTimeFormat.h
+++ b/js/src/builtin/intl/RelativeTimeFormat.h
@@ -57,16 +57,16 @@ extern MOZ_MUST_USE bool intl_RelativeTi
  * locale and the formatting options of the given RelativeTimeFormat.
  *
  * |t| should be a number representing a number to be formatted.
  * |unit| should be "second", "minute", "hour", "day", "week", "month",
  *                  "quarter", or "year".
  * |numeric| should be "always" or "auto".
  *
  * Usage: formatted = intl_FormatRelativeTime(relativeTimeFormat, t,
- *                                            unit, numeric)
+ *                                            unit, numeric, formatToParts)
  */
 extern MOZ_MUST_USE bool intl_FormatRelativeTime(JSContext* cx, unsigned argc,
                                                  JS::Value* vp);
 
 }  // namespace js
 
 #endif /* builtin_intl_RelativeTimeFormat_h */
--- a/js/src/builtin/intl/RelativeTimeFormat.js
+++ b/js/src/builtin/intl/RelativeTimeFormat.js
@@ -196,52 +196,58 @@ function Intl_RelativeTimeFormat_format(
     var internals = getRelativeTimeFormatInternals(relativeTimeFormat);
 
     // Step 3.
     let t = ToNumber(value);
 
     // Step 4.
     let u = ToString(unit);
 
-    // PartitionRelativeTimePattern, step 4.
-    if (!Number_isFinite(t)) {
-        ThrowRangeError(JSMSG_DATE_NOT_FINITE, "RelativeTimeFormat");
+    // Step 5.
+    return intl_FormatRelativeTime(relativeTimeFormat, t, u, internals.numeric,
+                                   false);
+}
+
+/**
+ * Returns an Array composed of the components of a relative date formatted
+ * according to the effective locale and the formatting options of this
+ * RelativeTimeFormat object.
+ *
+ * Spec: ECMAScript 402 API, RelativeTImeFormat, 1.4.4.
+ */
+function Intl_RelativeTimeFormat_formatToParts(value, unit) {
+    // Step 1.
+    let relativeTimeFormat = this;
+
+    // Step 2.
+    if (!IsObject(relativeTimeFormat) ||
+        (relativeTimeFormat = GuardToRelativeTimeFormat(relativeTimeFormat)) === null)
+    {
+        return callFunction(CallRelativeTimeFormatMethodIfWrapped, this, value, unit,
+                            "Intl_RelativeTimeFormat_formatToParts");
     }
 
-    // PartitionRelativeTimePattern, step 5.
-    switch (u) {
-      case "second":
-      case "seconds":
-      case "minute":
-      case "minutes":
-      case "hour":
-      case "hours":
-      case "day":
-      case "days":
-      case "week":
-      case "weeks":
-      case "month":
-      case "months":
-      case "quarter":
-      case "quarters":
-      case "year":
-      case "years":
-        break;
-      default:
-        ThrowRangeError(JSMSG_INVALID_OPTION_VALUE, "unit", u);
-    }
+    // Ensure the RelativeTimeFormat internals are resolved.
+    var internals = getRelativeTimeFormatInternals(relativeTimeFormat);
+
+    // Step 3.
+    let t = ToNumber(value);
+
+    // Step 4.
+    let u = ToString(unit);
 
     // Step 5.
-    return intl_FormatRelativeTime(relativeTimeFormat, t, u, internals.numeric);
+    return intl_FormatRelativeTime(relativeTimeFormat, t, u, internals.numeric,
+                                   true);
 }
 
 /**
  * Returns the resolved options for a RelativeTimeFormat object.
  *
- * Spec: ECMAScript 402 API, RelativeTimeFormat, 1.4.4.
+ * Spec: ECMAScript 402 API, RelativeTimeFormat, 1.4.5.
  */
 function Intl_RelativeTimeFormat_resolvedOptions() {
     // Step 1.
     var relativeTimeFormat = this;
 
     // Steps 2-3.
     if (!IsObject(relativeTimeFormat) ||
         (relativeTimeFormat = GuardToRelativeTimeFormat(relativeTimeFormat)) === null)
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -514,17 +514,17 @@ MSG_DEF(JSMSG_DEBUG_NO_BINARY_SOURCE,  0
 
 // Testing functions
 MSG_DEF(JSMSG_TESTING_SCRIPTS_ONLY, 0, JSEXN_TYPEERR, "only works on scripts")
 
 // Tracelogger
 MSG_DEF(JSMSG_TRACELOGGER_ENABLE_FAIL, 1, JSEXN_ERR, "enabling tracelogger failed: {0}")
 
 // Intl
-MSG_DEF(JSMSG_DATE_NOT_FINITE,         1, JSEXN_RANGEERR, "date value is not finite in {0}.format()")
+MSG_DEF(JSMSG_DATE_NOT_FINITE,         2, JSEXN_RANGEERR, "date value is not finite in {0}.{1}()")
 MSG_DEF(JSMSG_INTERNAL_INTL_ERROR,     0, JSEXN_ERR, "internal error while computing Intl data")
 MSG_DEF(JSMSG_INVALID_CURRENCY_CODE,   1, JSEXN_RANGEERR, "invalid currency code in NumberFormat(): {0}")
 MSG_DEF(JSMSG_INVALID_DIGITS_VALUE,    1, JSEXN_RANGEERR, "invalid digits value: {0}")
 MSG_DEF(JSMSG_INVALID_KEYS_TYPE,       0, JSEXN_TYPEERR, "calendar info keys must be an object or undefined")
 MSG_DEF(JSMSG_INVALID_KEY,             1, JSEXN_RANGEERR, "invalid key: {0}")
 MSG_DEF(JSMSG_INVALID_LANGUAGE_TAG,    1, JSEXN_RANGEERR, "invalid language tag: {0}")
 MSG_DEF(JSMSG_INVALID_LOCALES_ELEMENT, 0, JSEXN_TYPEERR, "invalid element in locales argument")
 MSG_DEF(JSMSG_INVALID_LOCALE_MATCHER,  1, JSEXN_RANGEERR, "invalid locale matcher in supportedLocalesOf(): {0}")
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -11,16 +11,17 @@ slow script test262/built-ins/decodeURI/
 slow script test262/built-ins/decodeURIComponent/S15.1.3.2_A2.5_T1.js
 
 # Windows10-aarch64 fails certain tests.
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1526003
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1526012
 skip-if((xulRuntime.XPCOMABI.match(/aarch64/))&&(xulRuntime.OS=="WINNT")) script non262/Math/fround.js
 skip-if((xulRuntime.XPCOMABI.match(/aarch64/))&&(xulRuntime.OS=="WINNT")) script non262/Math/log2-approx.js
 
+
 ###########################################################################
 # Generated jstests.list for test262 when inline |reftest| isn't possible #
 ###########################################################################
 
 include test262/jstests.list
 
 
 #################################################################
@@ -431,19 +432,16 @@ skip script test262/language/statements/
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1321616
 skip script test262/annexB/built-ins/Function/createdynfn-html-close-comment-params.js
 skip script test262/annexB/built-ins/Function/createdynfn-no-line-terminator-html-close-comment-body.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1462745
 skip script test262/annexB/language/function-code/block-decl-nested-blocks-with-fun-decl.js
 
-# https://bugzilla.mozilla.org/show_bug.cgi?id=1473229
-skip include test262/intl402/RelativeTimeFormat/prototype/formatToParts/jstests.list
-
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1508683
 skip script test262/built-ins/RegExp/prototype/multiline/cross-realm.js
 skip script test262/built-ins/RegExp/prototype/global/cross-realm.js
 skip script test262/built-ins/RegExp/prototype/sticky/cross-realm.js
 skip script test262/built-ins/RegExp/prototype/ignoreCase/cross-realm.js
 skip script test262/built-ins/RegExp/prototype/unicode/cross-realm.js
 skip script test262/built-ins/RegExp/prototype/source/cross-realm.js
 
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -336,16 +336,17 @@
   MACRO(preventExtensions, preventExtensions, "preventExtensions")             \
   MACRO(private, private_, "private")                                          \
   MACRO(promise, promise, "promise")                                           \
   MACRO(propertyIsEnumerable, propertyIsEnumerable, "propertyIsEnumerable")    \
   MACRO(protected, protected_, "protected")                                    \
   MACRO(proto, proto, "__proto__")                                             \
   MACRO(prototype, prototype, "prototype")                                     \
   MACRO(proxy, proxy, "proxy")                                                 \
+  MACRO(quarter, quarter, "quarter")                                           \
   MACRO(raw, raw, "raw")                                                       \
   MACRO(reason, reason, "reason")                                              \
   MACRO(RegExpBuiltinExec, RegExpBuiltinExec, "RegExpBuiltinExec")             \
   MACRO(RegExpFlagsGetter, RegExpFlagsGetter, "$RegExpFlagsGetter")            \
   MACRO(RegExpMatcher, RegExpMatcher, "RegExpMatcher")                         \
   MACRO(RegExpSearcher, RegExpSearcher, "RegExpSearcher")                      \
   MACRO(RegExpStringIterator, RegExpStringIterator, "RegExp String Iterator")  \
   MACRO(RegExpTester, RegExpTester, "RegExpTester")                            \
@@ -451,16 +452,17 @@
   MACRO(WasmAnyRef, WasmAnyRef, "WasmAnyRef")                                  \
   MACRO(wasmcall, wasmcall, "wasmcall")                                        \
   MACRO(watch, watch, "watch")                                                 \
   MACRO(WeakMapConstructorInit, WeakMapConstructorInit,                        \
         "WeakMapConstructorInit")                                              \
   MACRO(WeakSetConstructorInit, WeakSetConstructorInit,                        \
         "WeakSetConstructorInit")                                              \
   MACRO(WeakSet_add, WeakSet_add, "WeakSet_add")                               \
+  MACRO(week, week, "week")                                                    \
   MACRO(weekday, weekday, "weekday")                                           \
   MACRO(weekendEnd, weekendEnd, "weekendEnd")                                  \
   MACRO(weekendStart, weekendStart, "weekendStart")                            \
   MACRO(while, while_, "while")                                                \
   MACRO(with, with, "with")                                                    \
   MACRO(writable, writable, "writable")                                        \
   MACRO(year, year, "year")                                                    \
   MACRO(yield, yield, "yield")                                                 \