1/* 2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16#include "ecmascript/js_relative_time_format.h" 17#include "ecmascript/js_function.h" 18#include "ecmascript/object_factory-inl.h" 19 20namespace panda::ecmascript { 21// 14.1.1 InitializeRelativeTimeFormat ( relativeTimeFormat, locales, options ) 22JSHandle<JSRelativeTimeFormat> JSRelativeTimeFormat::InitializeRelativeTimeFormat( 23 JSThread *thread, const JSHandle<JSRelativeTimeFormat> &relativeTimeFormat, const JSHandle<JSTaggedValue> &locales, 24 const JSHandle<JSTaggedValue> &options) 25{ 26 EcmaVM *ecmaVm = thread->GetEcmaVM(); 27 ObjectFactory *factory = ecmaVm->GetFactory(); 28 29 // 1.Let requestedLocales be ? CanonicalizeLocaleList(locales). 30 JSHandle<TaggedArray> requestedLocales = intl::LocaleHelper::CanonicalizeLocaleList(thread, locales); 31 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread); 32 33 // 2&3. If options is undefined, then Let options be ObjectCreate(null). else Let options be ? ToObject(options). 34 JSHandle<JSObject> rtfOptions; 35 if (!options->IsUndefined()) { 36 rtfOptions = JSTaggedValue::ToObject(thread, options); 37 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread); 38 } else { 39 rtfOptions = factory->CreateNullJSObject(); 40 } 41 42 // 5. Let matcher be ? GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit"). 43 auto globalConst = thread->GlobalConstants(); 44 LocaleMatcherOption matcher = 45 JSLocale::GetOptionOfString(thread, rtfOptions, globalConst->GetHandledLocaleMatcherString(), 46 {LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT}, 47 {"lookup", "best fit"}, LocaleMatcherOption::BEST_FIT); 48 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread); 49 50 // 7. Let numberingSystem be ? GetOption(options, "numberingSystem", "string", undefined, undefined). 51 JSHandle<JSTaggedValue> property = globalConst->GetHandledNumberingSystemString(); 52 JSHandle<JSTaggedValue> undefinedValue(thread, JSTaggedValue::Undefined()); 53 JSHandle<JSTaggedValue> numberingSystemValue = 54 JSLocale::GetOption(thread, rtfOptions, property, OptionType::STRING, undefinedValue, undefinedValue); 55 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread); 56 57 // Check whether numberingSystem is well formed and set to %RelativeTimeFormat%.[[numberingSystem]] 58 std::string numberingSystemStdStr; 59 if (!numberingSystemValue->IsUndefined()) { 60 JSHandle<EcmaString> numberingSystemString = JSHandle<EcmaString>::Cast(numberingSystemValue); 61 if (EcmaStringAccessor(numberingSystemString).IsUtf16()) { 62 THROW_RANGE_ERROR_AND_RETURN(thread, "invalid numberingSystem", relativeTimeFormat); 63 } 64 numberingSystemStdStr = intl::LocaleHelper::ConvertToStdString(numberingSystemString); 65 if (!JSLocale::IsNormativeNumberingSystem(numberingSystemStdStr)) { 66 THROW_RANGE_ERROR_AND_RETURN(thread, "invalid numberingSystem", relativeTimeFormat); 67 } 68 } 69 70 // 10. Let localeData be %RelativeTimeFormat%.[[LocaleData]]. 71 // 11. Let r be ResolveLocale(%RelativeTimeFormat%.[[AvailableLocales]], requestedLocales, opt, 72 // %RelativeTimeFormat%.[[RelevantExtensionKeys]], localeData). 73 JSHandle<TaggedArray> availableLocales; 74 if (requestedLocales->GetLength() == 0) { 75 availableLocales = factory->EmptyArray(); 76 } else { 77 std::vector<std::string> availableStringLocales = 78 intl::LocaleHelper::GetAvailableLocales(thread, "calendar", nullptr); 79 availableLocales = JSLocale::ConstructLocaleList(thread, availableStringLocales); 80 } 81 std::set<std::string> relevantExtensionKeys{"nu"}; 82 ResolvedLocale r = 83 JSLocale::ResolveLocale(thread, availableLocales, requestedLocales, matcher, relevantExtensionKeys); 84 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread); 85 icu::Locale icuLocale = r.localeData; 86 87 // 12. Let locale be r.[[Locale]]. 88 JSHandle<EcmaString> localeStr = intl::LocaleHelper::ToLanguageTag(thread, icuLocale); 89 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread); 90 91 // 13. Set relativeTimeFormat.[[Locale]] to locale. 92 relativeTimeFormat->SetLocale(thread, localeStr.GetTaggedValue()); 93 94 // 15. Set relativeTimeFormat.[[NumberingSystem]] to r.[[nu]]. 95 UErrorCode status = U_ZERO_ERROR; 96 if (!numberingSystemStdStr.empty()) { 97 if (JSLocale::IsWellNumberingSystem(numberingSystemStdStr)) { 98 icuLocale.setUnicodeKeywordValue("nu", numberingSystemStdStr, status); 99 ASSERT(U_SUCCESS(status)); 100 } 101 } 102 103 // 16. Let s be ? GetOption(options, "style", "string", «"long", "short", "narrow"», "long"). 104 property = globalConst->GetHandledStyleString(); 105 RelativeStyleOption styleOption = JSLocale::GetOptionOfString(thread, rtfOptions, property, 106 {RelativeStyleOption::LONG, RelativeStyleOption::SHORT, RelativeStyleOption::NARROW}, 107 {"long", "short", "narrow"}, RelativeStyleOption::LONG); 108 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread); 109 110 // 17. Set relativeTimeFormat.[[Style]] to s. 111 relativeTimeFormat->SetStyle(styleOption); 112 113 // 18. Let numeric be ? GetOption(options, "numeric", "string", ?"always", "auto"?, "always"). 114 property = globalConst->GetHandledNumericString(); 115 NumericOption numericOption = 116 JSLocale::GetOptionOfString(thread, rtfOptions, property, {NumericOption::ALWAYS, NumericOption::AUTO}, 117 {"always", "auto"}, NumericOption::ALWAYS); 118 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread); 119 120 // 19. Set relativeTimeFormat.[[Numeric]] to numeric. 121 relativeTimeFormat->SetNumeric(numericOption); 122 123 // 20. Let relativeTimeFormat.[[NumberFormat]] be ! Construct(%NumberFormat%, « locale »). 124 icu::NumberFormat *icuNumberFormat = icu::NumberFormat::createInstance(icuLocale, UNUM_DECIMAL, status); 125 if (U_FAILURE(status) != 0) { 126 delete icuNumberFormat; 127 if (status == UErrorCode::U_MISSING_RESOURCE_ERROR) { 128 THROW_REFERENCE_ERROR_AND_RETURN(thread, "can not find icu data resources", relativeTimeFormat); 129 } 130 THROW_RANGE_ERROR_AND_RETURN(thread, "create icu::NumberFormat failed", relativeTimeFormat); 131 } 132 // Display grouping using the default strategy for all locales 133 if (icuNumberFormat->getDynamicClassID() == icu::DecimalFormat::getStaticClassID()) { 134 icu::DecimalFormat* icuDecimalFormat = static_cast<icu::DecimalFormat*>(icuNumberFormat); 135 icuDecimalFormat->setMinimumGroupingDigits(UNUM_MINIMUM_GROUPING_DIGITS_AUTO); 136 } 137 138 // Trans RelativeStyleOption to ICU Style 139 UDateRelativeDateTimeFormatterStyle uStyle; 140 switch (styleOption) { 141 case RelativeStyleOption::LONG: 142 uStyle = UDAT_STYLE_LONG; 143 break; 144 case RelativeStyleOption::SHORT: 145 uStyle = UDAT_STYLE_SHORT; 146 break; 147 case RelativeStyleOption::NARROW: 148 uStyle = UDAT_STYLE_NARROW; 149 break; 150 default: 151 LOG_ECMA(FATAL) << "this branch is unreachable"; 152 UNREACHABLE(); 153 } 154 icu::RelativeDateTimeFormatter rtfFormatter(icuLocale, icuNumberFormat, uStyle, UDISPCTX_CAPITALIZATION_NONE, 155 status); 156 if (U_FAILURE(status) != 0) { 157 THROW_RANGE_ERROR_AND_RETURN(thread, "icu Formatter Error", relativeTimeFormat); 158 } 159 160 std::string numberingSystem = JSLocale::GetNumberingSystem(icuLocale); 161 auto result = factory->NewFromStdString(numberingSystem); 162 relativeTimeFormat->SetNumberingSystem(thread, result); 163 164 // Set RelativeTimeFormat.[[IcuRelativeTimeFormatter]] 165 factory->NewJSIntlIcuData(relativeTimeFormat, rtfFormatter, JSRelativeTimeFormat::FreeIcuRTFFormatter); 166 167 // 22. Return relativeTimeFormat. 168 return relativeTimeFormat; 169} 170 171// 14.1.2 SingularRelativeTimeUnit ( unit ) 172bool SingularUnitToIcuUnit(JSThread *thread, const JSHandle<EcmaString> &unit, URelativeDateTimeUnit *unitEnum) 173{ 174 // 1. Assert: Type(unit) is String. 175 ASSERT(JSHandle<JSTaggedValue>::Cast(unit)->IsString()); 176 177 // 2. If unit is "seconds" or "second", return "second". 178 // 3. If unit is "minutes" or "minute", return "minute". 179 // 4. If unit is "hours" or "hour", return "hour". 180 // 5. If unit is "days" or "day", return "day". 181 // 6. If unit is "weeks" or "week", return "week". 182 // 7. If unit is "months" or "month", return "month". 183 // 8. If unit is "quarters" or "quarter", return "quarter". 184 // 9. If unit is "years" or "year", return "year". 185 auto globalConst = thread->GlobalConstants(); 186 JSHandle<EcmaString> second = JSHandle<EcmaString>::Cast(globalConst->GetHandledSecondString()); 187 JSHandle<EcmaString> minute = JSHandle<EcmaString>::Cast(globalConst->GetHandledMinuteString()); 188 JSHandle<EcmaString> hour = JSHandle<EcmaString>::Cast(globalConst->GetHandledHourString()); 189 JSHandle<EcmaString> day = JSHandle<EcmaString>::Cast(globalConst->GetHandledDayString()); 190 JSHandle<EcmaString> week = JSHandle<EcmaString>::Cast(globalConst->GetHandledWeekString()); 191 JSHandle<EcmaString> month = JSHandle<EcmaString>::Cast(globalConst->GetHandledMonthString()); 192 JSHandle<EcmaString> quarter = JSHandle<EcmaString>::Cast(globalConst->GetHandledQuarterString()); 193 JSHandle<EcmaString> year = JSHandle<EcmaString>::Cast(globalConst->GetHandledYearString()); 194 195 JSHandle<EcmaString> seconds = JSHandle<EcmaString>::Cast(globalConst->GetHandledSecondsString()); 196 JSHandle<EcmaString> minutes = JSHandle<EcmaString>::Cast(globalConst->GetHandledMinutesString()); 197 JSHandle<EcmaString> hours = JSHandle<EcmaString>::Cast(globalConst->GetHandledHoursString()); 198 JSHandle<EcmaString> days = JSHandle<EcmaString>::Cast(globalConst->GetHandledDaysString()); 199 JSHandle<EcmaString> weeks = JSHandle<EcmaString>::Cast(globalConst->GetHandledWeeksString()); 200 JSHandle<EcmaString> months = JSHandle<EcmaString>::Cast(globalConst->GetHandledMonthsString()); 201 JSHandle<EcmaString> quarters = JSHandle<EcmaString>::Cast(globalConst->GetHandledQuartersString()); 202 JSHandle<EcmaString> years = JSHandle<EcmaString>::Cast(globalConst->GetHandledYearsString()); 203 204 if (EcmaStringAccessor::StringsAreEqual(*second, *unit) || 205 EcmaStringAccessor::StringsAreEqual(*seconds, *unit)) { 206 *unitEnum = UDAT_REL_UNIT_SECOND; 207 } else if (EcmaStringAccessor::StringsAreEqual(*minute, *unit) || 208 EcmaStringAccessor::StringsAreEqual(*minutes, *unit)) { 209 *unitEnum = UDAT_REL_UNIT_MINUTE; 210 } else if (EcmaStringAccessor::StringsAreEqual(*hour, *unit) || 211 EcmaStringAccessor::StringsAreEqual(*hours, *unit)) { 212 *unitEnum = UDAT_REL_UNIT_HOUR; 213 } else if (EcmaStringAccessor::StringsAreEqual(*day, *unit) || 214 EcmaStringAccessor::StringsAreEqual(*days, *unit)) { 215 *unitEnum = UDAT_REL_UNIT_DAY; 216 } else if (EcmaStringAccessor::StringsAreEqual(*week, *unit) || 217 EcmaStringAccessor::StringsAreEqual(*weeks, *unit)) { 218 *unitEnum = UDAT_REL_UNIT_WEEK; 219 } else if (EcmaStringAccessor::StringsAreEqual(*month, *unit) || 220 EcmaStringAccessor::StringsAreEqual(*months, *unit)) { 221 *unitEnum = UDAT_REL_UNIT_MONTH; 222 } else if (EcmaStringAccessor::StringsAreEqual(*quarter, *unit) || 223 EcmaStringAccessor::StringsAreEqual(*quarters, *unit)) { 224 *unitEnum = UDAT_REL_UNIT_QUARTER; 225 } else if (EcmaStringAccessor::StringsAreEqual(*year, *unit) || 226 EcmaStringAccessor::StringsAreEqual(*years, *unit)) { 227 *unitEnum = UDAT_REL_UNIT_YEAR; 228 } else { 229 return false; 230 } 231 // 11. else return unit. 232 return true; 233} 234 235// Unwrap RelativeTimeFormat 236JSHandle<JSTaggedValue> JSRelativeTimeFormat::UnwrapRelativeTimeFormat(JSThread *thread, 237 const JSHandle<JSTaggedValue> &rtf) 238{ 239 ASSERT_PRINT(rtf->IsJSObject(), "rtf is not a JSObject"); 240 241 JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv(); 242 bool isInstanceOf = JSFunction::InstanceOf(thread, rtf, env->GetRelativeTimeFormatFunction()); 243 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, rtf); 244 if (!rtf->IsJSRelativeTimeFormat() && isInstanceOf) { 245 JSHandle<JSTaggedValue> key(thread, JSHandle<JSIntl>::Cast(env->GetIntlFunction())->GetFallbackSymbol()); 246 OperationResult operationResult = JSTaggedValue::GetProperty(thread, rtf, key); 247 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, rtf); 248 return operationResult.GetValue(); 249 } 250 251 // Perform ? RequireInternalSlot(relativeTimeFormat, [[InitializedRelativeTimeFormat]]). 252 if (!rtf->IsJSRelativeTimeFormat()) { 253 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, rtf); 254 } 255 return rtf; 256} 257 258// CommonFormat 259icu::FormattedRelativeDateTime GetIcuFormatted(JSThread *thread, 260 const JSHandle<JSRelativeTimeFormat> &relativeTimeFormat, 261 double value, const JSHandle<EcmaString> &unit) 262{ 263 icu::RelativeDateTimeFormatter *formatter = relativeTimeFormat->GetIcuRTFFormatter(); 264 ASSERT_PRINT(formatter != nullptr, "rtfFormatter is null"); 265 266 // If isFinite(value) is false, then throw a RangeError exception. 267 if (!std::isfinite(value)) { 268 THROW_RANGE_ERROR_AND_RETURN(thread, "", icu::FormattedRelativeDateTime()); 269 } 270 271 // 10. If unit is not one of "second", "minute", "hour", "day", "week", "month", "quarter", or "year", throw a 272 // RangeError exception. 273 URelativeDateTimeUnit unitEnum; 274 if (!SingularUnitToIcuUnit(thread, unit, &unitEnum)) { 275 THROW_RANGE_ERROR_AND_RETURN(thread, "", icu::FormattedRelativeDateTime()); 276 } 277 UErrorCode status = U_ZERO_ERROR; 278 NumericOption numeric = relativeTimeFormat->GetNumeric(); 279 280 icu::FormattedRelativeDateTime formatted; 281 switch (numeric) { 282 case NumericOption::ALWAYS: 283 formatted = formatter->formatNumericToValue(value, unitEnum, status); 284 ASSERT_PRINT(U_SUCCESS(status), "icu format to value error"); 285 break; 286 case NumericOption::AUTO: 287 formatted = formatter->formatToValue(value, unitEnum, status); 288 ASSERT_PRINT(U_SUCCESS(status), "icu format to value error"); 289 break; 290 default: 291 LOG_ECMA(FATAL) << "this branch is unreachable"; 292 UNREACHABLE(); 293 } 294 return formatted; 295} 296 297// 14.1.2 SingularRelativeTimeUnit ( unit ) 298JSHandle<EcmaString> SingularUnitString(JSThread *thread, const JSHandle<EcmaString> &unit) 299{ 300 auto globalConst = thread->GlobalConstants(); 301 JSHandle<EcmaString> second = JSHandle<EcmaString>::Cast(globalConst->GetHandledSecondString()); 302 JSHandle<EcmaString> minute = JSHandle<EcmaString>::Cast(globalConst->GetHandledMinuteString()); 303 JSHandle<EcmaString> hour = JSHandle<EcmaString>::Cast(globalConst->GetHandledHourString()); 304 JSHandle<EcmaString> day = JSHandle<EcmaString>::Cast(globalConst->GetHandledDayString()); 305 JSHandle<EcmaString> week = JSHandle<EcmaString>::Cast(globalConst->GetHandledWeekString()); 306 JSHandle<EcmaString> month = JSHandle<EcmaString>::Cast(globalConst->GetHandledMonthString()); 307 JSHandle<EcmaString> quarter = JSHandle<EcmaString>::Cast(globalConst->GetHandledQuarterString()); 308 JSHandle<EcmaString> year = JSHandle<EcmaString>::Cast(globalConst->GetHandledYearString()); 309 JSHandle<EcmaString> seconds = JSHandle<EcmaString>::Cast(globalConst->GetHandledSecondsString()); 310 JSHandle<EcmaString> minutes = JSHandle<EcmaString>::Cast(globalConst->GetHandledMinutesString()); 311 JSHandle<EcmaString> hours = JSHandle<EcmaString>::Cast(globalConst->GetHandledHoursString()); 312 JSHandle<EcmaString> days = JSHandle<EcmaString>::Cast(globalConst->GetHandledDaysString()); 313 JSHandle<EcmaString> weeks = JSHandle<EcmaString>::Cast(globalConst->GetHandledWeeksString()); 314 JSHandle<EcmaString> months = JSHandle<EcmaString>::Cast(globalConst->GetHandledMonthsString()); 315 JSHandle<EcmaString> quarters = JSHandle<EcmaString>::Cast(globalConst->GetHandledQuartersString()); 316 JSHandle<EcmaString> years = JSHandle<EcmaString>::Cast(globalConst->GetHandledYearsString()); 317 318 // 2. If unit is "seconds" or "second", return "second". 319 if (EcmaStringAccessor::StringsAreEqual(*second, *unit) || EcmaStringAccessor::StringsAreEqual(*seconds, *unit)) { 320 return second; 321 } 322 // 3. If unit is "minutes" or "minute", return "minute". 323 if (EcmaStringAccessor::StringsAreEqual(*minute, *unit) || EcmaStringAccessor::StringsAreEqual(*minutes, *unit)) { 324 return minute; 325 } 326 // 4. If unit is "hours" or "hour", return "hour". 327 if (EcmaStringAccessor::StringsAreEqual(*hour, *unit) || EcmaStringAccessor::StringsAreEqual(*hours, *unit)) { 328 return hour; 329 } 330 // 5. If unit is "days" or "day", return "day". 331 if (EcmaStringAccessor::StringsAreEqual(*day, *unit) || EcmaStringAccessor::StringsAreEqual(*days, *unit)) { 332 return day; 333 } 334 // 6. If unit is "weeks" or "week", return "week". 335 if (EcmaStringAccessor::StringsAreEqual(*week, *unit) || EcmaStringAccessor::StringsAreEqual(*weeks, *unit)) { 336 return week; 337 } 338 // 7. If unit is "months" or "month", return "month". 339 if (EcmaStringAccessor::StringsAreEqual(*month, *unit) || EcmaStringAccessor::StringsAreEqual(*months, *unit)) { 340 return month; 341 } 342 // 8. If unit is "quarters" or "quarter", return "quarter". 343 if (EcmaStringAccessor::StringsAreEqual(*quarter, *unit) || EcmaStringAccessor::StringsAreEqual(*quarters, *unit)) { 344 return quarter; 345 } 346 // 9. If unit is "years" or "year", return "year". 347 if (EcmaStringAccessor::StringsAreEqual(*year, *unit) || EcmaStringAccessor::StringsAreEqual(*years, *unit)) { 348 return year; 349 } 350 351 JSHandle<JSTaggedValue> undefinedValue(thread, JSTaggedValue::Undefined()); 352 return JSHandle<EcmaString>::Cast(undefinedValue); 353} 354 355// 14.1.5 FormatRelativeTime ( relativeTimeFormat, value, unit ) 356JSHandle<EcmaString> JSRelativeTimeFormat::Format(JSThread *thread, double value, const JSHandle<EcmaString> &unit, 357 const JSHandle<JSRelativeTimeFormat> &relativeTimeFormat) 358{ 359 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); 360 icu::FormattedRelativeDateTime formatted = GetIcuFormatted(thread, relativeTimeFormat, value, unit); 361 RETURN_HANDLE_IF_ABRUPT_COMPLETION(EcmaString, thread); 362 UErrorCode status = U_ZERO_ERROR; 363 icu::UnicodeString uString = formatted.toString(status); 364 if (U_FAILURE(status) != 0) { 365 THROW_RANGE_ERROR_AND_RETURN(thread, "icu formatted toString error", factory->GetEmptyString()); 366 } 367 JSHandle<EcmaString> string = 368 factory->NewFromUtf16(reinterpret_cast<const uint16_t *>(uString.getBuffer()), uString.length()); 369 return string; 370} 371 372void FormatToArray(JSThread *thread, const JSHandle<JSArray> &array, 373 const icu::FormattedRelativeDateTime &formatted, double value, 374 const JSHandle<EcmaString> &unit) 375{ 376 UErrorCode status = U_ZERO_ERROR; 377 icu::UnicodeString formattedText = formatted.toString(status); 378 if (U_FAILURE(status)) { 379 THROW_TYPE_ERROR(thread, "formattedRelativeDateTime toString failed"); 380 } 381 382 icu::ConstrainedFieldPosition cfpo; 383 // Set constrainCategory to UFIELD_CATEGORY_NUMBER which is specified for UNumberFormatFields 384 cfpo.constrainCategory(UFIELD_CATEGORY_NUMBER); 385 int32_t index = 0; 386 int32_t previousLimit = 0; 387 auto globalConst = thread->GlobalConstants(); 388 JSHandle<JSTaggedValue> taggedValue(thread, JSTaggedValue(value)); 389 JSMutableHandle<JSTaggedValue> typeString(thread, JSTaggedValue::Undefined()); 390 JSHandle<JSTaggedValue> unitString = globalConst->GetHandledUnitString(); 391 std::vector<std::pair<int32_t, int32_t>> separatorFields; 392 /** 393 * From ICU header file document @unumberformatter.h 394 * Sets a constraint on the field category. 395 * 396 * When this instance of ConstrainedFieldPosition is passed to FormattedValue#nextPosition, 397 * positions are skipped unless they have the given category. 398 * 399 * Any previously set constraints are cleared. 400 * 401 * For example, to loop over only the number-related fields: 402 * 403 * ConstrainedFieldPosition cfpo; 404 * cfpo.constrainCategory(UFIELDCATEGORY_NUMBER_FORMAT); 405 * while (fmtval.nextPosition(cfpo, status)) { 406 * // handle the number-related field position 407 * } 408 */ 409 while ((formatted.nextPosition(cfpo, status) != 0)) { 410 int32_t fieldId = cfpo.getField(); 411 int32_t start = cfpo.getStart(); 412 int32_t limit = cfpo.getLimit(); 413 // Special case when fieldId is UNUM_GROUPING_SEPARATOR_FIELD 414 if (static_cast<UNumberFormatFields>(fieldId) == UNUM_GROUPING_SEPARATOR_FIELD) { 415 separatorFields.push_back(std::pair<int32_t, int32_t>(start, limit)); 416 continue; 417 } 418 // If start greater than previousLimit, means a literal type exists before number fields 419 // so add a literal type with value of formattedText.sub(0, start) 420 if (start > previousLimit) { 421 typeString.Update(globalConst->GetLiteralString()); 422 JSHandle<EcmaString> substring = 423 intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, start); 424 JSLocale::PutElement(thread, index++, array, typeString, JSHandle<JSTaggedValue>::Cast(substring)); 425 RETURN_IF_ABRUPT_COMPLETION(thread); 426 } 427 // Add part when type is unit 428 // Iterate former grouping separator vector and add unit element to array 429 for (auto it = separatorFields.begin(); it != separatorFields.end(); it++) { 430 if (it->first > start) { 431 // Add Integer type element 432 JSHandle<EcmaString> resString = 433 intl::LocaleHelper::UStringToString(thread, formattedText, start, it->first); 434 typeString.Update( 435 JSLocale::GetNumberFieldType(thread, taggedValue.GetTaggedValue(), fieldId).GetTaggedValue()); 436 JSHandle<JSObject> record = 437 JSLocale::PutElement(thread, index++, array, typeString, JSHandle<JSTaggedValue>::Cast(resString)); 438 RETURN_IF_ABRUPT_COMPLETION(thread); 439 JSObject::CreateDataPropertyOrThrow(thread, record, unitString, JSHandle<JSTaggedValue>::Cast(unit)); 440 RETURN_IF_ABRUPT_COMPLETION(thread); 441 // Add Group type element 442 resString = intl::LocaleHelper::UStringToString(thread, formattedText, it->first, it->second); 443 typeString.Update(JSLocale::GetNumberFieldType(thread, taggedValue.GetTaggedValue(), 444 UNUM_GROUPING_SEPARATOR_FIELD).GetTaggedValue()); 445 record = 446 JSLocale::PutElement(thread, index++, array, typeString, JSHandle<JSTaggedValue>::Cast(resString)); 447 RETURN_IF_ABRUPT_COMPLETION(thread); 448 JSObject::CreateDataPropertyOrThrow(thread, record, unitString, JSHandle<JSTaggedValue>::Cast(unit)); 449 RETURN_IF_ABRUPT_COMPLETION(thread); 450 start = it->second; 451 } 452 } 453 // Add current field unit 454 JSHandle<EcmaString> subString = intl::LocaleHelper::UStringToString(thread, formattedText, start, limit); 455 typeString.Update(JSLocale::GetNumberFieldType(thread, taggedValue.GetTaggedValue(), fieldId).GetTaggedValue()); 456 JSHandle<JSObject> record = 457 JSLocale::PutElement(thread, index++, array, typeString, JSHandle<JSTaggedValue>::Cast(subString)); 458 RETURN_IF_ABRUPT_COMPLETION(thread); 459 JSObject::CreateDataPropertyOrThrow(thread, record, unitString, JSHandle<JSTaggedValue>::Cast(unit)); 460 RETURN_IF_ABRUPT_COMPLETION(thread); 461 previousLimit = limit; 462 } 463 // If iterated length is smaller than formattedText.length, means a literal type exists after number fields 464 // so add a literal type with value of formattedText.sub(previousLimit, formattedText.length) 465 if (formattedText.length() > previousLimit) { 466 typeString.Update(globalConst->GetLiteralString()); 467 JSHandle<EcmaString> substring = 468 intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, formattedText.length()); 469 JSLocale::PutElement(thread, index, array, typeString, JSHandle<JSTaggedValue>::Cast(substring)); 470 RETURN_IF_ABRUPT_COMPLETION(thread); 471 } 472} 473 474// 14.1.6 FormatRelativeTimeToParts ( relativeTimeFormat, value, unit ) 475JSHandle<JSArray> JSRelativeTimeFormat::FormatToParts(JSThread *thread, double value, const JSHandle<EcmaString> &unit, 476 const JSHandle<JSRelativeTimeFormat> &relativeTimeFormat) 477{ 478 icu::FormattedRelativeDateTime formatted = GetIcuFormatted(thread, relativeTimeFormat, value, unit); 479 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread); 480 JSHandle<EcmaString> singularUnit = SingularUnitString(thread, unit); 481 JSHandle<JSArray> array = JSHandle<JSArray>::Cast(JSArray::ArrayCreate(thread, JSTaggedNumber(0))); 482 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread); 483 FormatToArray(thread, array, formatted, value, singularUnit); 484 return array; 485} 486 487void JSRelativeTimeFormat::ResolvedOptions(JSThread *thread, const JSHandle<JSRelativeTimeFormat> &relativeTimeFormat, 488 const JSHandle<JSObject> &options) 489{ 490 if (relativeTimeFormat->GetIcuRTFFormatter() != nullptr) { 491 [[maybe_unused]] icu::RelativeDateTimeFormatter *formatter = relativeTimeFormat->GetIcuRTFFormatter(); 492 } else { 493 THROW_ERROR(thread, ErrorType::RANGE_ERROR, "rtf is not initialized"); 494 } 495 496 auto globalConst = thread->GlobalConstants(); 497 // [[locale]] 498 JSHandle<JSTaggedValue> property = globalConst->GetHandledLocaleString(); 499 JSHandle<EcmaString> locale(thread, relativeTimeFormat->GetLocale()); 500 PropertyDescriptor localeDesc(thread, JSHandle<JSTaggedValue>::Cast(locale), true, true, true); 501 JSObject::DefineOwnProperty(thread, options, property, localeDesc); 502 503 // [[Style]] 504 property = globalConst->GetHandledStyleString(); 505 RelativeStyleOption style = relativeTimeFormat->GetStyle(); 506 JSHandle<JSTaggedValue> styleValue; 507 if (style == RelativeStyleOption::LONG) { 508 styleValue = globalConst->GetHandledLongString(); 509 } else if (style == RelativeStyleOption::SHORT) { 510 styleValue = globalConst->GetHandledShortString(); 511 } else if (style == RelativeStyleOption::NARROW) { 512 styleValue = globalConst->GetHandledNarrowString(); 513 } 514 PropertyDescriptor styleDesc(thread, styleValue, true, true, true); 515 JSObject::DefineOwnProperty(thread, options, property, styleDesc); 516 517 // [[Numeric]] 518 property = globalConst->GetHandledNumericString(); 519 NumericOption numeric = relativeTimeFormat->GetNumeric(); 520 JSHandle<JSTaggedValue> numericValue; 521 if (numeric == NumericOption::ALWAYS) { 522 numericValue = globalConst->GetHandledAlwaysString(); 523 } else if (numeric == NumericOption::AUTO) { 524 numericValue = globalConst->GetHandledAutoString(); 525 } else { 526 THROW_ERROR(thread, ErrorType::RANGE_ERROR, "numeric is exception"); 527 } 528 PropertyDescriptor numericDesc(thread, numericValue, true, true, true); 529 JSObject::DefineOwnProperty(thread, options, property, numericDesc); 530 531 // [[NumberingSystem]] 532 property = JSHandle<JSTaggedValue>::Cast(globalConst->GetHandledNumberingSystemString()); 533 JSHandle<JSTaggedValue> numberingSystem(thread, relativeTimeFormat->GetNumberingSystem()); 534 PropertyDescriptor numberingSystemDesc(thread, numberingSystem, true, true, true); 535 JSObject::DefineOwnProperty(thread, options, property, numberingSystemDesc); 536} 537} // namespace panda::ecmascript