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