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