1 // Copyright 2018 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef V8_INTL_SUPPORT
6 #error Internationalization is expected to be enabled.
7 #endif  // V8_INTL_SUPPORT
8 
9 #include "src/objects/js-relative-time-format.h"
10 
11 #include <map>
12 #include <memory>
13 #include <string>
14 
15 #include "src/execution/isolate.h"
16 #include "src/heap/factory.h"
17 #include "src/objects/intl-objects.h"
18 #include "src/objects/js-number-format.h"
19 #include "src/objects/js-relative-time-format-inl.h"
20 #include "src/objects/managed-inl.h"
21 #include "src/objects/objects-inl.h"
22 #include "src/objects/option-utils.h"
23 #include "unicode/decimfmt.h"
24 #include "unicode/numfmt.h"
25 #include "unicode/reldatefmt.h"
26 #include "unicode/unum.h"
27 
28 namespace v8 {
29 namespace internal {
30 
31 namespace {
32 // Style: identifying the relative time format style used.
33 //
34 // ecma402/#sec-properties-of-intl-relativetimeformat-instances
35 
36 enum class Style {
37   LONG,   // Everything spelled out.
38   SHORT,  // Abbreviations used when possible.
39   NARROW  // Use the shortest possible form.
40 };
41 
toIcuStyle(Style style)42 UDateRelativeDateTimeFormatterStyle toIcuStyle(Style style) {
43   switch (style) {
44     case Style::LONG:
45       return UDAT_STYLE_LONG;
46     case Style::SHORT:
47       return UDAT_STYLE_SHORT;
48     case Style::NARROW:
49       return UDAT_STYLE_NARROW;
50   }
51   UNREACHABLE();
52 }
53 
fromIcuStyle(UDateRelativeDateTimeFormatterStyle icu_style)54 Style fromIcuStyle(UDateRelativeDateTimeFormatterStyle icu_style) {
55   switch (icu_style) {
56     case UDAT_STYLE_LONG:
57       return Style::LONG;
58     case UDAT_STYLE_SHORT:
59       return Style::SHORT;
60     case UDAT_STYLE_NARROW:
61       return Style::NARROW;
62     case UDAT_STYLE_COUNT:
63       UNREACHABLE();
64   }
65   UNREACHABLE();
66 }
67 }  // namespace
68 
New(Isolate * isolate,Handle<Map> map,Handle<Object> locales,Handle<Object> input_options)69 MaybeHandle<JSRelativeTimeFormat> JSRelativeTimeFormat::New(
70     Isolate* isolate, Handle<Map> map, Handle<Object> locales,
71     Handle<Object> input_options) {
72   // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
73   Maybe<std::vector<std::string>> maybe_requested_locales =
74       Intl::CanonicalizeLocaleList(isolate, locales);
75   MAYBE_RETURN(maybe_requested_locales, Handle<JSRelativeTimeFormat>());
76   std::vector<std::string> requested_locales =
77       maybe_requested_locales.FromJust();
78 
79   // 2. Set options to ? CoerceOptionsToObject(options).
80   Handle<JSReceiver> options;
81   const char* service = "Intl.RelativeTimeFormat";
82   ASSIGN_RETURN_ON_EXCEPTION(
83       isolate, options, CoerceOptionsToObject(isolate, input_options, service),
84       JSRelativeTimeFormat);
85 
86   // 4. Let opt be a new Record.
87   // 5. Let matcher be ? GetOption(options, "localeMatcher", "string", «
88   // "lookup", "best fit" », "best fit").
89   // 6. Set opt.[[localeMatcher]] to matcher.
90   Maybe<Intl::MatcherOption> maybe_locale_matcher =
91       Intl::GetLocaleMatcher(isolate, options, service);
92   MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSRelativeTimeFormat>());
93   Intl::MatcherOption matcher = maybe_locale_matcher.FromJust();
94 
95   // 7. Let _numberingSystem_ be ? GetOption(_options_, `"numberingSystem"`,
96   //    `"string"`, *undefined*, *undefined*).
97   std::unique_ptr<char[]> numbering_system_str = nullptr;
98   Maybe<bool> maybe_numberingSystem = Intl::GetNumberingSystem(
99       isolate, options, service, &numbering_system_str);
100   // 8. If _numberingSystem_ is not *undefined*, then
101   // a. If _numberingSystem_ does not match the
102   //    `(3*8alphanum) *("-" (3*8alphanum))` sequence, throw a *RangeError*
103   //     exception.
104   MAYBE_RETURN(maybe_numberingSystem, MaybeHandle<JSRelativeTimeFormat>());
105 
106   // 9. Set _opt_.[[nu]] to _numberingSystem_.
107 
108   // 10. Let localeData be %RelativeTimeFormat%.[[LocaleData]].
109   // 11. Let r be
110   // ResolveLocale(%RelativeTimeFormat%.[[AvailableLocales]],
111   //               requestedLocales, opt,
112   //               %RelativeTimeFormat%.[[RelevantExtensionKeys]], localeData).
113   Maybe<Intl::ResolvedLocale> maybe_resolve_locale =
114       Intl::ResolveLocale(isolate, JSRelativeTimeFormat::GetAvailableLocales(),
115                           requested_locales, matcher, {"nu"});
116   if (maybe_resolve_locale.IsNothing()) {
117     THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
118                     JSRelativeTimeFormat);
119   }
120   Intl::ResolvedLocale r = maybe_resolve_locale.FromJust();
121 
122   UErrorCode status = U_ZERO_ERROR;
123 
124   icu::Locale icu_locale = r.icu_locale;
125   if (numbering_system_str != nullptr) {
126     auto nu_extension_it = r.extensions.find("nu");
127     if (nu_extension_it != r.extensions.end() &&
128         nu_extension_it->second != numbering_system_str.get()) {
129       icu_locale.setUnicodeKeywordValue("nu", nullptr, status);
130       DCHECK(U_SUCCESS(status));
131     }
132   }
133   // 12. Let locale be r.[[Locale]].
134   Maybe<std::string> maybe_locale_str = Intl::ToLanguageTag(icu_locale);
135   MAYBE_RETURN(maybe_locale_str, MaybeHandle<JSRelativeTimeFormat>());
136 
137   // 13. Set relativeTimeFormat.[[Locale]] to locale.
138   Handle<String> locale_str = isolate->factory()->NewStringFromAsciiChecked(
139       maybe_locale_str.FromJust().c_str());
140 
141   // 14. Set relativeTimeFormat.[[NumberingSystem]] to r.[[nu]].
142   if (numbering_system_str != nullptr &&
143       Intl::IsValidNumberingSystem(numbering_system_str.get())) {
144     icu_locale.setUnicodeKeywordValue("nu", numbering_system_str.get(), status);
145     DCHECK(U_SUCCESS(status));
146   }
147   // 15. Let dataLocale be r.[[DataLocale]].
148 
149   // 16. Let s be ? GetOption(options, "style", "string",
150   //                          «"long", "short", "narrow"», "long").
151   Maybe<Style> maybe_style = GetStringOption<Style>(
152       isolate, options, "style", service, {"long", "short", "narrow"},
153       {Style::LONG, Style::SHORT, Style::NARROW}, Style::LONG);
154   MAYBE_RETURN(maybe_style, MaybeHandle<JSRelativeTimeFormat>());
155   Style style_enum = maybe_style.FromJust();
156 
157   // 17. Set relativeTimeFormat.[[Style]] to s.
158 
159   // 18. Let numeric be ? GetOption(options, "numeric", "string",
160   //                                «"always", "auto"», "always").
161   Maybe<Numeric> maybe_numeric = GetStringOption<Numeric>(
162       isolate, options, "numeric", service, {"always", "auto"},
163       {Numeric::ALWAYS, Numeric::AUTO}, Numeric::ALWAYS);
164   MAYBE_RETURN(maybe_numeric, MaybeHandle<JSRelativeTimeFormat>());
165   Numeric numeric_enum = maybe_numeric.FromJust();
166 
167   // 19. Set relativeTimeFormat.[[Numeric]] to numeric.
168 
169   // 23. Let relativeTimeFormat.[[NumberFormat]] be
170   //     ? Construct(%NumberFormat%, « nfLocale, nfOptions »).
171   icu::NumberFormat* number_format =
172       icu::NumberFormat::createInstance(icu_locale, UNUM_DECIMAL, status);
173   if (U_FAILURE(status)) {
174     // Data build filter files excluded data in "rbnf_tree" since ECMA402 does
175     // not support "algorithmic" numbering systems. Therefore we may get the
176     // U_MISSING_RESOURCE_ERROR here. Fallback to locale without the numbering
177     // system and create the object again.
178     if (status == U_MISSING_RESOURCE_ERROR) {
179       delete number_format;
180       status = U_ZERO_ERROR;
181       icu_locale.setUnicodeKeywordValue("nu", nullptr, status);
182       DCHECK(U_SUCCESS(status));
183       number_format =
184           icu::NumberFormat::createInstance(icu_locale, UNUM_DECIMAL, status);
185     }
186     if (U_FAILURE(status) || number_format == nullptr) {
187       delete number_format;
188       THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
189                       JSRelativeTimeFormat);
190     }
191   }
192 
193   if (number_format->getDynamicClassID() ==
194       icu::DecimalFormat::getStaticClassID()) {
195     icu::DecimalFormat* decimal_format =
196         static_cast<icu::DecimalFormat*>(number_format);
197     decimal_format->setMinimumGroupingDigits(-2);
198   }
199 
200   // Change UDISPCTX_CAPITALIZATION_NONE to other values if
201   // ECMA402 later include option to change capitalization.
202   // Ref: https://github.com/tc39/proposal-intl-relative-time/issues/11
203   icu::RelativeDateTimeFormatter* icu_formatter =
204       new icu::RelativeDateTimeFormatter(icu_locale, number_format,
205                                          toIcuStyle(style_enum),
206                                          UDISPCTX_CAPITALIZATION_NONE, status);
207   if (U_FAILURE(status) || icu_formatter == nullptr) {
208     delete icu_formatter;
209     THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
210                     JSRelativeTimeFormat);
211   }
212 
213   Handle<String> numbering_system_string =
214       isolate->factory()->NewStringFromAsciiChecked(
215           Intl::GetNumberingSystem(icu_locale).c_str());
216 
217   Handle<Managed<icu::RelativeDateTimeFormatter>> managed_formatter =
218       Managed<icu::RelativeDateTimeFormatter>::FromRawPtr(isolate, 0,
219                                                           icu_formatter);
220 
221   Handle<JSRelativeTimeFormat> relative_time_format_holder =
222       Handle<JSRelativeTimeFormat>::cast(
223           isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
224 
225   DisallowGarbageCollection no_gc;
226   relative_time_format_holder->set_flags(0);
227   relative_time_format_holder->set_locale(*locale_str);
228   relative_time_format_holder->set_numberingSystem(*numbering_system_string);
229   relative_time_format_holder->set_numeric(numeric_enum);
230   relative_time_format_holder->set_icu_formatter(*managed_formatter);
231 
232   // 25. Return relativeTimeFormat.
233   return relative_time_format_holder;
234 }
235 
236 namespace {
237 
StyleAsString(Isolate * isolate,Style style)238 Handle<String> StyleAsString(Isolate* isolate, Style style) {
239   switch (style) {
240     case Style::LONG:
241       return ReadOnlyRoots(isolate).long_string_handle();
242     case Style::SHORT:
243       return ReadOnlyRoots(isolate).short_string_handle();
244     case Style::NARROW:
245       return ReadOnlyRoots(isolate).narrow_string_handle();
246   }
247   UNREACHABLE();
248 }
249 
250 }  // namespace
251 
ResolvedOptions(Isolate * isolate,Handle<JSRelativeTimeFormat> format_holder)252 Handle<JSObject> JSRelativeTimeFormat::ResolvedOptions(
253     Isolate* isolate, Handle<JSRelativeTimeFormat> format_holder) {
254   Factory* factory = isolate->factory();
255   icu::RelativeDateTimeFormatter* formatter =
256       format_holder->icu_formatter().raw();
257   DCHECK_NOT_NULL(formatter);
258   Handle<JSObject> result = factory->NewJSObject(isolate->object_function());
259   Handle<String> locale(format_holder->locale(), isolate);
260   Handle<String> numberingSystem(format_holder->numberingSystem(), isolate);
261   JSObject::AddProperty(isolate, result, factory->locale_string(), locale,
262                         NONE);
263   JSObject::AddProperty(
264       isolate, result, factory->style_string(),
265       StyleAsString(isolate, fromIcuStyle(formatter->getFormatStyle())), NONE);
266   JSObject::AddProperty(isolate, result, factory->numeric_string(),
267                         format_holder->NumericAsString(), NONE);
268   JSObject::AddProperty(isolate, result, factory->numberingSystem_string(),
269                         numberingSystem, NONE);
270   return result;
271 }
272 
NumericAsString() const273 Handle<String> JSRelativeTimeFormat::NumericAsString() const {
274   switch (numeric()) {
275     case Numeric::ALWAYS:
276       return GetReadOnlyRoots().always_string_handle();
277     case Numeric::AUTO:
278       return GetReadOnlyRoots().auto_string_handle();
279   }
280   UNREACHABLE();
281 }
282 
283 namespace {
284 
UnitAsString(Isolate * isolate,URelativeDateTimeUnit unit_enum)285 Handle<String> UnitAsString(Isolate* isolate, URelativeDateTimeUnit unit_enum) {
286   Factory* factory = isolate->factory();
287   switch (unit_enum) {
288     case UDAT_REL_UNIT_SECOND:
289       return factory->second_string();
290     case UDAT_REL_UNIT_MINUTE:
291       return factory->minute_string();
292     case UDAT_REL_UNIT_HOUR:
293       return factory->hour_string();
294     case UDAT_REL_UNIT_DAY:
295       return factory->day_string();
296     case UDAT_REL_UNIT_WEEK:
297       return factory->week_string();
298     case UDAT_REL_UNIT_MONTH:
299       return factory->month_string();
300     case UDAT_REL_UNIT_QUARTER:
301       return factory->quarter_string();
302     case UDAT_REL_UNIT_YEAR:
303       return factory->year_string();
304     default:
305       UNREACHABLE();
306   }
307 }
308 
GetURelativeDateTimeUnit(Handle<String> unit,URelativeDateTimeUnit * unit_enum)309 bool GetURelativeDateTimeUnit(Handle<String> unit,
310                               URelativeDateTimeUnit* unit_enum) {
311   std::unique_ptr<char[]> unit_str = unit->ToCString();
312   if ((strcmp("second", unit_str.get()) == 0) ||
313       (strcmp("seconds", unit_str.get()) == 0)) {
314     *unit_enum = UDAT_REL_UNIT_SECOND;
315   } else if ((strcmp("minute", unit_str.get()) == 0) ||
316              (strcmp("minutes", unit_str.get()) == 0)) {
317     *unit_enum = UDAT_REL_UNIT_MINUTE;
318   } else if ((strcmp("hour", unit_str.get()) == 0) ||
319              (strcmp("hours", unit_str.get()) == 0)) {
320     *unit_enum = UDAT_REL_UNIT_HOUR;
321   } else if ((strcmp("day", unit_str.get()) == 0) ||
322              (strcmp("days", unit_str.get()) == 0)) {
323     *unit_enum = UDAT_REL_UNIT_DAY;
324   } else if ((strcmp("week", unit_str.get()) == 0) ||
325              (strcmp("weeks", unit_str.get()) == 0)) {
326     *unit_enum = UDAT_REL_UNIT_WEEK;
327   } else if ((strcmp("month", unit_str.get()) == 0) ||
328              (strcmp("months", unit_str.get()) == 0)) {
329     *unit_enum = UDAT_REL_UNIT_MONTH;
330   } else if ((strcmp("quarter", unit_str.get()) == 0) ||
331              (strcmp("quarters", unit_str.get()) == 0)) {
332     *unit_enum = UDAT_REL_UNIT_QUARTER;
333   } else if ((strcmp("year", unit_str.get()) == 0) ||
334              (strcmp("years", unit_str.get()) == 0)) {
335     *unit_enum = UDAT_REL_UNIT_YEAR;
336   } else {
337     return false;
338   }
339   return true;
340 }
341 
342 template <typename T>
FormatCommon(Isolate * isolate,Handle<JSRelativeTimeFormat> format,Handle<Object> value_obj,Handle<Object> unit_obj,const char * func_name,const std::function<MaybeHandle<T> (Isolate *,const icu::FormattedRelativeDateTime &,Handle<Object>,Handle<String>)> & formatToResult)343 MaybeHandle<T> FormatCommon(
344     Isolate* isolate, Handle<JSRelativeTimeFormat> format,
345     Handle<Object> value_obj, Handle<Object> unit_obj, const char* func_name,
346     const std::function<
347         MaybeHandle<T>(Isolate*, const icu::FormattedRelativeDateTime&,
348                        Handle<Object>, Handle<String>)>& formatToResult) {
349   // 3. Let value be ? ToNumber(value).
350   Handle<Object> value;
351   ASSIGN_RETURN_ON_EXCEPTION(isolate, value,
352                              Object::ToNumber(isolate, value_obj), T);
353   double number = value->Number();
354   // 4. Let unit be ? ToString(unit).
355   Handle<String> unit;
356   ASSIGN_RETURN_ON_EXCEPTION(isolate, unit, Object::ToString(isolate, unit_obj),
357                              T);
358   // 4. If isFinite(value) is false, then throw a RangeError exception.
359   if (!std::isfinite(number)) {
360     THROW_NEW_ERROR(
361         isolate,
362         NewRangeError(MessageTemplate::kNotFiniteNumber,
363                       isolate->factory()->NewStringFromAsciiChecked(func_name)),
364         T);
365   }
366   icu::RelativeDateTimeFormatter* formatter = format->icu_formatter().raw();
367   DCHECK_NOT_NULL(formatter);
368   URelativeDateTimeUnit unit_enum;
369   if (!GetURelativeDateTimeUnit(unit, &unit_enum)) {
370     THROW_NEW_ERROR(
371         isolate,
372         NewRangeError(MessageTemplate::kInvalidUnit,
373                       isolate->factory()->NewStringFromAsciiChecked(func_name),
374                       unit),
375         T);
376   }
377   UErrorCode status = U_ZERO_ERROR;
378   icu::FormattedRelativeDateTime formatted =
379       (format->numeric() == JSRelativeTimeFormat::Numeric::ALWAYS)
380           ? formatter->formatNumericToValue(number, unit_enum, status)
381           : formatter->formatToValue(number, unit_enum, status);
382   if (U_FAILURE(status)) {
383     THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), T);
384   }
385   return formatToResult(isolate, formatted, value,
386                         UnitAsString(isolate, unit_enum));
387 }
388 
FormatToString(Isolate * isolate,const icu::FormattedRelativeDateTime & formatted,Handle<Object> value,Handle<String> unit)389 MaybeHandle<String> FormatToString(
390     Isolate* isolate, const icu::FormattedRelativeDateTime& formatted,
391     Handle<Object> value, Handle<String> unit) {
392   UErrorCode status = U_ZERO_ERROR;
393   icu::UnicodeString result = formatted.toString(status);
394   if (U_FAILURE(status)) {
395     THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), String);
396   }
397   return Intl::ToString(isolate, result);
398 }
399 
AddLiteral(Isolate * isolate,Handle<JSArray> array,const icu::UnicodeString & string,int32_t index,int32_t start,int32_t limit)400 Maybe<bool> AddLiteral(Isolate* isolate, Handle<JSArray> array,
401                        const icu::UnicodeString& string, int32_t index,
402                        int32_t start, int32_t limit) {
403   Handle<String> substring;
404   ASSIGN_RETURN_ON_EXCEPTION_VALUE(
405       isolate, substring, Intl::ToString(isolate, string, start, limit),
406       Nothing<bool>());
407   Intl::AddElement(isolate, array, index, isolate->factory()->literal_string(),
408                    substring);
409   return Just(true);
410 }
411 
AddUnit(Isolate * isolate,Handle<JSArray> array,const icu::UnicodeString & string,int32_t index,int32_t start,int32_t limit,int32_t field_id,Handle<Object> value,Handle<String> unit)412 Maybe<bool> AddUnit(Isolate* isolate, Handle<JSArray> array,
413                     const icu::UnicodeString& string, int32_t index,
414                     int32_t start, int32_t limit, int32_t field_id,
415                     Handle<Object> value, Handle<String> unit) {
416   Handle<String> substring;
417   ASSIGN_RETURN_ON_EXCEPTION_VALUE(
418       isolate, substring, Intl::ToString(isolate, string, start, limit),
419       Nothing<bool>());
420   Intl::AddElement(isolate, array, index,
421                    Intl::NumberFieldToType(isolate, value, field_id), substring,
422                    isolate->factory()->unit_string(), unit);
423   return Just(true);
424 }
425 
FormatToJSArray(Isolate * isolate,const icu::FormattedRelativeDateTime & formatted,Handle<Object> value,Handle<String> unit)426 MaybeHandle<JSArray> FormatToJSArray(
427     Isolate* isolate, const icu::FormattedRelativeDateTime& formatted,
428     Handle<Object> value, Handle<String> unit) {
429   UErrorCode status = U_ZERO_ERROR;
430   icu::UnicodeString string = formatted.toString(status);
431 
432   Factory* factory = isolate->factory();
433   Handle<JSArray> array = factory->NewJSArray(0);
434   icu::ConstrainedFieldPosition cfpos;
435   cfpos.constrainCategory(UFIELD_CATEGORY_NUMBER);
436   int32_t index = 0;
437 
438   int32_t previous_end = 0;
439   Handle<String> substring;
440   std::vector<std::pair<int32_t, int32_t>> groups;
441   while (formatted.nextPosition(cfpos, status) && U_SUCCESS(status)) {
442     int32_t category = cfpos.getCategory();
443     int32_t field = cfpos.getField();
444     int32_t start = cfpos.getStart();
445     int32_t limit = cfpos.getLimit();
446     if (category == UFIELD_CATEGORY_NUMBER) {
447       if (field == UNUM_GROUPING_SEPARATOR_FIELD) {
448         groups.push_back(std::pair<int32_t, int32_t>(start, limit));
449         continue;
450       }
451       if (start > previous_end) {
452         Maybe<bool> maybe_added =
453             AddLiteral(isolate, array, string, index++, previous_end, start);
454         MAYBE_RETURN(maybe_added, Handle<JSArray>());
455       }
456       if (field == UNUM_INTEGER_FIELD) {
457         for (auto start_limit : groups) {
458           if (start_limit.first > start) {
459             Maybe<bool> maybe_added =
460                 AddUnit(isolate, array, string, index++, start,
461                         start_limit.first, field, value, unit);
462             MAYBE_RETURN(maybe_added, Handle<JSArray>());
463             maybe_added = AddUnit(isolate, array, string, index++,
464                                   start_limit.first, start_limit.second,
465                                   UNUM_GROUPING_SEPARATOR_FIELD, value, unit);
466             MAYBE_RETURN(maybe_added, Handle<JSArray>());
467             start = start_limit.second;
468           }
469         }
470       }
471       Maybe<bool> maybe_added = AddUnit(isolate, array, string, index++, start,
472                                         limit, field, value, unit);
473       MAYBE_RETURN(maybe_added, Handle<JSArray>());
474       previous_end = limit;
475     }
476   }
477   if (U_FAILURE(status)) {
478     THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), JSArray);
479   }
480   if (string.length() > previous_end) {
481     Maybe<bool> maybe_added = AddLiteral(isolate, array, string, index,
482                                          previous_end, string.length());
483     MAYBE_RETURN(maybe_added, Handle<JSArray>());
484   }
485 
486   JSObject::ValidateElements(*array);
487   return array;
488 }
489 
490 }  // namespace
491 
Format(Isolate * isolate,Handle<Object> value_obj,Handle<Object> unit_obj,Handle<JSRelativeTimeFormat> format)492 MaybeHandle<String> JSRelativeTimeFormat::Format(
493     Isolate* isolate, Handle<Object> value_obj, Handle<Object> unit_obj,
494     Handle<JSRelativeTimeFormat> format) {
495   return FormatCommon<String>(isolate, format, value_obj, unit_obj,
496                               "Intl.RelativeTimeFormat.prototype.format",
497                               FormatToString);
498 }
499 
FormatToParts(Isolate * isolate,Handle<Object> value_obj,Handle<Object> unit_obj,Handle<JSRelativeTimeFormat> format)500 MaybeHandle<JSArray> JSRelativeTimeFormat::FormatToParts(
501     Isolate* isolate, Handle<Object> value_obj, Handle<Object> unit_obj,
502     Handle<JSRelativeTimeFormat> format) {
503   return FormatCommon<JSArray>(
504       isolate, format, value_obj, unit_obj,
505       "Intl.RelativeTimeFormat.prototype.formatToParts", FormatToJSArray);
506 }
507 
GetAvailableLocales()508 const std::set<std::string>& JSRelativeTimeFormat::GetAvailableLocales() {
509   // Since RelativeTimeFormatter does not have a method to list all
510   // available locales, work around by calling the DateFormat.
511   return Intl::GetAvailableLocalesForDateFormat();
512 }
513 
514 }  // namespace internal
515 }  // namespace v8
516