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