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