• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2025 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 <array>
17 #include <vector>
18 #include <cstring>
19 #include <regex>
20 
21 #include "unicode/locid.h"
22 #include "unicode/unistr.h"
23 #include "unicode/datefmt.h"
24 #include "unicode/dtptngen.h"
25 #include "unicode/smpdtfmt.h"
26 #include "unicode/msgfmt.h"
27 #include <unicode/dtitvfmt.h>
28 #include <unicode/numsys.h>
29 
30 #include "libpandabase/macros.h"
31 #include "plugins/ets/stdlib/native/core/IntlDateTimeFormat.h"
32 
33 #include "stdlib_ani_helpers.h"
34 
35 namespace ark::ets::stdlib::intl {
36 constexpr const char *FORMAT_HOUR_SYMBOLS = "hHkK";
37 constexpr const char *FORMAT_AM_PM_SYMBOLS = "a";
38 
39 constexpr const char *OPTIONS_FIELD_HOUR_CYCLE = "hourCycle";
40 
41 constexpr const char *LOCALE_KEYWORD_HOUR_CYCLE = "hours";
42 constexpr const char *LOCALE_KEYWORD_CALENDAR = "calendar";
43 constexpr const char *HOUR_CYCLE_11 = "h11";
44 constexpr const char *HOUR_CYCLE_12 = "h12";
45 constexpr const char *HOUR_CYCLE_23 = "h23";
46 constexpr const char *HOUR_CYCLE_24 = "h24";
47 
48 constexpr const char *STYLE_FULL = "full";
49 constexpr const char *STYLE_LONG = "long";
50 constexpr const char *STYLE_MEDIUM = "medium";
51 constexpr const char *STYLE_SHORT = "short";
52 
53 constexpr const char *ERROR_CTOR_SIGNATURE = "Lstd/core/String;:V";
54 constexpr const char *DTF_PART_IMPL_CTOR_SIGNATURE = "Lstd/core/String;Lstd/core/String;:V";
55 constexpr const char *DTRF_PART_IMPL_CTOR_SIGNATURE = "Lstd/core/String;Lstd/core/String;Lstd/core/String;:V";
56 constexpr const char *RESOLVED_OPTS_CTOR_SIGNATURE =
57     "Lstd/core/String;Lstd/core/String;Lstd/core/String;Lstd/core/String;:V";
58 
59 constexpr const char *DTF_PART_LITERAL_TYPE = "literal";
60 constexpr const char *DTRF_PART_SOURCE_SHARED = "shared";
61 
62 constexpr std::array<const char *, 25> DTF_PART_TYPES = {"era",
63                                                          "year",
64                                                          "month",
65                                                          "day",
66                                                          "hourDay1",
67                                                          "hourDay0",
68                                                          "minute",
69                                                          "second",
70                                                          "fractionalSecond",
71                                                          "weekday",
72                                                          "dayOfYear",
73                                                          "dayOfWeekInMonth",
74                                                          "weekOfYear",
75                                                          "weekOfMonth",
76                                                          "dayPeriod",
77                                                          "hour",
78                                                          "hour0",
79                                                          "timeZoneName",
80                                                          "yearWoy",
81                                                          "dowLocal",
82                                                          "extYear",
83                                                          "julianDay",
84                                                          "msInDay",
85                                                          "timezone",
86                                                          "timezoneGeneric"};
87 
88 constexpr std::array<const char *, 3> DTRF_PART_SOURCES = {"startRange", "endRange"};
89 
ThrowRangeError(ani_env * env,const std::string & message)90 static void ThrowRangeError(ani_env *env, const std::string &message)
91 {
92     ani_class errorClass = nullptr;
93     ANI_FATAL_IF_ERROR(env->FindClass("Lstd/core/RangeError;", &errorClass));
94 
95     ThrowNewError(env, errorClass, message, ERROR_CTOR_SIGNATURE);
96 }
97 
ThrowInternalError(ani_env * env,const std::string & message)98 static std::nullptr_t ThrowInternalError(ani_env *env, const std::string &message)
99 {
100     ani_class errorClass = nullptr;
101     ANI_FATAL_IF_ERROR(env->FindClass("Lstd/core/InternalError;", &errorClass));
102 
103     ThrowNewError(env, errorClass, message, ERROR_CTOR_SIGNATURE);
104     return nullptr;
105 }
106 
ANIArrayNewRef(ani_env * env,const char * elemClsName,ani_size size)107 static ani_array_ref ANIArrayNewRef(ani_env *env, const char *elemClsName, ani_size size)
108 {
109     ani_class elemCls = nullptr;
110     ANI_FATAL_IF_ERROR(env->FindClass(elemClsName, &elemCls));
111 
112     ani_ref undefRef = nullptr;
113     ANI_FATAL_IF_ERROR(env->GetUndefined(&undefRef));
114 
115     ani_array_ref arrRef = nullptr;
116     ANI_FATAL_IF_ERROR(env->Array_New_Ref(elemCls, size, undefRef, &arrRef));
117 
118     return arrRef;
119 }
120 
ToICULocale(ani_env * env,ani_object self)121 static std::unique_ptr<icu::Locale> ToICULocale(ani_env *env, ani_object self)
122 {
123     ani_ref localeRef = nullptr;
124     ANI_FATAL_IF_ERROR(env->Object_GetFieldByName_Ref(self, "locale", &localeRef));
125 
126     ani_boolean localeIsUndefined = ANI_FALSE;
127     ANI_FATAL_IF_ERROR(env->Reference_IsUndefined(localeRef, &localeIsUndefined));
128     ANI_FATAL_IF(localeIsUndefined == ANI_TRUE);
129 
130     auto locale = static_cast<ani_string>(localeRef);
131     ASSERT(locale != nullptr);
132 
133     std::string localeStr = ConvertFromAniString(env, locale);
134 
135     UErrorCode status = U_ZERO_ERROR;
136     auto icuLocale = std::make_unique<icu::Locale>(icu::Locale::forLanguageTag(localeStr.data(), status));
137     if (U_FAILURE(status) == TRUE) {
138         return ThrowInternalError(env, std::string("failed to create locale by lang tag: ") + u_errorName(status));
139     }
140 
141     return icuLocale;
142 }
143 
ToICUDateTimeStyle(ani_env * env,ani_string style)144 static icu::DateFormat::EStyle ToICUDateTimeStyle(ani_env *env, ani_string style)
145 {
146     std::string styleStr = ConvertFromAniString(env, style);
147     if (styleStr == STYLE_FULL) {
148         return icu::DateFormat::FULL;
149     }
150     if (styleStr == STYLE_LONG) {
151         return icu::DateFormat::LONG;
152     }
153     if (styleStr == STYLE_MEDIUM) {
154         return icu::DateFormat::MEDIUM;
155     }
156     if (styleStr == STYLE_SHORT) {
157         return icu::DateFormat::SHORT;
158     }
159 
160     return icu::DateFormat::NONE;
161 }
162 
UnicodeStringFromAniString(ani_env * env,ani_string str)163 static std::unique_ptr<icu::UnicodeString> UnicodeStringFromAniString(ani_env *env, ani_string str)
164 {
165     ani_boolean strUndef = ANI_FALSE;
166     ANI_FATAL_IF_ERROR(env->Reference_IsUndefined(str, &strUndef));
167 
168     if (strUndef == ANI_TRUE) {
169         return nullptr;
170     }
171 
172     ASSERT(str != nullptr);
173 
174     ani_size strSize = 0;
175     ANI_FATAL_IF_ERROR(env->String_GetUTF16Size(str, &strSize));
176 
177     std::vector<uint16_t> strBuf(strSize + 1);
178 
179     ani_size charsCount = 0;
180     ANI_FATAL_IF_ERROR(env->String_GetUTF16(str, strBuf.data(), strBuf.size(), &charsCount));
181     ASSERT(charsCount == strSize);
182 
183     return std::make_unique<icu::UnicodeString>(strBuf.data(), strSize);
184 }
185 
DateFormatSetTimeZone(ani_env * env,icu::DateFormat * dateFormat,ani_object options)186 static ani_status DateFormatSetTimeZone(ani_env *env, icu::DateFormat *dateFormat, ani_object options)
187 {
188     ani_ref timeZoneRef = nullptr;
189     ANI_FATAL_IF_ERROR(env->Object_GetFieldByName_Ref(options, "timeZone", &timeZoneRef));
190 
191     auto timeZone = static_cast<ani_string>(timeZoneRef);
192     std::unique_ptr<icu::UnicodeString> timeZoneId = UnicodeStringFromAniString(env, timeZone);
193     if (!timeZoneId) {
194         return ANI_OK;
195     }
196 
197     std::unique_ptr<icu::TimeZone> formatTimeZone(icu::TimeZone::createTimeZone(*timeZoneId));
198     if (*formatTimeZone == icu::TimeZone::getUnknown()) {
199         std::string invalidTimeZoneId;
200         timeZoneId->toUTF8String(invalidTimeZoneId);
201 
202         ThrowRangeError(env, "Invalid time zone specified: " + invalidTimeZoneId);
203         return ANI_PENDING_ERROR;
204     }
205 
206     dateFormat->adoptTimeZone(formatTimeZone.release());
207 
208     return ANI_OK;
209 }
210 
GetLocaleHourCycle(ani_env * env,ani_object self)211 static ani_string GetLocaleHourCycle(ani_env *env, ani_object self)
212 {
213     std::unique_ptr<icu::Locale> icuLocale = ToICULocale(env, self);
214     if (!icuLocale) {
215         return nullptr;
216     }
217 
218     UErrorCode icuStatus = U_ZERO_ERROR;
219     std::unique_ptr<icu::DateTimePatternGenerator> hourCycleResolver(
220         icu::DateTimePatternGenerator::createInstance(*icuLocale, icuStatus));
221 
222     if (U_FAILURE(icuStatus) == TRUE) {
223         return ThrowInternalError(env, "Locale hour cycle resolver instance creation failed");
224     }
225 
226     UDateFormatHourCycle localeHourCycle = hourCycleResolver->getDefaultHourCycle(icuStatus);
227     if (U_FAILURE(icuStatus) == TRUE) {
228         return ThrowInternalError(env, "Failed to resolve locale hour cycle");
229     }
230 
231     const char *resolvedHourCycle = nullptr;
232     switch (localeHourCycle) {
233         case UDAT_HOUR_CYCLE_11:
234             resolvedHourCycle = HOUR_CYCLE_11;
235             break;
236         case UDAT_HOUR_CYCLE_12:
237             resolvedHourCycle = HOUR_CYCLE_12;
238             break;
239         case UDAT_HOUR_CYCLE_23:
240             resolvedHourCycle = HOUR_CYCLE_23;
241             break;
242         case UDAT_HOUR_CYCLE_24:
243             resolvedHourCycle = HOUR_CYCLE_24;
244             break;
245     }
246 
247     if (resolvedHourCycle == nullptr) {
248         return ThrowInternalError(env, "Failed to resolve locale hour cycle");
249     }
250 
251     return CreateUtf8String(env, resolvedHourCycle, strlen(resolvedHourCycle));
252 }
253 
CreateSkeletonBasedDateFormat(ani_env * env,const icu::Locale & locale,const icu::UnicodeString & skeleton)254 static std::unique_ptr<icu::DateFormat> CreateSkeletonBasedDateFormat(ani_env *env, const icu::Locale &locale,
255                                                                       const icu::UnicodeString &skeleton)
256 {
257     UErrorCode icuStatus = U_ZERO_ERROR;
258 
259     std::unique_ptr<icu::DateTimePatternGenerator> dateTimePatternGen(
260         icu::DateTimePatternGenerator::createInstance(locale, icuStatus));
261 
262     if (U_FAILURE(icuStatus) == TRUE) {
263         return ThrowInternalError(env, "DateTimePatternGenerator instance creation failed");
264     }
265 
266     icu::UnicodeString datePattern = dateTimePatternGen->getBestPattern(
267         skeleton, UDateTimePatternMatchOptions::UDATPG_MATCH_HOUR_FIELD_LENGTH, icuStatus);
268 
269     if (U_FAILURE(icuStatus) == TRUE) {
270         return ThrowInternalError(env, "DateFormat pattern creation failed");
271     }
272 
273     auto icuDateFormat = std::make_unique<icu::SimpleDateFormat>(datePattern, locale, icuStatus);
274     if (U_FAILURE(icuStatus) == TRUE) {
275         return ThrowInternalError(env, "DateFormat instance creation failed");
276     }
277 
278     return icuDateFormat;
279 }
280 
ConfigureLocaleCalendar(ani_env * env,icu::Locale * locale,ani_object options)281 static ani_status ConfigureLocaleCalendar(ani_env *env, icu::Locale *locale, ani_object options)
282 {
283     ani_ref calendarRef = nullptr;
284     ANI_FATAL_IF_ERROR(env->Object_GetFieldByName_Ref(options, "calendar", &calendarRef));
285 
286     ani_boolean calendarUndefined = ANI_FALSE;
287     ANI_FATAL_IF_ERROR(env->Reference_IsUndefined(calendarRef, &calendarUndefined));
288     if (calendarUndefined == TRUE) {
289         return ANI_OK;
290     }
291 
292     auto calendar = static_cast<ani_string>(calendarRef);
293     std::string calendarStr = ConvertFromAniString(env, calendar);
294 
295     UErrorCode icuStatus = U_ZERO_ERROR;
296     locale->setKeywordValue(LOCALE_KEYWORD_CALENDAR, calendarStr.data(), icuStatus);
297     if (U_FAILURE(icuStatus) == TRUE) {
298         ThrowInternalError(env, std::string("failed to set locale 'calendar' keyword: ") + u_errorName(icuStatus));
299         return ANI_PENDING_ERROR;
300     }
301 
302     return ANI_OK;
303 }
304 
ConfigureLocaleOptions(ani_env * env,icu::Locale * locale,ani_object options)305 static ani_status ConfigureLocaleOptions(ani_env *env, icu::Locale *locale, ani_object options)
306 {
307     ani_status aniStatus = ConfigureLocaleCalendar(env, locale, options);
308     if (aniStatus != ANI_OK) {
309         if (aniStatus != ANI_PENDING_ERROR) {
310             ThrowInternalError(env, "failed to configure locale calendar!");
311         }
312 
313         return ANI_PENDING_ERROR;
314     }
315 
316     ani_ref hourCycleRef = nullptr;
317     ANI_FATAL_IF_ERROR(env->Object_GetFieldByName_Ref(options, OPTIONS_FIELD_HOUR_CYCLE, &hourCycleRef));
318 
319     ani_boolean hourCycleUndefined = ANI_FALSE;
320     ANI_FATAL_IF_ERROR(env->Reference_IsUndefined(hourCycleRef, &hourCycleUndefined));
321 
322     UErrorCode icuStatus = U_ZERO_ERROR;
323     if (hourCycleUndefined == ANI_TRUE) {
324         if (strcmp(locale->getLanguage(), icu::Locale::getChinese().getLanguage()) != 0) {
325             return ANI_OK;
326         }
327 
328         // applying chinese locale hourCycle fix: h12 -> h23
329         locale->setKeywordValue(LOCALE_KEYWORD_HOUR_CYCLE, HOUR_CYCLE_23, icuStatus);
330         if (U_FAILURE(icuStatus) == TRUE) {
331             ThrowInternalError(env, std::string("failed to fix chinese locale hourCycle: ") + u_errorName(icuStatus));
332             return ANI_PENDING_ERROR;
333         }
334 
335         return ANI_OK;
336     }
337 
338     ASSERT(hourCycleRef != nullptr);
339 
340     auto hourCycle = static_cast<ani_string>(hourCycleRef);
341     std::string hourCycleStr = ConvertFromAniString(env, hourCycle);
342 
343     locale->setKeywordValue(LOCALE_KEYWORD_HOUR_CYCLE, hourCycleStr.data(), icuStatus);
344     if (U_FAILURE(icuStatus) == TRUE) {
345         ThrowInternalError(env, std::string("failed to set locale 'hours' keyword: ") + u_errorName(icuStatus));
346         return ANI_PENDING_ERROR;
347     }
348 
349     return ANI_OK;
350 }
351 
GetLocaleDateTimeFormat(const icu::Locale & locale,const icu::DateTimePatternGenerator & patternGenerator,icu::DateFormat::EStyle dateStyle,UErrorCode & icuStatus)352 static icu::UnicodeString GetLocaleDateTimeFormat(const icu::Locale &locale,
353                                                   const icu::DateTimePatternGenerator &patternGenerator,
354                                                   icu::DateFormat::EStyle dateStyle, UErrorCode &icuStatus)
355 {
356     if (locale == icu::Locale::getUS()) {
357         // en-LR locale uses 'correct' ( 'at' instead of ',' ) date-time separator
358         icu::Locale fixLocale("en-LR");
359         std::unique_ptr<icu::DateTimePatternGenerator> fixPatternGen(
360             icu::DateTimePatternGenerator::createInstance(fixLocale, icuStatus));
361         if (U_FAILURE(icuStatus) == TRUE) {
362             return icu::UnicodeString();
363         }
364 
365         return fixPatternGen->getDateTimeFormat(static_cast<UDateFormatStyle>(dateStyle), icuStatus);
366     }
367 
368     return patternGenerator.getDateTimeFormat(static_cast<UDateFormatStyle>(dateStyle), icuStatus);
369 }
370 
RemoveExplicitHourCycleSymbolsFromTimeFormatSkeleton(icu::UnicodeString * skeleton)371 static bool RemoveExplicitHourCycleSymbolsFromTimeFormatSkeleton(icu::UnicodeString *skeleton)
372 {
373     std::string skeletonStr;
374     skeleton->toUTF8String(skeletonStr);
375 
376     bool skeletonUpdated = false;
377 
378     // removing AM/PM related symbols from time format skeleton
379     std::size_t amPmStartIdx = skeletonStr.find_first_of(FORMAT_AM_PM_SYMBOLS);
380     std::size_t amPmEndIdx = skeletonStr.find_last_of(FORMAT_AM_PM_SYMBOLS);
381     if (amPmStartIdx != std::string::npos && amPmEndIdx != std::string::npos) {
382         skeletonStr.erase(amPmStartIdx, amPmEndIdx - amPmStartIdx + 1);
383         skeletonUpdated = true;
384     }
385 
386     // replacing strict hour format symbols with locale aware 'j'
387     std::size_t hourStartIdx = skeletonStr.find_first_of(FORMAT_HOUR_SYMBOLS);
388     std::size_t hourEndIdx = skeletonStr.find_last_of(FORMAT_HOUR_SYMBOLS);
389     if (hourStartIdx != std::string::npos && hourEndIdx != std::string::npos) {
390         auto hourPatternSize = hourEndIdx - hourStartIdx + 1;
391         skeletonStr.replace(hourStartIdx, hourPatternSize, hourPatternSize, 'j');
392         skeletonUpdated = true;
393     }
394 
395     if (skeletonUpdated) {
396         *skeleton = icu::UnicodeString(skeletonStr.data());
397     }
398 
399     return skeletonUpdated;
400 }
401 
CreateDateTimeFormatPattern(ani_env * env,const icu::UnicodeString & datePattern,const icu::UnicodeString & timePattern,const icu::UnicodeString & localeDateTimeFormat,const icu::Locale & locale)402 static std::unique_ptr<icu::UnicodeString> CreateDateTimeFormatPattern(ani_env *env,
403                                                                        const icu::UnicodeString &datePattern,
404                                                                        const icu::UnicodeString &timePattern,
405                                                                        const icu::UnicodeString &localeDateTimeFormat,
406                                                                        const icu::Locale &locale)
407 {
408     std::array formatPatterns {icu::Formattable(timePattern), icu::Formattable(datePattern)};
409 
410     UErrorCode icuStatus = U_ZERO_ERROR;
411     icu::MessageFormat messageFormat(localeDateTimeFormat, locale, icuStatus);
412     if (U_FAILURE(icuStatus) == TRUE) {
413         return ThrowInternalError(env, "locale aligned date+time format creation failed");
414     }
415 
416     // creating combined date + time format pattern
417     auto dateTimeFormatPattern = std::make_unique<icu::UnicodeString>();
418     icu::FieldPosition fieldPos = 0;
419     messageFormat.format(formatPatterns.data(), formatPatterns.size(), *dateTimeFormatPattern, fieldPos, icuStatus);
420     if (U_FAILURE(icuStatus) == TRUE) {
421         return ThrowInternalError(env, "locale aligned date+time format pattern creation failed");
422     }
423 
424     return dateTimeFormatPattern;
425 }
426 
CreateStyleBasedDateFormatAlignedWithLocale(ani_env * env,icu::DateFormat::EStyle dateStyle,icu::DateFormat::EStyle timeStyle,const icu::Locale & locale)427 static std::unique_ptr<icu::DateFormat> CreateStyleBasedDateFormatAlignedWithLocale(ani_env *env,
428                                                                                     icu::DateFormat::EStyle dateStyle,
429                                                                                     icu::DateFormat::EStyle timeStyle,
430                                                                                     const icu::Locale &locale)
431 {
432     if (timeStyle == icu::DateFormat::NONE) {
433         return std::unique_ptr<icu::DateFormat>(icu::DateFormat::createDateInstance(dateStyle, locale));
434     }
435 
436     std::unique_ptr<icu::DateFormat> timeFormat(icu::DateFormat::createTimeInstance(timeStyle, locale));
437 
438     icu::UnicodeString timeFormatPattern;
439     static_cast<icu::SimpleDateFormat *>(timeFormat.get())->toPattern(timeFormatPattern);
440 
441     UErrorCode icuStatus = U_ZERO_ERROR;
442 
443     // transforming time format pattern to more generic time format skeleton
444     icu::UnicodeString timeFormatSkeleton =
445         icu::DateTimePatternGenerator::staticGetSkeleton(timeFormatPattern, icuStatus);
446     if (U_FAILURE(icuStatus) == TRUE) {
447         return ThrowInternalError(env, "style format pattern -> style format skeleton transformation failed");
448     }
449 
450     RemoveExplicitHourCycleSymbolsFromTimeFormatSkeleton(&timeFormatSkeleton);
451 
452     // generating updated time format pattern based on updated time format skeleton
453     std::unique_ptr<icu::DateTimePatternGenerator> patternGen(
454         icu::DateTimePatternGenerator::createInstance(locale, icuStatus));
455     if (U_FAILURE(icuStatus) == TRUE) {
456         return ThrowInternalError(env, "DateTimePatternGenerator instance creation failed");
457     }
458 
459     UDateTimePatternMatchOptions matchOpts = UDateTimePatternMatchOptions::UDATPG_MATCH_HOUR_FIELD_LENGTH;
460     icu::UnicodeString alignedTimeFormatPattern = patternGen->getBestPattern(timeFormatSkeleton, matchOpts, icuStatus);
461     if (U_FAILURE(icuStatus) == TRUE) {
462         return ThrowInternalError(env, "locale aligned style based format pattern generation failed");
463     }
464 
465     if (dateStyle == icu::DateFormat::NONE) {
466         // dateStyle is not specified, so returning time pattern only based format
467         auto alignedTimeFormat = std::make_unique<icu::SimpleDateFormat>(alignedTimeFormatPattern, locale, icuStatus);
468         if (U_FAILURE(icuStatus) == TRUE) {
469             return ThrowInternalError(env, "locale aligned style based time formatter creation failed");
470         }
471 
472         return alignedTimeFormat;
473     }
474 
475     std::unique_ptr<icu::DateFormat> dateFormat(icu::DateFormat::createDateInstance(dateStyle, locale));
476 
477     icu::UnicodeString dateFormatPattern;
478     static_cast<icu::SimpleDateFormat *>(dateFormat.get())->toPattern(dateFormatPattern);
479 
480     // resolving locale specific date + time format
481     const icu::UnicodeString localeDateTimeFormat = GetLocaleDateTimeFormat(locale, *patternGen, dateStyle, icuStatus);
482     if (U_FAILURE(icuStatus) == TRUE) {
483         return ThrowInternalError(env, "failed to resolve locale specific date+time format");
484     }
485 
486     std::unique_ptr<icu::UnicodeString> alignedFormatPattern =
487         CreateDateTimeFormatPattern(env, dateFormatPattern, alignedTimeFormatPattern, localeDateTimeFormat, locale);
488     if (!alignedFormatPattern) {
489         return nullptr;
490     }
491 
492     auto alignedDateTimeFormat = std::make_unique<icu::SimpleDateFormat>(*alignedFormatPattern, locale, icuStatus);
493     if (U_FAILURE(icuStatus) == TRUE) {
494         return ThrowInternalError(env, "locale aligned style based date+time formatter creation failed");
495     }
496 
497     return alignedDateTimeFormat;
498 }
499 
CreateStyleBasedDateFormat(ani_env * env,const icu::Locale & locale,ani_object options)500 static std::unique_ptr<icu::DateFormat> CreateStyleBasedDateFormat(ani_env *env, const icu::Locale &locale,
501                                                                    ani_object options)
502 {
503     ani_ref dateStyleRef = nullptr;
504     ANI_FATAL_IF_ERROR(env->Object_GetFieldByName_Ref(options, "dateStyle", &dateStyleRef));
505 
506     ani_ref timeStyleRef = nullptr;
507     ANI_FATAL_IF_ERROR(env->Object_GetFieldByName_Ref(options, "timeStyle", &timeStyleRef));
508 
509     ani_boolean dateStyleUndefined = ANI_FALSE;
510     ANI_FATAL_IF_ERROR(env->Reference_IsUndefined(dateStyleRef, &dateStyleUndefined));
511 
512     icu::DateFormat::EStyle icuDateStyle = icu::DateFormat::NONE;
513     if (dateStyleUndefined != ANI_TRUE) {
514         ASSERT(dateStyleRef != nullptr);
515 
516         auto dateStyle = static_cast<ani_string>(dateStyleRef);
517         icuDateStyle = ToICUDateTimeStyle(env, dateStyle);
518     }
519 
520     ani_boolean timeStyleUndefined = ANI_FALSE;
521     ANI_FATAL_IF_ERROR(env->Reference_IsUndefined(timeStyleRef, &timeStyleUndefined));
522 
523     icu::DateFormat::EStyle icuTimeStyle = icu::DateFormat::NONE;
524     if (timeStyleUndefined != ANI_TRUE) {
525         ASSERT(timeStyleRef != nullptr);
526 
527         auto timeStyle = static_cast<ani_string>(timeStyleRef);
528         icuTimeStyle = ToICUDateTimeStyle(env, timeStyle);
529     }
530 
531     return CreateStyleBasedDateFormatAlignedWithLocale(env, icuDateStyle, icuTimeStyle, locale);
532 }
533 
DateTimeFormatGetOptions(ani_env * env,ani_object self)534 static ani_object DateTimeFormatGetOptions(ani_env *env, ani_object self)
535 {
536     ani_ref optionsRef = nullptr;
537     ANI_FATAL_IF_ERROR(env->Object_GetFieldByName_Ref(self, "options", &optionsRef));
538     ASSERT(optionsRef != nullptr);
539 
540     return static_cast<ani_object>(optionsRef);
541 }
542 
DateTimeFormatGetPatternSkeleton(ani_env * env,ani_object self)543 static std::unique_ptr<icu::UnicodeString> DateTimeFormatGetPatternSkeleton(ani_env *env, ani_object self)
544 {
545     ani_ref patternRef;
546     ANI_FATAL_IF_ERROR(env->Object_GetFieldByName_Ref(self, "pattern", &patternRef));
547 
548     auto pattern = static_cast<ani_string>(patternRef);
549 
550     ani_size patternSize = 0;
551     ANI_FATAL_IF_ERROR(env->String_GetUTF16Size(pattern, &patternSize));
552 
553     ani_size copiedCharsCount = 0;
554     std::vector<uint16_t> patternBuf(patternSize + 1);
555 
556     ANI_FATAL_IF_ERROR(env->String_GetUTF16(pattern, patternBuf.data(), patternBuf.size(), &copiedCharsCount));
557     ANI_FATAL_IF(copiedCharsCount != patternSize);
558 
559     return std::make_unique<icu::UnicodeString>(patternBuf.data(), patternSize);
560 }
561 
CreateICUDateFormat(ani_env * env,ani_object self)562 static std::unique_ptr<icu::DateFormat> CreateICUDateFormat(ani_env *env, ani_object self)
563 {
564     std::unique_ptr<icu::Locale> icuLocale = ToICULocale(env, self);
565     if (!icuLocale) {
566         return nullptr;
567     }
568 
569     std::unique_ptr<icu::UnicodeString> patternSkeleton = DateTimeFormatGetPatternSkeleton(env, self);
570     if (!patternSkeleton) {
571         return nullptr;
572     }
573 
574     ani_object options = DateTimeFormatGetOptions(env, self);
575     ani_status aniStatus = ConfigureLocaleOptions(env, icuLocale.get(), options);
576     if (aniStatus != ANI_OK) {
577         return nullptr;
578     }
579 
580     std::unique_ptr<icu::DateFormat> icuDateFormat;
581     if (patternSkeleton->isEmpty() == FALSE) {
582         icuDateFormat = CreateSkeletonBasedDateFormat(env, *icuLocale, *patternSkeleton);
583     } else {
584         icuDateFormat = CreateStyleBasedDateFormat(env, *icuLocale, options);
585     }
586 
587     if (!icuDateFormat) {
588         // DateFormat creation failed
589         return nullptr;
590     }
591 
592     aniStatus = DateFormatSetTimeZone(env, icuDateFormat.get(), options);
593     if (aniStatus != ANI_OK) {
594         if (aniStatus != ANI_PENDING_ERROR) {
595             ThrowInternalError(env, "DateFormat time zone initialization failed");
596         }
597         return nullptr;
598     }
599 
600     return icuDateFormat;
601 }
602 
FormatImpl(ani_env * env,ani_object self,ani_double timestamp)603 static ani_string FormatImpl(ani_env *env, ani_object self, ani_double timestamp)
604 {
605     std::unique_ptr<icu::DateFormat> icuDateFormat = CreateICUDateFormat(env, self);
606     if (!icuDateFormat) {
607         return nullptr;
608     }
609 
610     icu::UnicodeString icuFormattedDate;
611     icuDateFormat->format(timestamp, icuFormattedDate);
612 
613     auto formattedDateChars = reinterpret_cast<const uint16_t *>(icuFormattedDate.getBuffer());
614     return CreateUtf16String(env, formattedDateChars, icuFormattedDate.length());
615 }
616 
FormatToPartsImpl(ani_env * env,ani_object self,ani_double timestamp)617 static ani_array_ref FormatToPartsImpl(ani_env *env, ani_object self, ani_double timestamp)
618 {
619     std::unique_ptr<icu::DateFormat> dateFormat = CreateICUDateFormat(env, self);
620     if (!dateFormat) {
621         // DateFormat creation failed ( check for pending error )
622         return nullptr;
623     }
624 
625     UErrorCode status = U_ZERO_ERROR;
626 
627     icu::UnicodeString formattedDate;
628     icu::FieldPositionIterator fieldPosIter;
629 
630     dateFormat->format(timestamp, formattedDate, &fieldPosIter, status);
631     if (U_FAILURE(status) == TRUE) {
632         return ThrowInternalError(env, std::string("DateFormat.format() failed: ") + u_errorName(status));
633     }
634 
635     int32_t prevFieldEndIdx = 0;
636     const icu::UnicodeString literalType(DTF_PART_LITERAL_TYPE);
637 
638     icu::FieldPosition fieldPos;
639     std::vector<std::pair<icu::UnicodeString, icu::UnicodeString>> parts;
640     while (fieldPosIter.next(fieldPos) == TRUE) {
641         if (fieldPos.getBeginIndex() > prevFieldEndIdx) {
642             icu::UnicodeString literalValue;
643             formattedDate.extractBetween(prevFieldEndIdx, fieldPos.getBeginIndex(), literalValue);
644 
645             parts.emplace_back(literalType, literalValue);
646         }
647 
648         const char *partType = DTF_PART_TYPES.at(fieldPos.getField());
649 
650         icu::UnicodeString fieldValue;
651         formattedDate.extractBetween(fieldPos.getBeginIndex(), fieldPos.getEndIndex(), fieldValue);
652 
653         parts.emplace_back(partType, fieldValue);
654 
655         prevFieldEndIdx = fieldPos.getEndIndex();
656     }
657 
658     ani_class fmtPartImplCls = nullptr;
659     ANI_FATAL_IF_ERROR(env->FindClass("Lstd/core/Intl/DateTimeFormatPartImpl;", &fmtPartImplCls));
660 
661     ani_method fmtPartImplCtor;
662     ANI_FATAL_IF_ERROR(env->Class_FindMethod(fmtPartImplCls, "<ctor>", DTF_PART_IMPL_CTOR_SIGNATURE, &fmtPartImplCtor));
663 
664     ani_array_ref formattedDateParts = ANIArrayNewRef(env, "Lstd/core/Intl/DateTimeFormatPart;", parts.size());
665     for (size_t partIdx = 0; partIdx < parts.size(); partIdx++) {
666         const auto &part = parts[partIdx];
667 
668         auto partTypeChars = reinterpret_cast<const uint16_t *>(part.first.getBuffer());
669         ani_string partType = CreateUtf16String(env, partTypeChars, part.first.length());
670 
671         auto partValueChars = reinterpret_cast<const uint16_t *>(part.second.getBuffer());
672         ani_string partValue = CreateUtf16String(env, partValueChars, part.second.length());
673 
674         ani_object fmtPartImpl = nullptr;
675         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
676         ANI_FATAL_IF_ERROR(env->Object_New(fmtPartImplCls, fmtPartImplCtor, &fmtPartImpl, partType, partValue));
677         ANI_FATAL_IF_ERROR(env->Array_Set_Ref(formattedDateParts, partIdx, fmtPartImpl));
678     }
679 
680     return formattedDateParts;
681 }
682 
683 using UStr = icu::UnicodeString;
684 
FormatResolvedOptionsImpl(ani_env * env,ani_object self)685 static ani_object FormatResolvedOptionsImpl(ani_env *env, ani_object self)
686 {
687     std::unique_ptr<icu::DateFormat> dateFormat = CreateICUDateFormat(env, self);
688     if (!dateFormat) {
689         return nullptr;
690     }
691 
692     auto dateFormatImpl = static_cast<icu::SimpleDateFormat *>(dateFormat.get());
693     const icu::Locale &locale = dateFormatImpl->getSmpFmtLocale();
694 
695     UErrorCode status = U_ZERO_ERROR;
696 
697     auto langTagStr = locale.toLanguageTag<std::string>(status);
698     if (U_FAILURE(status) == TRUE) {
699         return ThrowInternalError(env, std::string("failed to get locale lang tag: ") + u_errorName(status));
700     }
701     ani_string langTag = CreateUtf8String(env, langTagStr.data(), langTagStr.size());
702 
703     const char *calendarType = dateFormat->getCalendar()->getType();
704     ani_string calendar = CreateUtf8String(env, calendarType, strlen(calendarType));
705 
706     std::unique_ptr<icu::NumberingSystem> numSys(icu::NumberingSystem::createInstance(locale, status));
707     if (U_FAILURE(status) == TRUE) {
708         return ThrowInternalError(env, std::string("NumberingSystem creation failed: ") + u_errorName(status));
709     }
710 
711     ani_string numSysName = CreateUtf8String(env, numSys->getName(), strlen(numSys->getName()));
712 
713     icu::UnicodeString timeZoneId;
714     dateFormat->getTimeZone().getID(timeZoneId);
715 
716     auto timeZoneIdChars = reinterpret_cast<const uint16_t *>(timeZoneId.getBuffer());
717     ani_string timeZone = CreateUtf16String(env, timeZoneIdChars, timeZoneId.length());
718 
719     ani_class optsClass = nullptr;
720     ANI_FATAL_IF_ERROR(env->FindClass("Lstd/core/Intl/ResolvedDateTimeFormatOptionsImpl;", &optsClass));
721 
722     ani_method optsCtor = nullptr;
723     ANI_FATAL_IF_ERROR(env->Class_FindMethod(optsClass, "<ctor>", RESOLVED_OPTS_CTOR_SIGNATURE, &optsCtor));
724 
725     ani_object resolvedOpts = nullptr;
726     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
727     env->Object_New(optsClass, optsCtor, &resolvedOpts, langTag, calendar, numSysName, timeZone);
728 
729     auto localeHours = locale.getKeywordValue<std::string>(LOCALE_KEYWORD_HOUR_CYCLE, status);
730     if (U_FAILURE(status) == TRUE) {
731         return ThrowInternalError(env, std::string("failed to get locale 'hours' keyword: ") + u_errorName(status));
732     }
733 
734     if (!localeHours.empty()) {
735         ani_string hourCycle = CreateUtf8String(env, localeHours.data(), localeHours.size());
736 
737         ani_method setter = nullptr;
738         ANI_FATAL_IF_ERROR(env->Class_FindSetter(optsClass, OPTIONS_FIELD_HOUR_CYCLE, &setter));
739 
740         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
741         ANI_FATAL_IF_ERROR(env->Object_CallMethod_Void(resolvedOpts, setter, hourCycle));
742     }
743 
744     return resolvedOpts;
745 }
746 
FillDateTimeRangeFormatPartArray(ani_env * env,ani_array_ref partsArr,const std::vector<std::tuple<UStr,UStr,UStr>> & parts)747 static void FillDateTimeRangeFormatPartArray(ani_env *env, ani_array_ref partsArr,
748                                              const std::vector<std::tuple<UStr, UStr, UStr>> &parts)
749 {
750     ani_class partImplCls = nullptr;
751     ANI_FATAL_IF_ERROR(env->FindClass("Lstd/core/Intl/DateTimeRangeFormatPartImpl;", &partImplCls));
752 
753     ani_method partImplCtor = nullptr;
754     ANI_FATAL_IF_ERROR(env->Class_FindMethod(partImplCls, "<ctor>", DTRF_PART_IMPL_CTOR_SIGNATURE, &partImplCtor));
755 
756     for (size_t partIdx = 0; partIdx < parts.size(); partIdx++) {
757         const auto &[partType, partValue, partSource] = parts[partIdx];
758 
759         auto partTypeChars = reinterpret_cast<const uint16_t *>(partType.getBuffer());
760         ani_string partTypeStr = CreateUtf16String(env, partTypeChars, partType.length());
761 
762         auto partValueChars = reinterpret_cast<const uint16_t *>(partValue.getBuffer());
763         ani_string partValStr = CreateUtf16String(env, partValueChars, partValue.length());
764 
765         auto partSourceChars = reinterpret_cast<const uint16_t *>(partSource.getBuffer());
766         ani_string partSrcStr = CreateUtf16String(env, partSourceChars, partSource.length());
767 
768         ani_object partImpl = nullptr;
769         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
770         ANI_FATAL_IF_ERROR(env->Object_New(partImplCls, partImplCtor, &partImpl, partTypeStr, partValStr, partSrcStr));
771         ANI_FATAL_IF_ERROR(env->Array_Set_Ref(partsArr, partIdx, partImpl));
772     }
773 }
774 
DateIntervalFormatSetTimeZone(ani_env * env,icu::DateIntervalFormat * format,ani_object options)775 static ani_status DateIntervalFormatSetTimeZone(ani_env *env, icu::DateIntervalFormat *format, ani_object options)
776 {
777     ani_ref timeZoneRef = nullptr;
778     ANI_FATAL_IF_ERROR(env->Object_GetFieldByName_Ref(options, "timeZone", &timeZoneRef));
779 
780     auto timeZone = static_cast<ani_string>(timeZoneRef);
781     std::unique_ptr<icu::UnicodeString> timeZoneId = UnicodeStringFromAniString(env, timeZone);
782     if (!timeZoneId) {
783         return ANI_OK;
784     }
785 
786     std::unique_ptr<icu::TimeZone> formatTimeZone(icu::TimeZone::createTimeZone(*timeZoneId));
787     if (*formatTimeZone == icu::TimeZone::getUnknown()) {
788         std::string invalidTimeZoneId;
789         timeZoneId->toUTF8String(invalidTimeZoneId);
790 
791         ThrowRangeError(env, "Invalid time zone specified: " + invalidTimeZoneId);
792         return ANI_PENDING_ERROR;
793     }
794 
795     format->adoptTimeZone(formatTimeZone.release());
796 
797     return ANI_OK;
798 }
799 
FormatDateInterval(ani_env * env,ani_object self,ani_double start,ani_double end)800 static std::unique_ptr<icu::FormattedDateInterval> FormatDateInterval(ani_env *env, ani_object self, ani_double start,
801                                                                       ani_double end)
802 {
803     std::unique_ptr<icu::DateFormat> dateFmt = CreateICUDateFormat(env, self);
804     if (!dateFmt) {
805         return nullptr;
806     }
807 
808     auto dateFmtImpl = static_cast<icu::SimpleDateFormat *>(dateFmt.get());
809 
810     UStr pattern;
811     dateFmtImpl->toPattern(pattern);
812 
813     UErrorCode status = U_ZERO_ERROR;
814     UStr skeleton = icu::DateTimePatternGenerator::staticGetSkeleton(pattern, status);
815     if (U_FAILURE(status) == TRUE) {
816         return ThrowInternalError(env, std::string("Failed to get skeleton: ") + u_errorName(status));
817     }
818 
819     const icu::Locale &locale = dateFmtImpl->getSmpFmtLocale();
820     std::unique_ptr<icu::DateIntervalFormat> intervalFmt(
821         icu::DateIntervalFormat::createInstance(skeleton, locale, status));
822     if (U_FAILURE(status) == TRUE) {
823         return ThrowInternalError(env, std::string("Failed to create DateIntervalFormat: ") + u_errorName(status));
824     }
825 
826     ani_object options = DateTimeFormatGetOptions(env, self);
827     DateIntervalFormatSetTimeZone(env, intervalFmt.get(), options);
828 
829     auto interval = std::make_unique<icu::DateInterval>(start, end);
830 
831     icu::FormattedDateInterval formattedIntervalVal = intervalFmt->formatToValue(*interval, status);
832     if (U_FAILURE(status) == TRUE) {
833         return ThrowInternalError(env, std::string("DateIntervalFormat::formatToValue failed: ") + u_errorName(status));
834     }
835 
836     return std::make_unique<icu::FormattedDateInterval>(std::move(formattedIntervalVal));
837 }
838 
839 enum DateIntervalPartSource { OUT_OF_RANGE = -1, START_RANGE = 0, END_RANGE = 1 };
840 
FormatRangeImpl(ani_env * env,ani_object self,ani_double start,ani_double end)841 static ani_string FormatRangeImpl(ani_env *env, ani_object self, ani_double start, ani_double end)
842 {
843     auto formattedIntervalVal = FormatDateInterval(env, self, start, end);
844     if (UNLIKELY(formattedIntervalVal == nullptr)) {
845         return ThrowInternalError(env, std::string("FormattedDateInterval failed"));
846     }
847 
848     ASSERT(formattedIntervalVal != nullptr);
849     UErrorCode status = U_ZERO_ERROR;
850     UStr formattedInterval = formattedIntervalVal->toString(status);
851     if (U_FAILURE(status) == TRUE) {
852         return ThrowInternalError(env, std::string("FormattedDateInterval::toString failed: ") + u_errorName(status));
853     }
854 
855     auto formattedIntervalChars = reinterpret_cast<const uint16_t *>(formattedInterval.getBuffer());
856     return CreateUtf16String(env, formattedIntervalChars, formattedInterval.length());
857 }
858 
FormatRangeToPartsImpl(ani_env * env,ani_object self,ani_double start,ani_double end)859 static ani_array_ref FormatRangeToPartsImpl(ani_env *env, ani_object self, ani_double start, ani_double end)
860 {
861     auto formattedIntervalVal = FormatDateInterval(env, self, start, end);
862     if (UNLIKELY(formattedIntervalVal == nullptr)) {
863         return ThrowInternalError(env, std::string("FormattedDateInterval failed"));
864     }
865 
866     ASSERT(formattedIntervalVal != nullptr);
867     UErrorCode status = U_ZERO_ERROR;
868     UStr formattedInterval = formattedIntervalVal->toString(status);
869     if (U_FAILURE(status) == TRUE) {
870         return ThrowInternalError(env, std::string("FormattedDateInterval::toString failed: ") + u_errorName(status));
871     }
872 
873     std::vector<std::tuple<UStr, UStr, UStr>> parts;
874     icu::ConstrainedFieldPosition partPos;
875     DateIntervalPartSource partSource = OUT_OF_RANGE;
876     int32_t prevPartEndIdx = 0;
877     int32_t prevSpanEndIdx = 0;
878     const UStr partLiteralType = DTF_PART_LITERAL_TYPE;
879     const UStr partSourceShared = DTRF_PART_SOURCE_SHARED;
880 
881     while (formattedIntervalVal->nextPosition(partPos, status) == TRUE) {
882         if (U_FAILURE(status) == TRUE) {
883             return ThrowInternalError(env, std::string("FormattedDateInterval::nextPos failed:") + u_errorName(status));
884         }
885 
886         if (partPos.getCategory() == UFIELD_CATEGORY_DATE_INTERVAL_SPAN) {
887             partSource = static_cast<DateIntervalPartSource>(partSource + 1);
888 
889             if (partPos.getStart() > prevSpanEndIdx) {
890                 UStr literalValue;
891                 formattedInterval.extractBetween(prevSpanEndIdx, partPos.getStart(), literalValue);
892 
893                 parts.emplace_back(partLiteralType, literalValue, partSourceShared);
894             }
895 
896             prevSpanEndIdx = partPos.getLimit(), prevPartEndIdx = partPos.getStart();
897         } else if (partPos.getCategory() == UFIELD_CATEGORY_DATE) {
898             const char *partSourceName = DTRF_PART_SOURCES.at(partSource);
899             if (partPos.getStart() > prevPartEndIdx) {
900                 UStr literalValue;
901                 formattedInterval.extractBetween(prevPartEndIdx, partPos.getStart(), literalValue);
902 
903                 parts.emplace_back(partLiteralType, literalValue, UStr(partSourceName));
904             }
905 
906             UStr partValue;
907             formattedInterval.extractBetween(partPos.getStart(), partPos.getLimit(), partValue);
908 
909             parts.emplace_back(UStr(DTF_PART_TYPES.at(partPos.getField())), partValue, UStr(partSourceName));
910 
911             prevPartEndIdx = partPos.getLimit();
912         } else {
913             UNREACHABLE();
914         }
915     }
916 
917     ani_array_ref partsArr = ANIArrayNewRef(env, "Lstd/core/Intl/DateTimeRangeFormatPart;", parts.size());
918     FillDateTimeRangeFormatPartArray(env, partsArr, parts);
919 
920     return partsArr;
921 }
922 
RegisterIntlDateTimeFormatMethods(ani_env * env)923 ani_status RegisterIntlDateTimeFormatMethods(ani_env *env)
924 {
925     ani_class dtfClass;
926     ANI_FATAL_IF_ERROR(env->FindClass("Lstd/core/Intl/DateTimeFormat;", &dtfClass));
927 
928     std::array dtfMethods {
929         ani_native_function {"formatImpl", "D:Lstd/core/String;", reinterpret_cast<void *>(FormatImpl)},
930         ani_native_function {"formatToPartsImpl", "D:[Lstd/core/Intl/DateTimeFormatPart;",
931                              reinterpret_cast<void *>(FormatToPartsImpl)},
932         ani_native_function {"formatRangeImpl", "DD:Lstd/core/String;", reinterpret_cast<void *>(FormatRangeImpl)},
933         ani_native_function {"formatRangeToPartsImpl", "DD:[Lstd/core/Intl/DateTimeRangeFormatPart;",
934                              reinterpret_cast<void *>(FormatRangeToPartsImpl)},
935         ani_native_function {"resolvedOptionsImpl", ":Lstd/core/Intl/ResolvedDateTimeFormatOptions;",
936                              reinterpret_cast<void *>(FormatResolvedOptionsImpl)},
937         ani_native_function {"getLocaleHourCycle", ":Lstd/core/String;", reinterpret_cast<void *>(GetLocaleHourCycle)},
938     };
939 
940     ani_status status = env->Class_BindNativeMethods(dtfClass, dtfMethods.data(), dtfMethods.size());
941     if (!(status == ANI_OK || status == ANI_ALREADY_BINDED)) {
942         return status;
943     }
944 
945     return ANI_OK;
946 }
947 }  // namespace ark::ets::stdlib::intl
948