LCOV - code coverage report
Current view: top level - src/objects - js-relative-time-format.cc (source / functions) Hit Total Coverage
Test: app.info Lines: 121 143 84.6 %
Date: 2019-03-21 Functions: 11 13 84.6 %

          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

Generated by: LCOV version 1.10