// Copyright 2022 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef V8_INTL_SUPPORT #error Internationalization is expected to be enabled. #endif // V8_INTL_SUPPORT #include "src/objects/js-duration-format.h" #include #include #include #include #include "src/execution/isolate.h" #include "src/heap/factory.h" #include "src/objects/intl-objects.h" #include "src/objects/js-duration-format-inl.h" #include "src/objects/js-number-format.h" #include "src/objects/js-temporal-objects.h" #include "src/objects/managed-inl.h" #include "src/objects/objects-inl.h" #include "src/objects/option-utils.h" #include "unicode/listformatter.h" #include "unicode/locid.h" #include "unicode/numberformatter.h" #include "unicode/ulistformatter.h" #include "unicode/unumberformatter.h" namespace v8 { namespace internal { using temporal::DurationRecord; namespace { // #sec-getdurationunitoptions enum class StylesList { k3Styles, k4Styles, k5Styles }; enum class UnitKind { kMinutesOrSeconds, kOthers }; struct DurationUnitOptions { JSDurationFormat::FieldStyle style; JSDurationFormat::Display display; }; Maybe GetDurationUnitOptions( Isolate* isolate, const char* unit, const char* display_field, Handle options, JSDurationFormat::Style base_style, StylesList styles_list, JSDurationFormat::FieldStyle prev_style, UnitKind unit_kind, const char* method_name) { JSDurationFormat::FieldStyle style; JSDurationFormat::FieldStyle digital_base; // 1. Let style be ? GetOption(options, unit, "string", stylesList, // undefined). switch (styles_list) { case StylesList::k3Styles: // For years, months, weeks, days MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE( isolate, style, GetStringOption( isolate, options, unit, method_name, {"long", "short", "narrow"}, {JSDurationFormat::FieldStyle::kLong, JSDurationFormat::FieldStyle::kShort, JSDurationFormat::FieldStyle::kNarrow}, JSDurationFormat::FieldStyle::kUndefined), Nothing()); digital_base = JSDurationFormat::FieldStyle::kShort; break; case StylesList::k4Styles: // For milliseconds, microseconds, nanoseconds MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE( isolate, style, GetStringOption( isolate, options, unit, method_name, {"long", "short", "narrow", "numeric"}, {JSDurationFormat::FieldStyle::kLong, JSDurationFormat::FieldStyle::kShort, JSDurationFormat::FieldStyle::kNarrow, JSDurationFormat::FieldStyle::kNumeric}, JSDurationFormat::FieldStyle::kUndefined), Nothing()); digital_base = JSDurationFormat::FieldStyle::kNumeric; break; case StylesList::k5Styles: // For hours, minutes, seconds MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE( isolate, style, GetStringOption( isolate, options, unit, method_name, {"long", "short", "narrow", "numeric", "2-digit"}, {JSDurationFormat::FieldStyle::kLong, JSDurationFormat::FieldStyle::kShort, JSDurationFormat::FieldStyle::kNarrow, JSDurationFormat::FieldStyle::kNumeric, JSDurationFormat::FieldStyle::k2Digit}, JSDurationFormat::FieldStyle::kUndefined), Nothing()); digital_base = JSDurationFormat::FieldStyle::kNumeric; break; } // 2. Let displayDefault be "always". JSDurationFormat::Display display_default = JSDurationFormat::Display::kAlways; // 3. If style is undefined, then if (style == JSDurationFormat::FieldStyle::kUndefined) { // a. If baseStyle is "digital", then if (base_style == JSDurationFormat::Style::kDigital) { // i. If unit is not one of "hours", "minutes", or "seconds", then if (styles_list != StylesList::k5Styles) { DCHECK_NE(0, strcmp(unit, "hours")); DCHECK_NE(0, strcmp(unit, "minutes")); DCHECK_NE(0, strcmp(unit, "seconds")); // a. Set displayDefault to "auto". display_default = JSDurationFormat::Display::kAuto; } // ii. Set style to digitalBase. style = digital_base; // b. Else } else { // i. Set displayDefault to "auto". display_default = JSDurationFormat::Display::kAuto; // ii. if prevStyle is "numeric" or "2-digit", then if (prev_style == JSDurationFormat::FieldStyle::kNumeric || prev_style == JSDurationFormat::FieldStyle::k2Digit) { // 1. Set style to "numeric". style = JSDurationFormat::FieldStyle::kNumeric; // iii. Else, } else { // 1. Set style to baseStyle. switch (base_style) { case JSDurationFormat::Style::kLong: style = JSDurationFormat::FieldStyle::kLong; break; case JSDurationFormat::Style::kShort: style = JSDurationFormat::FieldStyle::kShort; break; case JSDurationFormat::Style::kNarrow: style = JSDurationFormat::FieldStyle::kNarrow; break; default: UNREACHABLE(); } } } } // 4. Let displayField be the string-concatenation of unit and "Display". // 5. Let display be ? GetOption(options, displayField, "string", « "auto", // "always" », displayDefault). JSDurationFormat::Display display; MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE( isolate, display, GetStringOption( isolate, options, display_field, method_name, {"auto", "always"}, {JSDurationFormat::Display::kAuto, JSDurationFormat::Display::kAlways}, display_default), Nothing()); // 6. If prevStyle is "numeric" or "2-digit", then if (prev_style == JSDurationFormat::FieldStyle::kNumeric || prev_style == JSDurationFormat::FieldStyle::k2Digit) { // a. If style is not "numeric" or "2-digit", then if (style != JSDurationFormat::FieldStyle::kNumeric && style != JSDurationFormat::FieldStyle::k2Digit) { // i. Throw a RangeError exception. // b. Else if unit is "minutes" or "seconds", then } else if (unit_kind == UnitKind::kMinutesOrSeconds) { CHECK(strcmp(unit, "minutes") == 0 || strcmp(unit, "seconds") == 0); // i. Set style to "2-digit". style = JSDurationFormat::FieldStyle::k2Digit; } } // 7. Return the Record { [[Style]]: style, [[Display]]: display }. return Just(DurationUnitOptions({style, display})); } } // namespace MaybeHandle JSDurationFormat::New( Isolate* isolate, Handle map, Handle locales, Handle input_options) { Factory* factory = isolate->factory(); const char* method_name = "Intl.DurationFormat"; // 3. Let requestedLocales be ? CanonicalizeLocaleList(locales). std::vector requested_locales; MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE( isolate, requested_locales, Intl::CanonicalizeLocaleList(isolate, locales), Handle()); // 4. Let options be ? GetOptionsObject(options). Handle options; ASSIGN_RETURN_ON_EXCEPTION( isolate, options, GetOptionsObject(isolate, input_options, method_name), JSDurationFormat); // 5. Let matcher be ? GetOption(options, "localeMatcher", "string", « // "lookup", "best fit" », "best fit"). Intl::MatcherOption matcher; MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE( isolate, matcher, Intl::GetLocaleMatcher(isolate, options, method_name), Handle()); // 6. Let numberingSystem be ? GetOption(options, "numberingSystem", "string", // undefined, undefined). // // 7. If numberingSystem is not undefined, then // // a. If numberingSystem does not match the Unicode Locale Identifier type // nonterminal, throw a RangeError exception. // Note: The matching test and throw in Step 7-a is throw inside // Intl::GetNumberingSystem. std::unique_ptr numbering_system_str = nullptr; bool get; MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE( isolate, get, Intl::GetNumberingSystem(isolate, options, method_name, &numbering_system_str), Handle()); // 8. Let opt be the Record { [[localeMatcher]]: matcher, [[nu]]: // numberingSystem }. // 9. Let r be ResolveLocale(%DurationFormat%.[[AvailableLocales]], // requestedLocales, opt, %DurationFormat%.[[RelevantExtensionKeys]], // %DurationFormat%.[[LocaleData]]). std::set relevant_extension_keys{"nu"}; Intl::ResolvedLocale r; MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE( isolate, r, Intl::ResolveLocale(isolate, JSDurationFormat::GetAvailableLocales(), requested_locales, matcher, relevant_extension_keys), Handle()); // 10. Let locale be r.[[locale]]. icu::Locale r_locale = r.icu_locale; UErrorCode status = U_ZERO_ERROR; // 11. Set durationFormat.[[Locale]] to locale. // 12. Set durationFormat.[[NumberingSystem]] to r.[[nu]]. if (numbering_system_str != nullptr) { auto nu_extension_it = r.extensions.find("nu"); if (nu_extension_it != r.extensions.end() && nu_extension_it->second != numbering_system_str.get()) { r_locale.setUnicodeKeywordValue("nu", nullptr, status); DCHECK(U_SUCCESS(status)); } } icu::Locale icu_locale = r_locale; if (numbering_system_str != nullptr && Intl::IsValidNumberingSystem(numbering_system_str.get())) { r_locale.setUnicodeKeywordValue("nu", numbering_system_str.get(), status); DCHECK(U_SUCCESS(status)); } std::string numbering_system = Intl::GetNumberingSystem(r_locale); // 13. Let style be ? GetOption(options, "style", "string", « "long", "short", // "narrow", "digital" », "long"). Style style; MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE( isolate, style, GetStringOption