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