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