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_date_time_format.h" 17 18#include "ecmascript/checkpoint/thread_state_transition.h" 19#include "ecmascript/intl/locale_helper.h" 20#include "ecmascript/global_env.h" 21#include "ecmascript/js_date.h" 22#include "ecmascript/js_function.h" 23#include "ecmascript/js_intl.h" 24#include "ecmascript/object_factory-inl.h" 25 26namespace panda::ecmascript { 27struct CommonDateFormatPart { 28 int32_t fField = 0; 29 int32_t fBeginIndex = 0; // NOLINT(misc-non-private-member-variables-in-classes) 30 int32_t fEndIndex = 0; // NOLINT(misc-non-private-member-variables-in-classes) 31 int32_t index = 0; // NOLINT(misc-non-private-member-variables-in-classes) 32 bool isPreExist = false; 33 34 CommonDateFormatPart() = default; 35 CommonDateFormatPart(int32_t fField, int32_t fBeginIndex, int32_t fEndIndex, int32_t index, bool isPreExist) 36 : fField(fField), fBeginIndex(fBeginIndex), fEndIndex(fEndIndex), index(index), isPreExist(isPreExist) 37 { 38 } 39 40 ~CommonDateFormatPart() = default; 41 42 DEFAULT_COPY_SEMANTIC(CommonDateFormatPart); 43 DEFAULT_MOVE_SEMANTIC(CommonDateFormatPart); 44}; 45 46namespace { 47const std::vector<std::string> ICU_LONG_SHORT = { 48 "long", "short", 49 "longOffset", "shortOffset", 50 "longGeneric", "shortGeneric" 51}; 52const std::vector<std::string> ICU_NARROW_LONG_SHORT = {"narrow", "long", "short"}; 53const std::vector<std::string> ICU2_DIGIT_NUMERIC = {"2-digit", "numeric"}; 54const std::vector<std::string> ICU_NARROW_LONG_SHORT2_DIGIT_NUMERIC = {"narrow", "long", "short", "2-digit", "numeric"}; 55const std::vector<IcuPatternEntry> ICU_WEEKDAY_PE = { 56 {"EEEEE", "narrow"}, {"EEEE", "long"}, {"EEE", "short"}, 57 {"ccccc", "narrow"}, {"cccc", "long"}, {"ccc", "short"} 58}; 59const std::vector<IcuPatternEntry> ICU_ERA_PE = {{"GGGGG", "narrow"}, {"GGGG", "long"}, {"GGG", "short"}}; 60const std::vector<IcuPatternEntry> ICU_YEAR_PE = {{"yy", "2-digit"}, {"y", "numeric"}}; 61const std::vector<IcuPatternEntry> ICU_MONTH_PE = { 62 {"MMMMM", "narrow"}, {"MMMM", "long"}, {"MMM", "short"}, {"MM", "2-digit"}, {"M", "numeric"}, 63 {"LLLLL", "narrow"}, {"LLLL", "long"}, {"LLL", "short"}, {"LL", "2-digit"}, {"L", "numeric"} 64}; 65const std::vector<IcuPatternEntry> ICU_DAY_PE = {{"dd", "2-digit"}, {"d", "numeric"}}; 66const std::vector<IcuPatternEntry> ICU_DAY_PERIOD_PE = { 67 {"BBBBB", "narrow"}, {"bbbbb", "narrow"}, {"BBBB", "long"}, 68 {"bbbb", "long"}, {"B", "short"}, {"b", "short"} 69}; 70const std::vector<IcuPatternEntry> ICU_HOUR_PE = { 71 {"HH", "2-digit"}, {"H", "numeric"}, {"hh", "2-digit"}, {"h", "numeric"}, 72 {"kk", "2-digit"}, {"k", "numeric"}, {"KK", "2-digit"}, {"K", "numeric"} 73}; 74const std::vector<IcuPatternEntry> ICU_MINUTE_PE = {{"mm", "2-digit"}, {"m", "numeric"}}; 75const std::vector<IcuPatternEntry> ICU_SECOND_PE = {{"ss", "2-digit"}, {"s", "numeric"}}; 76const std::vector<IcuPatternEntry> ICU_YIME_ZONE_NAME_PE = { 77 {"zzzz", "long"}, {"z", "short"}, 78 {"OOOO", "longOffset"}, {"O", "shortOffset"}, 79 {"vvvv", "longGeneric"}, {"v", "shortGeneric"} 80}; 81 82const std::map<char16_t, HourCycleOption> HOUR_CYCLE_MAP = { 83 {'K', HourCycleOption::H11}, 84 {'h', HourCycleOption::H12}, 85 {'H', HourCycleOption::H23}, 86 {'k', HourCycleOption::H24} 87}; 88const std::map<std::string, HourCycleOption> TO_HOUR_CYCLE_MAP = { 89 {"h11", HourCycleOption::H11}, 90 {"h12", HourCycleOption::H12}, 91 {"h23", HourCycleOption::H23}, 92 {"h24", HourCycleOption::H24} 93}; 94 95// The value of the [[RelevantExtensionKeys]] internal slot is « "ca", "nu", "hc" ». 96const std::set<std::string> RELEVANT_EXTENSION_KEYS = {"nu", "ca", "hc"}; 97} 98 99icu::Locale *JSDateTimeFormat::GetIcuLocale() const 100{ 101 ASSERT(GetLocaleIcu().IsJSNativePointer()); 102 auto result = JSNativePointer::Cast(GetLocaleIcu().GetTaggedObject())->GetExternalPointer(); 103 return reinterpret_cast<icu::Locale *>(result); 104} 105 106/* static */ 107void JSDateTimeFormat::SetIcuLocale(JSThread *thread, JSHandle<JSDateTimeFormat> obj, 108 const icu::Locale &icuLocale, const NativePointerCallback &callback) 109{ 110 EcmaVM *ecmaVm = thread->GetEcmaVM(); 111 ObjectFactory *factory = ecmaVm->GetFactory(); 112 icu::Locale *icuPointer = ecmaVm->GetNativeAreaAllocator()->New<icu::Locale>(icuLocale); 113 ASSERT(icuPointer != nullptr); 114 JSTaggedValue data = obj->GetLocaleIcu(); 115 if (data.IsHeapObject() && data.IsJSNativePointer()) { 116 JSNativePointer *native = JSNativePointer::Cast(data.GetTaggedObject()); 117 native->ResetExternalPointer(thread, icuPointer); 118 return; 119 } 120 JSHandle<JSNativePointer> pointer = factory->NewJSNativePointer(icuPointer, callback, ecmaVm); 121 obj->SetLocaleIcu(thread, pointer.GetTaggedValue()); 122} 123 124void JSDateTimeFormat::FreeIcuLocale([[maybe_unused]] void *env, void *pointer, void *data) 125{ 126 if (pointer == nullptr) { 127 return; 128 } 129 auto icuLocale = reinterpret_cast<icu::Locale *>(pointer); 130 icuLocale->~Locale(); 131 if (data != nullptr) { 132 reinterpret_cast<EcmaVM *>(data)->GetNativeAreaAllocator()->FreeBuffer(pointer); 133 } 134} 135 136icu::SimpleDateFormat *JSDateTimeFormat::GetIcuSimpleDateFormat() const 137{ 138 ASSERT(GetSimpleDateTimeFormatIcu().IsJSNativePointer()); 139 auto result = JSNativePointer::Cast(GetSimpleDateTimeFormatIcu().GetTaggedObject())->GetExternalPointer(); 140 return reinterpret_cast<icu::SimpleDateFormat *>(result); 141} 142 143/* static */ 144void JSDateTimeFormat::SetIcuSimpleDateFormat(JSThread *thread, JSHandle<JSDateTimeFormat> obj, 145 const icu::SimpleDateFormat &icuSimpleDateTimeFormat, const NativePointerCallback &callback) 146{ 147 EcmaVM *ecmaVm = thread->GetEcmaVM(); 148 ObjectFactory *factory = ecmaVm->GetFactory(); 149 icu::SimpleDateFormat *icuPointer = 150 ecmaVm->GetNativeAreaAllocator()->New<icu::SimpleDateFormat>(icuSimpleDateTimeFormat); 151 ASSERT(icuPointer != nullptr); 152 JSTaggedValue data = obj->GetSimpleDateTimeFormatIcu(); 153 if (data.IsHeapObject() && data.IsJSNativePointer()) { 154 JSNativePointer *native = JSNativePointer::Cast(data.GetTaggedObject()); 155 native->ResetExternalPointer(thread, icuPointer); 156 return; 157 } 158 // According to the observed native memory, we give an approximate native binding value. 159 constexpr static size_t icuBindingNativeSize = 64 * 1024; 160 JSHandle<JSNativePointer> pointer = factory->NewJSNativePointer(icuPointer, callback, ecmaVm, 161 false, icuBindingNativeSize); 162 obj->SetSimpleDateTimeFormatIcu(thread, pointer.GetTaggedValue()); 163} 164 165void JSDateTimeFormat::FreeSimpleDateFormat([[maybe_unused]] void *env, void *pointer, void *data) 166{ 167 if (pointer == nullptr) { 168 return; 169 } 170 auto icuSimpleDateFormat = reinterpret_cast<icu::SimpleDateFormat *>(pointer); 171 icuSimpleDateFormat->~SimpleDateFormat(); 172 if (data != nullptr) { 173 reinterpret_cast<EcmaVM *>(data)->GetNativeAreaAllocator()->FreeBuffer(pointer); 174 } 175} 176 177JSHandle<EcmaString> JSDateTimeFormat::ToValueString(JSThread *thread, const Value value) 178{ 179 auto globalConst = thread->GlobalConstants(); 180 JSMutableHandle<EcmaString> result(thread, JSTaggedValue::Undefined()); 181 switch (value) { 182 case Value::SHARED: 183 result.Update(globalConst->GetHandledSharedString().GetTaggedValue()); 184 break; 185 case Value::START_RANGE: 186 result.Update(globalConst->GetHandledStartRangeString().GetTaggedValue()); 187 break; 188 case Value::END_RANGE: 189 result.Update(globalConst->GetHandledEndRangeString().GetTaggedValue()); 190 break; 191 default: 192 LOG_ECMA(FATAL) << "this branch is unreachable"; 193 UNREACHABLE(); 194 } 195 return result; 196} 197 198icu::DateFormat::EStyle DateTimeStyleToEStyle(DateTimeStyleOption style) 199{ 200 switch (style) { 201 case DateTimeStyleOption::FULL: { 202 return icu::DateFormat::kFull; 203 } 204 case DateTimeStyleOption::LONG: { 205 return icu::DateFormat::kLong; 206 } 207 case DateTimeStyleOption::MEDIUM: { 208 return icu::DateFormat::kMedium; 209 } 210 case DateTimeStyleOption::SHORT: { 211 return icu::DateFormat::kShort; 212 } 213 case DateTimeStyleOption::UNDEFINED: { 214 return icu::DateFormat::kNone; 215 } 216 default: { 217 return icu::DateFormat::kNone; 218 } 219 } 220} 221 222HourCycleOption HourCycleFromPattern(const icu::UnicodeString pattern) 223{ 224 bool inQuote = false; 225 for (int32_t i = 0; i < pattern.length(); i++) { 226 char16_t ch = pattern[i]; 227 switch (ch) { 228 case '\'': 229 inQuote = !inQuote; 230 break; 231 case 'K': 232 if (!inQuote) { 233 return HourCycleOption::H11; 234 } 235 break; 236 case 'h': 237 if (!inQuote) { 238 return HourCycleOption::H12; 239 } 240 break; 241 case 'H': 242 if (!inQuote) { 243 return HourCycleOption::H23; 244 } 245 break; 246 case 'k': 247 if (!inQuote) { 248 return HourCycleOption::H24; 249 } 250 break; 251 default : { 252 break; 253 } 254 } 255 } 256 return HourCycleOption::UNDEFINED; 257} 258 259icu::UnicodeString ReplaceSkeleton(const icu::UnicodeString input, HourCycleOption hc) 260{ 261 icu::UnicodeString result; 262 char16_t to; 263 switch (hc) { 264 case HourCycleOption::H11: 265 to = 'K'; 266 break; 267 case HourCycleOption::H12: 268 to = 'h'; 269 break; 270 case HourCycleOption::H23: 271 to = 'H'; 272 break; 273 case HourCycleOption::H24: 274 to = 'k'; 275 break; 276 default: 277 UNREACHABLE(); 278 break; 279 } 280 int inputLength = input.length(); 281 for (int32_t i = 0; i < inputLength; ++i) { 282 switch (input[i]) { 283 case 'a': 284 case 'b': 285 case 'B': 286 break; 287 case 'h': 288 case 'H': 289 case 'k': 290 case 'K': 291 result += to; 292 break; 293 default: 294 result += input[i]; 295 break; 296 } 297 } 298 return result; 299} 300 301std::unique_ptr<icu::SimpleDateFormat> DateTimeStylePattern(DateTimeStyleOption dateStyle, 302 DateTimeStyleOption timeStyle, 303 icu::Locale &icuLocale, 304 HourCycleOption hc, 305 icu::DateTimePatternGenerator *generator) 306{ 307 std::unique_ptr<icu::SimpleDateFormat> result; 308 icu::DateFormat::EStyle icuDateStyle = DateTimeStyleToEStyle(dateStyle); 309 icu::DateFormat::EStyle icuTimeStyle = DateTimeStyleToEStyle(timeStyle); 310 result.reset(reinterpret_cast<icu::SimpleDateFormat *>( 311 icu::DateFormat::createDateTimeInstance(icuDateStyle, icuTimeStyle, icuLocale))); 312 UErrorCode status = U_ZERO_ERROR; 313 icu::UnicodeString pattern(""); 314 pattern = result->toPattern(pattern); 315 icu::UnicodeString skeleton = icu::DateTimePatternGenerator::staticGetSkeleton(pattern, status); 316 ASSERT_PRINT(U_SUCCESS(status), "staticGetSkeleton failed"); 317 if (hc == HourCycleFromPattern(pattern)) { 318 return result; 319 } 320 skeleton = ReplaceSkeleton(skeleton, hc); 321 pattern = generator->getBestPattern(skeleton, UDATPG_MATCH_HOUR_FIELD_LENGTH, status); 322 result = std::make_unique<icu::SimpleDateFormat>(pattern, icuLocale, status); 323 return result; 324} 325 326// 13.1.1 InitializeDateTimeFormat (dateTimeFormat, locales, options) 327// NOLINTNEXTLINE(readability-function-size) 328JSHandle<JSDateTimeFormat> JSDateTimeFormat::InitializeDateTimeFormat(JSThread *thread, 329 const JSHandle<JSDateTimeFormat> &dateTimeFormat, 330 const JSHandle<JSTaggedValue> &locales, 331 const JSHandle<JSTaggedValue> &options, 332 IcuCacheType type) 333{ 334 EcmaVM *ecmaVm = thread->GetEcmaVM(); 335 ObjectFactory *factory = ecmaVm->GetFactory(); 336 const GlobalEnvConstants *globalConst = thread->GlobalConstants(); 337 338 // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales). 339 JSHandle<TaggedArray> requestedLocales = intl::LocaleHelper::CanonicalizeLocaleList(thread, locales); 340 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDateTimeFormat, thread); 341 342 // 2. Let options be ? ToDateTimeOptions(options, "any", "date"). 343 JSHandle<JSObject> dateTimeOptions; 344 if (options->IsUndefined()) { 345 dateTimeOptions = factory->CreateNullJSObject(); 346 } else { 347 dateTimeOptions = JSTaggedValue::ToObject(thread, options); 348 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDateTimeFormat, thread); 349 } 350 351 // 4. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit"). 352 auto matcher = JSLocale::GetOptionOfString<LocaleMatcherOption>( 353 thread, dateTimeOptions, globalConst->GetHandledLocaleMatcherString(), 354 {LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT}, {"lookup", "best fit"}, 355 LocaleMatcherOption::BEST_FIT); 356 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDateTimeFormat, thread); 357 358 // 6. Let calendar be ? GetOption(options, "calendar", "string", undefined, undefined). 359 JSHandle<JSTaggedValue> calendar = 360 JSLocale::GetOption(thread, dateTimeOptions, globalConst->GetHandledCalendarString(), OptionType::STRING, 361 globalConst->GetHandledUndefined(), globalConst->GetHandledUndefined()); 362 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDateTimeFormat, thread); 363 dateTimeFormat->SetCalendar(thread, calendar); 364 365 // 7. If calendar is not undefined, then 366 // a. If calendar does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception. 367 std::string calendarStr; 368 if (!calendar->IsUndefined()) { 369 JSHandle<EcmaString> calendarEcmaStr = JSHandle<EcmaString>::Cast(calendar); 370 calendarStr = intl::LocaleHelper::ConvertToStdString(calendarEcmaStr); 371 if (!JSLocale::IsNormativeCalendar(calendarStr)) { 372 THROW_RANGE_ERROR_AND_RETURN(thread, "invalid calendar", dateTimeFormat); 373 } 374 } 375 376 // 9. Let numberingSystem be ? GetOption(options, "numberingSystem", "string", undefined, undefined). 377 JSHandle<JSTaggedValue> numberingSystem = 378 JSLocale::GetOption(thread, dateTimeOptions, globalConst->GetHandledNumberingSystemString(), OptionType::STRING, 379 globalConst->GetHandledUndefined(), globalConst->GetHandledUndefined()); 380 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDateTimeFormat, thread); 381 dateTimeFormat->SetNumberingSystem(thread, numberingSystem); 382 383 // 10. If numberingSystem is not undefined, then 384 // a. If numberingSystem does not match the Unicode Locale Identifier type nonterminal, throw a RangeError 385 // exception. 386 std::string nsStr; 387 if (!numberingSystem->IsUndefined()) { 388 JSHandle<EcmaString> nsEcmaStr = JSHandle<EcmaString>::Cast(numberingSystem); 389 nsStr = intl::LocaleHelper::ConvertToStdString(nsEcmaStr); 390 if (!JSLocale::IsNormativeNumberingSystem(nsStr)) { 391 THROW_RANGE_ERROR_AND_RETURN(thread, "invalid numberingSystem", dateTimeFormat); 392 } 393 } 394 395 // 12. Let hour12 be ? GetOption(options, "hour12", "boolean", undefined, undefined). 396 JSHandle<JSTaggedValue> hour12 = 397 JSLocale::GetOption(thread, dateTimeOptions, globalConst->GetHandledHour12String(), OptionType::BOOLEAN, 398 globalConst->GetHandledUndefined(), globalConst->GetHandledUndefined()); 399 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDateTimeFormat, thread); 400 401 // 13. Let hourCycle be ? GetOption(options, "hourCycle", "string", « "h11", "h12", "h23", "h24" », undefined). 402 auto hourCycle = JSLocale::GetOptionOfString<HourCycleOption>( 403 thread, dateTimeOptions, globalConst->GetHandledHourCycleString(), 404 {HourCycleOption::H11, HourCycleOption::H12, HourCycleOption::H23, HourCycleOption::H24}, 405 {"h11", "h12", "h23", "h24"}, HourCycleOption::UNDEFINED); 406 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDateTimeFormat, thread); 407 408 // 14. If hour12 is not undefined, then 409 // a. Let hourCycle be null. 410 if (!hour12->IsUndefined()) { 411 hourCycle = HourCycleOption::UNDEFINED; 412 } 413 414 // 16. Let localeData be %DateTimeFormat%.[[LocaleData]]. 415 JSHandle<TaggedArray> availableLocales = (requestedLocales->GetLength() == 0) ? factory->EmptyArray() : 416 GainAvailableLocales(thread); 417 418 // 17. Let r be ResolveLocale(%DateTimeFormat%.[[AvailableLocales]], requestedLocales, opt, %DateTimeFormat% 419 // .[[RelevantExtensionKeys]], localeData). 420 ResolvedLocale resolvedLocale = 421 JSLocale::ResolveLocale(thread, availableLocales, requestedLocales, matcher, RELEVANT_EXTENSION_KEYS); 422 423 // 18. Set icuLocale to r.[[locale]]. 424 icu::Locale icuLocale = resolvedLocale.localeData; 425 ASSERT_PRINT(!icuLocale.isBogus(), "icuLocale is bogus"); 426 UErrorCode status = U_ZERO_ERROR; 427 428 if (numberingSystem->IsUndefined() || !JSLocale::IsWellNumberingSystem(nsStr)) { 429 std::string numberingSystemStr = JSLocale::GetNumberingSystem(icuLocale); 430 auto result = factory->NewFromStdString(numberingSystemStr); 431 dateTimeFormat->SetNumberingSystem(thread, result); 432 } 433 434 // Set resolvedIcuLocaleCopy to a copy of icuLocale. 435 // Set icuLocale.[[ca]] to calendar. 436 // Set icuLocale.[[nu]] to numberingSystem. 437 icu::Locale resolvedIcuLocaleCopy(icuLocale); 438 if (!calendar->IsUndefined() && JSLocale::IsWellCalendar(icuLocale, calendarStr)) { 439 icuLocale.setUnicodeKeywordValue("ca", calendarStr, status); 440 } 441 if (!numberingSystem->IsUndefined() && JSLocale::IsWellNumberingSystem(nsStr)) { 442 icuLocale.setUnicodeKeywordValue("nu", nsStr, status); 443 } 444 445 // 24. Let timeZone be ? Get(options, "timeZone"). 446 OperationResult operationResult = 447 JSObject::GetProperty(thread, dateTimeOptions, globalConst->GetHandledTimeZoneString()); 448 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDateTimeFormat, thread); 449 dateTimeFormat->SetTimeZone(thread, operationResult.GetValue()); 450 451 // 25. If timeZone is not undefined, then 452 // a. Let timeZone be ? ToString(timeZone). 453 // b. If the result of IsValidTimeZoneName(timeZone) is false, then 454 // i. Throw a RangeError exception. 455 std::unique_ptr<icu::TimeZone> icuTimeZone; 456 if (!operationResult.GetValue()->IsUndefined()) { 457 JSHandle<EcmaString> timezone = JSTaggedValue::ToString(thread, operationResult.GetValue()); 458 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDateTimeFormat, thread); 459 icuTimeZone = ConstructTimeZone(intl::LocaleHelper::ConvertToStdString(timezone)); 460 if (icuTimeZone == nullptr) { 461 THROW_RANGE_ERROR_AND_RETURN(thread, "invalid timeZone", dateTimeFormat); 462 } 463 } else { 464 // 26. Else, 465 // a. Let timeZone be DefaultTimeZone(). 466 icuTimeZone = std::unique_ptr<icu::TimeZone>(icu::TimeZone::createDefault()); 467 } 468 469 // 36.a. Let hcDefault be dataLocaleData.[[hourCycle]]. 470 std::unique_ptr<icu::DateTimePatternGenerator> generator; 471 { 472 ThreadNativeScope nativeScope(thread); 473 generator.reset(icu::DateTimePatternGenerator::createInstance(icuLocale, status)); 474 } 475 if (U_FAILURE(status) || generator == nullptr) { 476 if (status == UErrorCode::U_MISSING_RESOURCE_ERROR) { 477 THROW_REFERENCE_ERROR_AND_RETURN(thread, "can not find icu data resources", dateTimeFormat); 478 } 479 THROW_RANGE_ERROR_AND_RETURN(thread, "create icu::DateTimePatternGenerator failed", dateTimeFormat); 480 } 481 HourCycleOption hcDefault = OptionToHourCycle(generator->getDefaultHourCycle(status)); 482 // b. Let hc be dateTimeFormat.[[HourCycle]]. 483 HourCycleOption hc = hourCycle; 484 if (hourCycle == HourCycleOption::UNDEFINED 485 && resolvedLocale.extensions.find("hc") != resolvedLocale.extensions.end()) { 486 hc = OptionToHourCycle(resolvedLocale.extensions.find("hc")->second); 487 } 488 // c. If hc is null, then 489 // i. Set hc to hcDefault. 490 if (hc == HourCycleOption::UNDEFINED) { 491 hc = hcDefault; 492 } 493 // d. If hour12 is not undefined, then 494 if (!hour12->IsUndefined()) { 495 // i. If hour12 is true, then 496 if (JSTaggedValue::SameValue(hour12.GetTaggedValue(), JSTaggedValue::True())) { 497 // 1. If hcDefault is "h11" or "h23", then 498 if (hcDefault == HourCycleOption::H11 || hcDefault == HourCycleOption::H23) { 499 // a. Set hc to "h11". 500 hc = HourCycleOption::H11; 501 } else { 502 // 2. Else, 503 // a. Set hc to "h12". 504 hc = HourCycleOption::H12; 505 } 506 } else { 507 // ii. Else, 508 // 2. If hcDefault is "h11" or "h23", then 509 if (hcDefault == HourCycleOption::H11 || hcDefault == HourCycleOption::H23) { 510 // a. Set hc to "h23". 511 hc = HourCycleOption::H23; 512 } else { 513 // 3. Else, 514 // a. Set hc to "h24". 515 hc = HourCycleOption::H24; 516 } 517 } 518 } 519 520 // Set isHourDefined be false when dateTimeFormat.[[Hour]] is not undefined. 521 bool isHourDefined = false; 522 523 // 29. For each row of Table 6, except the header row, in table order, do 524 // a. Let prop be the name given in the Property column of the row. 525 // b. Let value be ? GetOption(options, prop, "string", « the strings given in the Values column of the 526 // row », undefined). 527 // c. Set opt.[[<prop>]] to value. 528 std::string skeleton; 529 std::vector<IcuPatternDesc> data = GetIcuPatternDesc(hc); 530 int32_t explicitFormatComponents = 0; 531 std::vector<std::string> skeletonOpts; 532 for (const IcuPatternDesc &item : data) { 533 // prop be [[TimeZoneName]] 534 if (item.property == "timeZoneName") { 535 // b. If prop is "fractionalSecondDigits", then 536 // i. Let value be ? GetNumberOption(options, "fractionalSecondDigits", 1, 3, undefined). 537 int secondDigitsString = JSLocale::GetNumberOption(thread, dateTimeOptions, 538 globalConst->GetHandledFractionalSecondDigitsString(), 539 1, 3, 0); 540 skeleton.append(secondDigitsString, 'S'); 541 // e. If value is not undefined, then 542 // i. Set hasExplicitFormatComponents to true. 543 if (secondDigitsString > 0) { 544 explicitFormatComponents = 1; 545 skeletonOpts.emplace_back(item.property); 546 } 547 } 548 JSHandle<JSTaggedValue> property(thread, factory->NewFromStdString(item.property).GetTaggedValue()); 549 std::string value; 550 bool isFind = JSLocale::GetOptionOfString(thread, dateTimeOptions, property, item.allowedValues, &value); 551 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDateTimeFormat, thread); 552 if (isFind) { 553 skeletonOpts.emplace_back(item.property); 554 explicitFormatComponents = 1; 555 skeleton += item.map.find(value)->second; 556 // [[Hour]] is defined. 557 isHourDefined = (item.property == "hour") ? true : isHourDefined; 558 } 559 } 560 561 // 13.1.3 BasicFormatMatcher (options, formats) 562 [[maybe_unused]] auto formatMatcher = JSLocale::GetOptionOfString<FormatMatcherOption>( 563 thread, dateTimeOptions, globalConst->GetHandledFormatMatcherString(), 564 {FormatMatcherOption::BASIC, FormatMatcherOption::BEST_FIT}, {"basic", "best fit"}, 565 FormatMatcherOption::BEST_FIT); 566 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDateTimeFormat, thread); 567 568 // Let dateStyle be ? GetOption(options, "string", «"full", "long", "medium", "short"», undefined). 569 // Set dateTimeFormat.[[dateStyle]] 570 auto dateStyle = JSLocale::GetOptionOfString<DateTimeStyleOption>( 571 thread, dateTimeOptions, globalConst->GetHandledDateStyleString(), 572 {DateTimeStyleOption::FULL, DateTimeStyleOption::LONG, DateTimeStyleOption::MEDIUM, DateTimeStyleOption::SHORT}, 573 {"full", "long", "medium", "short"}, DateTimeStyleOption::UNDEFINED); 574 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDateTimeFormat, thread); 575 dateTimeFormat->SetDateStyle(dateStyle); 576 577 // Let timeStyle be ? GetOption(options, "string", «"full", "long", "medium", "short"», undefined). 578 // Set dateTimeFormat.[[timeStyle]] 579 auto timeStyle = JSLocale::GetOptionOfString<DateTimeStyleOption>( 580 thread, dateTimeOptions, globalConst->GetHandledTimeStyleString(), 581 {DateTimeStyleOption::FULL, DateTimeStyleOption::LONG, DateTimeStyleOption::MEDIUM, DateTimeStyleOption::SHORT}, 582 {"full", "long", "medium", "short"}, DateTimeStyleOption::UNDEFINED); 583 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDateTimeFormat, thread); 584 dateTimeFormat->SetTimeStyle(timeStyle); 585 586 HourCycleOption dtfHourCycle = HourCycleOption::UNDEFINED; 587 588 if (timeStyle != DateTimeStyleOption::UNDEFINED) { 589 // Set dateTimeFormat.[[HourCycle]] to hc. 590 dtfHourCycle = hc; 591 } 592 593 if (dateStyle == DateTimeStyleOption::UNDEFINED 594 && timeStyle == DateTimeStyleOption::UNDEFINED) { 595 ToDateTimeSkeleton(thread, skeletonOpts, skeleton, hc, RequiredOption::ANY, DefaultsOption::DATE); 596 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDateTimeFormat, thread); 597 // If dateTimeFormat.[[Hour]] is defined, then 598 if (isHourDefined) { 599 // e. Set dateTimeFormat.[[HourCycle]] to hc. 600 dtfHourCycle = hc; 601 } else { 602 // 37. Else, 603 // a. Set dateTimeFormat.[[HourCycle]] to undefined. 604 dtfHourCycle = HourCycleOption::UNDEFINED; 605 } 606 } 607 608 // Set dateTimeFormat.[[hourCycle]]. 609 dateTimeFormat->SetHourCycle(dtfHourCycle); 610 611 // Set dateTimeFormat.[[icuLocale]]. 612 JSDateTimeFormat::SetIcuLocale(thread, dateTimeFormat, icuLocale, JSDateTimeFormat::FreeIcuLocale); 613 614 // Creates a Calendar using the given timezone and given locale. 615 // Set dateTimeFormat.[[icuSimpleDateFormat]]. 616 icu::UnicodeString dtfSkeleton(skeleton.c_str()); 617 status = U_ZERO_ERROR; 618 icu::UnicodeString pattern = ChangeHourCyclePattern( 619 generator.get()->getBestPattern(dtfSkeleton, UDATPG_MATCH_HOUR_FIELD_LENGTH, status), dtfHourCycle); 620 ASSERT_PRINT((U_SUCCESS(status) != 0), "get best pattern failed"); 621 auto simpleDateFormatIcu(std::make_unique<icu::SimpleDateFormat>(pattern, icuLocale, status)); 622 if (dateStyle != DateTimeStyleOption::UNDEFINED || timeStyle != DateTimeStyleOption::UNDEFINED) { 623 if (explicitFormatComponents != 0) { 624 THROW_TYPE_ERROR_AND_RETURN(thread, "Invalid option : option", dateTimeFormat); 625 } 626 simpleDateFormatIcu = DateTimeStylePattern(dateStyle, timeStyle, icuLocale, 627 hc, generator.get()); 628 } 629 if (U_FAILURE(status) != 0) { 630 simpleDateFormatIcu = std::unique_ptr<icu::SimpleDateFormat>(); 631 } 632 ASSERT_PRINT(simpleDateFormatIcu != nullptr, "invalid icuSimpleDateFormat"); 633 std::unique_ptr<icu::Calendar> calendarPtr = BuildCalendar(icuLocale, *icuTimeZone); 634 ASSERT_PRINT(calendarPtr != nullptr, "invalid calendar"); 635 simpleDateFormatIcu->adoptCalendar(calendarPtr.release()); 636 if (type != IcuCacheType::NOT_CACHE) { 637 std::string cacheEntry = 638 locales->IsUndefined() ? "" : EcmaStringAccessor(locales.GetTaggedValue()).ToStdString(); 639 switch (type) { 640 case IcuCacheType::DEFAULT: 641 thread->GetCurrentEcmaContext()->SetIcuFormatterToCache(IcuFormatterType::SIMPLE_DATE_FORMAT_DEFAULT, 642 cacheEntry, simpleDateFormatIcu.release(), JSDateTimeFormat::FreeSimpleDateFormat); 643 break; 644 case IcuCacheType::DATE: 645 thread->GetCurrentEcmaContext()->SetIcuFormatterToCache(IcuFormatterType::SIMPLE_DATE_FORMAT_DATE, 646 cacheEntry, simpleDateFormatIcu.release(), JSDateTimeFormat::FreeSimpleDateFormat); 647 break; 648 case IcuCacheType::TIME: 649 thread->GetCurrentEcmaContext()->SetIcuFormatterToCache(IcuFormatterType::SIMPLE_DATE_FORMAT_TIME, 650 cacheEntry, simpleDateFormatIcu.release(), JSDateTimeFormat::FreeSimpleDateFormat); 651 break; 652 default: 653 UNREACHABLE(); 654 } 655 } else { 656 SetIcuSimpleDateFormat(thread, dateTimeFormat, *simpleDateFormatIcu, JSDateTimeFormat::FreeSimpleDateFormat); 657 } 658 659 // Set dateTimeFormat.[[iso8601]]. 660 bool iso8601 = strstr(icuLocale.getName(), "calendar=iso8601") != nullptr; 661 dateTimeFormat->SetIso8601(thread, JSTaggedValue(iso8601)); 662 663 // Set dateTimeFormat.[[locale]]. 664 if (!hour12->IsUndefined() || hourCycle != HourCycleOption::UNDEFINED) { 665 if ((resolvedLocale.extensions.find("hc") != resolvedLocale.extensions.end()) && 666 (dtfHourCycle != OptionToHourCycle((resolvedLocale.extensions.find("hc")->second)))) { 667 resolvedIcuLocaleCopy.setUnicodeKeywordValue("hc", nullptr, status); 668 ASSERT_PRINT(U_SUCCESS(status), "resolvedIcuLocaleCopy set hc failed"); 669 } 670 } 671 JSHandle<EcmaString> localeStr = intl::LocaleHelper::ToLanguageTag(thread, resolvedIcuLocaleCopy); 672 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDateTimeFormat, thread); 673 dateTimeFormat->SetLocale(thread, localeStr.GetTaggedValue()); 674 675 // Set dateTimeFormat.[[boundFormat]]. 676 dateTimeFormat->SetBoundFormat(thread, JSTaggedValue::Undefined()); 677 678 // 39. Return dateTimeFormat. 679 return dateTimeFormat; 680} 681 682icu::SimpleDateFormat *JSDateTimeFormat::GetCachedIcuSimpleDateFormat(JSThread *thread, 683 const JSHandle<JSTaggedValue> &locales, 684 IcuFormatterType type) 685{ 686 std::string cacheEntry = locales->IsUndefined() ? "" : EcmaStringAccessor(locales.GetTaggedValue()).ToStdString(); 687 void *cachedSimpleDateFormat = thread->GetCurrentEcmaContext()->GetIcuFormatterFromCache(type, cacheEntry); 688 if (cachedSimpleDateFormat != nullptr) { 689 return reinterpret_cast<icu::SimpleDateFormat*>(cachedSimpleDateFormat); 690 } 691 return nullptr; 692} 693 694// 13.1.2 ToDateTimeOptions (options, required, defaults) 695JSHandle<JSObject> JSDateTimeFormat::ToDateTimeOptions(JSThread *thread, const JSHandle<JSTaggedValue> &options, 696 const RequiredOption &required, const DefaultsOption &defaults) 697{ 698 EcmaVM *ecmaVm = thread->GetEcmaVM(); 699 ObjectFactory *factory = ecmaVm->GetFactory(); 700 701 // 1. If options is undefined, let options be null; otherwise let options be ? ToObject(options). 702 JSHandle<JSObject> optionsResult(thread, JSTaggedValue::Null()); 703 if (!options->IsUndefined()) { 704 optionsResult = JSTaggedValue::ToObject(thread, options); 705 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSObject, thread); 706 } 707 708 // 2. Let options be ObjectCreate(options). 709 optionsResult = JSObject::ObjectCreate(thread, optionsResult); 710 711 // 3. Let needDefaults be true. 712 bool needDefaults = true; 713 714 // 4. If required is "date" or "any", then 715 // a. For each of the property names "weekday", "year", "month", "day", do 716 // i. Let prop be the property name. 717 // ii. Let value be ? Get(options, prop). 718 // iii. If value is not undefined, let needDefaults be false. 719 auto globalConst = thread->GlobalConstants(); 720 if (required == RequiredOption::DATE || required == RequiredOption::ANY) { 721 JSHandle<TaggedArray> array = factory->NewTaggedArray(CAPACITY_4); 722 array->Set(thread, 0, globalConst->GetHandledWeekdayString()); 723 array->Set(thread, 1, globalConst->GetHandledYearString()); 724 array->Set(thread, 2, globalConst->GetHandledMonthString()); // 2 means the third slot 725 array->Set(thread, 3, globalConst->GetHandledDayString()); // 3 means the fourth slot 726 JSMutableHandle<JSTaggedValue> key(thread, JSTaggedValue::Undefined()); 727 uint32_t len = array->GetLength(); 728 for (uint32_t i = 0; i < len; i++) { 729 key.Update(array->Get(thread, i)); 730 OperationResult operationResult = JSObject::GetProperty(thread, optionsResult, key); 731 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSObject, thread); 732 if (!operationResult.GetValue()->IsUndefined()) { 733 needDefaults = false; 734 } 735 } 736 } 737 738 // 5. If required is "time" or "any", then 739 // a. For each of the property names "dayPeriod", "hour", "minute", "second", "fractionalSecondDigits", do 740 // i. Let prop be the property name. 741 // ii. Let value be ? Get(options, prop). 742 // iii. If value is not undefined, let needDefaults be false. 743 if (required == RequiredOption::TIME || required == RequiredOption::ANY) { 744 JSHandle<TaggedArray> array = factory->NewTaggedArray(CAPACITY_5); 745 array->Set(thread, 0, globalConst->GetHandledDayPeriodString()); 746 array->Set(thread, 1, globalConst->GetHandledHourString()); 747 array->Set(thread, 2, globalConst->GetHandledMinuteString()); // 2 means the second slot 748 array->Set(thread, 3, globalConst->GetHandledSecondString()); // 3 means the third slot 749 array->Set(thread, 4, globalConst->GetHandledFractionalSecondDigitsString()); // 4 means the fourth slot 750 JSMutableHandle<JSTaggedValue> key(thread, JSTaggedValue::Undefined()); 751 uint32_t len = array->GetLength(); 752 for (uint32_t i = 0; i < len; i++) { 753 key.Update(array->Get(thread, i)); 754 OperationResult operationResult = JSObject::GetProperty(thread, optionsResult, key); 755 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSObject, thread); 756 if (!operationResult.GetValue()->IsUndefined()) { 757 needDefaults = false; 758 } 759 } 760 } 761 762 // Let dateStyle/timeStyle be ? Get(options, "dateStyle"/"timeStyle"). 763 OperationResult dateStyleResult = 764 JSObject::GetProperty(thread, optionsResult, globalConst->GetHandledDateStyleString()); 765 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSObject, thread); 766 JSHandle<JSTaggedValue> dateStyle = dateStyleResult.GetValue(); 767 OperationResult timeStyleResult = 768 JSObject::GetProperty(thread, optionsResult, globalConst->GetHandledTimeStyleString()); 769 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSObject, thread); 770 JSHandle<JSTaggedValue> timeStyle = timeStyleResult.GetValue(); 771 772 // If dateStyle is not undefined or timeStyle is not undefined, let needDefaults be false. 773 if (!dateStyle->IsUndefined() || !timeStyle->IsUndefined()) { 774 needDefaults = false; 775 } 776 777 // If required is "date"/"time" and timeStyle is not undefined, throw a TypeError exception. 778 if (required == RequiredOption::DATE && !timeStyle->IsUndefined()) { 779 THROW_TYPE_ERROR_AND_RETURN(thread, "timeStyle is not undefined", optionsResult); 780 } 781 if (required == RequiredOption::TIME && !dateStyle->IsUndefined()) { 782 THROW_TYPE_ERROR_AND_RETURN(thread, "dateStyle is not undefined", optionsResult); 783 } 784 785 // 6. If needDefaults is true and defaults is either "date" or "all", then 786 // a. For each of the property names "year", "month", "day", do 787 // i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric"). 788 if (needDefaults && (defaults == DefaultsOption::DATE || defaults == DefaultsOption::ALL)) { 789 JSHandle<TaggedArray> array = factory->NewTaggedArray(CAPACITY_3); 790 array->Set(thread, 0, globalConst->GetHandledYearString()); 791 array->Set(thread, 1, globalConst->GetHandledMonthString()); 792 array->Set(thread, 2, globalConst->GetHandledDayString()); // 2 means the third slot 793 JSMutableHandle<JSTaggedValue> key(thread, JSTaggedValue::Undefined()); 794 uint32_t len = array->GetLength(); 795 for (uint32_t i = 0; i < len; i++) { 796 key.Update(array->Get(thread, i)); 797 JSObject::CreateDataPropertyOrThrow(thread, optionsResult, key, globalConst->GetHandledNumericString()); 798 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSObject, thread); 799 } 800 } 801 802 // 7. If needDefaults is true and defaults is either "time" or "all", then 803 // a. For each of the property names "hour", "minute", "second", do 804 // i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric"). 805 if (needDefaults && (defaults == DefaultsOption::TIME || defaults == DefaultsOption::ALL)) { 806 JSHandle<TaggedArray> array = factory->NewTaggedArray(CAPACITY_3); 807 array->Set(thread, 0, globalConst->GetHandledHourString()); 808 array->Set(thread, 1, globalConst->GetHandledMinuteString()); 809 array->Set(thread, 2, globalConst->GetHandledSecondString()); // 2 means the third slot 810 JSMutableHandle<JSTaggedValue> key(thread, JSTaggedValue::Undefined()); 811 uint32_t len = array->GetLength(); 812 for (uint32_t i = 0; i < len; i++) { 813 key.Update(array->Get(thread, i)); 814 JSObject::CreateDataPropertyOrThrow(thread, optionsResult, key, globalConst->GetHandledNumericString()); 815 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSObject, thread); 816 } 817 } 818 819 // 8. Return options. 820 return optionsResult; 821} 822 823void JSDateTimeFormat::ToDateTimeSkeleton(JSThread *thread, const std::vector<std::string> &options, 824 std::string &skeleton, HourCycleOption hc, 825 const RequiredOption &required, const DefaultsOption &defaults) 826{ 827 EcmaVM *ecmaVm = thread->GetEcmaVM(); 828 ObjectFactory *factory = ecmaVm->GetFactory(); 829 830 // 1. Let needDefaults be true. 831 bool needDefaults = true; 832 833 // 2. If required is "date" or "any", then 834 // a. For each of the property names "weekday", "year", "month", "day", do 835 // i. Let prop be the property name. 836 // ii. Let value be ? Get(options, prop). 837 // iii. If value is not undefined, let needDefaults be false. 838 auto globalConst = thread->GlobalConstants(); 839 if (required == RequiredOption::DATE || required == RequiredOption::ANY) { 840 JSHandle<TaggedArray> array = factory->NewTaggedArray(CAPACITY_4); 841 array->Set(thread, 0, globalConst->GetHandledWeekdayString()); 842 array->Set(thread, 1, globalConst->GetHandledYearString()); 843 array->Set(thread, 2, globalConst->GetHandledMonthString()); // 2 means the third slot 844 array->Set(thread, 3, globalConst->GetHandledDayString()); // 3 means the fourth slot 845 JSMutableHandle<JSTaggedValue> key(thread, JSTaggedValue::Undefined()); 846 uint32_t len = array->GetLength(); 847 for (uint32_t i = 0; i < len; i++) { 848 key.Update(array->Get(thread, i)); 849 std::string result = EcmaStringAccessor(key.GetTaggedValue()).ToStdString(); 850 auto it = std::find(options.begin(), options.end(), result); 851 if (it != options.end()) { 852 needDefaults = false; 853 break; 854 } 855 } 856 } 857 858 // 3. If required is "time" or "any", then 859 // a. For each of the property names "dayPeriod", "hour", "minute", "second", "fractionalSecondDigits", do 860 // i. Let prop be the property name. 861 // ii. Let value be ? Get(options, prop). 862 // iii. If value is not undefined, let needDefaults be false. 863 if (required == RequiredOption::TIME || required == RequiredOption::ANY) { 864 JSHandle<TaggedArray> array = factory->NewTaggedArray(CAPACITY_5); 865 array->Set(thread, 0, globalConst->GetHandledDayPeriodString()); 866 array->Set(thread, 1, globalConst->GetHandledHourString()); 867 array->Set(thread, 2, globalConst->GetHandledMinuteString()); // 2 means the second slot 868 array->Set(thread, 3, globalConst->GetHandledSecondString()); // 3 means the third slot 869 array->Set(thread, 4, globalConst->GetHandledFractionalSecondDigitsString()); // 4 means the fourth slot 870 JSMutableHandle<JSTaggedValue> key(thread, JSTaggedValue::Undefined()); 871 uint32_t len = array->GetLength(); 872 for (uint32_t i = 0; i < len; i++) { 873 key.Update(array->Get(thread, i)); 874 std::string result = EcmaStringAccessor(key.GetTaggedValue()).ToStdString(); 875 auto it = std::find(options.begin(), options.end(), result); 876 if (it != options.end()) { 877 needDefaults = false; 878 break; 879 } 880 } 881 } 882 883 // 4. If needDefaults is true and defaults is either "date" or "all", then 884 // skeleton += "year", "month", "day" 885 if (needDefaults && (defaults == DefaultsOption::DATE || defaults == DefaultsOption::ALL)) { 886 skeleton += "yMd"; 887 } 888 889 // 5. If needDefaults is true and defaults is either "time" or "all", then 890 // skeleton += "hour", "minute", "second" 891 if (needDefaults && (defaults == DefaultsOption::TIME || defaults == DefaultsOption::ALL)) { 892 switch (hc) { 893 case HourCycleOption::H12: 894 skeleton += "hms"; 895 break; 896 case HourCycleOption::H23: 897 case HourCycleOption::UNDEFINED: 898 skeleton += "Hms"; 899 break; 900 case HourCycleOption::H11: 901 skeleton += "Kms"; 902 break; 903 case HourCycleOption::H24: 904 skeleton += "kms"; 905 break; 906 default: 907 break; 908 } 909 } 910} 911 912// 13.1.7 FormatDateTime(dateTimeFormat, x) 913JSHandle<EcmaString> JSDateTimeFormat::FormatDateTime(JSThread *thread, 914 const JSHandle<JSDateTimeFormat> &dateTimeFormat, double x) 915{ 916 icu::SimpleDateFormat *simpleDateFormat = dateTimeFormat->GetIcuSimpleDateFormat(); 917 JSHandle<EcmaString> res = FormatDateTime(thread, simpleDateFormat, x); 918 RETURN_HANDLE_IF_ABRUPT_COMPLETION(EcmaString, thread); 919 return res; 920} 921 922JSHandle<EcmaString> JSDateTimeFormat::FormatDateTime(JSThread *thread, 923 const icu::SimpleDateFormat *simpleDateFormat, double x) 924{ 925 // 1. Let parts be ? PartitionDateTimePattern(dateTimeFormat, x). 926 double xValue = JSDate::TimeClip(x); 927 if (std::isnan(xValue)) { 928 THROW_RANGE_ERROR_AND_RETURN(thread, "Invalid time value", thread->GetEcmaVM()->GetFactory()->GetEmptyString()); 929 } 930 931 // 2. Let result be the empty String. 932 icu::UnicodeString result; 933 934 // 3. Set result to the string-concatenation of result and part.[[Value]]. 935 { 936 ThreadNativeScope nativeScope(thread); 937 simpleDateFormat->format(xValue, result); 938 } 939 940 // 4. Return result. 941 return intl::LocaleHelper::UStringToString(thread, result); 942} 943 944// 13.1.8 FormatDateTimeToParts (dateTimeFormat, x) 945JSHandle<JSArray> JSDateTimeFormat::FormatDateTimeToParts(JSThread *thread, 946 const JSHandle<JSDateTimeFormat> &dateTimeFormat, double x) 947{ 948 icu::SimpleDateFormat *simpleDateFormat = dateTimeFormat->GetIcuSimpleDateFormat(); 949 ASSERT(simpleDateFormat != nullptr); 950 UErrorCode status = U_ZERO_ERROR; 951 icu::FieldPositionIterator fieldPositionIter; 952 icu::UnicodeString formattedParts; 953 { 954 ThreadNativeScope nativeScope(thread); 955 simpleDateFormat->format(x, formattedParts, &fieldPositionIter, status); 956 } 957 if (U_FAILURE(status) != 0) { 958 THROW_TYPE_ERROR_AND_RETURN(thread, "format failed", thread->GetEcmaVM()->GetFactory()->NewJSArray()); 959 } 960 961 // 2. Let result be ArrayCreate(0). 962 JSHandle<JSArray> result(JSArray::ArrayCreate(thread, JSTaggedNumber(0))); 963 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread); 964 if (formattedParts.isBogus()) { 965 return result; 966 } 967 968 // 3. Let n be 0. 969 int32_t index = 0; 970 int32_t preEdgePos = 0; 971 std::vector<CommonDateFormatPart> parts; 972 icu::FieldPosition fieldPosition; 973 while (fieldPositionIter.next(fieldPosition)) { 974 int32_t fField = fieldPosition.getField(); 975 int32_t fBeginIndex = fieldPosition.getBeginIndex(); 976 int32_t fEndIndex = fieldPosition.getEndIndex(); 977 if (preEdgePos < fBeginIndex) { 978 parts.emplace_back(CommonDateFormatPart(fField, preEdgePos, fBeginIndex, index, true)); 979 ++index; 980 } 981 parts.emplace_back(CommonDateFormatPart(fField, fBeginIndex, fEndIndex, index, false)); 982 preEdgePos = fEndIndex; 983 ++index; 984 } 985 int32_t length = formattedParts.length(); 986 if (preEdgePos < length) { 987 parts.emplace_back(CommonDateFormatPart(-1, preEdgePos, length, index, true)); 988 } 989 JSMutableHandle<EcmaString> substring(thread, JSTaggedValue::Undefined()); 990 991 // 4. For each part in parts, do 992 for (auto part : parts) { 993 substring.Update(intl::LocaleHelper::UStringToString(thread, formattedParts, part.fBeginIndex, 994 part.fEndIndex).GetTaggedValue()); 995 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread); 996 // Let O be ObjectCreate(%ObjectPrototype%). 997 // Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]). 998 // Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]). 999 // Perform ! CreateDataProperty(result, ! ToString(n), O). 1000 if (part.isPreExist) { 1001 JSLocale::PutElement(thread, part.index, result, ConvertFieldIdToDateType(thread, -1), 1002 JSHandle<JSTaggedValue>::Cast(substring)); 1003 } else { 1004 JSLocale::PutElement(thread, part.index, result, ConvertFieldIdToDateType(thread, part.fField), 1005 JSHandle<JSTaggedValue>::Cast(substring)); 1006 } 1007 } 1008 1009 // 5. Return result. 1010 return result; 1011} 1012 1013// 13.1.10 UnwrapDateTimeFormat(dtf) 1014JSHandle<JSTaggedValue> JSDateTimeFormat::UnwrapDateTimeFormat(JSThread *thread, 1015 const JSHandle<JSTaggedValue> &dateTimeFormat) 1016{ 1017 // 1. Assert: Type(dtf) is Object. 1018 ASSERT_PRINT(dateTimeFormat->IsJSObject(), "dateTimeFormat is not object"); 1019 1020 // 2. If dateTimeFormat does not have an [[InitializedDateTimeFormat]] internal slot 1021 // and ? InstanceofOperator(dateTimeFormat, %DateTimeFormat%) is true, then 1022 // a. Let dateTimeFormat be ? Get(dateTimeFormat, %Intl%.[[FallbackSymbol]]). 1023 JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv(); 1024 bool isInstanceOf = JSFunction::OrdinaryHasInstance(thread, env->GetDateTimeFormatFunction(), dateTimeFormat); 1025 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, dateTimeFormat); 1026 if (!dateTimeFormat->IsJSDateTimeFormat() && isInstanceOf) { 1027 JSHandle<JSTaggedValue> key(thread, JSHandle<JSIntl>::Cast(env->GetIntlFunction())->GetFallbackSymbol()); 1028 OperationResult operationResult = JSTaggedValue::GetProperty(thread, dateTimeFormat, key); 1029 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, dateTimeFormat); 1030 return operationResult.GetValue(); 1031 } 1032 1033 // 3. Perform ? RequireInternalSlot(dateTimeFormat, [[InitializedDateTimeFormat]]). 1034 if (!dateTimeFormat->IsJSDateTimeFormat()) { 1035 THROW_TYPE_ERROR_AND_RETURN(thread, "is not JSDateTimeFormat", 1036 JSHandle<JSTaggedValue>(thread, JSTaggedValue::Exception())); 1037 } 1038 1039 // 4. Return dateTimeFormat. 1040 return dateTimeFormat; 1041} 1042 1043JSHandle<JSTaggedValue> ToHourCycleEcmaString(JSThread *thread, HourCycleOption hc) 1044{ 1045 JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined()); 1046 auto globalConst = thread->GlobalConstants(); 1047 switch (hc) { 1048 case HourCycleOption::H11: 1049 result.Update(globalConst->GetHandledH11String().GetTaggedValue()); 1050 break; 1051 case HourCycleOption::H12: 1052 result.Update(globalConst->GetHandledH12String().GetTaggedValue()); 1053 break; 1054 case HourCycleOption::H23: 1055 result.Update(globalConst->GetHandledH23String().GetTaggedValue()); 1056 break; 1057 case HourCycleOption::H24: 1058 result.Update(globalConst->GetHandledH24String().GetTaggedValue()); 1059 break; 1060 default: 1061 LOG_ECMA(FATAL) << "this branch is unreachable"; 1062 UNREACHABLE(); 1063 } 1064 return result; 1065} 1066 1067JSHandle<JSTaggedValue> ToDateTimeStyleEcmaString(JSThread *thread, DateTimeStyleOption style) 1068{ 1069 JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined()); 1070 auto globalConst = thread->GlobalConstants(); 1071 switch (style) { 1072 case DateTimeStyleOption::FULL: 1073 result.Update(globalConst->GetHandledFullString().GetTaggedValue()); 1074 break; 1075 case DateTimeStyleOption::LONG: 1076 result.Update(globalConst->GetHandledLongString().GetTaggedValue()); 1077 break; 1078 case DateTimeStyleOption::MEDIUM: 1079 result.Update(globalConst->GetHandledMediumString().GetTaggedValue()); 1080 break; 1081 case DateTimeStyleOption::SHORT: 1082 result.Update(globalConst->GetHandledShortString().GetTaggedValue()); 1083 break; 1084 default: 1085 LOG_ECMA(FATAL) << "this branch is unreachable"; 1086 UNREACHABLE(); 1087 } 1088 return result; 1089} 1090 1091// 13.4.5 Intl.DateTimeFormat.prototype.resolvedOptions () 1092void JSDateTimeFormat::ResolvedOptions(JSThread *thread, const JSHandle<JSDateTimeFormat> &dateTimeFormat, 1093 const JSHandle<JSObject> &options) 1094{ // Table 8: Resolved Options of DateTimeFormat Instances 1095 // Internal Slot Property 1096 // [[Locale]] "locale" 1097 // [[Calendar]] "calendar" 1098 // [[NumberingSystem]] "numberingSystem" 1099 // [[TimeZone]] "timeZone" 1100 // [[HourCycle]] "hourCycle" 1101 // "hour12" 1102 // [[Weekday]] "weekday" 1103 // [[Era]] "era" 1104 // [[Year]] "year" 1105 // [[Month]] "month" 1106 // [[Day]] "day" 1107 // [[Hour]] "hour" 1108 // [[Minute]] "minute" 1109 // [[Second]] "second" 1110 // [[TimeZoneName]] "timeZoneName" 1111 auto globalConst = thread->GlobalConstants(); 1112 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); 1113 1114 // 5. For each row of Table 8, except the header row, in table order, do 1115 // Let p be the Property value of the current row. 1116 // [[Locale]] 1117 JSHandle<JSTaggedValue> locale(thread, dateTimeFormat->GetLocale()); 1118 JSHandle<JSTaggedValue> property = globalConst->GetHandledLocaleString(); 1119 JSObject::CreateDataPropertyOrThrow(thread, options, property, locale); 1120 RETURN_IF_ABRUPT_COMPLETION(thread); 1121 // [[Calendar]] 1122 JSMutableHandle<JSTaggedValue> calendarValue(thread, dateTimeFormat->GetCalendar()); 1123 icu::SimpleDateFormat *icuSimpleDateFormat = dateTimeFormat->GetIcuSimpleDateFormat(); 1124 const icu::Calendar *calendar = icuSimpleDateFormat->getCalendar(); 1125 std::string icuCalendar = calendar->getType(); 1126 if (icuCalendar == "gregorian") { 1127 if (dateTimeFormat->GetIso8601().IsTrue()) { 1128 calendarValue.Update(globalConst->GetHandledIso8601String().GetTaggedValue()); 1129 } else { 1130 calendarValue.Update(globalConst->GetHandledGregoryString().GetTaggedValue()); 1131 } 1132 } else if (icuCalendar == "ethiopic-amete-alem") { 1133 calendarValue.Update(globalConst->GetHandledEthioaaString().GetTaggedValue()); 1134 } else if (icuCalendar.length() != 0) { 1135 calendarValue.Update(factory->NewFromStdString(icuCalendar).GetTaggedValue()); 1136 } 1137 property = globalConst->GetHandledCalendarString(); 1138 JSObject::CreateDataPropertyOrThrow(thread, options, property, calendarValue); 1139 RETURN_IF_ABRUPT_COMPLETION(thread); 1140 // [[NumberingSystem]] 1141 JSHandle<JSTaggedValue> numberingSystem(thread, dateTimeFormat->GetNumberingSystem()); 1142 if (numberingSystem->IsUndefined()) { 1143 numberingSystem = globalConst->GetHandledLatnString(); 1144 } 1145 property = globalConst->GetHandledNumberingSystemString(); 1146 JSObject::CreateDataPropertyOrThrow(thread, options, property, numberingSystem); 1147 RETURN_IF_ABRUPT_COMPLETION(thread); 1148 // [[TimeZone]] 1149 JSMutableHandle<JSTaggedValue> timezoneValue(thread, dateTimeFormat->GetTimeZone()); 1150 const icu::TimeZone &icuTZ = calendar->getTimeZone(); 1151 icu::UnicodeString timezone; 1152 icuTZ.getID(timezone); 1153 UErrorCode status = U_ZERO_ERROR; 1154 icu::UnicodeString canonicalTimezone; 1155 icu::TimeZone::getCanonicalID(timezone, canonicalTimezone, status); 1156 if (U_SUCCESS(status) != 0) { 1157 if ((canonicalTimezone == UNICODE_STRING_SIMPLE("Etc/UTC")) != 0 || 1158 (canonicalTimezone == UNICODE_STRING_SIMPLE("Etc/GMT")) != 0) { 1159 timezoneValue.Update(globalConst->GetUTCString()); 1160 } else { 1161 timezoneValue.Update(intl::LocaleHelper::UStringToString(thread, canonicalTimezone).GetTaggedValue()); 1162 } 1163 } 1164 property = globalConst->GetHandledTimeZoneString(); 1165 JSObject::CreateDataPropertyOrThrow(thread, options, property, timezoneValue); 1166 RETURN_IF_ABRUPT_COMPLETION(thread); 1167 // [[HourCycle]] 1168 // For web compatibility reasons, if the property "hourCycle" is set, the "hour12" property should be set to true 1169 // when "hourCycle" is "h11" or "h12", or to false when "hourCycle" is "h23" or "h24". 1170 // i. Let hc be dtf.[[HourCycle]]. 1171 JSHandle<JSTaggedValue> hcValue; 1172 HourCycleOption hc = dateTimeFormat->GetHourCycle(); 1173 if (hc != HourCycleOption::UNDEFINED) { 1174 property = globalConst->GetHandledHourCycleString(); 1175 hcValue = ToHourCycleEcmaString(thread, dateTimeFormat->GetHourCycle()); 1176 JSObject::CreateDataPropertyOrThrow(thread, options, property, hcValue); 1177 RETURN_IF_ABRUPT_COMPLETION(thread); 1178 if (hc == HourCycleOption::H11 || hc == HourCycleOption::H12) { 1179 JSHandle<JSTaggedValue> trueValue(thread, JSTaggedValue::True()); 1180 hcValue = trueValue; 1181 } else if (hc == HourCycleOption::H23 || hc == HourCycleOption::H24) { 1182 JSHandle<JSTaggedValue> falseValue(thread, JSTaggedValue::False()); 1183 hcValue = falseValue; 1184 } 1185 property = globalConst->GetHandledHour12String(); 1186 JSObject::CreateDataPropertyOrThrow(thread, options, property, hcValue); 1187 RETURN_IF_ABRUPT_COMPLETION(thread); 1188 } 1189 // [[DateStyle]], [[TimeStyle]]. 1190 if (dateTimeFormat->GetDateStyle() == DateTimeStyleOption::UNDEFINED && 1191 dateTimeFormat->GetTimeStyle() == DateTimeStyleOption::UNDEFINED) { 1192 icu::UnicodeString patternUnicode; 1193 icuSimpleDateFormat->toPattern(patternUnicode); 1194 std::string pattern; 1195 patternUnicode.toUTF8String(pattern); 1196 for (const auto &item : BuildIcuPatternDescs()) { 1197 // fractionalSecondsDigits need to be added before timeZoneName. 1198 if (item.property == "timeZoneName") { 1199 int tmpResult = count(pattern.begin(), pattern.end(), 'S'); 1200 int fsd = (tmpResult >= STRING_LENGTH_3) ? STRING_LENGTH_3 : tmpResult; 1201 if (fsd > 0) { 1202 JSHandle<JSTaggedValue> fsdValue(thread, JSTaggedValue(fsd)); 1203 property = globalConst->GetHandledFractionalSecondDigitsString(); 1204 JSObject::CreateDataPropertyOrThrow(thread, options, property, fsdValue); 1205 RETURN_IF_ABRUPT_COMPLETION(thread); 1206 } 1207 } 1208 for (const auto &pair : item.pairs) { 1209 if (pattern.find(pair.first) != std::string::npos) { 1210 hcValue = JSHandle<JSTaggedValue>::Cast(factory->NewFromStdString(pair.second)); 1211 property = JSHandle<JSTaggedValue>::Cast(factory->NewFromStdString(item.property)); 1212 JSObject::CreateDataPropertyOrThrow(thread, options, property, hcValue); 1213 RETURN_IF_ABRUPT_COMPLETION(thread); 1214 break; 1215 } 1216 } 1217 } 1218 } 1219 if (dateTimeFormat->GetDateStyle() != DateTimeStyleOption::UNDEFINED) { 1220 property = globalConst->GetHandledDateStyleString(); 1221 hcValue = ToDateTimeStyleEcmaString(thread, dateTimeFormat->GetDateStyle()); 1222 JSObject::CreateDataPropertyOrThrow(thread, options, property, hcValue); 1223 RETURN_IF_ABRUPT_COMPLETION(thread); 1224 } 1225 if (dateTimeFormat->GetTimeStyle() != DateTimeStyleOption::UNDEFINED) { 1226 property = globalConst->GetHandledTimeStyleString(); 1227 hcValue = ToDateTimeStyleEcmaString(thread, dateTimeFormat->GetTimeStyle()); 1228 JSObject::CreateDataPropertyOrThrow(thread, options, property, hcValue); 1229 RETURN_IF_ABRUPT_COMPLETION(thread); 1230 } 1231} 1232 1233// Use dateInterval(x, y) construct datetimeformatrange 1234icu::FormattedDateInterval JSDateTimeFormat::ConstructDTFRange(JSThread *thread, const JSHandle<JSDateTimeFormat> &dtf, 1235 double x, double y) 1236{ 1237 std::unique_ptr<icu::DateIntervalFormat> dateIntervalFormat(ConstructDateIntervalFormat(dtf)); 1238 if (dateIntervalFormat == nullptr) { 1239 icu::FormattedDateInterval emptyValue; 1240 THROW_TYPE_ERROR_AND_RETURN(thread, "create dateIntervalFormat failed", emptyValue); 1241 } 1242 UErrorCode status = U_ZERO_ERROR; 1243 icu::DateInterval dateInterval(x, y); 1244 icu::FormattedDateInterval formatted = dateIntervalFormat->formatToValue(dateInterval, status); 1245 return formatted; 1246} 1247 1248JSHandle<EcmaString> JSDateTimeFormat::NormDateTimeRange(JSThread *thread, const JSHandle<JSDateTimeFormat> &dtf, 1249 double x, double y) 1250{ 1251 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); 1252 JSHandle<EcmaString> result = factory->GetEmptyString(); 1253 // 1. Let x be TimeClip(x). 1254 x = JSDate::TimeClip(x); 1255 // 2. If x is NaN, throw a RangeError exception. 1256 if (std::isnan(x)) { 1257 THROW_RANGE_ERROR_AND_RETURN(thread, "x is NaN", result); 1258 } 1259 // 3. Let y be TimeClip(y). 1260 y = JSDate::TimeClip(y); 1261 // 4. If y is NaN, throw a RangeError exception. 1262 if (std::isnan(y)) { 1263 THROW_RANGE_ERROR_AND_RETURN(thread, "y is NaN", result); 1264 } 1265 1266 icu::FormattedDateInterval formatted = ConstructDTFRange(thread, dtf, x, y); 1267 RETURN_HANDLE_IF_ABRUPT_COMPLETION(EcmaString, thread); 1268 1269 // Formatted to string. 1270 bool outputRange = false; 1271 UErrorCode status = U_ZERO_ERROR; 1272 icu::UnicodeString formatResult = formatted.toString(status); 1273 if (U_FAILURE(status) != 0) { 1274 THROW_TYPE_ERROR_AND_RETURN(thread, "format to string failed", 1275 thread->GetEcmaVM()->GetFactory()->GetEmptyString()); 1276 } 1277 icu::ConstrainedFieldPosition cfpos; 1278 while (formatted.nextPosition(cfpos, status) != 0) { 1279 if (cfpos.getCategory() == UFIELD_CATEGORY_DATE_INTERVAL_SPAN) { 1280 outputRange = true; 1281 break; 1282 } 1283 } 1284 result = intl::LocaleHelper::UStringToString(thread, formatResult); 1285 if (!outputRange) { 1286 return FormatDateTime(thread, dtf, x); 1287 } 1288 return result; 1289} 1290 1291JSHandle<JSArray> JSDateTimeFormat::NormDateTimeRangeToParts(JSThread *thread, const JSHandle<JSDateTimeFormat> &dtf, 1292 double x, double y) 1293{ 1294 JSHandle<JSArray> result(JSArray::ArrayCreate(thread, JSTaggedNumber(0))); 1295 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread); 1296 // 1. Let x be TimeClip(x). 1297 x = JSDate::TimeClip(x); 1298 // 2. If x is NaN, throw a RangeError exception. 1299 if (std::isnan(x)) { 1300 THROW_RANGE_ERROR_AND_RETURN(thread, "x is invalid time value", result); 1301 } 1302 // 3. Let y be TimeClip(y). 1303 y = JSDate::TimeClip(y); 1304 // 4. If y is NaN, throw a RangeError exception. 1305 if (std::isnan(y)) { 1306 THROW_RANGE_ERROR_AND_RETURN(thread, "y is invalid time value", result); 1307 } 1308 1309 icu::FormattedDateInterval formatted = ConstructDTFRange(thread, dtf, x, y); 1310 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread); 1311 return ConstructFDateIntervalToJSArray(thread, formatted); 1312} 1313 1314JSHandle<TaggedArray> JSDateTimeFormat::GainAvailableLocales(JSThread *thread) 1315{ 1316 JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv(); 1317 JSHandle<JSTaggedValue> dateTimeFormatLocales = env->GetDateTimeFormatLocales(); 1318 const char *key = "calendar"; 1319 const char *path = nullptr; 1320 if (dateTimeFormatLocales->IsUndefined()) { 1321 std::vector<std::string> availableStringLocales = intl::LocaleHelper::GetAvailableLocales(thread, key, path); 1322 JSHandle<TaggedArray> availableLocales = JSLocale::ConstructLocaleList(thread, availableStringLocales); 1323 env->SetDateTimeFormatLocales(thread, availableLocales); 1324 return availableLocales; 1325 } 1326 return JSHandle<TaggedArray>::Cast(dateTimeFormatLocales); 1327} 1328 1329JSHandle<JSArray> JSDateTimeFormat::ConstructFDateIntervalToJSArray(JSThread *thread, 1330 const icu::FormattedDateInterval &formatted) 1331{ 1332 UErrorCode status = U_ZERO_ERROR; 1333 icu::UnicodeString formattedValue = formatted.toTempString(status); 1334 // Let result be ArrayCreate(0). 1335 JSHandle<JSArray> array(JSArray::ArrayCreate(thread, JSTaggedNumber(0))); 1336 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread); 1337 // Let index be 0. 1338 int index = 0; 1339 int32_t preEndPos = 0; 1340 // 2: number of elements 1341 std::array<int32_t, 2> begin {}; 1342 std::array<int32_t, 2> end {}; // 2: number of elements 1343 begin[0] = begin[1] = end[0] = end[1] = 0; 1344 std::vector<CommonDateFormatPart> parts; 1345 1346 /** 1347 * From ICU header file document @unumberformatter.h 1348 * Sets a constraint on the field category. 1349 * 1350 * When this instance of ConstrainedFieldPosition is passed to FormattedValue#nextPosition, 1351 * positions are skipped unless they have the given category. 1352 * 1353 * Any previously set constraints are cleared. 1354 * 1355 * For example, to loop over only the number-related fields: 1356 * 1357 * ConstrainedFieldPosition cfpo; 1358 * cfpo.constrainCategory(UFIELDCATEGORY_NUMBER_FORMAT); 1359 * while (fmtval.nextPosition(cfpo, status)) { 1360 * // handle the number-related field position 1361 * } 1362 */ 1363 JSMutableHandle<EcmaString> substring(thread, JSTaggedValue::Undefined()); 1364 icu::ConstrainedFieldPosition cfpos; 1365 while (formatted.nextPosition(cfpos, status)) { 1366 int32_t fCategory = cfpos.getCategory(); 1367 int32_t fField = cfpos.getField(); 1368 int32_t fStart = cfpos.getStart(); 1369 int32_t fLimit = cfpos.getLimit(); 1370 1371 // 2 means the number of elements in category 1372 if (fCategory == UFIELD_CATEGORY_DATE_INTERVAL_SPAN && (fField == 0 || fField == 1)) { 1373 begin[fField] = fStart; 1374 end[fField] = fLimit; 1375 } 1376 if (fCategory == UFIELD_CATEGORY_DATE) { 1377 if (preEndPos < fStart) { 1378 parts.emplace_back(CommonDateFormatPart(fField, preEndPos, fStart, index, true)); 1379 index++; 1380 } 1381 parts.emplace_back(CommonDateFormatPart(fField, fStart, fLimit, index, false)); 1382 preEndPos = fLimit; 1383 ++index; 1384 } 1385 } 1386 if (U_FAILURE(status) != 0) { 1387 THROW_TYPE_ERROR_AND_RETURN(thread, "format date interval error", array); 1388 } 1389 int32_t length = formattedValue.length(); 1390 if (length > preEndPos) { 1391 parts.emplace_back(CommonDateFormatPart(-1, preEndPos, length, index, true)); 1392 } 1393 for (auto part : parts) { 1394 substring.Update(intl::LocaleHelper::UStringToString(thread, formattedValue, part.fBeginIndex, 1395 part.fEndIndex).GetTaggedValue()); 1396 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread); 1397 JSHandle<JSObject> element; 1398 if (part.isPreExist) { 1399 element = JSLocale::PutElement(thread, part.index, array, ConvertFieldIdToDateType(thread, -1), 1400 JSHandle<JSTaggedValue>::Cast(substring)); 1401 } else { 1402 element = JSLocale::PutElement(thread, part.index, array, ConvertFieldIdToDateType(thread, part.fField), 1403 JSHandle<JSTaggedValue>::Cast(substring)); 1404 } 1405 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread); 1406 JSHandle<JSTaggedValue> value = JSHandle<JSTaggedValue>::Cast( 1407 ToValueString(thread, TrackValue(part.fBeginIndex, part.fEndIndex, begin, end))); 1408 JSObject::SetProperty(thread, element, thread->GlobalConstants()->GetHandledSourceString(), value, true); 1409 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread); 1410 } 1411 return array; 1412} 1413 1414Value JSDateTimeFormat::TrackValue(int32_t beginning, int32_t ending, 1415 std::array<int32_t, 2> begin, std::array<int32_t, 2> end) // 2: number of elements 1416{ 1417 Value value = Value::SHARED; 1418 if ((begin[0] <= beginning) && (beginning <= end[0]) && (begin[0] <= ending) && (ending <= end[0])) { 1419 value = Value::START_RANGE; 1420 } else if ((begin[1] <= beginning) && (beginning <= end[1]) && (begin[1] <= ending) && (ending <= end[1])) { 1421 value = Value::END_RANGE; 1422 } 1423 return value; 1424} 1425 1426std::vector<IcuPatternDesc> BuildIcuPatternDescs() 1427{ 1428 static const std::vector<IcuPatternDesc> items = { 1429 IcuPatternDesc("weekday", ICU_WEEKDAY_PE, ICU_NARROW_LONG_SHORT), 1430 IcuPatternDesc("era", ICU_ERA_PE, ICU_NARROW_LONG_SHORT), 1431 IcuPatternDesc("year", ICU_YEAR_PE, ICU2_DIGIT_NUMERIC), 1432 IcuPatternDesc("month", ICU_MONTH_PE, ICU_NARROW_LONG_SHORT2_DIGIT_NUMERIC), 1433 IcuPatternDesc("day", ICU_DAY_PE, ICU2_DIGIT_NUMERIC), 1434 IcuPatternDesc("dayPeriod", ICU_DAY_PERIOD_PE, ICU_NARROW_LONG_SHORT), 1435 IcuPatternDesc("hour", ICU_HOUR_PE, ICU2_DIGIT_NUMERIC), 1436 IcuPatternDesc("minute", ICU_MINUTE_PE, ICU2_DIGIT_NUMERIC), 1437 IcuPatternDesc("second", ICU_SECOND_PE, ICU2_DIGIT_NUMERIC), 1438 IcuPatternDesc("timeZoneName", ICU_YIME_ZONE_NAME_PE, ICU_LONG_SHORT) 1439 }; 1440 return items; 1441} 1442 1443std::vector<IcuPatternDesc> InitializePattern(const IcuPatternDesc &hourData) 1444{ 1445 std::vector<IcuPatternDesc> result; 1446 std::vector<IcuPatternDesc> items = BuildIcuPatternDescs(); 1447 std::vector<IcuPatternDesc>::iterator item = items.begin(); 1448 while (item != items.end()) { 1449 if (item->property != "hour") { 1450 result.emplace_back(IcuPatternDesc(item->property, item->pairs, item->allowedValues)); 1451 } else { 1452 result.emplace_back(hourData); 1453 } 1454 ++item; 1455 } 1456 return result; 1457} 1458 1459std::vector<IcuPatternDesc> JSDateTimeFormat::GetIcuPatternDesc(const HourCycleOption &hourCycle) 1460{ 1461 if (hourCycle == HourCycleOption::H11) { 1462 Pattern h11("KK", "K"); 1463 return h11.Get(); 1464 } else if (hourCycle == HourCycleOption::H12) { 1465 Pattern h12("hh", "h"); 1466 return h12.Get(); 1467 } else if (hourCycle == HourCycleOption::H23) { 1468 Pattern h23("HH", "H"); 1469 return h23.Get(); 1470 } else if (hourCycle == HourCycleOption::H24) { 1471 Pattern h24("kk", "k"); 1472 return h24.Get(); 1473 } else if (hourCycle == HourCycleOption::UNDEFINED) { 1474 Pattern pattern("jj", "j"); 1475 return pattern.Get(); 1476 } 1477 LOG_ECMA(FATAL) << "this branch is unreachable"; 1478 UNREACHABLE(); 1479} 1480 1481icu::UnicodeString JSDateTimeFormat::ChangeHourCyclePattern(const icu::UnicodeString &pattern, HourCycleOption hc) 1482{ 1483 if (hc == HourCycleOption::UNDEFINED || hc == HourCycleOption::EXCEPTION) { 1484 return pattern; 1485 } 1486 icu::UnicodeString result; 1487 char16_t key = u'\0'; 1488 auto mapIter = std::find_if(HOUR_CYCLE_MAP.begin(), HOUR_CYCLE_MAP.end(), 1489 [hc](const std::map<char16_t, HourCycleOption>::value_type item) { 1490 return item.second == hc; 1491 }); 1492 if (mapIter != HOUR_CYCLE_MAP.end()) { 1493 key = mapIter->first; 1494 } 1495 bool needChange = true; 1496 char16_t last = u'\0'; 1497 for (int32_t i = 0; i < pattern.length(); i++) { 1498 char16_t ch = pattern.charAt(i); 1499 if (ch == '\'') { 1500 needChange = !needChange; 1501 result.append(ch); 1502 } else if (HOUR_CYCLE_MAP.find(ch) != HOUR_CYCLE_MAP.end()) { 1503 result = (needChange && last == u'd') ? result.append(' ') : result; 1504 result.append(needChange ? key : ch); 1505 } else { 1506 result.append(ch); 1507 } 1508 last = ch; 1509 } 1510 return result; 1511} 1512 1513std::unique_ptr<icu::SimpleDateFormat> JSDateTimeFormat::CreateICUSimpleDateFormat(const icu::Locale &icuLocale, 1514 const icu::UnicodeString &skeleton, 1515 icu::DateTimePatternGenerator *gn, 1516 HourCycleOption hc) 1517{ 1518 // See https://github.com/tc39/ecma402/issues/225 1519 UErrorCode status = U_ZERO_ERROR; 1520 icu::UnicodeString pattern = ChangeHourCyclePattern( 1521 gn->getBestPattern(skeleton, UDATPG_MATCH_HOUR_FIELD_LENGTH, status), hc); 1522 ASSERT_PRINT((U_SUCCESS(status) != 0), "get best pattern failed"); 1523 1524 status = U_ZERO_ERROR; 1525 auto dateFormat(std::make_unique<icu::SimpleDateFormat>(pattern, icuLocale, status)); 1526 if (U_FAILURE(status) != 0) { 1527 return std::unique_ptr<icu::SimpleDateFormat>(); 1528 } 1529 ASSERT_PRINT(dateFormat != nullptr, "dateFormat failed"); 1530 return dateFormat; 1531} 1532 1533std::unique_ptr<icu::Calendar> JSDateTimeFormat::BuildCalendar(const icu::Locale &locale, const icu::TimeZone &timeZone) 1534{ 1535 UErrorCode status = U_ZERO_ERROR; 1536 std::unique_ptr<icu::Calendar> calendar(icu::Calendar::createInstance(timeZone, locale, status)); 1537 if (U_FAILURE(status) || calendar == nullptr) { 1538 return nullptr; 1539 } 1540 ASSERT_PRINT(U_SUCCESS(status), "buildCalendar failed"); 1541 ASSERT_PRINT(calendar.get() != nullptr, "calendar is nullptr"); 1542 1543 /** 1544 * Return the class ID for this class. 1545 * 1546 * This is useful only for comparing to a return value from getDynamicClassID(). For example: 1547 * 1548 * Base* polymorphic_pointer = createPolymorphicObject(); 1549 * if (polymorphic_pointer->getDynamicClassID() == 1550 * Derived::getStaticClassID()) ... 1551 */ 1552 if (calendar->getDynamicClassID() == icu::GregorianCalendar::getStaticClassID()) { 1553 auto gregorianCalendar = static_cast<icu::GregorianCalendar *>(calendar.get()); 1554 // ECMAScript start time, value = -(2**53) 1555 const double beginTime = -9007199254740992; 1556 gregorianCalendar->setGregorianChange(beginTime, status); 1557 ASSERT(U_SUCCESS(status)); 1558 } 1559 return calendar; 1560} 1561 1562std::unique_ptr<icu::TimeZone> JSDateTimeFormat::ConstructTimeZone(const std::string &timezone) 1563{ 1564 if (timezone.empty()) { 1565 return std::unique_ptr<icu::TimeZone>(); 1566 } 1567 std::string canonicalized = ConstructFormattedTimeZoneID(timezone); 1568 1569 std::unique_ptr<icu::TimeZone> tz(icu::TimeZone::createTimeZone(canonicalized.c_str())); 1570 if (!JSLocale::IsValidTimeZoneName(*tz)) { 1571 return std::unique_ptr<icu::TimeZone>(); 1572 } 1573 return tz; 1574} 1575 1576std::map<std::string, std::string> JSDateTimeFormat::GetSpecialTimeZoneMap() 1577{ 1578 std::vector<std::string> specialTimeZones = { 1579 "America/Argentina/ComodRivadavia", 1580 "America/Knox_IN", 1581 "Antarctica/McMurdo", 1582 "Australia/ACT", 1583 "Australia/LHI", 1584 "Australia/NSW", 1585 "Antarctica/DumontDUrville", 1586 "Brazil/DeNoronha", 1587 "CET", 1588 "CST6CDT", 1589 "Chile/EasterIsland", 1590 "EET", 1591 "EST", 1592 "EST5EDT", 1593 "GB", 1594 "GB-Eire", 1595 "HST", 1596 "MET", 1597 "MST", 1598 "MST7MDT", 1599 "Mexico/BajaNorte", 1600 "Mexico/BajaSur", 1601 "NZ", 1602 "NZ-CHAT", 1603 "PRC", 1604 "PST8PDT", 1605 "ROC", 1606 "ROK", 1607 "UCT", 1608 "W-SU", 1609 "WET"}; 1610 std::map<std::string, std::string> map; 1611 for (const auto &item : specialTimeZones) { 1612 std::string upper(item); 1613 transform(upper.begin(), upper.end(), upper.begin(), toupper); 1614 map.emplace(upper, item); 1615 } 1616 return map; 1617} 1618 1619std::string JSDateTimeFormat::ConstructFormattedTimeZoneID(const std::string &input) 1620{ 1621 std::string result = input; 1622 transform(result.begin(), result.end(), result.begin(), toupper); 1623 std::map<std::string, std::string> map = JSDateTimeFormat::GetSpecialTimeZoneMap(); 1624 auto it = map.find(result); 1625 if (it != map.end()) { 1626 return it->second; 1627 } 1628 static const std::vector<std::string> tzStyleEntry = { 1629 "GMT", "ETC/UTC", "ETC/UCT", "GMT0", "ETC/GMT", "GMT+0", "GMT-0" 1630 }; 1631 if (result.find("SYSTEMV/") == 0) { 1632 result.replace(0, STRING_LENGTH_8, "SystemV/"); 1633 } else if (result.find("US/") == 0) { 1634 result = (result.length() == STRING_LENGTH_3) ? result : "US/" + ToTitleCaseTimezonePosition( 1635 input.substr(STRING_LENGTH_3)); 1636 } else if (result.find("ETC/GMT") == 0 && result.length() > STRING_LENGTH_7) { 1637 result = ConstructGMTTimeZoneID(input); 1638 } else if (count(tzStyleEntry.begin(), tzStyleEntry.end(), result)) { 1639 result = "UTC"; 1640 } else if (result.length() == STRING_LENGTH_3) { 1641 return result; 1642 } else { 1643 return ToTitleCaseTimezonePosition(result); 1644 } 1645 1646 return result; 1647} 1648 1649std::string JSDateTimeFormat::ToTitleCaseFunction(const std::string &input) 1650{ 1651 std::string result(input); 1652 transform(result.begin(), result.end(), result.begin(), tolower); 1653 result[0] = static_cast<int8_t>(toupper(result[0])); 1654 return result; 1655} 1656 1657bool JSDateTimeFormat::IsValidTimeZoneInput(const std::string &input) 1658{ 1659 std::regex r("[a-zA-Z_\\-/]*"); 1660 bool isValid = regex_match(input, r); 1661 return isValid; 1662} 1663 1664std::string JSDateTimeFormat::ToTitleCaseTimezonePosition(const std::string &input) 1665{ 1666 if (!IsValidTimeZoneInput(input)) { 1667 return std::string(); 1668 } 1669 std::vector<std::string> titleEntry; 1670 std::vector<std::string> charEntry; 1671 int32_t leftPosition = 0; 1672 int32_t titleLength = 0; 1673 for (int32_t i = 0; i < static_cast<int>(input.length()); i++) { 1674 if (input[i] == '_' || input[i] == '-' || input[i] == '/') { 1675 std::string s(1, input[i]); 1676 charEntry.emplace_back(s); 1677 titleLength = i - leftPosition; 1678 titleEntry.emplace_back(input.substr(leftPosition, titleLength)); 1679 leftPosition = i + 1; 1680 } else { 1681 continue; 1682 } 1683 } 1684 ASSERT(input.length() >= static_cast<size_t>(leftPosition)); 1685 titleEntry.emplace_back(input.substr(leftPosition, input.length() - leftPosition)); 1686 std::string result; 1687 size_t len = titleEntry.size(); 1688 if (len == 0) { 1689 return ToTitleCaseFunction(input); 1690 } 1691 for (size_t i = 0; i < len - 1; i++) { 1692 std::string titleValue = ToTitleCaseFunction(titleEntry[i]); 1693 if (titleValue == "Of" || titleValue == "Es" || titleValue == "Au") { 1694 titleValue[0] = static_cast<int8_t>(tolower(titleValue[0])); 1695 } 1696 result = result + titleValue + charEntry[i]; 1697 } 1698 result = result + ToTitleCaseFunction(titleEntry[len - 1]); 1699 return result; 1700} 1701 1702std::string JSDateTimeFormat::ConstructGMTTimeZoneID(const std::string &input) 1703{ 1704 if (input.length() < STRING_LENGTH_8 || input.length() > STRING_LENGTH_10) { 1705 return ""; 1706 } 1707 std::string ret = "Etc/GMT"; 1708 int timeZoneOffsetFlag = 7; // The offset of time zone flag, to match RegExp starting with the correct string 1709 if (regex_match(input.substr(timeZoneOffsetFlag), std::regex("[+-][1][0-4]")) || 1710 (regex_match(input.substr(timeZoneOffsetFlag), std::regex("[+-][0-9]")) || 1711 input.substr(timeZoneOffsetFlag) == "0")) { 1712 return ret + input.substr(timeZoneOffsetFlag); 1713 } 1714 return ""; 1715} 1716 1717std::string JSDateTimeFormat::ToHourCycleString(HourCycleOption hc) 1718{ 1719 auto mapIter = std::find_if(TO_HOUR_CYCLE_MAP.begin(), TO_HOUR_CYCLE_MAP.end(), 1720 [hc](const std::map<std::string, HourCycleOption>::value_type item) { 1721 return item.second == hc; 1722 }); 1723 if (mapIter != TO_HOUR_CYCLE_MAP.end()) { 1724 return mapIter->first; 1725 } 1726 return ""; 1727} 1728 1729HourCycleOption JSDateTimeFormat::OptionToHourCycle(const std::string &hc) 1730{ 1731 auto iter = TO_HOUR_CYCLE_MAP.find(hc); 1732 if (iter != TO_HOUR_CYCLE_MAP.end()) { 1733 return iter->second; 1734 } 1735 return HourCycleOption::UNDEFINED; 1736} 1737 1738HourCycleOption JSDateTimeFormat::OptionToHourCycle(UDateFormatHourCycle hc) 1739{ 1740 HourCycleOption hcOption = HourCycleOption::UNDEFINED; 1741 switch (hc) { 1742 case UDAT_HOUR_CYCLE_11: 1743 hcOption = HourCycleOption::H11; 1744 break; 1745 case UDAT_HOUR_CYCLE_12: 1746 hcOption = HourCycleOption::H12; 1747 break; 1748 case UDAT_HOUR_CYCLE_23: 1749 hcOption = HourCycleOption::H23; 1750 break; 1751 case UDAT_HOUR_CYCLE_24: 1752 hcOption = HourCycleOption::H24; 1753 break; 1754 default: 1755 LOG_ECMA(FATAL) << "this branch is unreachable"; 1756 UNREACHABLE(); 1757 } 1758 return hcOption; 1759} 1760 1761JSHandle<JSTaggedValue> JSDateTimeFormat::ConvertFieldIdToDateType(JSThread *thread, int32_t fieldId) 1762{ 1763 JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined()); 1764 auto globalConst = thread->GlobalConstants(); 1765 if (fieldId == -1) { 1766 result.Update(globalConst->GetHandledLiteralString().GetTaggedValue()); 1767 } else if (fieldId == UDAT_YEAR_FIELD || fieldId == UDAT_EXTENDED_YEAR_FIELD) { 1768 result.Update(globalConst->GetHandledYearString().GetTaggedValue()); 1769 } else if (fieldId == UDAT_YEAR_NAME_FIELD) { 1770 result.Update(globalConst->GetHandledYearNameString().GetTaggedValue()); 1771 } else if (fieldId == UDAT_MONTH_FIELD || fieldId == UDAT_STANDALONE_MONTH_FIELD) { 1772 result.Update(globalConst->GetHandledMonthString().GetTaggedValue()); 1773 } else if (fieldId == UDAT_DATE_FIELD) { 1774 result.Update(globalConst->GetHandledDayString().GetTaggedValue()); 1775 } else if (fieldId == UDAT_HOUR_OF_DAY1_FIELD || 1776 fieldId == UDAT_HOUR_OF_DAY0_FIELD || fieldId == UDAT_HOUR1_FIELD || fieldId == UDAT_HOUR0_FIELD) { 1777 result.Update(globalConst->GetHandledHourString().GetTaggedValue()); 1778 } else if (fieldId == UDAT_MINUTE_FIELD) { 1779 result.Update(globalConst->GetHandledMinuteString().GetTaggedValue()); 1780 } else if (fieldId == UDAT_SECOND_FIELD) { 1781 result.Update(globalConst->GetHandledSecondString().GetTaggedValue()); 1782 } else if (fieldId == UDAT_DAY_OF_WEEK_FIELD || fieldId == UDAT_DOW_LOCAL_FIELD || 1783 fieldId == UDAT_STANDALONE_DAY_FIELD) { 1784 result.Update(globalConst->GetHandledWeekdayString().GetTaggedValue()); 1785 } else if (fieldId == UDAT_AM_PM_FIELD || fieldId == UDAT_AM_PM_MIDNIGHT_NOON_FIELD || 1786 fieldId == UDAT_FLEXIBLE_DAY_PERIOD_FIELD) { 1787 result.Update(globalConst->GetHandledDayPeriodString().GetTaggedValue()); 1788 } else if (fieldId == UDAT_TIMEZONE_FIELD || fieldId == UDAT_TIMEZONE_RFC_FIELD || 1789 fieldId == UDAT_TIMEZONE_GENERIC_FIELD || fieldId == UDAT_TIMEZONE_SPECIAL_FIELD || 1790 fieldId == UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD || fieldId == UDAT_TIMEZONE_ISO_FIELD || 1791 fieldId == UDAT_TIMEZONE_ISO_LOCAL_FIELD) { 1792 result.Update(globalConst->GetHandledTimeZoneNameString().GetTaggedValue()); 1793 } else if (fieldId == UDAT_ERA_FIELD) { 1794 result.Update(globalConst->GetHandledEraString().GetTaggedValue()); 1795 } else if (fieldId == UDAT_FRACTIONAL_SECOND_FIELD) { 1796 result.Update(globalConst->GetHandledFractionalSecondString().GetTaggedValue()); 1797 } else if (fieldId == UDAT_RELATED_YEAR_FIELD) { 1798 result.Update(globalConst->GetHandledRelatedYearString().GetTaggedValue()); 1799 } else if (fieldId == UDAT_QUARTER_FIELD || fieldId == UDAT_STANDALONE_QUARTER_FIELD) { 1800 LOG_ECMA(FATAL) << "this branch is unreachable"; 1801 UNREACHABLE(); 1802 } 1803 return result; 1804} 1805 1806std::unique_ptr<icu::DateIntervalFormat> JSDateTimeFormat::ConstructDateIntervalFormat( 1807 const JSHandle<JSDateTimeFormat> &dtf) 1808{ 1809 icu::SimpleDateFormat *icuSimpleDateFormat = dtf->GetIcuSimpleDateFormat(); 1810 icu::Locale locale = *(dtf->GetIcuLocale()); 1811 std::string hcString = ToHourCycleString(dtf->GetHourCycle()); 1812 UErrorCode status = U_ZERO_ERROR; 1813 // Sets the Unicode value for a Unicode keyword. 1814 if (!hcString.empty()) { 1815 locale.setUnicodeKeywordValue("hc", hcString, status); 1816 } 1817 icu::UnicodeString pattern; 1818 // Return a pattern string describing this date format. 1819 pattern = icuSimpleDateFormat->toPattern(pattern); 1820 // Utility to return a unique skeleton from a given pattern. 1821 icu::UnicodeString skeleton = icu::DateTimePatternGenerator::staticGetSkeleton(pattern, status); 1822 // Construct a DateIntervalFormat from skeleton and a given locale. 1823 std::unique_ptr<icu::DateIntervalFormat> dateIntervalFormat( 1824 icu::DateIntervalFormat::createInstance(skeleton, locale, status)); 1825 if (U_FAILURE(status)) { 1826 return nullptr; 1827 } 1828 dateIntervalFormat->setTimeZone(icuSimpleDateFormat->getTimeZone()); 1829 return dateIntervalFormat; 1830} 1831} // namespace panda::ecmascript 1832