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
20namespace panda::ecmascript {
21// 14.1.1 InitializeRelativeTimeFormat ( relativeTimeFormat, locales, options )
22JSHandle<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(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 )
172bool 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(*second, *unit) ||
205        EcmaStringAccessor::StringsAreEqual(*seconds, *unit)) {
206        *unitEnum = UDAT_REL_UNIT_SECOND;
207    } else if (EcmaStringAccessor::StringsAreEqual(*minute, *unit) ||
208        EcmaStringAccessor::StringsAreEqual(*minutes, *unit)) {
209        *unitEnum = UDAT_REL_UNIT_MINUTE;
210    } else if (EcmaStringAccessor::StringsAreEqual(*hour, *unit) ||
211        EcmaStringAccessor::StringsAreEqual(*hours, *unit)) {
212        *unitEnum = UDAT_REL_UNIT_HOUR;
213    } else if (EcmaStringAccessor::StringsAreEqual(*day, *unit) ||
214        EcmaStringAccessor::StringsAreEqual(*days, *unit)) {
215        *unitEnum = UDAT_REL_UNIT_DAY;
216    } else if (EcmaStringAccessor::StringsAreEqual(*week, *unit) ||
217        EcmaStringAccessor::StringsAreEqual(*weeks, *unit)) {
218        *unitEnum = UDAT_REL_UNIT_WEEK;
219    } else if (EcmaStringAccessor::StringsAreEqual(*month, *unit) ||
220        EcmaStringAccessor::StringsAreEqual(*months, *unit)) {
221        *unitEnum = UDAT_REL_UNIT_MONTH;
222    } else if (EcmaStringAccessor::StringsAreEqual(*quarter, *unit) ||
223        EcmaStringAccessor::StringsAreEqual(*quarters, *unit)) {
224        *unitEnum = UDAT_REL_UNIT_QUARTER;
225    } else if (EcmaStringAccessor::StringsAreEqual(*year, *unit) ||
226        EcmaStringAccessor::StringsAreEqual(*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
236JSHandle<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());
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
259icu::FormattedRelativeDateTime GetIcuFormatted(JSThread *thread,
260                                               const JSHandle<JSRelativeTimeFormat> &relativeTimeFormat,
261                                               double value, const JSHandle<EcmaString> &unit)
262{
263    icu::RelativeDateTimeFormatter *formatter = relativeTimeFormat->GetIcuRTFFormatter();
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 )
298JSHandle<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(*second, *unit) || EcmaStringAccessor::StringsAreEqual(*seconds, *unit)) {
320        return second;
321    }
322    // 3. If unit is "minutes" or "minute", return "minute".
323    if (EcmaStringAccessor::StringsAreEqual(*minute, *unit) || EcmaStringAccessor::StringsAreEqual(*minutes, *unit)) {
324        return minute;
325    }
326    // 4. If unit is "hours" or "hour", return "hour".
327    if (EcmaStringAccessor::StringsAreEqual(*hour, *unit) || EcmaStringAccessor::StringsAreEqual(*hours, *unit)) {
328        return hour;
329    }
330    // 5. If unit is "days" or "day", return "day".
331    if (EcmaStringAccessor::StringsAreEqual(*day, *unit) || EcmaStringAccessor::StringsAreEqual(*days, *unit)) {
332        return day;
333    }
334    // 6. If unit is "weeks" or "week", return "week".
335    if (EcmaStringAccessor::StringsAreEqual(*week, *unit) || EcmaStringAccessor::StringsAreEqual(*weeks, *unit)) {
336        return week;
337    }
338    // 7. If unit is "months" or "month", return "month".
339    if (EcmaStringAccessor::StringsAreEqual(*month, *unit) || EcmaStringAccessor::StringsAreEqual(*months, *unit)) {
340        return month;
341    }
342    // 8. If unit is "quarters" or "quarter", return "quarter".
343    if (EcmaStringAccessor::StringsAreEqual(*quarter, *unit) || EcmaStringAccessor::StringsAreEqual(*quarters, *unit)) {
344        return quarter;
345    }
346    // 9. If unit is "years" or "year", return "year".
347    if (EcmaStringAccessor::StringsAreEqual(*year, *unit) || EcmaStringAccessor::StringsAreEqual(*years, *unit)) {
348        return year;
349    }
350
351    JSHandle<JSTaggedValue> undefinedValue(thread, JSTaggedValue::Undefined());
352    return JSHandle<EcmaString>::Cast(undefinedValue);
353}
354
355// 14.1.5 FormatRelativeTime ( relativeTimeFormat, value, unit )
356JSHandle<EcmaString> JSRelativeTimeFormat::Format(JSThread *thread, double value, const JSHandle<EcmaString> &unit,
357                                                  const JSHandle<JSRelativeTimeFormat> &relativeTimeFormat)
358{
359    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
360    icu::FormattedRelativeDateTime formatted = GetIcuFormatted(thread, relativeTimeFormat, value, unit);
361    RETURN_HANDLE_IF_ABRUPT_COMPLETION(EcmaString, thread);
362    UErrorCode status = U_ZERO_ERROR;
363    icu::UnicodeString uString = formatted.toString(status);
364    if (U_FAILURE(status) != 0) {
365        THROW_RANGE_ERROR_AND_RETURN(thread, "icu formatted toString error", factory->GetEmptyString());
366    }
367    JSHandle<EcmaString> string =
368        factory->NewFromUtf16(reinterpret_cast<const uint16_t *>(uString.getBuffer()), uString.length());
369    return string;
370}
371
372void FormatToArray(JSThread *thread, const JSHandle<JSArray> &array,
373                   const icu::FormattedRelativeDateTime &formatted, double value,
374                   const JSHandle<EcmaString> &unit)
375{
376    UErrorCode status = U_ZERO_ERROR;
377    icu::UnicodeString formattedText = formatted.toString(status);
378    if (U_FAILURE(status)) {
379        THROW_TYPE_ERROR(thread, "formattedRelativeDateTime toString failed");
380    }
381
382    icu::ConstrainedFieldPosition cfpo;
383    // Set constrainCategory to UFIELD_CATEGORY_NUMBER which is specified for UNumberFormatFields
384    cfpo.constrainCategory(UFIELD_CATEGORY_NUMBER);
385    int32_t index = 0;
386    int32_t previousLimit = 0;
387    auto globalConst = thread->GlobalConstants();
388    JSHandle<JSTaggedValue> taggedValue(thread, JSTaggedValue(value));
389    JSMutableHandle<JSTaggedValue> typeString(thread, JSTaggedValue::Undefined());
390    JSHandle<JSTaggedValue> unitString = globalConst->GetHandledUnitString();
391    std::vector<std::pair<int32_t, int32_t>> separatorFields;
392    /**
393     * From ICU header file document @unumberformatter.h
394     * Sets a constraint on the field category.
395     *
396     * When this instance of ConstrainedFieldPosition is passed to FormattedValue#nextPosition,
397     * positions are skipped unless they have the given category.
398     *
399     * Any previously set constraints are cleared.
400     *
401     * For example, to loop over only the number-related fields:
402     *
403     *     ConstrainedFieldPosition cfpo;
404     *     cfpo.constrainCategory(UFIELDCATEGORY_NUMBER_FORMAT);
405     *     while (fmtval.nextPosition(cfpo, status)) {
406     *         // handle the number-related field position
407     *     }
408     */
409    while ((formatted.nextPosition(cfpo, status) != 0)) {
410        int32_t fieldId = cfpo.getField();
411        int32_t start = cfpo.getStart();
412        int32_t limit = cfpo.getLimit();
413        // Special case when fieldId is UNUM_GROUPING_SEPARATOR_FIELD
414        if (static_cast<UNumberFormatFields>(fieldId) == UNUM_GROUPING_SEPARATOR_FIELD) {
415            separatorFields.push_back(std::pair<int32_t, int32_t>(start, limit));
416            continue;
417        }
418        // If start greater than previousLimit, means a literal type exists before number fields
419        // so add a literal type with value of formattedText.sub(0, start)
420        if (start > previousLimit) {
421            typeString.Update(globalConst->GetLiteralString());
422            JSHandle<EcmaString> substring =
423                intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, start);
424            JSLocale::PutElement(thread, index++, array, typeString, JSHandle<JSTaggedValue>::Cast(substring));
425            RETURN_IF_ABRUPT_COMPLETION(thread);
426        }
427        // Add part when type is unit
428        // Iterate former grouping separator vector and add unit element to array
429        for (auto it = separatorFields.begin(); it != separatorFields.end(); it++) {
430            if (it->first > start) {
431                // Add Integer type element
432                JSHandle<EcmaString> resString =
433                    intl::LocaleHelper::UStringToString(thread, formattedText, start, it->first);
434                typeString.Update(
435                    JSLocale::GetNumberFieldType(thread, taggedValue.GetTaggedValue(), fieldId).GetTaggedValue());
436                JSHandle<JSObject> record =
437                    JSLocale::PutElement(thread, index++, array, typeString, JSHandle<JSTaggedValue>::Cast(resString));
438                RETURN_IF_ABRUPT_COMPLETION(thread);
439                JSObject::CreateDataPropertyOrThrow(thread, record, unitString, JSHandle<JSTaggedValue>::Cast(unit));
440                RETURN_IF_ABRUPT_COMPLETION(thread);
441                // Add Group type element
442                resString = intl::LocaleHelper::UStringToString(thread, formattedText, it->first, it->second);
443                typeString.Update(JSLocale::GetNumberFieldType(thread, taggedValue.GetTaggedValue(),
444                    UNUM_GROUPING_SEPARATOR_FIELD).GetTaggedValue());
445                record =
446                    JSLocale::PutElement(thread, index++, array, typeString, JSHandle<JSTaggedValue>::Cast(resString));
447                RETURN_IF_ABRUPT_COMPLETION(thread);
448                JSObject::CreateDataPropertyOrThrow(thread, record, unitString, JSHandle<JSTaggedValue>::Cast(unit));
449                RETURN_IF_ABRUPT_COMPLETION(thread);
450                start = it->second;
451            }
452        }
453        // Add current field unit
454        JSHandle<EcmaString> subString = intl::LocaleHelper::UStringToString(thread, formattedText, start, limit);
455        typeString.Update(JSLocale::GetNumberFieldType(thread, taggedValue.GetTaggedValue(), fieldId).GetTaggedValue());
456        JSHandle<JSObject> record =
457            JSLocale::PutElement(thread, index++, array, typeString, JSHandle<JSTaggedValue>::Cast(subString));
458        RETURN_IF_ABRUPT_COMPLETION(thread);
459        JSObject::CreateDataPropertyOrThrow(thread, record, unitString, JSHandle<JSTaggedValue>::Cast(unit));
460        RETURN_IF_ABRUPT_COMPLETION(thread);
461        previousLimit = limit;
462    }
463    // If iterated length is smaller than formattedText.length, means a literal type exists after number fields
464    // so add a literal type with value of formattedText.sub(previousLimit, formattedText.length)
465    if (formattedText.length() > previousLimit) {
466        typeString.Update(globalConst->GetLiteralString());
467        JSHandle<EcmaString> substring =
468            intl::LocaleHelper::UStringToString(thread, formattedText, previousLimit, formattedText.length());
469        JSLocale::PutElement(thread, index, array, typeString, JSHandle<JSTaggedValue>::Cast(substring));
470        RETURN_IF_ABRUPT_COMPLETION(thread);
471    }
472}
473
474// 14.1.6 FormatRelativeTimeToParts ( relativeTimeFormat, value, unit )
475JSHandle<JSArray> JSRelativeTimeFormat::FormatToParts(JSThread *thread, double value, const JSHandle<EcmaString> &unit,
476                                                      const JSHandle<JSRelativeTimeFormat> &relativeTimeFormat)
477{
478    icu::FormattedRelativeDateTime formatted = GetIcuFormatted(thread, relativeTimeFormat, value, unit);
479    RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
480    JSHandle<EcmaString> singularUnit = SingularUnitString(thread, unit);
481    JSHandle<JSArray> array = JSHandle<JSArray>::Cast(JSArray::ArrayCreate(thread, JSTaggedNumber(0)));
482    RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
483    FormatToArray(thread, array, formatted, value, singularUnit);
484    return array;
485}
486
487void JSRelativeTimeFormat::ResolvedOptions(JSThread *thread, const JSHandle<JSRelativeTimeFormat> &relativeTimeFormat,
488                                           const JSHandle<JSObject> &options)
489{
490    if (relativeTimeFormat->GetIcuRTFFormatter() != nullptr) {
491        [[maybe_unused]] icu::RelativeDateTimeFormatter *formatter = relativeTimeFormat->GetIcuRTFFormatter();
492    } else {
493        THROW_ERROR(thread, ErrorType::RANGE_ERROR, "rtf is not initialized");
494    }
495
496    auto globalConst = thread->GlobalConstants();
497    // [[locale]]
498    JSHandle<JSTaggedValue> property = globalConst->GetHandledLocaleString();
499    JSHandle<EcmaString> locale(thread, relativeTimeFormat->GetLocale());
500    PropertyDescriptor localeDesc(thread, JSHandle<JSTaggedValue>::Cast(locale), true, true, true);
501    JSObject::DefineOwnProperty(thread, options, property, localeDesc);
502
503    // [[Style]]
504    property = globalConst->GetHandledStyleString();
505    RelativeStyleOption style = relativeTimeFormat->GetStyle();
506    JSHandle<JSTaggedValue> styleValue;
507    if (style == RelativeStyleOption::LONG) {
508        styleValue = globalConst->GetHandledLongString();
509    } else if (style == RelativeStyleOption::SHORT) {
510        styleValue = globalConst->GetHandledShortString();
511    } else if (style == RelativeStyleOption::NARROW) {
512        styleValue = globalConst->GetHandledNarrowString();
513    }
514    PropertyDescriptor styleDesc(thread, styleValue, true, true, true);
515    JSObject::DefineOwnProperty(thread, options, property, styleDesc);
516
517    // [[Numeric]]
518    property = globalConst->GetHandledNumericString();
519    NumericOption numeric = relativeTimeFormat->GetNumeric();
520    JSHandle<JSTaggedValue> numericValue;
521    if (numeric == NumericOption::ALWAYS) {
522        numericValue = globalConst->GetHandledAlwaysString();
523    } else if (numeric == NumericOption::AUTO) {
524        numericValue = globalConst->GetHandledAutoString();
525    } else {
526        THROW_ERROR(thread, ErrorType::RANGE_ERROR, "numeric is exception");
527    }
528    PropertyDescriptor numericDesc(thread, numericValue, true, true, true);
529    JSObject::DefineOwnProperty(thread, options, property, numericDesc);
530
531    // [[NumberingSystem]]
532    property = JSHandle<JSTaggedValue>::Cast(globalConst->GetHandledNumberingSystemString());
533    JSHandle<JSTaggedValue> numberingSystem(thread, relativeTimeFormat->GetNumberingSystem());
534    PropertyDescriptor numberingSystemDesc(thread, numberingSystem, true, true, true);
535    JSObject::DefineOwnProperty(thread, options, property, numberingSystemDesc);
536}
537}  // namespace panda::ecmascript