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 4239 : Handle<String> UnitAsString(Isolate* isolate, URelativeDateTimeUnit unit_enum) {
216 : Factory* factory = isolate->factory();
217 4239 : 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 4419 : bool GetURelativeDateTimeUnit(Handle<String> unit,
240 : URelativeDateTimeUnit* unit_enum) {
241 4419 : std::unique_ptr<char[]> unit_str = unit->ToCString();
242 8370 : if ((strcmp("second", unit_str.get()) == 0) ||
243 3951 : (strcmp("seconds", unit_str.get()) == 0)) {
244 531 : *unit_enum = UDAT_REL_UNIT_SECOND;
245 7308 : } else if ((strcmp("minute", unit_str.get()) == 0) ||
246 3420 : (strcmp("minutes", unit_str.get()) == 0)) {
247 513 : *unit_enum = UDAT_REL_UNIT_MINUTE;
248 6282 : } else if ((strcmp("hour", unit_str.get()) == 0) ||
249 2907 : (strcmp("hours", unit_str.get()) == 0)) {
250 513 : *unit_enum = UDAT_REL_UNIT_HOUR;
251 5139 : } else if ((strcmp("day", unit_str.get()) == 0) ||
252 2277 : (strcmp("days", unit_str.get()) == 0)) {
253 630 : *unit_enum = UDAT_REL_UNIT_DAY;
254 3996 : } else if ((strcmp("week", unit_str.get()) == 0) ||
255 1764 : (strcmp("weeks", unit_str.get()) == 0)) {
256 513 : *unit_enum = UDAT_REL_UNIT_WEEK;
257 2970 : } else if ((strcmp("month", unit_str.get()) == 0) ||
258 1251 : (strcmp("months", unit_str.get()) == 0)) {
259 513 : *unit_enum = UDAT_REL_UNIT_MONTH;
260 1944 : } else if ((strcmp("quarter", unit_str.get()) == 0) ||
261 738 : (strcmp("quarters", unit_str.get()) == 0)) {
262 513 : *unit_enum = UDAT_REL_UNIT_QUARTER;
263 918 : } else if ((strcmp("year", unit_str.get()) == 0) ||
264 225 : (strcmp("years", unit_str.get()) == 0)) {
265 513 : *unit_enum = UDAT_REL_UNIT_YEAR;
266 : } else {
267 : return false;
268 : }
269 : return true;
270 : }
271 :
272 : template <typename T>
273 6453 : MaybeHandle<T> FormatCommon(
274 : Isolate* isolate, Handle<JSRelativeTimeFormat> format,
275 : Handle<Object> value_obj, Handle<Object> unit_obj, const char* func_name,
276 : MaybeHandle<T> (*formatToResult)(Isolate*,
277 : const icu::FormattedRelativeDateTime&,
278 : Handle<Object>, Handle<String>)) {
279 : // 3. Let value be ? ToNumber(value).
280 : Handle<Object> value;
281 12906 : ASSIGN_RETURN_ON_EXCEPTION(isolate, value,
282 : Object::ToNumber(isolate, value_obj), T);
283 : double number = value->Number();
284 : // 4. Let unit be ? ToString(unit).
285 : Handle<String> unit;
286 12906 : ASSIGN_RETURN_ON_EXCEPTION(isolate, unit, Object::ToString(isolate, unit_obj),
287 : T);
288 : // 4. If isFinite(value) is false, then throw a RangeError exception.
289 6453 : if (!std::isfinite(number)) {
290 6102 : THROW_NEW_ERROR(
291 : isolate,
292 : NewRangeError(MessageTemplate::kNotFiniteNumber,
293 : isolate->factory()->NewStringFromAsciiChecked(func_name)),
294 : T);
295 : }
296 : icu::RelativeDateTimeFormatter* formatter = format->icu_formatter()->raw();
297 4419 : CHECK_NOT_NULL(formatter);
298 : URelativeDateTimeUnit unit_enum;
299 4419 : if (!GetURelativeDateTimeUnit(unit, &unit_enum)) {
300 540 : THROW_NEW_ERROR(
301 : isolate,
302 : NewRangeError(MessageTemplate::kInvalidUnit,
303 : isolate->factory()->NewStringFromAsciiChecked(func_name),
304 : unit),
305 : T);
306 : }
307 4239 : UErrorCode status = U_ZERO_ERROR;
308 : icu::FormattedRelativeDateTime formatted =
309 : (format->numeric() == JSRelativeTimeFormat::Numeric::ALWAYS)
310 : ? formatter->formatNumericToValue(number, unit_enum, status)
311 8478 : : formatter->formatToValue(number, unit_enum, status);
312 4239 : if (U_FAILURE(status)) {
313 0 : THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), T);
314 : }
315 4239 : return formatToResult(isolate, formatted, value,
316 4239 : UnitAsString(isolate, unit_enum));
317 : }
318 :
319 3816 : MaybeHandle<String> FormatToString(
320 : Isolate* isolate, const icu::FormattedRelativeDateTime& formatted,
321 : Handle<Object> value, Handle<String> unit) {
322 3816 : UErrorCode status = U_ZERO_ERROR;
323 7632 : icu::UnicodeString result = formatted.toString(status);
324 3816 : if (U_FAILURE(status)) {
325 0 : THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), String);
326 : }
327 3816 : return Intl::ToString(isolate, result);
328 : }
329 :
330 531 : Maybe<bool> AddLiteral(Isolate* isolate, Handle<JSArray> array,
331 : const icu::UnicodeString& string, int32_t index,
332 : int32_t start, int32_t limit) {
333 : Handle<String> substring;
334 1062 : ASSIGN_RETURN_ON_EXCEPTION_VALUE(
335 : isolate, substring, Intl::ToString(isolate, string, start, limit),
336 : Nothing<bool>());
337 : Intl::AddElement(isolate, array, index, isolate->factory()->literal_string(),
338 531 : substring);
339 : return Just(true);
340 : }
341 :
342 450 : Maybe<bool> AddUnit(Isolate* isolate, Handle<JSArray> array,
343 : const icu::UnicodeString& string, int32_t index,
344 : int32_t start, int32_t limit, int32_t field_id,
345 : Handle<Object> value, Handle<String> unit) {
346 : Handle<String> substring;
347 900 : ASSIGN_RETURN_ON_EXCEPTION_VALUE(
348 : isolate, substring, Intl::ToString(isolate, string, start, limit),
349 : Nothing<bool>());
350 450 : Intl::AddElement(isolate, array, index,
351 : Intl::NumberFieldToType(isolate, value, field_id), substring,
352 450 : isolate->factory()->unit_string(), unit);
353 : return Just(true);
354 : }
355 :
356 423 : MaybeHandle<JSArray> FormatToJSArray(
357 : Isolate* isolate, const icu::FormattedRelativeDateTime& formatted,
358 : Handle<Object> value, Handle<String> unit) {
359 423 : UErrorCode status = U_ZERO_ERROR;
360 846 : icu::UnicodeString string = formatted.toString(status);
361 :
362 : Factory* factory = isolate->factory();
363 : Handle<JSArray> array = factory->NewJSArray(0);
364 846 : icu::ConstrainedFieldPosition cfpos;
365 423 : cfpos.constrainCategory(UFIELD_CATEGORY_NUMBER);
366 : int32_t index = 0;
367 :
368 : int32_t previous_end = 0;
369 : Handle<String> substring;
370 : std::vector<std::pair<int32_t, int32_t>> groups;
371 864 : while (formatted.nextPosition(cfpos, status) && U_SUCCESS(status)) {
372 : int32_t category = cfpos.getCategory();
373 : int32_t field = cfpos.getField();
374 : int32_t start = cfpos.getStart();
375 : int32_t limit = cfpos.getLimit();
376 441 : if (category == UFIELD_CATEGORY_NUMBER) {
377 441 : if (field == UNUM_GROUPING_SEPARATOR_FIELD) {
378 9 : groups.push_back(std::pair<int32_t, int32_t>(start, limit));
379 9 : continue;
380 : }
381 432 : if (start > previous_end) {
382 : Maybe<bool> maybe_added =
383 108 : AddLiteral(isolate, array, string, index++, previous_end, start);
384 108 : MAYBE_RETURN(maybe_added, Handle<JSArray>());
385 : }
386 432 : if (field == UNUM_INTEGER_FIELD) {
387 423 : for (auto start_limit : groups) {
388 9 : if (start_limit.first > start) {
389 : Maybe<bool> maybe_added =
390 : AddUnit(isolate, array, string, index++, start,
391 9 : start_limit.first, field, value, unit);
392 9 : MAYBE_RETURN(maybe_added, Handle<JSArray>());
393 : maybe_added = AddUnit(isolate, array, string, index++,
394 : start_limit.first, start_limit.second,
395 9 : UNUM_GROUPING_SEPARATOR_FIELD, value, unit);
396 9 : MAYBE_RETURN(maybe_added, Handle<JSArray>());
397 : start = start_limit.second;
398 : }
399 : }
400 : }
401 : Maybe<bool> maybe_added = AddUnit(isolate, array, string, index++, start,
402 432 : limit, field, value, unit);
403 432 : MAYBE_RETURN(maybe_added, Handle<JSArray>());
404 : previous_end = limit;
405 : }
406 : }
407 423 : if (U_FAILURE(status)) {
408 0 : THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), JSArray);
409 : }
410 423 : if (string.length() > previous_end) {
411 : Maybe<bool> maybe_added = AddLiteral(isolate, array, string, index,
412 423 : previous_end, string.length());
413 423 : MAYBE_RETURN(maybe_added, Handle<JSArray>());
414 : }
415 :
416 423 : JSObject::ValidateElements(*array);
417 423 : return array;
418 : }
419 :
420 : } // namespace
421 :
422 5796 : MaybeHandle<String> JSRelativeTimeFormat::Format(
423 : Isolate* isolate, Handle<Object> value_obj, Handle<Object> unit_obj,
424 : Handle<JSRelativeTimeFormat> format) {
425 : return FormatCommon<String>(isolate, format, value_obj, unit_obj,
426 : "Intl.RelativeTimeFormat.prototype.format",
427 5796 : FormatToString);
428 : }
429 :
430 657 : MaybeHandle<JSArray> JSRelativeTimeFormat::FormatToParts(
431 : Isolate* isolate, Handle<Object> value_obj, Handle<Object> unit_obj,
432 : Handle<JSRelativeTimeFormat> format) {
433 : return FormatCommon<JSArray>(
434 : isolate, format, value_obj, unit_obj,
435 657 : "Intl.RelativeTimeFormat.prototype.formatToParts", FormatToJSArray);
436 : }
437 :
438 41 : const std::set<std::string>& JSRelativeTimeFormat::GetAvailableLocales() {
439 : // Since RelativeTimeFormatter does not have a method to list all
440 : // available locales, work around by calling the DateFormat.
441 797 : return Intl::GetAvailableLocalesForDateFormat();
442 : }
443 :
444 : } // namespace internal
445 121996 : } // namespace v8
|