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/datefmt.h"
22 : #include "unicode/numfmt.h"
23 : #include "unicode/reldatefmt.h"
24 :
25 : namespace v8 {
26 : namespace internal {
27 :
28 : namespace {
29 738 : UDateRelativeDateTimeFormatterStyle getIcuStyle(
30 : JSRelativeTimeFormat::Style style) {
31 738 : switch (style) {
32 : case JSRelativeTimeFormat::Style::LONG:
33 : return UDAT_STYLE_LONG;
34 : case JSRelativeTimeFormat::Style::SHORT:
35 108 : return UDAT_STYLE_SHORT;
36 : case JSRelativeTimeFormat::Style::NARROW:
37 81 : return UDAT_STYLE_NARROW;
38 : case JSRelativeTimeFormat::Style::COUNT:
39 0 : UNREACHABLE();
40 : }
41 0 : }
42 : } // namespace
43 :
44 0 : JSRelativeTimeFormat::Style JSRelativeTimeFormat::getStyle(const char* str) {
45 0 : if (strcmp(str, "long") == 0) return JSRelativeTimeFormat::Style::LONG;
46 0 : if (strcmp(str, "short") == 0) return JSRelativeTimeFormat::Style::SHORT;
47 0 : if (strcmp(str, "narrow") == 0) return JSRelativeTimeFormat::Style::NARROW;
48 0 : UNREACHABLE();
49 : }
50 :
51 0 : JSRelativeTimeFormat::Numeric JSRelativeTimeFormat::getNumeric(
52 : const char* str) {
53 0 : if (strcmp(str, "auto") == 0) return JSRelativeTimeFormat::Numeric::AUTO;
54 0 : if (strcmp(str, "always") == 0) return JSRelativeTimeFormat::Numeric::ALWAYS;
55 0 : UNREACHABLE();
56 : }
57 :
58 783 : 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 783 : Intl::CanonicalizeLocaleList(isolate, locales);
66 783 : MAYBE_RETURN(maybe_requested_locales, Handle<JSRelativeTimeFormat>());
67 : std::vector<std::string> requested_locales =
68 783 : maybe_requested_locales.FromJust();
69 :
70 : // 2. If options is undefined, then
71 : Handle<JSReceiver> options;
72 1566 : if (input_options->IsUndefined(isolate)) {
73 : // 2. a. Let options be ObjectCreate(null).
74 324 : options = isolate->factory()->NewJSObjectWithNullProto();
75 : // 3. Else
76 : } else {
77 : // 3. a. Let options be ? ToObject(options).
78 918 : 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 783 : Intl::GetLocaleMatcher(isolate, options, "Intl.RelativeTimeFormat");
89 783 : 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 3780 : requested_locales, matcher, {"nu"});
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 756 : isolate->factory()->NewStringFromAsciiChecked(r.locale.c_str());
106 756 : 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 2268 : Style::LONG);
114 756 : MAYBE_RETURN(maybe_style, MaybeHandle<JSRelativeTimeFormat>());
115 : Style style_enum = maybe_style.FromJust();
116 :
117 : // 13. Set relativeTimeFormat.[[Style]] to s.
118 747 : 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 2241 : {"always", "auto"}, {Numeric::ALWAYS, Numeric::AUTO}, Numeric::ALWAYS);
125 747 : MAYBE_RETURN(maybe_numeric, MaybeHandle<JSRelativeTimeFormat>());
126 : Numeric numeric_enum = maybe_numeric.FromJust();
127 :
128 : // 15. Set relativeTimeFormat.[[Numeric]] to numeric.
129 738 : relative_time_format_holder->set_numeric(numeric_enum);
130 :
131 1476 : icu::Locale icu_locale = r.icu_locale;
132 738 : UErrorCode status = U_ZERO_ERROR;
133 :
134 : // 19. Let relativeTimeFormat.[[NumberFormat]] be
135 : // ? Construct(%NumberFormat%, « nfLocale, nfOptions »).
136 : icu::NumberFormat* number_format =
137 738 : icu::NumberFormat::createInstance(icu_locale, UNUM_DECIMAL, status);
138 738 : if (U_FAILURE(status)) {
139 0 : delete number_format;
140 0 : FATAL("Failed to create ICU number format, are ICU data files missing?");
141 : }
142 738 : 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 738 : UDISPCTX_CAPITALIZATION_NONE, status);
151 738 : if (U_FAILURE(status)) {
152 0 : delete icu_formatter;
153 : FATAL(
154 : "Failed to create ICU relative date time formatter, are ICU data files "
155 0 : "missing?");
156 : }
157 738 : CHECK_NOT_NULL(icu_formatter);
158 :
159 : Handle<Managed<icu::RelativeDateTimeFormatter>> managed_formatter =
160 : Managed<icu::RelativeDateTimeFormatter>::FromRawPtr(isolate, 0,
161 738 : icu_formatter);
162 :
163 : // 21. Set relativeTimeFormat.[[InitializedRelativeTimeFormat]] to true.
164 738 : relative_time_format_holder->set_icu_formatter(*managed_formatter);
165 :
166 : // 22. Return relativeTimeFormat.
167 738 : return relative_time_format_holder;
168 : }
169 :
170 495 : Handle<JSObject> JSRelativeTimeFormat::ResolvedOptions(
171 : Isolate* isolate, Handle<JSRelativeTimeFormat> format_holder) {
172 : Factory* factory = isolate->factory();
173 495 : Handle<JSObject> result = factory->NewJSObject(isolate->object_function());
174 990 : Handle<String> locale(format_holder->locale(), isolate);
175 : JSObject::AddProperty(isolate, result, factory->locale_string(), locale,
176 495 : NONE);
177 : JSObject::AddProperty(isolate, result, factory->style_string(),
178 990 : format_holder->StyleAsString(), NONE);
179 : JSObject::AddProperty(isolate, result, factory->numeric_string(),
180 990 : format_holder->NumericAsString(), NONE);
181 1485 : std::string locale_str(format_holder->locale()->ToCString().get());
182 990 : icu::Locale icu_locale = Intl::CreateICULocale(locale_str);
183 495 : std::string numbering_system = Intl::GetNumberingSystem(icu_locale);
184 : JSObject::AddProperty(
185 : isolate, result, factory->numberingSystem_string(),
186 990 : factory->NewStringFromAsciiChecked(numbering_system.c_str()), NONE);
187 990 : return result;
188 : }
189 :
190 495 : Handle<String> JSRelativeTimeFormat::StyleAsString() const {
191 495 : switch (style()) {
192 : case Style::LONG:
193 738 : return GetReadOnlyRoots().long_string_handle();
194 : case Style::SHORT:
195 144 : return GetReadOnlyRoots().short_string_handle();
196 : case Style::NARROW:
197 108 : return GetReadOnlyRoots().narrow_string_handle();
198 : case Style::COUNT:
199 0 : UNREACHABLE();
200 : }
201 0 : }
202 :
203 495 : Handle<String> JSRelativeTimeFormat::NumericAsString() const {
204 495 : switch (numeric()) {
205 : case Numeric::ALWAYS:
206 810 : return GetReadOnlyRoots().always_string_handle();
207 : case Numeric::AUTO:
208 180 : return GetReadOnlyRoots().auto_string_handle();
209 : case Numeric::COUNT:
210 0 : UNREACHABLE();
211 : }
212 0 : }
213 :
214 : namespace {
215 :
216 414 : Handle<String> UnitAsString(Isolate* isolate, URelativeDateTimeUnit unit_enum) {
217 : Factory* factory = isolate->factory();
218 414 : switch (unit_enum) {
219 : case UDAT_REL_UNIT_SECOND:
220 : return factory->second_string();
221 : case UDAT_REL_UNIT_MINUTE:
222 : return factory->minute_string();
223 : case UDAT_REL_UNIT_HOUR:
224 : return factory->hour_string();
225 : case UDAT_REL_UNIT_DAY:
226 : return factory->day_string();
227 : case UDAT_REL_UNIT_WEEK:
228 : return factory->week_string();
229 : case UDAT_REL_UNIT_MONTH:
230 : return factory->month_string();
231 : case UDAT_REL_UNIT_QUARTER:
232 : return factory->quarter_string();
233 : case UDAT_REL_UNIT_YEAR:
234 : return factory->year_string();
235 : default:
236 0 : UNREACHABLE();
237 : }
238 : }
239 :
240 423 : MaybeHandle<JSArray> GenerateRelativeTimeFormatParts(
241 : Isolate* isolate, const icu::UnicodeString& formatted,
242 : const icu::UnicodeString& integer_part, URelativeDateTimeUnit unit_enum,
243 : double number, const icu::NumberFormat& nf) {
244 : Factory* factory = isolate->factory();
245 423 : Handle<JSArray> array = factory->NewJSArray(0);
246 423 : int32_t found = formatted.indexOf(integer_part);
247 :
248 : Handle<String> substring;
249 423 : if (found < 0) {
250 : // Cannot find the integer_part in the formatted.
251 : // Return [{'type': 'literal', 'value': formatted}]
252 18 : ASSIGN_RETURN_ON_EXCEPTION(isolate, substring,
253 : Intl::ToString(isolate, formatted), JSArray);
254 : Intl::AddElement(isolate, array,
255 : 0, // index
256 : factory->literal_string(), // field_type_string
257 9 : substring);
258 : } else {
259 : // Found the formatted integer in the result.
260 : int index = 0;
261 :
262 : // array.push({
263 : // 'type': 'literal',
264 : // 'value': formatted.substring(0, found)})
265 414 : if (found > 0) {
266 216 : ASSIGN_RETURN_ON_EXCEPTION(isolate, substring,
267 : Intl::ToString(isolate, formatted, 0, found),
268 : JSArray);
269 : Intl::AddElement(isolate, array, index++,
270 : factory->literal_string(), // field_type_string
271 108 : substring);
272 : }
273 :
274 414 : Handle<String> unit = UnitAsString(isolate, unit_enum);
275 :
276 : Maybe<int> maybe_format_to_parts =
277 414 : JSNumberFormat::FormatToParts(isolate, array, index, nf, number, 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 6453 : 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 4068 : 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 360 : 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 : 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 : 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 846 : 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 797 : std::set<std::string> JSRelativeTimeFormat::GetAvailableLocales() {
412 797 : int32_t num_locales = 0;
413 : const icu::Locale* icu_available_locales =
414 797 : icu::DateFormat::getAvailableLocales(num_locales);
415 797 : return Intl::BuildLocaleSet(icu_available_locales, num_locales);
416 : }
417 :
418 : } // namespace internal
419 183867 : } // namespace v8
|