• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021-2024 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_relative_time_format.h"
17 #include "ecmascript/js_function.h"
18 #include "ecmascript/object_factory-inl.h"
19 
20 namespace panda::ecmascript {
21 // 14.1.1 InitializeRelativeTimeFormat ( relativeTimeFormat, locales, options )
InitializeRelativeTimeFormat(JSThread * thread,const JSHandle<JSRelativeTimeFormat> & relativeTimeFormat,const JSHandle<JSTaggedValue> & locales,const JSHandle<JSTaggedValue> & options)22 JSHandle<JSRelativeTimeFormat> JSRelativeTimeFormat::InitializeRelativeTimeFormat(
23     JSThread *thread, const JSHandle<JSRelativeTimeFormat> &relativeTimeFormat, const JSHandle<JSTaggedValue> &locales,
24     const JSHandle<JSTaggedValue> &options)
25 {
26     EcmaVM *ecmaVm = thread->GetEcmaVM();
27     ObjectFactory *factory = ecmaVm->GetFactory();
28 
29     // 1.Let requestedLocales be ? CanonicalizeLocaleList(locales).
30     JSHandle<TaggedArray> requestedLocales = intl::LocaleHelper::CanonicalizeLocaleList(thread, locales);
31     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread);
32 
33     // 2&3. If options is undefined, then Let options be ObjectCreate(null). else Let options be ? ToObject(options).
34     JSHandle<JSObject> rtfOptions;
35     if (!options->IsUndefined()) {
36         rtfOptions = JSTaggedValue::ToObject(thread, options);
37         RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread);
38     } else {
39         rtfOptions = factory->CreateNullJSObject();
40     }
41 
42     // 5. Let matcher be ? GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit").
43     auto globalConst = thread->GlobalConstants();
44     LocaleMatcherOption matcher =
45         JSLocale::GetOptionOfString(thread, rtfOptions, globalConst->GetHandledLocaleMatcherString(),
46                                     {LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT},
47                                     {"lookup", "best fit"}, LocaleMatcherOption::BEST_FIT);
48     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread);
49 
50     // 7. Let numberingSystem be ? GetOption(options, "numberingSystem", "string", undefined, undefined).
51     JSHandle<JSTaggedValue> property = globalConst->GetHandledNumberingSystemString();
52     JSHandle<JSTaggedValue> undefinedValue(thread, JSTaggedValue::Undefined());
53     JSHandle<JSTaggedValue> numberingSystemValue =
54         JSLocale::GetOption(thread, rtfOptions, property, OptionType::STRING, undefinedValue, undefinedValue);
55     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread);
56 
57     // Check whether numberingSystem is well formed and set to %RelativeTimeFormat%.[[numberingSystem]]
58     std::string numberingSystemStdStr;
59     if (!numberingSystemValue->IsUndefined()) {
60         JSHandle<EcmaString> numberingSystemString = JSHandle<EcmaString>::Cast(numberingSystemValue);
61         if (EcmaStringAccessor(numberingSystemString).IsUtf16()) {
62             THROW_RANGE_ERROR_AND_RETURN(thread, "invalid numberingSystem", relativeTimeFormat);
63         }
64         numberingSystemStdStr = intl::LocaleHelper::ConvertToStdString(thread, numberingSystemString);
65         if (!JSLocale::IsNormativeNumberingSystem(numberingSystemStdStr)) {
66             THROW_RANGE_ERROR_AND_RETURN(thread, "invalid numberingSystem", relativeTimeFormat);
67         }
68     }
69 
70     // 10. Let localeData be %RelativeTimeFormat%.[[LocaleData]].
71     // 11. Let r be ResolveLocale(%RelativeTimeFormat%.[[AvailableLocales]], requestedLocales, opt,
72     // %RelativeTimeFormat%.[[RelevantExtensionKeys]], localeData).
73     JSHandle<TaggedArray> availableLocales;
74     if (requestedLocales->GetLength() == 0) {
75         availableLocales = factory->EmptyArray();
76     } else {
77         std::vector<std::string> availableStringLocales =
78             intl::LocaleHelper::GetAvailableLocales(thread, "calendar", nullptr);
79         availableLocales = JSLocale::ConstructLocaleList(thread, availableStringLocales);
80     }
81     std::set<std::string> relevantExtensionKeys{"nu"};
82     ResolvedLocale r =
83         JSLocale::ResolveLocale(thread, availableLocales, requestedLocales, matcher, relevantExtensionKeys);
84     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread);
85     icu::Locale icuLocale = r.localeData;
86 
87     // 12. Let locale be r.[[Locale]].
88     JSHandle<EcmaString> localeStr = intl::LocaleHelper::ToLanguageTag(thread, icuLocale);
89     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread);
90 
91     // 13. Set relativeTimeFormat.[[Locale]] to locale.
92     relativeTimeFormat->SetLocale(thread, localeStr.GetTaggedValue());
93 
94     // 15. Set relativeTimeFormat.[[NumberingSystem]] to r.[[nu]].
95     UErrorCode status = U_ZERO_ERROR;
96     if (!numberingSystemStdStr.empty()) {
97         if (JSLocale::IsWellNumberingSystem(numberingSystemStdStr)) {
98             icuLocale.setUnicodeKeywordValue("nu", numberingSystemStdStr, status);
99             ASSERT(U_SUCCESS(status));
100         }
101     }
102 
103     // 16. Let s be ? GetOption(options, "style", "string", «"long", "short", "narrow"», "long").
104     property = globalConst->GetHandledStyleString();
105     RelativeStyleOption styleOption = JSLocale::GetOptionOfString(thread, rtfOptions, property,
106         {RelativeStyleOption::LONG, RelativeStyleOption::SHORT, RelativeStyleOption::NARROW},
107         {"long", "short", "narrow"}, RelativeStyleOption::LONG);
108     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread);
109 
110     // 17. Set relativeTimeFormat.[[Style]] to s.
111     relativeTimeFormat->SetStyle(styleOption);
112 
113     // 18. Let numeric be ? GetOption(options, "numeric", "string", ?"always", "auto"?, "always").
114     property = globalConst->GetHandledNumericString();
115     NumericOption numericOption =
116         JSLocale::GetOptionOfString(thread, rtfOptions, property, {NumericOption::ALWAYS, NumericOption::AUTO},
117                                     {"always", "auto"}, NumericOption::ALWAYS);
118     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread);
119 
120     // 19. Set relativeTimeFormat.[[Numeric]] to numeric.
121     relativeTimeFormat->SetNumeric(numericOption);
122 
123     // 20. Let relativeTimeFormat.[[NumberFormat]] be ! Construct(%NumberFormat%, « locale »).
124     icu::NumberFormat *icuNumberFormat = icu::NumberFormat::createInstance(icuLocale, UNUM_DECIMAL, status);
125     if (U_FAILURE(status) != 0) {
126         delete icuNumberFormat;
127         if (status == UErrorCode::U_MISSING_RESOURCE_ERROR) {
128             THROW_REFERENCE_ERROR_AND_RETURN(thread, "can not find icu data resources", relativeTimeFormat);
129         }
130         THROW_RANGE_ERROR_AND_RETURN(thread, "create icu::NumberFormat failed", relativeTimeFormat);
131     }
132     // Display grouping using the default strategy for all locales
133     if (icuNumberFormat->getDynamicClassID() == icu::DecimalFormat::getStaticClassID()) {
134         icu::DecimalFormat* icuDecimalFormat = static_cast<icu::DecimalFormat*>(icuNumberFormat);
135         icuDecimalFormat->setMinimumGroupingDigits(UNUM_MINIMUM_GROUPING_DIGITS_AUTO);
136     }
137 
138     // Trans RelativeStyleOption to ICU Style
139     UDateRelativeDateTimeFormatterStyle uStyle;
140     switch (styleOption) {
141         case RelativeStyleOption::LONG:
142             uStyle = UDAT_STYLE_LONG;
143             break;
144         case RelativeStyleOption::SHORT:
145             uStyle = UDAT_STYLE_SHORT;
146             break;
147         case RelativeStyleOption::NARROW:
148             uStyle = UDAT_STYLE_NARROW;
149             break;
150         default:
151             LOG_ECMA(FATAL) << "this branch is unreachable";
152             UNREACHABLE();
153     }
154     icu::RelativeDateTimeFormatter rtfFormatter(icuLocale, icuNumberFormat, uStyle, UDISPCTX_CAPITALIZATION_NONE,
155                                                 status);
156     if (U_FAILURE(status) != 0) {
157         THROW_RANGE_ERROR_AND_RETURN(thread, "icu Formatter Error", relativeTimeFormat);
158     }
159 
160     std::string numberingSystem = JSLocale::GetNumberingSystem(icuLocale);
161     auto result = factory->NewFromStdString(numberingSystem);
162     relativeTimeFormat->SetNumberingSystem(thread, result);
163 
164     // Set RelativeTimeFormat.[[IcuRelativeTimeFormatter]]
165     factory->NewJSIntlIcuData(relativeTimeFormat, rtfFormatter, JSRelativeTimeFormat::FreeIcuRTFFormatter);
166 
167     // 22. Return relativeTimeFormat.
168     return relativeTimeFormat;
169 }
170 
171 // 14.1.2  SingularRelativeTimeUnit ( unit )
SingularUnitToIcuUnit(JSThread * thread,const JSHandle<EcmaString> & unit,URelativeDateTimeUnit * unitEnum)172 bool SingularUnitToIcuUnit(JSThread *thread, const JSHandle<EcmaString> &unit, URelativeDateTimeUnit *unitEnum)
173 {
174     // 1. Assert: Type(unit) is String.
175     ASSERT(JSHandle<JSTaggedValue>::Cast(unit)->IsString());
176 
177     // 2. If unit is "seconds" or "second", return "second".
178     // 3. If unit is "minutes" or "minute", return "minute".
179     // 4. If unit is "hours" or "hour", return "hour".
180     // 5. If unit is "days" or "day", return "day".
181     // 6. If unit is "weeks" or "week", return "week".
182     // 7. If unit is "months" or "month", return "month".
183     // 8. If unit is "quarters" or "quarter", return "quarter".
184     // 9. If unit is "years" or "year", return "year".
185     auto globalConst = thread->GlobalConstants();
186     JSHandle<EcmaString> second = JSHandle<EcmaString>::Cast(globalConst->GetHandledSecondString());
187     JSHandle<EcmaString> minute = JSHandle<EcmaString>::Cast(globalConst->GetHandledMinuteString());
188     JSHandle<EcmaString> hour = JSHandle<EcmaString>::Cast(globalConst->GetHandledHourString());
189     JSHandle<EcmaString> day = JSHandle<EcmaString>::Cast(globalConst->GetHandledDayString());
190     JSHandle<EcmaString> week = JSHandle<EcmaString>::Cast(globalConst->GetHandledWeekString());
191     JSHandle<EcmaString> month = JSHandle<EcmaString>::Cast(globalConst->GetHandledMonthString());
192     JSHandle<EcmaString> quarter = JSHandle<EcmaString>::Cast(globalConst->GetHandledQuarterString());
193     JSHandle<EcmaString> year = JSHandle<EcmaString>::Cast(globalConst->GetHandledYearString());
194 
195     JSHandle<EcmaString> seconds = JSHandle<EcmaString>::Cast(globalConst->GetHandledSecondsString());
196     JSHandle<EcmaString> minutes = JSHandle<EcmaString>::Cast(globalConst->GetHandledMinutesString());
197     JSHandle<EcmaString> hours = JSHandle<EcmaString>::Cast(globalConst->GetHandledHoursString());
198     JSHandle<EcmaString> days = JSHandle<EcmaString>::Cast(globalConst->GetHandledDaysString());
199     JSHandle<EcmaString> weeks = JSHandle<EcmaString>::Cast(globalConst->GetHandledWeeksString());
200     JSHandle<EcmaString> months = JSHandle<EcmaString>::Cast(globalConst->GetHandledMonthsString());
201     JSHandle<EcmaString> quarters = JSHandle<EcmaString>::Cast(globalConst->GetHandledQuartersString());
202     JSHandle<EcmaString> years = JSHandle<EcmaString>::Cast(globalConst->GetHandledYearsString());
203 
204     if (EcmaStringAccessor::StringsAreEqual(thread, *second, *unit) ||
205         EcmaStringAccessor::StringsAreEqual(thread, *seconds, *unit)) {
206         *unitEnum = UDAT_REL_UNIT_SECOND;
207     } else if (EcmaStringAccessor::StringsAreEqual(thread, *minute, *unit) ||
208         EcmaStringAccessor::StringsAreEqual(thread, *minutes, *unit)) {
209         *unitEnum = UDAT_REL_UNIT_MINUTE;
210     } else if (EcmaStringAccessor::StringsAreEqual(thread, *hour, *unit) ||
211         EcmaStringAccessor::StringsAreEqual(thread, *hours, *unit)) {
212         *unitEnum = UDAT_REL_UNIT_HOUR;
213     } else if (EcmaStringAccessor::StringsAreEqual(thread, *day, *unit) ||
214         EcmaStringAccessor::StringsAreEqual(thread, *days, *unit)) {
215         *unitEnum = UDAT_REL_UNIT_DAY;
216     } else if (EcmaStringAccessor::StringsAreEqual(thread, *week, *unit) ||
217         EcmaStringAccessor::StringsAreEqual(thread, *weeks, *unit)) {
218         *unitEnum = UDAT_REL_UNIT_WEEK;
219     } else if (EcmaStringAccessor::StringsAreEqual(thread, *month, *unit) ||
220         EcmaStringAccessor::StringsAreEqual(thread, *months, *unit)) {
221         *unitEnum = UDAT_REL_UNIT_MONTH;
222     } else if (EcmaStringAccessor::StringsAreEqual(thread, *quarter, *unit) ||
223         EcmaStringAccessor::StringsAreEqual(thread, *quarters, *unit)) {
224         *unitEnum = UDAT_REL_UNIT_QUARTER;
225     } else if (EcmaStringAccessor::StringsAreEqual(thread, *year, *unit) ||
226         EcmaStringAccessor::StringsAreEqual(thread, *years, *unit)) {
227         *unitEnum = UDAT_REL_UNIT_YEAR;
228     } else {
229         return false;
230     }
231     // 11. else return unit.
232     return true;
233 }
234 
235 // Unwrap RelativeTimeFormat
UnwrapRelativeTimeFormat(JSThread * thread,const JSHandle<JSTaggedValue> & rtf)236 JSHandle<JSTaggedValue> JSRelativeTimeFormat::UnwrapRelativeTimeFormat(JSThread *thread,
237                                                                        const JSHandle<JSTaggedValue> &rtf)
238 {
239     ASSERT_PRINT(rtf->IsJSObject(), "rtf is not a JSObject");
240 
241     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
242     bool isInstanceOf = JSFunction::InstanceOf(thread, rtf, env->GetRelativeTimeFormatFunction());
243     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, rtf);
244     if (!rtf->IsJSRelativeTimeFormat() && isInstanceOf) {
245         JSHandle<JSTaggedValue> key(thread, JSHandle<JSIntl>::Cast(env->GetIntlFunction())->GetFallbackSymbol(thread));
246         OperationResult operationResult = JSTaggedValue::GetProperty(thread, rtf, key);
247         RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, rtf);
248         return operationResult.GetValue();
249     }
250 
251     // Perform ? RequireInternalSlot(relativeTimeFormat, [[InitializedRelativeTimeFormat]]).
252     if (!rtf->IsJSRelativeTimeFormat()) {
253         RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, rtf);
254     }
255     return rtf;
256 }
257 
258 // CommonFormat
GetIcuFormatted(JSThread * thread,const JSHandle<JSRelativeTimeFormat> & relativeTimeFormat,double value,const JSHandle<EcmaString> & unit)259 icu::FormattedRelativeDateTime GetIcuFormatted(JSThread *thread,
260                                                const JSHandle<JSRelativeTimeFormat> &relativeTimeFormat,
261                                                double value, const JSHandle<EcmaString> &unit)
262 {
263     icu::RelativeDateTimeFormatter *formatter = relativeTimeFormat->GetIcuRTFFormatter(thread);
264     ASSERT_PRINT(formatter != nullptr, "rtfFormatter is null");
265 
266     // If isFinite(value) is false, then throw a RangeError exception.
267     if (!std::isfinite(value)) {
268         THROW_RANGE_ERROR_AND_RETURN(thread, "", icu::FormattedRelativeDateTime());
269     }
270 
271     // 10. If unit is not one of "second", "minute", "hour", "day", "week", "month", "quarter", or "year", throw a
272     // RangeError exception.
273     URelativeDateTimeUnit unitEnum;
274     if (!SingularUnitToIcuUnit(thread, unit, &unitEnum)) {
275         THROW_RANGE_ERROR_AND_RETURN(thread, "", icu::FormattedRelativeDateTime());
276     }
277     UErrorCode status = U_ZERO_ERROR;
278     NumericOption numeric = relativeTimeFormat->GetNumeric();
279 
280     icu::FormattedRelativeDateTime formatted;
281     switch (numeric) {
282         case NumericOption::ALWAYS:
283             formatted = formatter->formatNumericToValue(value, unitEnum, status);
284             ASSERT_PRINT(U_SUCCESS(status), "icu format to value error");
285             break;
286         case NumericOption::AUTO:
287             formatted = formatter->formatToValue(value, unitEnum, status);
288             ASSERT_PRINT(U_SUCCESS(status), "icu format to value error");
289             break;
290         default:
291             LOG_ECMA(FATAL) << "this branch is unreachable";
292             UNREACHABLE();
293     }
294     return formatted;
295 }
296 
297 // 14.1.2 SingularRelativeTimeUnit ( unit )
SingularUnitString(JSThread * thread,const JSHandle<EcmaString> & unit)298 JSHandle<EcmaString> SingularUnitString(JSThread *thread, const JSHandle<EcmaString> &unit)
299 {
300     auto globalConst = thread->GlobalConstants();
301     JSHandle<EcmaString> second = JSHandle<EcmaString>::Cast(globalConst->GetHandledSecondString());
302     JSHandle<EcmaString> minute = JSHandle<EcmaString>::Cast(globalConst->GetHandledMinuteString());
303     JSHandle<EcmaString> hour = JSHandle<EcmaString>::Cast(globalConst->GetHandledHourString());
304     JSHandle<EcmaString> day = JSHandle<EcmaString>::Cast(globalConst->GetHandledDayString());
305     JSHandle<EcmaString> week = JSHandle<EcmaString>::Cast(globalConst->GetHandledWeekString());
306     JSHandle<EcmaString> month = JSHandle<EcmaString>::Cast(globalConst->GetHandledMonthString());
307     JSHandle<EcmaString> quarter = JSHandle<EcmaString>::Cast(globalConst->GetHandledQuarterString());
308     JSHandle<EcmaString> year = JSHandle<EcmaString>::Cast(globalConst->GetHandledYearString());
309     JSHandle<EcmaString> seconds = JSHandle<EcmaString>::Cast(globalConst->GetHandledSecondsString());
310     JSHandle<EcmaString> minutes = JSHandle<EcmaString>::Cast(globalConst->GetHandledMinutesString());
311     JSHandle<EcmaString> hours = JSHandle<EcmaString>::Cast(globalConst->GetHandledHoursString());
312     JSHandle<EcmaString> days = JSHandle<EcmaString>::Cast(globalConst->GetHandledDaysString());
313     JSHandle<EcmaString> weeks = JSHandle<EcmaString>::Cast(globalConst->GetHandledWeeksString());
314     JSHandle<EcmaString> months = JSHandle<EcmaString>::Cast(globalConst->GetHandledMonthsString());
315     JSHandle<EcmaString> quarters = JSHandle<EcmaString>::Cast(globalConst->GetHandledQuartersString());
316     JSHandle<EcmaString> years = JSHandle<EcmaString>::Cast(globalConst->GetHandledYearsString());
317 
318     // 2. If unit is "seconds" or "second", return "second".
319     if (EcmaStringAccessor::StringsAreEqual(thread, *second, *unit) ||
320         EcmaStringAccessor::StringsAreEqual(thread, *seconds, *unit)) {
321         return second;
322     }
323     // 3. If unit is "minutes" or "minute", return "minute".
324     if (EcmaStringAccessor::StringsAreEqual(thread, *minute, *unit) ||
325         EcmaStringAccessor::StringsAreEqual(thread, *minutes, *unit)) {
326         return minute;
327     }
328     // 4. If unit is "hours" or "hour", return "hour".
329     if (EcmaStringAccessor::StringsAreEqual(thread, *hour, *unit) ||
330         EcmaStringAccessor::StringsAreEqual(thread, *hours, *unit)) {
331         return hour;
332     }
333     // 5. If unit is "days" or "day", return "day".
334     if (EcmaStringAccessor::StringsAreEqual(thread, *day, *unit) ||
335         EcmaStringAccessor::StringsAreEqual(thread, *days, *unit)) {
336         return day;
337     }
338     // 6. If unit is "weeks" or "week", return "week".
339     if (EcmaStringAccessor::StringsAreEqual(thread, *week, *unit) ||
340         EcmaStringAccessor::StringsAreEqual(thread, *weeks, *unit)) {
341         return week;
342     }
343     // 7. If unit is "months" or "month", return "month".
344     if (EcmaStringAccessor::StringsAreEqual(thread, *month, *unit) ||
345         EcmaStringAccessor::StringsAreEqual(thread, *months, *unit)) {
346         return month;
347     }
348     // 8. If unit is "quarters" or "quarter", return "quarter".
349     if (EcmaStringAccessor::StringsAreEqual(thread, *quarter, *unit) ||
350         EcmaStringAccessor::StringsAreEqual(thread, *quarters, *unit)) {
351         return quarter;
352     }
353     // 9. If unit is "years" or "year", return "year".
354     if (EcmaStringAccessor::StringsAreEqual(thread, *year, *unit) ||
355         EcmaStringAccessor::StringsAreEqual(thread, *years, *unit)) {
356         return year;
357     }
358 
359     JSHandle<JSTaggedValue> undefinedValue(thread, JSTaggedValue::Undefined());
360     return JSHandle<EcmaString>::Cast(undefinedValue);
361 }
362 
363 // 14.1.5 FormatRelativeTime ( relativeTimeFormat, value, unit )
Format(JSThread * thread,double value,const JSHandle<EcmaString> & unit,const JSHandle<JSRelativeTimeFormat> & relativeTimeFormat)364 JSHandle<EcmaString> JSRelativeTimeFormat::Format(JSThread *thread, double value, const JSHandle<EcmaString> &unit,
365                                                   const JSHandle<JSRelativeTimeFormat> &relativeTimeFormat)
366 {
367     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
368     icu::FormattedRelativeDateTime formatted = GetIcuFormatted(thread, relativeTimeFormat, value, unit);
369     RETURN_HANDLE_IF_ABRUPT_COMPLETION(EcmaString, thread);
370     UErrorCode status = U_ZERO_ERROR;
371     icu::UnicodeString uString = formatted.toString(status);
372     if (U_FAILURE(status) != 0) {
373         THROW_RANGE_ERROR_AND_RETURN(thread, "icu formatted toString error", factory->GetEmptyString());
374     }
375     JSHandle<EcmaString> string =
376         factory->NewFromUtf16(reinterpret_cast<const uint16_t *>(uString.getBuffer()), uString.length());
377     return string;
378 }
379 
FormatToArray(JSThread * thread,const JSHandle<JSArray> & array,const icu::FormattedRelativeDateTime & formatted,double value,const JSHandle<EcmaString> & unit)380 void FormatToArray(JSThread *thread, const JSHandle<JSArray> &array,
381                    const icu::FormattedRelativeDateTime &formatted, double value,
382                    const JSHandle<EcmaString> &unit)
383 {
384     UErrorCode status = U_ZERO_ERROR;
385     icu::UnicodeString formattedText = formatted.toString(status);
386     if (U_FAILURE(status)) {
387         THROW_TYPE_ERROR(thread, "formattedRelativeDateTime toString failed");
388     }
389 
390     icu::ConstrainedFieldPosition cfpo;
391     // Set constrainCategory to UFIELD_CATEGORY_NUMBER which is specified for UNumberFormatFields
392     cfpo.constrainCategory(UFIELD_CATEGORY_NUMBER);
393     int32_t index = 0;
394     int32_t previousLimit = 0;
395     auto globalConst = thread->GlobalConstants();
396     JSHandle<JSTaggedValue> taggedValue(thread, JSTaggedValue(value));
397     JSMutableHandle<JSTaggedValue> typeString(thread, JSTaggedValue::Undefined());
398     JSHandle<JSTaggedValue> unitString = globalConst->GetHandledUnitString();
399     std::vector<std::pair<int32_t, int32_t>> separatorFields;
400     /**
401      * From ICU header file document @unumberformatter.h
402      * Sets a constraint on the field category.
403      *
404      * When this instance of ConstrainedFieldPosition is passed to FormattedValue#nextPosition,
405      * positions are skipped unless they have the given category.
406      *
407      * Any previously set constraints are cleared.
408      *
409      * For example, to loop over only the number-related fields:
410      *
411      *     ConstrainedFieldPosition cfpo;
412      *     cfpo.constrainCategory(UFIELDCATEGORY_NUMBER_FORMAT);
413      *     while (fmtval.nextPosition(cfpo, status)) {
414      *         // handle the number-related field position
415      *     }
416      */
417     while ((formatted.nextPosition(cfpo, status) != 0)) {
418         int32_t fieldId = cfpo.getField();
419         int32_t start = cfpo.getStart();
420         int32_t limit = cfpo.getLimit();
421         // Special case when fieldId is UNUM_GROUPING_SEPARATOR_FIELD
422         if (static_cast<UNumberFormatFields>(fieldId) == UNUM_GROUPING_SEPARATOR_FIELD) {
423             separatorFields.push_back(std::pair<int32_t, int32_t>(start, limit));
424             continue;
425         }
426         // If start greater than previousLimit, means a literal type exists before number fields
427         // so add a literal type with value of formattedText.sub(0, start)
428         if (start > previousLimit) {
429             typeString.Update(globalConst->GetLiteralString());
430             JSHandle<EcmaString> substring =
431                 intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, start);
432             JSLocale::PutElement(thread, index++, array, typeString, JSHandle<JSTaggedValue>::Cast(substring));
433             RETURN_IF_ABRUPT_COMPLETION(thread);
434         }
435         // Add part when type is unit
436         // Iterate former grouping separator vector and add unit element to array
437         for (auto it = separatorFields.begin(); it != separatorFields.end(); it++) {
438             if (it->first > start) {
439                 // Add Integer type element
440                 JSHandle<EcmaString> resString =
441                     intl::LocaleHelper::UStringToString(thread, formattedText, start, it->first);
442                 typeString.Update(
443                     JSLocale::GetNumberFieldType(thread, taggedValue.GetTaggedValue(), fieldId).GetTaggedValue());
444                 JSHandle<JSObject> record =
445                     JSLocale::PutElement(thread, index++, array, typeString, JSHandle<JSTaggedValue>::Cast(resString));
446                 RETURN_IF_ABRUPT_COMPLETION(thread);
447                 JSObject::CreateDataPropertyOrThrow(thread, record, unitString, JSHandle<JSTaggedValue>::Cast(unit));
448                 RETURN_IF_ABRUPT_COMPLETION(thread);
449                 // Add Group type element
450                 resString = intl::LocaleHelper::UStringToString(thread, formattedText, it->first, it->second);
451                 typeString.Update(JSLocale::GetNumberFieldType(thread, taggedValue.GetTaggedValue(),
452                     UNUM_GROUPING_SEPARATOR_FIELD).GetTaggedValue());
453                 record =
454                     JSLocale::PutElement(thread, index++, array, typeString, JSHandle<JSTaggedValue>::Cast(resString));
455                 RETURN_IF_ABRUPT_COMPLETION(thread);
456                 JSObject::CreateDataPropertyOrThrow(thread, record, unitString, JSHandle<JSTaggedValue>::Cast(unit));
457                 RETURN_IF_ABRUPT_COMPLETION(thread);
458                 start = it->second;
459             }
460         }
461         // Add current field unit
462         JSHandle<EcmaString> subString = intl::LocaleHelper::UStringToString(thread, formattedText, start, limit);
463         typeString.Update(JSLocale::GetNumberFieldType(thread, taggedValue.GetTaggedValue(), fieldId).GetTaggedValue());
464         JSHandle<JSObject> record =
465             JSLocale::PutElement(thread, index++, array, typeString, JSHandle<JSTaggedValue>::Cast(subString));
466         RETURN_IF_ABRUPT_COMPLETION(thread);
467         JSObject::CreateDataPropertyOrThrow(thread, record, unitString, JSHandle<JSTaggedValue>::Cast(unit));
468         RETURN_IF_ABRUPT_COMPLETION(thread);
469         previousLimit = limit;
470     }
471     // If iterated length is smaller than formattedText.length, means a literal type exists after number fields
472     // so add a literal type with value of formattedText.sub(previousLimit, formattedText.length)
473     if (formattedText.length() > previousLimit) {
474         typeString.Update(globalConst->GetLiteralString());
475         JSHandle<EcmaString> substring =
476             intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, formattedText.length());
477         JSLocale::PutElement(thread, index, array, typeString, JSHandle<JSTaggedValue>::Cast(substring));
478         RETURN_IF_ABRUPT_COMPLETION(thread);
479     }
480 }
481 
482 // 14.1.6 FormatRelativeTimeToParts ( relativeTimeFormat, value, unit )
FormatToParts(JSThread * thread,double value,const JSHandle<EcmaString> & unit,const JSHandle<JSRelativeTimeFormat> & relativeTimeFormat)483 JSHandle<JSArray> JSRelativeTimeFormat::FormatToParts(JSThread *thread, double value, const JSHandle<EcmaString> &unit,
484                                                       const JSHandle<JSRelativeTimeFormat> &relativeTimeFormat)
485 {
486     icu::FormattedRelativeDateTime formatted = GetIcuFormatted(thread, relativeTimeFormat, value, unit);
487     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
488     JSHandle<EcmaString> singularUnit = SingularUnitString(thread, unit);
489     JSHandle<JSArray> array = JSHandle<JSArray>::Cast(JSArray::ArrayCreate(thread, JSTaggedNumber(0)));
490     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
491     FormatToArray(thread, array, formatted, value, singularUnit);
492     return array;
493 }
494 
ResolvedOptions(JSThread * thread,const JSHandle<JSRelativeTimeFormat> & relativeTimeFormat,const JSHandle<JSObject> & options)495 void JSRelativeTimeFormat::ResolvedOptions(JSThread *thread, const JSHandle<JSRelativeTimeFormat> &relativeTimeFormat,
496                                            const JSHandle<JSObject> &options)
497 {
498     if (relativeTimeFormat->GetIcuRTFFormatter(thread) != nullptr) {
499         [[maybe_unused]] icu::RelativeDateTimeFormatter *formatter = relativeTimeFormat->GetIcuRTFFormatter(thread);
500     } else {
501         THROW_ERROR(thread, ErrorType::RANGE_ERROR, "rtf is not initialized");
502     }
503 
504     auto globalConst = thread->GlobalConstants();
505     // [[locale]]
506     JSHandle<JSTaggedValue> property = globalConst->GetHandledLocaleString();
507     JSHandle<EcmaString> locale(thread, relativeTimeFormat->GetLocale(thread));
508     PropertyDescriptor localeDesc(thread, JSHandle<JSTaggedValue>::Cast(locale), true, true, true);
509     JSObject::DefineOwnProperty(thread, options, property, localeDesc);
510 
511     // [[Style]]
512     property = globalConst->GetHandledStyleString();
513     RelativeStyleOption style = relativeTimeFormat->GetStyle();
514     JSHandle<JSTaggedValue> styleValue;
515     if (style == RelativeStyleOption::LONG) {
516         styleValue = globalConst->GetHandledLongString();
517     } else if (style == RelativeStyleOption::SHORT) {
518         styleValue = globalConst->GetHandledShortString();
519     } else if (style == RelativeStyleOption::NARROW) {
520         styleValue = globalConst->GetHandledNarrowString();
521     }
522     PropertyDescriptor styleDesc(thread, styleValue, true, true, true);
523     JSObject::DefineOwnProperty(thread, options, property, styleDesc);
524 
525     // [[Numeric]]
526     property = globalConst->GetHandledNumericString();
527     NumericOption numeric = relativeTimeFormat->GetNumeric();
528     JSHandle<JSTaggedValue> numericValue;
529     if (numeric == NumericOption::ALWAYS) {
530         numericValue = globalConst->GetHandledAlwaysString();
531     } else if (numeric == NumericOption::AUTO) {
532         numericValue = globalConst->GetHandledAutoString();
533     } else {
534         THROW_ERROR(thread, ErrorType::RANGE_ERROR, "numeric is exception");
535     }
536     PropertyDescriptor numericDesc(thread, numericValue, true, true, true);
537     JSObject::DefineOwnProperty(thread, options, property, numericDesc);
538 
539     // [[NumberingSystem]]
540     property = JSHandle<JSTaggedValue>::Cast(globalConst->GetHandledNumberingSystemString());
541     JSHandle<JSTaggedValue> numberingSystem(thread, relativeTimeFormat->GetNumberingSystem(thread));
542     PropertyDescriptor numberingSystemDesc(thread, numberingSystem, true, true, true);
543     JSObject::DefineOwnProperty(thread, options, property, numberingSystemDesc);
544 }
545 }  // namespace panda::ecmascript
546