V8 API Reference, 7.2.502.16 (for Deno 0.2.4)
js-relative-time-format.cc
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/heap/factory.h"
16 #include "src/isolate.h"
17 #include "src/objects-inl.h"
18 #include "src/objects/intl-objects.h"
19 #include "src/objects/js-number-format.h"
20 #include "src/objects/js-relative-time-format-inl.h"
21 #include "unicode/datefmt.h"
22 #include "unicode/numfmt.h"
23 #include "unicode/reldatefmt.h"
24 
25 namespace v8 {
26 namespace internal {
27 
28 namespace {
29 UDateRelativeDateTimeFormatterStyle getIcuStyle(
30  JSRelativeTimeFormat::Style style) {
31  switch (style) {
32  case JSRelativeTimeFormat::Style::LONG:
33  return UDAT_STYLE_LONG;
34  case JSRelativeTimeFormat::Style::SHORT:
35  return UDAT_STYLE_SHORT;
36  case JSRelativeTimeFormat::Style::NARROW:
37  return UDAT_STYLE_NARROW;
38  case JSRelativeTimeFormat::Style::COUNT:
39  UNREACHABLE();
40  }
41 }
42 } // namespace
43 
44 JSRelativeTimeFormat::Style JSRelativeTimeFormat::getStyle(const char* str) {
45  if (strcmp(str, "long") == 0) return JSRelativeTimeFormat::Style::LONG;
46  if (strcmp(str, "short") == 0) return JSRelativeTimeFormat::Style::SHORT;
47  if (strcmp(str, "narrow") == 0) return JSRelativeTimeFormat::Style::NARROW;
48  UNREACHABLE();
49 }
50 
51 JSRelativeTimeFormat::Numeric JSRelativeTimeFormat::getNumeric(
52  const char* str) {
53  if (strcmp(str, "auto") == 0) return JSRelativeTimeFormat::Numeric::AUTO;
54  if (strcmp(str, "always") == 0) return JSRelativeTimeFormat::Numeric::ALWAYS;
55  UNREACHABLE();
56 }
57 
58 MaybeHandle<JSRelativeTimeFormat> JSRelativeTimeFormat::Initialize(
59  Isolate* isolate, Handle<JSRelativeTimeFormat> relative_time_format_holder,
60  Handle<Object> locales, Handle<Object> input_options) {
61  relative_time_format_holder->set_flags(0);
62 
63  // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
64  Maybe<std::vector<std::string>> maybe_requested_locales =
65  Intl::CanonicalizeLocaleList(isolate, locales);
66  MAYBE_RETURN(maybe_requested_locales, Handle<JSRelativeTimeFormat>());
67  std::vector<std::string> requested_locales =
68  maybe_requested_locales.FromJust();
69 
70  // 2. If options is undefined, then
71  Handle<JSReceiver> options;
72  if (input_options->IsUndefined(isolate)) {
73  // 2. a. Let options be ObjectCreate(null).
74  options = isolate->factory()->NewJSObjectWithNullProto();
75  // 3. Else
76  } else {
77  // 3. a. Let options be ? ToObject(options).
78  ASSIGN_RETURN_ON_EXCEPTION(isolate, options,
79  Object::ToObject(isolate, input_options),
80  JSRelativeTimeFormat);
81  }
82 
83  // 4. Let opt be a new Record.
84  // 5. Let matcher be ? GetOption(options, "localeMatcher", "string", «
85  // "lookup", "best fit" », "best fit").
86  // 6. Set opt.[[localeMatcher]] to matcher.
87  Maybe<Intl::MatcherOption> maybe_locale_matcher =
88  Intl::GetLocaleMatcher(isolate, options, "Intl.RelativeTimeFormat");
89  MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSRelativeTimeFormat>());
90  Intl::MatcherOption matcher = maybe_locale_matcher.FromJust();
91 
92  // 7. Let localeData be %RelativeTimeFormat%.[[LocaleData]].
93  // 8. Let r be
94  // ResolveLocale(%RelativeTimeFormat%.[[AvailableLocales]],
95  // requestedLocales, opt,
96  // %RelativeTimeFormat%.[[RelevantExtensionKeys]], localeData).
97  Intl::ResolvedLocale r =
98  Intl::ResolveLocale(isolate, JSRelativeTimeFormat::GetAvailableLocales(),
99  requested_locales, matcher, {});
100 
101  // 9. Let locale be r.[[Locale]].
102  // 10. Set relativeTimeFormat.[[Locale]] to locale.
103  // 11. Let dataLocale be r.[[DataLocale]].
104  Handle<String> locale_str =
105  isolate->factory()->NewStringFromAsciiChecked(r.locale.c_str());
106  relative_time_format_holder->set_locale(*locale_str);
107 
108  // 12. Let s be ? GetOption(options, "style", "string",
109  // «"long", "short", "narrow"», "long").
110  Maybe<Style> maybe_style = Intl::GetStringOption<Style>(
111  isolate, options, "style", "Intl.RelativeTimeFormat",
112  {"long", "short", "narrow"}, {Style::LONG, Style::SHORT, Style::NARROW},
113  Style::LONG);
114  MAYBE_RETURN(maybe_style, MaybeHandle<JSRelativeTimeFormat>());
115  Style style_enum = maybe_style.FromJust();
116 
117  // 13. Set relativeTimeFormat.[[Style]] to s.
118  relative_time_format_holder->set_style(style_enum);
119 
120  // 14. Let numeric be ? GetOption(options, "numeric", "string",
121  // «"always", "auto"», "always").
122  Maybe<Numeric> maybe_numeric = Intl::GetStringOption<Numeric>(
123  isolate, options, "numeric", "Intl.RelativeTimeFormat",
124  {"always", "auto"}, {Numeric::ALWAYS, Numeric::AUTO}, Numeric::ALWAYS);
125  MAYBE_RETURN(maybe_numeric, MaybeHandle<JSRelativeTimeFormat>());
126  Numeric numeric_enum = maybe_numeric.FromJust();
127 
128  // 15. Set relativeTimeFormat.[[Numeric]] to numeric.
129  relative_time_format_holder->set_numeric(numeric_enum);
130 
131  icu::Locale icu_locale = r.icu_locale;
132  UErrorCode status = U_ZERO_ERROR;
133 
134  // 19. Let relativeTimeFormat.[[NumberFormat]] be
135  // ? Construct(%NumberFormat%, « nfLocale, nfOptions »).
136  icu::NumberFormat* number_format =
137  icu::NumberFormat::createInstance(icu_locale, UNUM_DECIMAL, status);
138  if (U_FAILURE(status)) {
139  delete number_format;
140  FATAL("Failed to create ICU number format, are ICU data files missing?");
141  }
142  CHECK_NOT_NULL(number_format);
143 
144  // Change UDISPCTX_CAPITALIZATION_NONE to other values if
145  // ECMA402 later include option to change capitalization.
146  // Ref: https://github.com/tc39/proposal-intl-relative-time/issues/11
147  icu::RelativeDateTimeFormatter* icu_formatter =
148  new icu::RelativeDateTimeFormatter(icu_locale, number_format,
149  getIcuStyle(style_enum),
150  UDISPCTX_CAPITALIZATION_NONE, status);
151  if (U_FAILURE(status)) {
152  delete icu_formatter;
153  FATAL(
154  "Failed to create ICU relative date time formatter, are ICU data files "
155  "missing?");
156  }
157  CHECK_NOT_NULL(icu_formatter);
158 
159  Handle<Managed<icu::RelativeDateTimeFormatter>> managed_formatter =
160  Managed<icu::RelativeDateTimeFormatter>::FromRawPtr(isolate, 0,
161  icu_formatter);
162 
163  // 21. Set relativeTimeFormat.[[InitializedRelativeTimeFormat]] to true.
164  relative_time_format_holder->set_icu_formatter(*managed_formatter);
165 
166  // 22. Return relativeTimeFormat.
167  return relative_time_format_holder;
168 }
169 
170 Handle<JSObject> JSRelativeTimeFormat::ResolvedOptions(
171  Isolate* isolate, Handle<JSRelativeTimeFormat> format_holder) {
172  Factory* factory = isolate->factory();
173  Handle<JSObject> result = factory->NewJSObject(isolate->object_function());
174  Handle<String> locale(format_holder->locale(), isolate);
175  JSObject::AddProperty(isolate, result, factory->locale_string(), locale,
176  NONE);
177  JSObject::AddProperty(isolate, result, factory->style_string(),
178  format_holder->StyleAsString(), NONE);
179  JSObject::AddProperty(isolate, result, factory->numeric_string(),
180  format_holder->NumericAsString(), NONE);
181  return result;
182 }
183 
184 Handle<String> JSRelativeTimeFormat::StyleAsString() const {
185  switch (style()) {
186  case Style::LONG:
187  return GetReadOnlyRoots().long_string_handle();
188  case Style::SHORT:
189  return GetReadOnlyRoots().short_string_handle();
190  case Style::NARROW:
191  return GetReadOnlyRoots().narrow_string_handle();
192  case Style::COUNT:
193  UNREACHABLE();
194  }
195 }
196 
197 Handle<String> JSRelativeTimeFormat::NumericAsString() const {
198  switch (numeric()) {
199  case Numeric::ALWAYS:
200  return GetReadOnlyRoots().always_string_handle();
201  case Numeric::AUTO:
202  return GetReadOnlyRoots().auto_string_handle();
203  case Numeric::COUNT:
204  UNREACHABLE();
205  }
206 }
207 
208 namespace {
209 
210 Handle<String> UnitAsString(Isolate* isolate, URelativeDateTimeUnit unit_enum) {
211  Factory* factory = isolate->factory();
212  switch (unit_enum) {
213  case UDAT_REL_UNIT_SECOND:
214  return factory->second_string();
215  case UDAT_REL_UNIT_MINUTE:
216  return factory->minute_string();
217  case UDAT_REL_UNIT_HOUR:
218  return factory->hour_string();
219  case UDAT_REL_UNIT_DAY:
220  return factory->day_string();
221  case UDAT_REL_UNIT_WEEK:
222  return factory->week_string();
223  case UDAT_REL_UNIT_MONTH:
224  return factory->month_string();
225  case UDAT_REL_UNIT_QUARTER:
226  return factory->quarter_string();
227  case UDAT_REL_UNIT_YEAR:
228  return factory->year_string();
229  default:
230  UNREACHABLE();
231  }
232 }
233 
234 MaybeHandle<JSArray> GenerateRelativeTimeFormatParts(
235  Isolate* isolate, const icu::UnicodeString& formatted,
236  const icu::UnicodeString& integer_part, URelativeDateTimeUnit unit_enum,
237  double number, const icu::NumberFormat& nf) {
238  Factory* factory = isolate->factory();
239  Handle<JSArray> array = factory->NewJSArray(0);
240  int32_t found = formatted.indexOf(integer_part);
241 
242  Handle<String> substring;
243  if (found < 0) {
244  // Cannot find the integer_part in the formatted.
245  // Return [{'type': 'literal', 'value': formatted}]
246  ASSIGN_RETURN_ON_EXCEPTION(isolate, substring,
247  Intl::ToString(isolate, formatted), JSArray);
248  Intl::AddElement(isolate, array,
249  0, // index
250  factory->literal_string(), // field_type_string
251  substring);
252  } else {
253  // Found the formatted integer in the result.
254  int index = 0;
255 
256  // array.push({
257  // 'type': 'literal',
258  // 'value': formatted.substring(0, found)})
259  if (found > 0) {
260  ASSIGN_RETURN_ON_EXCEPTION(isolate, substring,
261  Intl::ToString(isolate, formatted, 0, found),
262  JSArray);
263  Intl::AddElement(isolate, array, index++,
264  factory->literal_string(), // field_type_string
265  substring);
266  }
267 
268  Handle<String> unit = UnitAsString(isolate, unit_enum);
269 
270  Maybe<int> maybe_format_to_parts =
271  JSNumberFormat::FormatToParts(isolate, array, index, nf, number, unit);
272  MAYBE_RETURN(maybe_format_to_parts, Handle<JSArray>());
273  index = maybe_format_to_parts.FromJust();
274 
275  // array.push({
276  // 'type': 'literal',
277  // 'value': formatted.substring(
278  // found + integer_part.length, formatted.length)})
279  if (found + integer_part.length() < formatted.length()) {
280  ASSIGN_RETURN_ON_EXCEPTION(
281  isolate, substring,
282  Intl::ToString(isolate, formatted, found + integer_part.length(),
283  formatted.length()),
284  JSArray);
285  Intl::AddElement(isolate, array, index,
286  factory->literal_string(), // field_type_string
287  substring);
288  }
289  }
290  return array;
291 }
292 
293 bool GetURelativeDateTimeUnit(Handle<String> unit,
294  URelativeDateTimeUnit* unit_enum) {
295  std::unique_ptr<char[]> unit_str = unit->ToCString();
296  if ((strcmp("second", unit_str.get()) == 0) ||
297  (strcmp("seconds", unit_str.get()) == 0)) {
298  *unit_enum = UDAT_REL_UNIT_SECOND;
299  } else if ((strcmp("minute", unit_str.get()) == 0) ||
300  (strcmp("minutes", unit_str.get()) == 0)) {
301  *unit_enum = UDAT_REL_UNIT_MINUTE;
302  } else if ((strcmp("hour", unit_str.get()) == 0) ||
303  (strcmp("hours", unit_str.get()) == 0)) {
304  *unit_enum = UDAT_REL_UNIT_HOUR;
305  } else if ((strcmp("day", unit_str.get()) == 0) ||
306  (strcmp("days", unit_str.get()) == 0)) {
307  *unit_enum = UDAT_REL_UNIT_DAY;
308  } else if ((strcmp("week", unit_str.get()) == 0) ||
309  (strcmp("weeks", unit_str.get()) == 0)) {
310  *unit_enum = UDAT_REL_UNIT_WEEK;
311  } else if ((strcmp("month", unit_str.get()) == 0) ||
312  (strcmp("months", unit_str.get()) == 0)) {
313  *unit_enum = UDAT_REL_UNIT_MONTH;
314  } else if ((strcmp("quarter", unit_str.get()) == 0) ||
315  (strcmp("quarters", unit_str.get()) == 0)) {
316  *unit_enum = UDAT_REL_UNIT_QUARTER;
317  } else if ((strcmp("year", unit_str.get()) == 0) ||
318  (strcmp("years", unit_str.get()) == 0)) {
319  *unit_enum = UDAT_REL_UNIT_YEAR;
320  } else {
321  return false;
322  }
323  return true;
324 }
325 
326 } // namespace
327 
328 MaybeHandle<Object> JSRelativeTimeFormat::Format(
329  Isolate* isolate, Handle<Object> value_obj, Handle<Object> unit_obj,
330  Handle<JSRelativeTimeFormat> format_holder, const char* func_name,
331  bool to_parts) {
332  Factory* factory = isolate->factory();
333 
334  // 3. Let value be ? ToNumber(value).
335  Handle<Object> value;
336  ASSIGN_RETURN_ON_EXCEPTION(isolate, value,
337  Object::ToNumber(isolate, value_obj), Object);
338  double number = value->Number();
339  // 4. Let unit be ? ToString(unit).
340  Handle<String> unit;
341  ASSIGN_RETURN_ON_EXCEPTION(isolate, unit, Object::ToString(isolate, unit_obj),
342  Object);
343 
344  // 4. If isFinite(value) is false, then throw a RangeError exception.
345  if (!std::isfinite(number)) {
346  THROW_NEW_ERROR(
347  isolate,
348  NewRangeError(MessageTemplate::kNotFiniteNumber,
349  isolate->factory()->NewStringFromAsciiChecked(func_name)),
350  Object);
351  }
352 
353  icu::RelativeDateTimeFormatter* formatter =
354  format_holder->icu_formatter()->raw();
355  CHECK_NOT_NULL(formatter);
356 
357  URelativeDateTimeUnit unit_enum;
358  if (!GetURelativeDateTimeUnit(unit, &unit_enum)) {
359  THROW_NEW_ERROR(
360  isolate,
361  NewRangeError(MessageTemplate::kInvalidUnit,
362  isolate->factory()->NewStringFromAsciiChecked(func_name),
363  unit),
364  Object);
365  }
366 
367  UErrorCode status = U_ZERO_ERROR;
368  icu::UnicodeString formatted;
369 
370  if (format_holder->numeric() == JSRelativeTimeFormat::Numeric::ALWAYS) {
371  formatter->formatNumeric(number, unit_enum, formatted, status);
372  } else {
373  DCHECK_EQ(JSRelativeTimeFormat::Numeric::AUTO, format_holder->numeric());
374  formatter->format(number, unit_enum, formatted, status);
375  }
376 
377  if (U_FAILURE(status)) {
378  THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), Object);
379  }
380 
381  if (to_parts) {
382  icu::UnicodeString number_str;
383  icu::FieldPosition pos;
384  double abs_number = std::abs(number);
385  formatter->getNumberFormat().format(abs_number, number_str, pos, status);
386  if (U_FAILURE(status)) {
387  THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError),
388  Object);
389  }
390 
391  Handle<JSArray> elements;
392  ASSIGN_RETURN_ON_EXCEPTION(isolate, elements,
393  GenerateRelativeTimeFormatParts(
394  isolate, formatted, number_str, unit_enum,
395  abs_number, formatter->getNumberFormat()),
396  Object);
397  return elements;
398  }
399 
400  return factory->NewStringFromTwoByte(Vector<const uint16_t>(
401  reinterpret_cast<const uint16_t*>(formatted.getBuffer()),
402  formatted.length()));
403 }
404 
405 std::set<std::string> JSRelativeTimeFormat::GetAvailableLocales() {
406  int32_t num_locales = 0;
407  const icu::Locale* icu_available_locales =
408  icu::DateFormat::getAvailableLocales(num_locales);
409  return Intl::BuildLocaleSet(icu_available_locales, num_locales);
410 }
411 
412 } // namespace internal
413 } // namespace v8
Definition: libplatform.h:13