/* * Copyright (c) 2021 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "ecmascript/js_relative_time_format.h" #include "ecmascript/object_factory-inl.h" #include "unicode/decimfmt.h" #include "unicode/numfmt.h" #include "unicode/unum.h" namespace panda::ecmascript { // 14.1.1 InitializeRelativeTimeFormat ( relativeTimeFormat, locales, options ) JSHandle JSRelativeTimeFormat::InitializeRelativeTimeFormat( JSThread *thread, const JSHandle &relativeTimeFormat, const JSHandle &locales, const JSHandle &options) { EcmaVM *ecmaVm = thread->GetEcmaVM(); ObjectFactory *factory = ecmaVm->GetFactory(); // 1.Let requestedLocales be ? CanonicalizeLocaleList(locales). JSHandle requestedLocales = intl::LocaleHelper::CanonicalizeLocaleList(thread, locales); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread); // 2&3. If options is undefined, then Let options be ObjectCreate(null). else Let options be ? ToObject(options). JSHandle rtfOptions; if (!options->IsUndefined()) { rtfOptions = JSTaggedValue::ToObject(thread, options); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread); } else { rtfOptions = factory->CreateNullJSObject(); } // 5. Let matcher be ? GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit"). auto globalConst = thread->GlobalConstants(); LocaleMatcherOption matcher = JSLocale::GetOptionOfString(thread, rtfOptions, globalConst->GetHandledLocaleMatcherString(), {LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT}, {"lookup", "best fit"}, LocaleMatcherOption::BEST_FIT); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread); // 7. Let numberingSystem be ? GetOption(options, "numberingSystem", "string", undefined, undefined). JSHandle property = globalConst->GetHandledNumberingSystemString(); JSHandle undefinedValue(thread, JSTaggedValue::Undefined()); JSHandle numberingSystemValue = JSLocale::GetOption(thread, rtfOptions, property, OptionType::STRING, undefinedValue, undefinedValue); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread); // Check whether numberingSystem is well formed and set to %RelativeTimeFormat%.[[numberingSystem]] std::string numberingSystemStdStr; if (!numberingSystemValue->IsUndefined()) { JSHandle numberingSystemString = JSHandle::Cast(numberingSystemValue); if (EcmaStringAccessor(numberingSystemString).IsUtf16()) { THROW_RANGE_ERROR_AND_RETURN(thread, "invalid numberingSystem", relativeTimeFormat); } numberingSystemStdStr = intl::LocaleHelper::ConvertToStdString(numberingSystemString); if (!JSLocale::IsNormativeNumberingSystem(numberingSystemStdStr)) { THROW_RANGE_ERROR_AND_RETURN(thread, "invalid numberingSystem", relativeTimeFormat); } } // 10. Let localeData be %RelativeTimeFormat%.[[LocaleData]]. // 11. Let r be ResolveLocale(%RelativeTimeFormat%.[[AvailableLocales]], requestedLocales, opt, // %RelativeTimeFormat%.[[RelevantExtensionKeys]], localeData). JSHandle availableLocales; if (requestedLocales->GetLength() == 0) { availableLocales = factory->EmptyArray(); } else { std::vector availableStringLocales = intl::LocaleHelper::GetAvailableLocales(thread, "calendar", nullptr); availableLocales = JSLocale::ConstructLocaleList(thread, availableStringLocales); } std::set relevantExtensionKeys{"nu"}; ResolvedLocale r = JSLocale::ResolveLocale(thread, availableLocales, requestedLocales, matcher, relevantExtensionKeys); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread); icu::Locale icuLocale = r.localeData; // 12. Let locale be r.[[Locale]]. JSHandle localeStr = intl::LocaleHelper::ToLanguageTag(thread, icuLocale); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread); // 13. Set relativeTimeFormat.[[Locale]] to locale. relativeTimeFormat->SetLocale(thread, localeStr.GetTaggedValue()); // 15. Set relativeTimeFormat.[[NumberingSystem]] to r.[[nu]]. UErrorCode status = U_ZERO_ERROR; if (!numberingSystemStdStr.empty()) { if (JSLocale::IsWellNumberingSystem(numberingSystemStdStr)) { icuLocale.setUnicodeKeywordValue("nu", numberingSystemStdStr, status); ASSERT(U_SUCCESS(status)); } } // 16. Let s be ? GetOption(options, "style", "string", «"long", "short", "narrow"», "long"). property = globalConst->GetHandledStyleString(); RelativeStyleOption styleOption = JSLocale::GetOptionOfString(thread, rtfOptions, property, {RelativeStyleOption::LONG, RelativeStyleOption::SHORT, RelativeStyleOption::NARROW}, {"long", "short", "narrow"}, RelativeStyleOption::LONG); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread); // 17. Set relativeTimeFormat.[[Style]] to s. relativeTimeFormat->SetStyle(styleOption); // 18. Let numeric be ? GetOption(options, "numeric", "string", ?"always", "auto"?, "always"). property = globalConst->GetHandledNumericString(); NumericOption numericOption = JSLocale::GetOptionOfString(thread, rtfOptions, property, {NumericOption::ALWAYS, NumericOption::AUTO}, {"always", "auto"}, NumericOption::ALWAYS); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread); // 19. Set relativeTimeFormat.[[Numeric]] to numeric. relativeTimeFormat->SetNumeric(numericOption); // 20. Let relativeTimeFormat.[[NumberFormat]] be ! Construct(%NumberFormat%, « locale »). icu::NumberFormat *icuNumberFormat = icu::NumberFormat::createInstance(icuLocale, UNUM_DECIMAL, status); if (U_FAILURE(status) != 0) { delete icuNumberFormat; if (status == UErrorCode::U_MISSING_RESOURCE_ERROR) { THROW_REFERENCE_ERROR_AND_RETURN(thread, "can not find icu data resources", relativeTimeFormat); } THROW_RANGE_ERROR_AND_RETURN(thread, "create icu::NumberFormat failed", relativeTimeFormat); } // Display grouping using the default strategy for all locales if (icuNumberFormat->getDynamicClassID() == icu::DecimalFormat::getStaticClassID()) { icu::DecimalFormat* icuDecimalFormat = static_cast(icuNumberFormat); icuDecimalFormat->setMinimumGroupingDigits(UNUM_MINIMUM_GROUPING_DIGITS_AUTO); } // Trans RelativeStyleOption to ICU Style UDateRelativeDateTimeFormatterStyle uStyle; switch (styleOption) { case RelativeStyleOption::LONG: uStyle = UDAT_STYLE_LONG; break; case RelativeStyleOption::SHORT: uStyle = UDAT_STYLE_SHORT; break; case RelativeStyleOption::NARROW: uStyle = UDAT_STYLE_NARROW; break; default: LOG_ECMA(FATAL) << "this branch is unreachable"; UNREACHABLE(); } icu::RelativeDateTimeFormatter rtfFormatter(icuLocale, icuNumberFormat, uStyle, UDISPCTX_CAPITALIZATION_NONE, status); if (U_FAILURE(status) != 0) { THROW_RANGE_ERROR_AND_RETURN(thread, "icu Formatter Error", relativeTimeFormat); } std::string numberingSystem = JSLocale::GetNumberingSystem(icuLocale); auto result = factory->NewFromStdString(numberingSystem); relativeTimeFormat->SetNumberingSystem(thread, result); // Set RelativeTimeFormat.[[IcuRelativeTimeFormatter]] factory->NewJSIntlIcuData(relativeTimeFormat, rtfFormatter, JSRelativeTimeFormat::FreeIcuRTFFormatter); // 22. Return relativeTimeFormat. return relativeTimeFormat; } // 14.1.2 SingularRelativeTimeUnit ( unit ) bool SingularUnitToIcuUnit(JSThread *thread, const JSHandle &unit, URelativeDateTimeUnit *unitEnum) { // 1. Assert: Type(unit) is String. ASSERT(JSHandle::Cast(unit)->IsString()); // 2. If unit is "seconds" or "second", return "second". // 3. If unit is "minutes" or "minute", return "minute". // 4. If unit is "hours" or "hour", return "hour". // 5. If unit is "days" or "day", return "day". // 6. If unit is "weeks" or "week", return "week". // 7. If unit is "months" or "month", return "month". // 8. If unit is "quarters" or "quarter", return "quarter". // 9. If unit is "years" or "year", return "year". auto globalConst = thread->GlobalConstants(); JSHandle second = JSHandle::Cast(globalConst->GetHandledSecondString()); JSHandle minute = JSHandle::Cast(globalConst->GetHandledMinuteString()); JSHandle hour = JSHandle::Cast(globalConst->GetHandledHourString()); JSHandle day = JSHandle::Cast(globalConst->GetHandledDayString()); JSHandle week = JSHandle::Cast(globalConst->GetHandledWeekString()); JSHandle month = JSHandle::Cast(globalConst->GetHandledMonthString()); JSHandle quarter = JSHandle::Cast(globalConst->GetHandledQuarterString()); JSHandle year = JSHandle::Cast(globalConst->GetHandledYearString()); JSHandle seconds = JSHandle::Cast(globalConst->GetHandledSecondsString()); JSHandle minutes = JSHandle::Cast(globalConst->GetHandledMinutesString()); JSHandle hours = JSHandle::Cast(globalConst->GetHandledHoursString()); JSHandle days = JSHandle::Cast(globalConst->GetHandledDaysString()); JSHandle weeks = JSHandle::Cast(globalConst->GetHandledWeeksString()); JSHandle months = JSHandle::Cast(globalConst->GetHandledMonthsString()); JSHandle quarters = JSHandle::Cast(globalConst->GetHandledQuartersString()); JSHandle years = JSHandle::Cast(globalConst->GetHandledYearsString()); if (EcmaStringAccessor::StringsAreEqual(*second, *unit) || EcmaStringAccessor::StringsAreEqual(*seconds, *unit)) { *unitEnum = UDAT_REL_UNIT_SECOND; } else if (EcmaStringAccessor::StringsAreEqual(*minute, *unit) || EcmaStringAccessor::StringsAreEqual(*minutes, *unit)) { *unitEnum = UDAT_REL_UNIT_MINUTE; } else if (EcmaStringAccessor::StringsAreEqual(*hour, *unit) || EcmaStringAccessor::StringsAreEqual(*hours, *unit)) { *unitEnum = UDAT_REL_UNIT_HOUR; } else if (EcmaStringAccessor::StringsAreEqual(*day, *unit) || EcmaStringAccessor::StringsAreEqual(*days, *unit)) { *unitEnum = UDAT_REL_UNIT_DAY; } else if (EcmaStringAccessor::StringsAreEqual(*week, *unit) || EcmaStringAccessor::StringsAreEqual(*weeks, *unit)) { *unitEnum = UDAT_REL_UNIT_WEEK; } else if (EcmaStringAccessor::StringsAreEqual(*month, *unit) || EcmaStringAccessor::StringsAreEqual(*months, *unit)) { *unitEnum = UDAT_REL_UNIT_MONTH; } else if (EcmaStringAccessor::StringsAreEqual(*quarter, *unit) || EcmaStringAccessor::StringsAreEqual(*quarters, *unit)) { *unitEnum = UDAT_REL_UNIT_QUARTER; } else if (EcmaStringAccessor::StringsAreEqual(*year, *unit) || EcmaStringAccessor::StringsAreEqual(*years, *unit)) { *unitEnum = UDAT_REL_UNIT_YEAR; } else { return false; } // 11. else return unit. return true; } // Unwrap RelativeTimeFormat JSHandle JSRelativeTimeFormat::UnwrapRelativeTimeFormat(JSThread *thread, const JSHandle &rtf) { ASSERT_PRINT(rtf->IsJSObject(), "rtf is not a JSObject"); JSHandle env = thread->GetEcmaVM()->GetGlobalEnv(); bool isInstanceOf = JSFunction::InstanceOf(thread, rtf, env->GetRelativeTimeFormatFunction()); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, rtf); if (!rtf->IsJSRelativeTimeFormat() && isInstanceOf) { JSHandle key(thread, JSHandle::Cast(env->GetIntlFunction())->GetFallbackSymbol()); OperationResult operationResult = JSTaggedValue::GetProperty(thread, rtf, key); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, rtf); return operationResult.GetValue(); } // Perform ? RequireInternalSlot(relativeTimeFormat, [[InitializedRelativeTimeFormat]]). if (!rtf->IsJSRelativeTimeFormat()) { RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, rtf); } return rtf; } // CommonFormat icu::FormattedRelativeDateTime GetIcuFormatted(JSThread *thread, const JSHandle &relativeTimeFormat, double value, const JSHandle &unit) { icu::RelativeDateTimeFormatter *formatter = relativeTimeFormat->GetIcuRTFFormatter(); ASSERT_PRINT(formatter != nullptr, "rtfFormatter is null"); // If isFinite(value) is false, then throw a RangeError exception. if (!std::isfinite(value)) { THROW_RANGE_ERROR_AND_RETURN(thread, "", icu::FormattedRelativeDateTime()); } // 10. If unit is not one of "second", "minute", "hour", "day", "week", "month", "quarter", or "year", throw a // RangeError exception. URelativeDateTimeUnit unitEnum; if (!SingularUnitToIcuUnit(thread, unit, &unitEnum)) { THROW_RANGE_ERROR_AND_RETURN(thread, "", icu::FormattedRelativeDateTime()); } UErrorCode status = U_ZERO_ERROR; NumericOption numeric = relativeTimeFormat->GetNumeric(); icu::FormattedRelativeDateTime formatted; switch (numeric) { case NumericOption::ALWAYS: formatted = formatter->formatNumericToValue(value, unitEnum, status); ASSERT_PRINT(U_SUCCESS(status), "icu format to value error"); break; case NumericOption::AUTO: formatted = formatter->formatToValue(value, unitEnum, status); ASSERT_PRINT(U_SUCCESS(status), "icu format to value error"); break; default: LOG_ECMA(FATAL) << "this branch is unreachable"; UNREACHABLE(); } return formatted; } // 14.1.2 SingularRelativeTimeUnit ( unit ) JSHandle SingularUnitString(JSThread *thread, const JSHandle &unit) { auto globalConst = thread->GlobalConstants(); JSHandle second = JSHandle::Cast(globalConst->GetHandledSecondString()); JSHandle minute = JSHandle::Cast(globalConst->GetHandledMinuteString()); JSHandle hour = JSHandle::Cast(globalConst->GetHandledHourString()); JSHandle day = JSHandle::Cast(globalConst->GetHandledDayString()); JSHandle week = JSHandle::Cast(globalConst->GetHandledWeekString()); JSHandle month = JSHandle::Cast(globalConst->GetHandledMonthString()); JSHandle quarter = JSHandle::Cast(globalConst->GetHandledQuarterString()); JSHandle year = JSHandle::Cast(globalConst->GetHandledYearString()); JSHandle seconds = JSHandle::Cast(globalConst->GetHandledSecondsString()); JSHandle minutes = JSHandle::Cast(globalConst->GetHandledMinutesString()); JSHandle hours = JSHandle::Cast(globalConst->GetHandledHoursString()); JSHandle days = JSHandle::Cast(globalConst->GetHandledDaysString()); JSHandle weeks = JSHandle::Cast(globalConst->GetHandledWeeksString()); JSHandle months = JSHandle::Cast(globalConst->GetHandledMonthsString()); JSHandle quarters = JSHandle::Cast(globalConst->GetHandledQuartersString()); JSHandle years = JSHandle::Cast(globalConst->GetHandledYearsString()); // 2. If unit is "seconds" or "second", return "second". if (EcmaStringAccessor::StringsAreEqual(*second, *unit) || EcmaStringAccessor::StringsAreEqual(*seconds, *unit)) { return second; } // 3. If unit is "minutes" or "minute", return "minute". if (EcmaStringAccessor::StringsAreEqual(*minute, *unit) || EcmaStringAccessor::StringsAreEqual(*minutes, *unit)) { return minute; } // 4. If unit is "hours" or "hour", return "hour". if (EcmaStringAccessor::StringsAreEqual(*hour, *unit) || EcmaStringAccessor::StringsAreEqual(*hours, *unit)) { return hour; } // 5. If unit is "days" or "day", return "day". if (EcmaStringAccessor::StringsAreEqual(*day, *unit) || EcmaStringAccessor::StringsAreEqual(*days, *unit)) { return day; } // 6. If unit is "weeks" or "week", return "week". if (EcmaStringAccessor::StringsAreEqual(*week, *unit) || EcmaStringAccessor::StringsAreEqual(*weeks, *unit)) { return week; } // 7. If unit is "months" or "month", return "month". if (EcmaStringAccessor::StringsAreEqual(*month, *unit) || EcmaStringAccessor::StringsAreEqual(*months, *unit)) { return month; } // 8. If unit is "quarters" or "quarter", return "quarter". if (EcmaStringAccessor::StringsAreEqual(*quarter, *unit) || EcmaStringAccessor::StringsAreEqual(*quarters, *unit)) { return quarter; } // 9. If unit is "years" or "year", return "year". if (EcmaStringAccessor::StringsAreEqual(*year, *unit) || EcmaStringAccessor::StringsAreEqual(*years, *unit)) { return year; } JSHandle undefinedValue(thread, JSTaggedValue::Undefined()); return JSHandle::Cast(undefinedValue); } // 14.1.5 FormatRelativeTime ( relativeTimeFormat, value, unit ) JSHandle JSRelativeTimeFormat::Format(JSThread *thread, double value, const JSHandle &unit, const JSHandle &relativeTimeFormat) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); icu::FormattedRelativeDateTime formatted = GetIcuFormatted(thread, relativeTimeFormat, value, unit); RETURN_HANDLE_IF_ABRUPT_COMPLETION(EcmaString, thread); UErrorCode status = U_ZERO_ERROR; icu::UnicodeString uString = formatted.toString(status); if (U_FAILURE(status) != 0) { THROW_RANGE_ERROR_AND_RETURN(thread, "icu formatted toString error", factory->GetEmptyString()); } JSHandle string = factory->NewFromUtf16(reinterpret_cast(uString.getBuffer()), uString.length()); return string; } void FormatToArray(JSThread *thread, const JSHandle &array, const icu::FormattedRelativeDateTime &formatted, double value, const JSHandle &unit) { UErrorCode status = U_ZERO_ERROR; icu::UnicodeString formattedText = formatted.toString(status); if (U_FAILURE(status)) { THROW_TYPE_ERROR(thread, "formattedRelativeDateTime toString failed"); } icu::ConstrainedFieldPosition cfpo; // Set constrainCategory to UFIELD_CATEGORY_NUMBER which is specified for UNumberFormatFields cfpo.constrainCategory(UFIELD_CATEGORY_NUMBER); int32_t index = 0; int32_t previousLimit = 0; auto globalConst = thread->GlobalConstants(); JSHandle taggedValue(thread, JSTaggedValue(value)); JSMutableHandle typeString(thread, JSTaggedValue::Undefined()); JSHandle unitString = globalConst->GetHandledUnitString(); std::vector> separatorFields; /** * From ICU header file document @unumberformatter.h * Sets a constraint on the field category. * * When this instance of ConstrainedFieldPosition is passed to FormattedValue#nextPosition, * positions are skipped unless they have the given category. * * Any previously set constraints are cleared. * * For example, to loop over only the number-related fields: * * ConstrainedFieldPosition cfpo; * cfpo.constrainCategory(UFIELDCATEGORY_NUMBER_FORMAT); * while (fmtval.nextPosition(cfpo, status)) { * // handle the number-related field position * } */ while ((formatted.nextPosition(cfpo, status) != 0)) { int32_t fieldId = cfpo.getField(); int32_t start = cfpo.getStart(); int32_t limit = cfpo.getLimit(); // Special case when fieldId is UNUM_GROUPING_SEPARATOR_FIELD if (static_cast(fieldId) == UNUM_GROUPING_SEPARATOR_FIELD) { separatorFields.push_back(std::pair(start, limit)); continue; } // If start greater than previousLimit, means a literal type exists before number fields // so add a literal type with value of formattedText.sub(0, start) if (start > previousLimit) { typeString.Update(globalConst->GetLiteralString()); JSHandle substring = intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, start); JSLocale::PutElement(thread, index++, array, typeString, JSHandle::Cast(substring)); RETURN_IF_ABRUPT_COMPLETION(thread); } // Add part when type is unit // Iterate former grouping separator vector and add unit element to array for (auto it = separatorFields.begin(); it != separatorFields.end(); it++) { if (it->first > start) { // Add Integer type element JSHandle resString = intl::LocaleHelper::UStringToString(thread, formattedText, start, it->first); typeString.Update( JSLocale::GetNumberFieldType(thread, taggedValue.GetTaggedValue(), fieldId).GetTaggedValue()); JSHandle record = JSLocale::PutElement(thread, index++, array, typeString, JSHandle::Cast(resString)); RETURN_IF_ABRUPT_COMPLETION(thread); JSObject::CreateDataPropertyOrThrow(thread, record, unitString, JSHandle::Cast(unit)); RETURN_IF_ABRUPT_COMPLETION(thread); // Add Group type element resString = intl::LocaleHelper::UStringToString(thread, formattedText, it->first, it->second); typeString.Update(JSLocale::GetNumberFieldType(thread, taggedValue.GetTaggedValue(), UNUM_GROUPING_SEPARATOR_FIELD).GetTaggedValue()); record = JSLocale::PutElement(thread, index++, array, typeString, JSHandle::Cast(resString)); RETURN_IF_ABRUPT_COMPLETION(thread); JSObject::CreateDataPropertyOrThrow(thread, record, unitString, JSHandle::Cast(unit)); RETURN_IF_ABRUPT_COMPLETION(thread); start = it->second; } } // Add current field unit JSHandle subString = intl::LocaleHelper::UStringToString(thread, formattedText, start, limit); typeString.Update(JSLocale::GetNumberFieldType(thread, taggedValue.GetTaggedValue(), fieldId).GetTaggedValue()); JSHandle record = JSLocale::PutElement(thread, index++, array, typeString, JSHandle::Cast(subString)); RETURN_IF_ABRUPT_COMPLETION(thread); JSObject::CreateDataPropertyOrThrow(thread, record, unitString, JSHandle::Cast(unit)); RETURN_IF_ABRUPT_COMPLETION(thread); previousLimit = limit; } // If iterated length is smaller than formattedText.length, means a literal type exists after number fields // so add a literal type with value of formattedText.sub(previousLimit, formattedText.length) if (formattedText.length() > previousLimit) { typeString.Update(globalConst->GetLiteralString()); JSHandle substring = intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, formattedText.length()); JSLocale::PutElement(thread, index, array, typeString, JSHandle::Cast(substring)); RETURN_IF_ABRUPT_COMPLETION(thread); } } // 14.1.6 FormatRelativeTimeToParts ( relativeTimeFormat, value, unit ) JSHandle JSRelativeTimeFormat::FormatToParts(JSThread *thread, double value, const JSHandle &unit, const JSHandle &relativeTimeFormat) { icu::FormattedRelativeDateTime formatted = GetIcuFormatted(thread, relativeTimeFormat, value, unit); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread); JSHandle singularUnit = SingularUnitString(thread, unit); JSHandle array = JSHandle::Cast(JSArray::ArrayCreate(thread, JSTaggedNumber(0))); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread); FormatToArray(thread, array, formatted, value, singularUnit); return array; } void JSRelativeTimeFormat::ResolvedOptions(JSThread *thread, const JSHandle &relativeTimeFormat, const JSHandle &options) { if (relativeTimeFormat->GetIcuRTFFormatter() != nullptr) { [[maybe_unused]] icu::RelativeDateTimeFormatter *formatter = relativeTimeFormat->GetIcuRTFFormatter(); } else { THROW_ERROR(thread, ErrorType::RANGE_ERROR, "rtf is not initialized"); } auto globalConst = thread->GlobalConstants(); // [[locale]] JSHandle property = globalConst->GetHandledLocaleString(); JSHandle locale(thread, relativeTimeFormat->GetLocale()); PropertyDescriptor localeDesc(thread, JSHandle::Cast(locale), true, true, true); JSObject::DefineOwnProperty(thread, options, property, localeDesc); // [[Style]] property = globalConst->GetHandledStyleString(); RelativeStyleOption style = relativeTimeFormat->GetStyle(); JSHandle styleValue; if (style == RelativeStyleOption::LONG) { styleValue = globalConst->GetHandledLongString(); } else if (style == RelativeStyleOption::SHORT) { styleValue = globalConst->GetHandledShortString(); } else if (style == RelativeStyleOption::NARROW) { styleValue = globalConst->GetHandledNarrowString(); } PropertyDescriptor styleDesc(thread, styleValue, true, true, true); JSObject::DefineOwnProperty(thread, options, property, styleDesc); // [[Numeric]] property = globalConst->GetHandledNumericString(); NumericOption numeric = relativeTimeFormat->GetNumeric(); JSHandle numericValue; if (numeric == NumericOption::ALWAYS) { numericValue = globalConst->GetHandledAlwaysString(); } else if (numeric == NumericOption::AUTO) { numericValue = globalConst->GetHandledAutoString(); } else { THROW_ERROR(thread, ErrorType::RANGE_ERROR, "numeric is exception"); } PropertyDescriptor numericDesc(thread, numericValue, true, true, true); JSObject::DefineOwnProperty(thread, options, property, numericDesc); // [[NumberingSystem]] property = JSHandle::Cast(globalConst->GetHandledNumberingSystemString()); JSHandle numberingSystem(thread, relativeTimeFormat->GetNumberingSystem()); PropertyDescriptor numberingSystemDesc(thread, numberingSystem, true, true, true); JSObject::DefineOwnProperty(thread, options, property, numberingSystemDesc); } } // namespace panda::ecmascript