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