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