1 /*
2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "ecmascript/js_relative_time_format.h"
17 #include "ecmascript/js_function.h"
18 #include "ecmascript/object_factory-inl.h"
19
20 namespace panda::ecmascript {
21 // 14.1.1 InitializeRelativeTimeFormat ( relativeTimeFormat, locales, options )
InitializeRelativeTimeFormat(JSThread * thread,const JSHandle<JSRelativeTimeFormat> & relativeTimeFormat,const JSHandle<JSTaggedValue> & locales,const JSHandle<JSTaggedValue> & options)22 JSHandle<JSRelativeTimeFormat> JSRelativeTimeFormat::InitializeRelativeTimeFormat(
23 JSThread *thread, const JSHandle<JSRelativeTimeFormat> &relativeTimeFormat, const JSHandle<JSTaggedValue> &locales,
24 const JSHandle<JSTaggedValue> &options)
25 {
26 EcmaVM *ecmaVm = thread->GetEcmaVM();
27 ObjectFactory *factory = ecmaVm->GetFactory();
28
29 // 1.Let requestedLocales be ? CanonicalizeLocaleList(locales).
30 JSHandle<TaggedArray> requestedLocales = intl::LocaleHelper::CanonicalizeLocaleList(thread, locales);
31 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread);
32
33 // 2&3. If options is undefined, then Let options be ObjectCreate(null). else Let options be ? ToObject(options).
34 JSHandle<JSObject> rtfOptions;
35 if (!options->IsUndefined()) {
36 rtfOptions = JSTaggedValue::ToObject(thread, options);
37 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread);
38 } else {
39 rtfOptions = factory->CreateNullJSObject();
40 }
41
42 // 5. Let matcher be ? GetOption(options, "localeMatcher", "string", «"lookup", "best fit"», "best fit").
43 auto globalConst = thread->GlobalConstants();
44 LocaleMatcherOption matcher =
45 JSLocale::GetOptionOfString(thread, rtfOptions, globalConst->GetHandledLocaleMatcherString(),
46 {LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT},
47 {"lookup", "best fit"}, LocaleMatcherOption::BEST_FIT);
48 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread);
49
50 // 7. Let numberingSystem be ? GetOption(options, "numberingSystem", "string", undefined, undefined).
51 JSHandle<JSTaggedValue> property = globalConst->GetHandledNumberingSystemString();
52 JSHandle<JSTaggedValue> undefinedValue(thread, JSTaggedValue::Undefined());
53 JSHandle<JSTaggedValue> numberingSystemValue =
54 JSLocale::GetOption(thread, rtfOptions, property, OptionType::STRING, undefinedValue, undefinedValue);
55 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread);
56
57 // Check whether numberingSystem is well formed and set to %RelativeTimeFormat%.[[numberingSystem]]
58 std::string numberingSystemStdStr;
59 if (!numberingSystemValue->IsUndefined()) {
60 JSHandle<EcmaString> numberingSystemString = JSHandle<EcmaString>::Cast(numberingSystemValue);
61 if (EcmaStringAccessor(numberingSystemString).IsUtf16()) {
62 THROW_RANGE_ERROR_AND_RETURN(thread, "invalid numberingSystem", relativeTimeFormat);
63 }
64 numberingSystemStdStr = intl::LocaleHelper::ConvertToStdString(numberingSystemString);
65 if (!JSLocale::IsNormativeNumberingSystem(numberingSystemStdStr)) {
66 THROW_RANGE_ERROR_AND_RETURN(thread, "invalid numberingSystem", relativeTimeFormat);
67 }
68 }
69
70 // 10. Let localeData be %RelativeTimeFormat%.[[LocaleData]].
71 // 11. Let r be ResolveLocale(%RelativeTimeFormat%.[[AvailableLocales]], requestedLocales, opt,
72 // %RelativeTimeFormat%.[[RelevantExtensionKeys]], localeData).
73 JSHandle<TaggedArray> availableLocales;
74 if (requestedLocales->GetLength() == 0) {
75 availableLocales = factory->EmptyArray();
76 } else {
77 std::vector<std::string> availableStringLocales =
78 intl::LocaleHelper::GetAvailableLocales(thread, "calendar", nullptr);
79 availableLocales = JSLocale::ConstructLocaleList(thread, availableStringLocales);
80 }
81 std::set<std::string> relevantExtensionKeys{"nu"};
82 ResolvedLocale r =
83 JSLocale::ResolveLocale(thread, availableLocales, requestedLocales, matcher, relevantExtensionKeys);
84 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread);
85 icu::Locale icuLocale = r.localeData;
86
87 // 12. Let locale be r.[[Locale]].
88 JSHandle<EcmaString> localeStr = intl::LocaleHelper::ToLanguageTag(thread, icuLocale);
89 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread);
90
91 // 13. Set relativeTimeFormat.[[Locale]] to locale.
92 relativeTimeFormat->SetLocale(thread, localeStr.GetTaggedValue());
93
94 // 15. Set relativeTimeFormat.[[NumberingSystem]] to r.[[nu]].
95 UErrorCode status = U_ZERO_ERROR;
96 if (!numberingSystemStdStr.empty()) {
97 if (JSLocale::IsWellNumberingSystem(numberingSystemStdStr)) {
98 icuLocale.setUnicodeKeywordValue("nu", numberingSystemStdStr, status);
99 ASSERT(U_SUCCESS(status));
100 }
101 }
102
103 // 16. Let s be ? GetOption(options, "style", "string", «"long", "short", "narrow"», "long").
104 property = globalConst->GetHandledStyleString();
105 RelativeStyleOption styleOption = JSLocale::GetOptionOfString(thread, rtfOptions, property,
106 {RelativeStyleOption::LONG, RelativeStyleOption::SHORT, RelativeStyleOption::NARROW},
107 {"long", "short", "narrow"}, RelativeStyleOption::LONG);
108 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread);
109
110 // 17. Set relativeTimeFormat.[[Style]] to s.
111 relativeTimeFormat->SetStyle(styleOption);
112
113 // 18. Let numeric be ? GetOption(options, "numeric", "string", ?"always", "auto"?, "always").
114 property = globalConst->GetHandledNumericString();
115 NumericOption numericOption =
116 JSLocale::GetOptionOfString(thread, rtfOptions, property, {NumericOption::ALWAYS, NumericOption::AUTO},
117 {"always", "auto"}, NumericOption::ALWAYS);
118 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSRelativeTimeFormat, thread);
119
120 // 19. Set relativeTimeFormat.[[Numeric]] to numeric.
121 relativeTimeFormat->SetNumeric(numericOption);
122
123 // 20. Let relativeTimeFormat.[[NumberFormat]] be ! Construct(%NumberFormat%, « locale »).
124 icu::NumberFormat *icuNumberFormat = icu::NumberFormat::createInstance(icuLocale, UNUM_DECIMAL, status);
125 if (U_FAILURE(status) != 0) {
126 delete icuNumberFormat;
127 if (status == UErrorCode::U_MISSING_RESOURCE_ERROR) {
128 THROW_REFERENCE_ERROR_AND_RETURN(thread, "can not find icu data resources", relativeTimeFormat);
129 }
130 THROW_RANGE_ERROR_AND_RETURN(thread, "create icu::NumberFormat failed", relativeTimeFormat);
131 }
132 // Display grouping using the default strategy for all locales
133 if (icuNumberFormat->getDynamicClassID() == icu::DecimalFormat::getStaticClassID()) {
134 icu::DecimalFormat* icuDecimalFormat = static_cast<icu::DecimalFormat*>(icuNumberFormat);
135 icuDecimalFormat->setMinimumGroupingDigits(UNUM_MINIMUM_GROUPING_DIGITS_AUTO);
136 }
137
138 // Trans RelativeStyleOption to ICU Style
139 UDateRelativeDateTimeFormatterStyle uStyle;
140 switch (styleOption) {
141 case RelativeStyleOption::LONG:
142 uStyle = UDAT_STYLE_LONG;
143 break;
144 case RelativeStyleOption::SHORT:
145 uStyle = UDAT_STYLE_SHORT;
146 break;
147 case RelativeStyleOption::NARROW:
148 uStyle = UDAT_STYLE_NARROW;
149 break;
150 default:
151 LOG_ECMA(FATAL) << "this branch is unreachable";
152 UNREACHABLE();
153 }
154 icu::RelativeDateTimeFormatter rtfFormatter(icuLocale, icuNumberFormat, uStyle, UDISPCTX_CAPITALIZATION_NONE,
155 status);
156 if (U_FAILURE(status) != 0) {
157 THROW_RANGE_ERROR_AND_RETURN(thread, "icu Formatter Error", relativeTimeFormat);
158 }
159
160 std::string numberingSystem = JSLocale::GetNumberingSystem(icuLocale);
161 auto result = factory->NewFromStdString(numberingSystem);
162 relativeTimeFormat->SetNumberingSystem(thread, result);
163
164 // Set RelativeTimeFormat.[[IcuRelativeTimeFormatter]]
165 factory->NewJSIntlIcuData(relativeTimeFormat, rtfFormatter, JSRelativeTimeFormat::FreeIcuRTFFormatter);
166
167 // 22. Return relativeTimeFormat.
168 return relativeTimeFormat;
169 }
170
171 // 14.1.2 SingularRelativeTimeUnit ( unit )
SingularUnitToIcuUnit(JSThread * thread,const JSHandle<EcmaString> & unit,URelativeDateTimeUnit * unitEnum)172 bool SingularUnitToIcuUnit(JSThread *thread, const JSHandle<EcmaString> &unit, URelativeDateTimeUnit *unitEnum)
173 {
174 // 1. Assert: Type(unit) is String.
175 ASSERT(JSHandle<JSTaggedValue>::Cast(unit)->IsString());
176
177 // 2. If unit is "seconds" or "second", return "second".
178 // 3. If unit is "minutes" or "minute", return "minute".
179 // 4. If unit is "hours" or "hour", return "hour".
180 // 5. If unit is "days" or "day", return "day".
181 // 6. If unit is "weeks" or "week", return "week".
182 // 7. If unit is "months" or "month", return "month".
183 // 8. If unit is "quarters" or "quarter", return "quarter".
184 // 9. If unit is "years" or "year", return "year".
185 auto globalConst = thread->GlobalConstants();
186 JSHandle<EcmaString> second = JSHandle<EcmaString>::Cast(globalConst->GetHandledSecondString());
187 JSHandle<EcmaString> minute = JSHandle<EcmaString>::Cast(globalConst->GetHandledMinuteString());
188 JSHandle<EcmaString> hour = JSHandle<EcmaString>::Cast(globalConst->GetHandledHourString());
189 JSHandle<EcmaString> day = JSHandle<EcmaString>::Cast(globalConst->GetHandledDayString());
190 JSHandle<EcmaString> week = JSHandle<EcmaString>::Cast(globalConst->GetHandledWeekString());
191 JSHandle<EcmaString> month = JSHandle<EcmaString>::Cast(globalConst->GetHandledMonthString());
192 JSHandle<EcmaString> quarter = JSHandle<EcmaString>::Cast(globalConst->GetHandledQuarterString());
193 JSHandle<EcmaString> year = JSHandle<EcmaString>::Cast(globalConst->GetHandledYearString());
194
195 JSHandle<EcmaString> seconds = JSHandle<EcmaString>::Cast(globalConst->GetHandledSecondsString());
196 JSHandle<EcmaString> minutes = JSHandle<EcmaString>::Cast(globalConst->GetHandledMinutesString());
197 JSHandle<EcmaString> hours = JSHandle<EcmaString>::Cast(globalConst->GetHandledHoursString());
198 JSHandle<EcmaString> days = JSHandle<EcmaString>::Cast(globalConst->GetHandledDaysString());
199 JSHandle<EcmaString> weeks = JSHandle<EcmaString>::Cast(globalConst->GetHandledWeeksString());
200 JSHandle<EcmaString> months = JSHandle<EcmaString>::Cast(globalConst->GetHandledMonthsString());
201 JSHandle<EcmaString> quarters = JSHandle<EcmaString>::Cast(globalConst->GetHandledQuartersString());
202 JSHandle<EcmaString> years = JSHandle<EcmaString>::Cast(globalConst->GetHandledYearsString());
203
204 if (EcmaStringAccessor::StringsAreEqual(*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
UnwrapRelativeTimeFormat(JSThread * thread,const JSHandle<JSTaggedValue> & rtf)236 JSHandle<JSTaggedValue> JSRelativeTimeFormat::UnwrapRelativeTimeFormat(JSThread *thread,
237 const JSHandle<JSTaggedValue> &rtf)
238 {
239 ASSERT_PRINT(rtf->IsJSObject(), "rtf is not a JSObject");
240
241 JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
242 bool isInstanceOf = JSFunction::InstanceOf(thread, rtf, env->GetRelativeTimeFormatFunction());
243 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, rtf);
244 if (!rtf->IsJSRelativeTimeFormat() && isInstanceOf) {
245 JSHandle<JSTaggedValue> key(thread, JSHandle<JSIntl>::Cast(env->GetIntlFunction())->GetFallbackSymbol());
246 OperationResult operationResult = JSTaggedValue::GetProperty(thread, rtf, key);
247 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, rtf);
248 return operationResult.GetValue();
249 }
250
251 // Perform ? RequireInternalSlot(relativeTimeFormat, [[InitializedRelativeTimeFormat]]).
252 if (!rtf->IsJSRelativeTimeFormat()) {
253 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, rtf);
254 }
255 return rtf;
256 }
257
258 // CommonFormat
GetIcuFormatted(JSThread * thread,const JSHandle<JSRelativeTimeFormat> & relativeTimeFormat,double value,const JSHandle<EcmaString> & unit)259 icu::FormattedRelativeDateTime GetIcuFormatted(JSThread *thread,
260 const JSHandle<JSRelativeTimeFormat> &relativeTimeFormat,
261 double value, const JSHandle<EcmaString> &unit)
262 {
263 icu::RelativeDateTimeFormatter *formatter = relativeTimeFormat->GetIcuRTFFormatter();
264 ASSERT_PRINT(formatter != nullptr, "rtfFormatter is null");
265
266 // If isFinite(value) is false, then throw a RangeError exception.
267 if (!std::isfinite(value)) {
268 THROW_RANGE_ERROR_AND_RETURN(thread, "", icu::FormattedRelativeDateTime());
269 }
270
271 // 10. If unit is not one of "second", "minute", "hour", "day", "week", "month", "quarter", or "year", throw a
272 // RangeError exception.
273 URelativeDateTimeUnit unitEnum;
274 if (!SingularUnitToIcuUnit(thread, unit, &unitEnum)) {
275 THROW_RANGE_ERROR_AND_RETURN(thread, "", icu::FormattedRelativeDateTime());
276 }
277 UErrorCode status = U_ZERO_ERROR;
278 NumericOption numeric = relativeTimeFormat->GetNumeric();
279
280 icu::FormattedRelativeDateTime formatted;
281 switch (numeric) {
282 case NumericOption::ALWAYS:
283 formatted = formatter->formatNumericToValue(value, unitEnum, status);
284 ASSERT_PRINT(U_SUCCESS(status), "icu format to value error");
285 break;
286 case NumericOption::AUTO:
287 formatted = formatter->formatToValue(value, unitEnum, status);
288 ASSERT_PRINT(U_SUCCESS(status), "icu format to value error");
289 break;
290 default:
291 LOG_ECMA(FATAL) << "this branch is unreachable";
292 UNREACHABLE();
293 }
294 return formatted;
295 }
296
297 // 14.1.2 SingularRelativeTimeUnit ( unit )
SingularUnitString(JSThread * thread,const JSHandle<EcmaString> & unit)298 JSHandle<EcmaString> SingularUnitString(JSThread *thread, const JSHandle<EcmaString> &unit)
299 {
300 auto globalConst = thread->GlobalConstants();
301 JSHandle<EcmaString> second = JSHandle<EcmaString>::Cast(globalConst->GetHandledSecondString());
302 JSHandle<EcmaString> minute = JSHandle<EcmaString>::Cast(globalConst->GetHandledMinuteString());
303 JSHandle<EcmaString> hour = JSHandle<EcmaString>::Cast(globalConst->GetHandledHourString());
304 JSHandle<EcmaString> day = JSHandle<EcmaString>::Cast(globalConst->GetHandledDayString());
305 JSHandle<EcmaString> week = JSHandle<EcmaString>::Cast(globalConst->GetHandledWeekString());
306 JSHandle<EcmaString> month = JSHandle<EcmaString>::Cast(globalConst->GetHandledMonthString());
307 JSHandle<EcmaString> quarter = JSHandle<EcmaString>::Cast(globalConst->GetHandledQuarterString());
308 JSHandle<EcmaString> year = JSHandle<EcmaString>::Cast(globalConst->GetHandledYearString());
309 JSHandle<EcmaString> seconds = JSHandle<EcmaString>::Cast(globalConst->GetHandledSecondsString());
310 JSHandle<EcmaString> minutes = JSHandle<EcmaString>::Cast(globalConst->GetHandledMinutesString());
311 JSHandle<EcmaString> hours = JSHandle<EcmaString>::Cast(globalConst->GetHandledHoursString());
312 JSHandle<EcmaString> days = JSHandle<EcmaString>::Cast(globalConst->GetHandledDaysString());
313 JSHandle<EcmaString> weeks = JSHandle<EcmaString>::Cast(globalConst->GetHandledWeeksString());
314 JSHandle<EcmaString> months = JSHandle<EcmaString>::Cast(globalConst->GetHandledMonthsString());
315 JSHandle<EcmaString> quarters = JSHandle<EcmaString>::Cast(globalConst->GetHandledQuartersString());
316 JSHandle<EcmaString> years = JSHandle<EcmaString>::Cast(globalConst->GetHandledYearsString());
317
318 // 2. If unit is "seconds" or "second", return "second".
319 if (EcmaStringAccessor::StringsAreEqual(*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 )
Format(JSThread * thread,double value,const JSHandle<EcmaString> & unit,const JSHandle<JSRelativeTimeFormat> & relativeTimeFormat)356 JSHandle<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
FormatToArray(JSThread * thread,const JSHandle<JSArray> & array,const icu::FormattedRelativeDateTime & formatted,double value,const JSHandle<EcmaString> & unit)372 void 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 )
FormatToParts(JSThread * thread,double value,const JSHandle<EcmaString> & unit,const JSHandle<JSRelativeTimeFormat> & relativeTimeFormat)475 JSHandle<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
ResolvedOptions(JSThread * thread,const JSHandle<JSRelativeTimeFormat> & relativeTimeFormat,const JSHandle<JSObject> & options)487 void 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