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