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