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