From ac7d2df9d7cf4c32987ffdbe72b80f36dd8ab9ca Mon Sep 17 00:00:00 2001 From: TatianaKapos Date: Mon, 29 Aug 2022 17:29:47 -0700 Subject: [PATCH 01/14] adds default Intl.DateTimeFormat --- lib/Platform/Intl/PlatformIntlWindows.cpp | 896 ++++++++++++++++++++++ unittests/PlatformIntl/DateTimeFormat.cpp | 340 ++++++++ 2 files changed, 1236 insertions(+) create mode 100644 lib/Platform/Intl/PlatformIntlWindows.cpp create mode 100644 unittests/PlatformIntl/DateTimeFormat.cpp diff --git a/lib/Platform/Intl/PlatformIntlWindows.cpp b/lib/Platform/Intl/PlatformIntlWindows.cpp new file mode 100644 index 00000000000..de5d8c44050 --- /dev/null +++ b/lib/Platform/Intl/PlatformIntlWindows.cpp @@ -0,0 +1,896 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +#include "hermes/Platform/Intl/PlatformIntl.h" + +#include +#include +#include +#include +#include "llvh/Support/ConvertUTF.h" + +using namespace ::hermes; + +namespace hermes { +namespace platform_intl { + +vm::CallResult> getCanonicalLocales( + vm::Runtime &runtime, + const std::vector &locales) { + return std::vector{u"fr-FR", u"es-ES"}; +} + +vm::CallResult toLocaleLowerCase( + vm::Runtime &runtime, + const std::vector &locales, + const std::u16string &str) { + return std::u16string(u"lowered"); +} +vm::CallResult toLocaleUpperCase( + vm::Runtime &runtime, + const std::vector &locales, + const std::u16string &str) { + return std::u16string(u"uppered"); +} + +/// https://402.ecma-international.org/8.0/#sec-getoption +/// Split into getOptionString and getOptionBool to help readability +vm::CallResult getOptionString( + vm::Runtime &runtime, + const Options &options, + const std::u16string &property, + std::vector values, + std::u16string fallback) { + // 1. Assert type(options) is object + // 2. Let value be ? Get(options, property). + auto valueIt = options.find(property); + // 3. If value is undefined, return fallback. + if (valueIt == options.end()) + return std::u16string(fallback); + + const auto &value = valueIt->second.getString(); + // 4. Assert: type is "boolean" or "string". + // 5. If type is "boolean", then + // a. Set value to ! ToBoolean(value). + // 6. If type is "string", then + // a. Set value to ? ToString(value). + // 7. If values is not undefined and values does not contain an element equal + // to value, throw a RangeError exception. + if (!values.empty() && llvh::find(values, value) == values.end()) + return runtime.raiseRangeError( + vm::TwineChar16(property.c_str()) + + vm::TwineChar16("Value is invalid.")); + // 8. Return value. + return std::u16string(value); +} + +// boolean + null option +enum BoolNull { eFalse, eTrue, eNull }; +BoolNull getOptionBool( + vm::Runtime &runtime, + const Options &options, + const std::u16string &property, + bool fallback) { + // 1. Assert: Type(options) is Object. + // 2. Let value be ? Get(options, property). + auto value = options.find(property); + // 3. If value is undefined, return fallback. + if (value == options.end()) { + return eNull; + } + // 8. Return value. + if (value->second.getBool()) { + return eTrue; + } + return eFalse; +} + +struct Collator::Impl { + std::u16string locale; +}; + +Collator::Collator() : impl_(std::make_unique()) {} +Collator::~Collator() {} + +vm::CallResult> Collator::supportedLocalesOf( + vm::Runtime &runtime, + const std::vector &locales, + const Options &options) noexcept { + return std::vector{u"en-CA", u"de-DE"}; +} + +vm::ExecutionStatus Collator::initialize( + vm::Runtime &runtime, + const std::vector &locales, + const Options &options) noexcept { + impl_->locale = u"en-US"; + return vm::ExecutionStatus::RETURNED; +} + +Options Collator::resolvedOptions() noexcept { + Options options; + options.emplace(u"locale", Option(impl_->locale)); + options.emplace(u"numeric", Option(false)); + return options; +} + +double Collator::compare( + const std::u16string &x, + const std::u16string &y) noexcept { + return x.compare(y); +} + +// Implementation of +// https://402.ecma-international.org/8.0/#sec-todatetimeoptions +Options toDateTimeOptions( + vm::Runtime &runtime, + Options options, + std::u16string required, + std::u16string defaults) { + // 1. If options is undefined, let options be null; otherwise let options be ? + // ToObject(options). + // 2. Let options be OrdinaryObjectCreate(options). + // 3. Let needDefaults be true. + bool needDefaults = true; + // 4. If required is "date" or "any", then + if (required == u"date" || required == u"any") { + // a. For each property name prop of « "weekday", "year", "month", "day" », + // do + // TODO(T116352920): Make this a std::u16string props[] once we have + // constexpr std::u16string. + static const std::vector props = { + u"weekday", u"year", u"month", u"day"}; + for (const auto &prop : props) { + // i. Let value be ? Get(options, prop). + if (options.find(std::u16string(prop)) != options.end()) { + // ii. If value is not undefined, let needDefaults be false. + needDefaults = false; + } + } + } + // 5. If required is "time" or "any", then + if (required == u"time" || required == u"any") { + // a. For each property name prop of « "dayPeriod", "hour", "minute", + // "second", "fractionalSecondDigits" », do + static const std::vector props = { + u"dayPeriod", u"hour", u"minute", u"second", u"fractionalSecondDigits"}; + for (const auto &prop : props) { + // i. Let value be ? Get(options, prop). + if (options.find(std::u16string(prop)) != options.end()) { + // ii. If value is not undefined, let needDefaults be false. + needDefaults = false; + } + } + } + // 6. Let dateStyle be ? Get(options, "dateStyle"). + auto dateStyle = options.find(u"dateStyle"); + // 7. Let timeStyle be ? Get(options, "timeStyle"). + auto timeStyle = options.find(u"timeStyle"); + // 8. If dateStyle is not undefined or timeStyle is not undefined, let + // needDefaults be false. + if (dateStyle != options.end() || timeStyle != options.end()) { + needDefaults = false; + } + // 9. If required is "date" and timeStyle is not undefined, then + if (required == u"date" && timeStyle != options.end()) { + // a. Throw a TypeError exception. + } + // 10. If required is "time" and dateStyle is not undefined, then + if (required == u"time" && dateStyle != options.end()) { + // a. Throw a TypeError exception. + } + // 11. If needDefaults is true and defaults is either "date" or "all", then + if (needDefaults && (defaults == u"date" || defaults == u"all")) { + // a. For each property name prop of « "year", "month", "day" », do + static const std::vector props = { + u"year", u"month", u"day"}; + for (const auto &prop : props) { + // i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric"). + options.emplace(prop, Option(std::u16string(u"numeric"))); + } + } + // 12. If needDefaults is true and defaults is either "time" or "all", then + if (needDefaults && (defaults == u"time" || defaults == u"all")) { + // a. For each property name prop of « "hour", "minute", "second" », do + static const std::vector props = { + u"hour", u"minute", u"second"}; + for (const auto &prop : props) { + // i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric"). + options.emplace(prop, Option(std::u16string(u"numeric"))); + } + } + // 13. return options + return options; +} + +struct DateTimeFormat::Impl { + // Options used with DateTimeFormat + std::u16string locale; + std::u16string timeZone; + std::u16string weekday; + std::u16string era; + std::u16string year; + std::u16string month; + std::u16string day; + std::u16string dayPeriod; // Not Supported + std::u16string hour; + std::u16string minute; + std::u16string second; + std::u16string timeZoneName; + std::u16string dateStyle; + std::u16string timeStyle; + std::u16string hourCycle; + // Internal use + UDateFormat *dtf; + const char *locale8; + UDateFormat *getUDateFormatter(vm::Runtime &runtime); + vm::CallResult getDefaultHourCycle(vm::Runtime &runtime); +}; + +DateTimeFormat::DateTimeFormat() : impl_(std::make_unique()) {} +DateTimeFormat::~DateTimeFormat() {} + +// get the supported locales of dateTimeFormat +vm::CallResult> DateTimeFormat::supportedLocalesOf( + vm::Runtime &runtime, + const std::vector &locales, + const Options &options) noexcept { + std::vector result = {}; + for (int32_t i = 0; i < uloc_countAvailable(); i++) { + auto locale = uloc_getAvailable(i); + result.push_back(UTF8toUTF16(runtime, locale).getValue()); + } + return result; +} + +// initalize dtf +vm::ExecutionStatus DateTimeFormat::initialize( + vm::Runtime &runtime, + const std::vector &locales, + const Options &inputOptions) noexcept { + auto requestedLocalesRes = CanonicalizeLocaleList(runtime, locales); + if (requestedLocalesRes.getStatus() == vm::ExecutionStatus::EXCEPTION) { + return requestedLocalesRes.getStatus(); + } + impl_->locale = locales.front(); + + auto conversion = UTF16toUTF8(runtime, impl_->locale); + if (conversion.getStatus() == vm::ExecutionStatus::EXCEPTION) { + return conversion.getStatus(); + } + const char *locale8 = conversion.getValue().c_str(); + impl_->locale8 = locale8; // store the UTF8 version of locale since it is used + // in almost all other functions + + // 2. Let options be ? ToDateTimeOptions(options, "any", "date"). + Options options = toDateTimeOptions(runtime, inputOptions, u"any", u"date"); + // 3. Let opt be a new Record. + std::unordered_map opt; + // 4. Let matcher be ? GetOption(options, "localeMatcher", "string", + // «"lookup", "best fit" », "best fit"). + auto matcher = getOptionString( + runtime, + options, + u"localeMatcher", + {u"lookup", u"best fit"}, + u"best fit"); + // 5. Set opt.[[localeMatcher]] to matcher. + opt.emplace(u"localeMatcher", matcher.getValue()); + // 6. Let calendar be ? GetOption(options, "calendar", "string", undefined, + // undefined). + auto calendar = getOptionString(runtime, options, u"calendar", {}, {}); + opt.emplace(u"ca", calendar.getValue()); + + // 9. Let numberingSystem be ? GetOption(options, "numberingSystem", + // "string", undefined, undefined). + // 10. If numberingSystem is not undefined, then + // a. If numberingSystem does not match the Unicode Locale Identifier + // type nonterminal, throw a RangeError exception. + + // 11. Set opt.[[nu]] to numberingSystem. + opt.emplace(u"nu", u""); + + // 12. Let hour12 be ? GetOption(options, "hour12", "boolean", undefined, + // undefined). + BoolNull hour12 = getOptionBool(runtime, options, u"hour12", {}); + + // 13. Let hourCycle be ? GetOption(options, "hourCycle", "string", «"h11", + // "h12", "h23", "h24" », undefined). + static const std::vector hourCycles = { + u"h11", u"h12", u"h23", u"h24"}; + auto hourCycleRes = + getOptionString(runtime, options, u"hourCycle", hourCycles, {}); + std::u16string hourCycle = hourCycleRes.getValue(); + + // 14. If hour12 is not undefined, then a. Set hourCycle to null. + if (!(hour12 == eNull)) { + hourCycle = u""; + } + // 15. Set opt.[[hc]] to hourCycle. + opt.emplace(u"hc", hourCycle); + impl_->hourCycle = hourCycle; + + // 16. Let localeData be %DateTimeFormat%.[[LocaleData]]. + // 17. Let r be ResolveLocale(%DateTimeFormat%.[[AvailableLocales]], + // requestedLocales, opt, %DateTimeFormat%.[[RelevantExtensionKeys]], + // localeData). + // 18. Set dateTimeFormat.[[Locale]] to r.[[locale]]. + // 19. Let calendar be r.[[ca] + // 20. Set dateTimeFormat.[[Calendar]] to calendar. + // 21. Set dateTimeFormat.[[HourCycle]] to r.[[hc]]. + // 22. Set dateTimeFormat.[[NumberingSystem]] to r.[[nu]]. + // 23. Let dataLocale be r.[[dataLocale]]. + + // 24. Let timeZone be ? Get(options, "timeZone"). + auto timeZoneRes = options.find(u"timeZone"); + // 25. If timeZone is undefined, then + if (timeZoneRes == options.end()) { + // a. Let timeZone be DefaultTimeZone(). + // 26. Else, + } else { + // a. Let timeZone be ? ToString(timeZone). + std::u16string timeZone = std::u16string(timeZoneRes->second.getString()); + // b. If the result of IsValidTimeZoneName(timeZone) is false, then + // i. Throw a RangeError exception. + // c. Let timeZone be CanonicalizeTimeZoneName(timeZone). + // 27. Set dateTimeFormat.[[TimeZone]] to timeZone. + impl_->timeZone = timeZone; + } + + // 28. Let opt be a new Record. + // 29. For each row of Table 4, except the header row, in table order, do + // a. Let prop be the name given in the Property column of the row. + // b. If prop is "fractionalSecondDigits", then + // i. Let value be ? GetNumberOption(options, "fractionalSecondDigits", 1, + // 3, undefined). + // d. Set opt.[[]] to value. + // c. Else, + // i. Let value be ? GetOption(options, prop, "string", « the strings + // given in the Values column of the row », undefined). + // d. Set opt.[[]] to value. + // 30. Let dataLocaleData be localeData.[[]]. + // 31. Let matcher be ? GetOption(options, "formatMatcher", "string", « + // "basic", "best fit" », "best fit"). + + // 32. Let dateStyle be ? GetOption(options, "dateStyle", "string", « "full", + // "long", "medium", "short" », undefined). + static const std::vector dateStyles = { + u"full", u"long", u"medium", u"short"}; + auto dateStyleRes = + getOptionString(runtime, options, u"dateStyle", dateStyles, {}); + if (dateStyleRes.getStatus() == vm::ExecutionStatus::EXCEPTION) { + return dateStyleRes.getStatus(); + } + // 33. Set dateTimeFormat.[[DateStyle]] to dateStyle. + impl_->dateStyle = dateStyleRes.getValue(); + + // 34. Let timeStyle be ? GetOption(options, "timeStyle", "string", « "full", + // "long", "medium", "short" », undefined). + static const std::vector timeStyles = { + u"full", u"long", u"medium", u"short"}; + auto timeStyleRes = + getOptionString(runtime, options, u"timeStyle", timeStyles, {}); + if (timeStyleRes.getStatus() == vm::ExecutionStatus::EXCEPTION) { + return timeStyleRes.getStatus(); + } + // 35. Set dateTimeFormat.[[TimeStyle]] to timeStyle. + impl_->timeStyle = timeStyleRes.getValue(); + + // Initialize properties using values from the options. + static const std::vector weekdayValues = { + u"narrow", u"short", u"long"}; + auto weekdayRes = + getOptionString(runtime, options, u"weekday", weekdayValues, {}); + if (weekdayRes.getStatus() == vm::ExecutionStatus::EXCEPTION) { + return weekdayRes.getStatus(); + } + impl_->weekday = weekdayRes.getValue(); + + static const std::vector eraValues = { + u"narrow", u"short", u"long"}; + auto eraRes = getOptionString(runtime, options, u"era", eraValues, {}); + if (eraRes.getStatus() == vm::ExecutionStatus::EXCEPTION) { + return eraRes.getStatus(); + } + impl_->era = *eraRes; + + static const std::vector yearValues = { + u"2-digit", u"numeric"}; + auto yearRes = getOptionString(runtime, options, u"year", yearValues, {}); + if (yearRes.getStatus() == vm::ExecutionStatus::EXCEPTION) { + return yearRes.getStatus(); + } + impl_->year = *yearRes; + + static const std::vector monthValues = { + u"2-digit", u"numeric", u"narrow", u"short", u"long"}; + auto monthRes = getOptionString(runtime, options, u"month", monthValues, {}); + if (monthRes.getStatus() == vm::ExecutionStatus::EXCEPTION) { + return monthRes.getStatus(); + } + impl_->month = *monthRes; + + static const std::vector dayValues = {u"2-digit", u"numeric"}; + auto dayRes = getOptionString(runtime, options, u"day", dayValues, {}); + if (dayRes.getStatus() == vm::ExecutionStatus::EXCEPTION) { + return dayRes.getStatus(); + } + impl_->day = *dayRes; + + static const std::vector dayPeriodValues = { + u"narrow", u"short", u"long"}; + auto dayPeriodRes = + getOptionString(runtime, options, u"dayPeriod", dayPeriodValues, {}); + if (dayPeriodRes.getStatus() == vm::ExecutionStatus::EXCEPTION) { + return dayPeriodRes.getStatus(); + } + impl_->dayPeriod = *dayPeriodRes; + + static const std::vector hourValues = { + u"2-digit", u"numeric"}; + auto hourRes = getOptionString(runtime, options, u"hour", hourValues, {}); + if (hourRes.getStatus() == vm::ExecutionStatus::EXCEPTION) { + return hourRes.getStatus(); + } + impl_->hour = *hourRes; + + static const std::vector minuteValues = { + u"2-digit", u"numeric"}; + auto minuteRes = + getOptionString(runtime, options, u"minute", minuteValues, {}); + if (minuteRes.getStatus() == vm::ExecutionStatus::EXCEPTION) { + return minuteRes.getStatus(); + } + impl_->minute = *minuteRes; + + static const std::vector secondValues = { + u"2-digit", u"numeric"}; + auto secondRes = + getOptionString(runtime, options, u"second", secondValues, {}); + if (secondRes.getStatus() == vm::ExecutionStatus::EXCEPTION) { + return secondRes.getStatus(); + } + impl_->second = *secondRes; + + static const std::vector timeZoneNameValues = { + u"short", + u"long", + u"shortOffset", + u"longOffset", + u"shortGeneric", + u"longGeneric"}; + auto timeZoneNameRes = getOptionString( + runtime, options, u"timeZoneName", timeZoneNameValues, {}); + if (timeZoneNameRes.getStatus() == vm::ExecutionStatus::EXCEPTION) { + return timeZoneNameRes.getStatus(); + } + impl_->timeZoneName = *timeZoneNameRes; + + // 36. If dateStyle is not undefined or timeStyle is not undefined, then + // a. For each row in Table 4, except the header row, do + // i. Let prop be the name given in the Property column of the row. + // ii. Let p be opt.[[]]. + // iii. If p is not undefined, then + // 1. Throw a TypeError exception. + // b. Let styles be dataLocaleData.[[styles]].[[]]. + // c. Let bestFormat be DateTimeStyleFormat(dateStyle, timeStyle, styles). + // 37. Else, + // a. Let formats be dataLocaleData.[[formats]].[[]]. + // b. If matcher is "basic", then + // i. Let bestFormat be BasicFormatMatcher(opt, formats). + // c. Else, + // i. Let bestFormat be BestFitFormatMatcher(opt, formats). + // 38. For each row in Table 4, except the header row, in table order, do + // for (auto const &row : table4) { + // a. Let prop be the name given in the Property column of the row. + // auto prop = row.first; + // b. If bestFormat has a field [[]], then + // i. Let p be bestFormat.[[]]. + // ii. Set dateTimeFormat's internal slot whose name is the Internal + // Slot column of the row to p. + + // 39. If dateTimeFormat.[[Hour]] is undefined, then + if (impl_->hour.empty()) { + // a. Set dateTimeFormat.[[HourCycle]] to undefined. + impl_->hourCycle = u""; + // b. Let pattern be bestFormat.[[pattern]]. + // c. Let rangePatterns be bestFormat.[[rangePatterns]]. + // 40. Else, + } else { + // a. Let hcDefault be dataLocaleData.[[hourCycle]]. + std::u16string hcDefault = impl_->getDefaultHourCycle(runtime).getValue(); + // b. Let hc be dateTimeFormat.[[HourCycle]]. + auto hc = impl_->hourCycle; + // c. If hc is null, then + if (hc.empty()) + // i. Set hc to hcDefault. + hc = hcDefault; + // d. If hour12 is not undefined, then + if (!(hour12 == eNull)) { + // i. If hour12 is true, then + if ((hour12 == eTrue)) { + // 1. If hcDefault is "h11" or "h23", then + if (hcDefault == u"h11" || hcDefault == u"h23") { + // a. Set hc to "h11". + hc = u"h11"; + // 2. Else, + } else { + // a. Set hc to "h12". + hc = u"h12"; + } + // ii. Else, + } else { + // 1. Assert: hour12 is false. + // 2. If hcDefault is "h11" or "h23", then + if (hcDefault == u"h11" || hcDefault == u"h23") { + // a. Set hc to "h23". + hc = u"h23"; + // 3. Else, + } else { + // a. Set hc to "h24". + hc = u"h24"; + } + } + } + // e. Set dateTimeFormat.[[HourCycle]] to hc. + impl_->hourCycle = hc; + // f. If dateTimeformat.[[HourCycle]] is "h11" or "h12", then + // i. Let pattern be bestFormat.[[pattern12]]. + // ii. Let rangePatterns be bestFormat.[[rangePatterns12]]. + // g. Else, + // i. Let pattern be bestFormat.[[pattern]]. + // ii. Let rangePatterns be bestFormat.[[rangePatterns]]. + } + // 41. Set dateTimeFormat.[[Pattern]] to pattern. + // 42. Set dateTimeFormat.[[RangePatterns]] to rangePatterns. + // 43. Return dateTimeFormat + + impl_->dtf = impl_->getUDateFormatter(runtime); + return vm::ExecutionStatus::RETURNED; +} + +Options DateTimeFormat::resolvedOptions() noexcept { + Options options; + options.emplace(u"locale", Option(impl_->locale)); + options.emplace(u"numeric", Option(false)); + options.emplace(u"timeZone", Option(impl_->timeZone)); + options.emplace(u"weekday", impl_->weekday); + options.emplace(u"era", impl_->era); + options.emplace(u"year", impl_->year); + options.emplace(u"month", impl_->month); + options.emplace(u"day", impl_->day); + options.emplace(u"hour", impl_->hour); + options.emplace(u"minute", impl_->minute); + options.emplace(u"second", impl_->second); + options.emplace(u"timeZoneName", impl_->timeZoneName); + options.emplace(u"timeZone", impl_->timeZone); + options.emplace(u"dateStyle", impl_->dateStyle); + options.emplace(u"timeStyle", impl_->timeStyle); + return options; +} + +std::u16string DateTimeFormat::format(double jsTimeValue) noexcept { + auto timeInSeconds = jsTimeValue; + UDate *date = new UDate(timeInSeconds); + UErrorCode status = U_ZERO_ERROR; + UChar *myString; + int32_t myStrlen = 0; + + myStrlen = udat_format(impl_->dtf, *date, NULL, myStrlen, NULL, &status); + if (status == U_BUFFER_OVERFLOW_ERROR) { + status = U_ZERO_ERROR; + myString = (UChar *)malloc(sizeof(UChar) * (myStrlen + 1)); + udat_format(impl_->dtf, *date, myString, myStrlen + 1, NULL, &status); + return myString; + } + + return u""; +} + +vm::CallResult DateTimeFormat::Impl::getDefaultHourCycle( + vm::Runtime &runtime) { + UErrorCode status = U_ZERO_ERROR; + UChar *myString; + // open the default UDateFormat and Pattern of locale + UDateFormat *defaultDTF = + udat_open(UDAT_DEFAULT, UDAT_DEFAULT, locale8, 0, -1, NULL, -1, &status); + int32_t size = udat_toPattern(defaultDTF, true, NULL, 0, &status); + if (status == U_BUFFER_OVERFLOW_ERROR) { + status = U_ZERO_ERROR; + myString = (UChar *)malloc(sizeof(UChar) * (size + 1)); + udat_toPattern(defaultDTF, true, myString, 40, &status); + // find the default hour cycle and return it + for (int32_t i = 0; i < size; i++) { + char16_t ch = myString[i]; + switch (ch) { + case 'K': + return u"h11"; + break; + case 'h': + return u"h12"; + break; + case 'H': + return u"h23"; + break; + case 'k': + return u"h24"; + break; + } + } + } + + // There should always be a default hour cycle, return an exception if not + return vm::ExecutionStatus::EXCEPTION; +} + +// gets the UDateFormat with options set in initalize +UDateFormat *DateTimeFormat::Impl::getUDateFormatter(vm::Runtime &runtime) { + static std::u16string eLong = u"long", eShort = u"short", eNarrow = u"narrow", + eMedium = u"medium", eFull = u"full", + eNumeric = u"numeric", eTwoDigit = u"2-digit", + eShortOffset = u"shortOffset", + eLongOffset = u"longOffset", + eShortGeneric = u"shortGeneric", + eLongGeneric = u"longGeneric"; + + // timeStyle and dateStyle cannot be used in conjunction with the other + // options. + if (!timeStyle.empty() || !dateStyle.empty()) { + UDateFormatStyle dateStyleRes = UDAT_DEFAULT; + UDateFormatStyle timeStyleRes = UDAT_DEFAULT; + + if (!dateStyle.empty()) { + if (dateStyle == eFull) + dateStyleRes = UDAT_FULL; + else if (dateStyle == eLong) + dateStyleRes = UDAT_LONG; + else if (dateStyle == eMedium) + dateStyleRes = UDAT_MEDIUM; + else if (dateStyle == eShort) + dateStyleRes = UDAT_SHORT; + } + + if (!timeStyle.empty()) { + if (timeStyle == eFull) + timeStyleRes = UDAT_FULL; + else if (timeStyle == eLong) + timeStyleRes = UDAT_LONG; + else if (timeStyle == eMedium) + timeStyleRes = UDAT_MEDIUM; + else if (timeStyle == eShort) + timeStyleRes = UDAT_SHORT; + } + + UErrorCode status = U_ZERO_ERROR; + // if timezone is specified, use that instead, else use default + if (!timeZone.empty()) { + const UChar *timeZoneRes = + reinterpret_cast(timeZone.c_str()); + int32_t timeZoneLength = timeZone.length(); + return udat_open( + timeStyleRes, + dateStyleRes, + locale8, + timeZoneRes, + timeZoneLength, + NULL, + -1, + &status); + } + return udat_open( + timeStyleRes, dateStyleRes, locale8, 0, -1, NULL, -1, &status); + } + + // Else: lets create the skeleton + std::u16string customDate = u""; + if (!weekday.empty()) { + if (weekday == eNarrow) + customDate += u"EEEEE"; + else if (weekday == eLong) + customDate += u"EEEE"; + else if (weekday == eShort) + customDate += u"EEE"; + } + + if (!timeZoneName.empty()) { + if (timeZoneName == eShort) + customDate += u"z"; + else if (timeZoneName == eLong) + customDate += u"zzzz"; + else if (timeZoneName == eShortOffset) + customDate += u"O"; + else if (timeZoneName == eLongOffset) + customDate += u"OOOO"; + else if (timeZoneName == eShortGeneric) + customDate += u"v"; + else if (timeZoneName == eLongGeneric) + customDate += u"vvvv"; + } + + if (!era.empty()) { + if (era == eNarrow) + customDate += u"GGGGG"; + else if (era == eShort) + customDate += u"G"; + else if (era == eLong) + customDate += u"GGGG"; + } + + if (!year.empty()) { + if (year == eNumeric) + customDate += u"y"; + else if (year == eTwoDigit) + customDate += u"yy"; + } + + if (!month.empty()) { + if (month == eTwoDigit) + customDate += u"MM"; + else if (month == eNumeric) + customDate += u'M'; + else if (month == eNarrow) + customDate += u"MMMMM"; + else if (month == eShort) + customDate += u"MMM"; + else if (month == eLong) + customDate += u"MMMM"; + } + + if (!day.empty()) { + if (day == eNumeric) + customDate += u"d"; + else if (day == eTwoDigit) + customDate += u"dd"; + } + + if (!hour.empty()) { + if (hourCycle == u"h12") { + if (hour == eNumeric) + customDate += u"h"; + else if (hour == eTwoDigit) + customDate += u"hh"; + } else if (hourCycle == u"h24") { + if (hour == eNumeric) + customDate += u"k"; + else if (hour == eTwoDigit) + customDate += u"kk"; + } else if (hourCycle == u"h23") { + if (hour == eNumeric) + customDate += u"k"; + else if (hour == eTwoDigit) + customDate += u"KK"; + } else { + if (hour == eNumeric) + customDate += u"h"; + else if (hour == eTwoDigit) + customDate += u"HH"; + } + } + + if (!minute.empty()) { + if (minute == eNumeric) + customDate += u"m"; + else if (minute == eTwoDigit) + customDate += u"mm"; + } + + if (!second.empty()) { + if (second == eNumeric) + customDate += u"s"; + else if (second == eTwoDigit) + customDate += u"ss"; + } + + UErrorCode status = U_ZERO_ERROR; + const UChar *skeleton = reinterpret_cast(customDate.c_str()); + UChar *bestpattern; + int32_t patternLength; + + UDateTimePatternGenerator *dtpGenerator = udatpg_open(locale8, &status); + patternLength = udatpg_getBestPatternWithOptions( + dtpGenerator, + skeleton, + -1, + UDATPG_MATCH_ALL_FIELDS_LENGTH, + NULL, + 0, + &status); + + if (status == U_BUFFER_OVERFLOW_ERROR) { + status = U_ZERO_ERROR; + bestpattern = (UChar *)malloc(sizeof(UChar) * (patternLength + 1)); + udatpg_getBestPatternWithOptions( + dtpGenerator, + skeleton, + customDate.length(), + UDATPG_MATCH_ALL_FIELDS_LENGTH, + bestpattern, + patternLength, + &status); + } + + // if timezone is specified, use that instead, else use default + if (!timeZone.empty()) { + const UChar *timeZoneRes = + reinterpret_cast(timeZone.c_str()); + int32_t timeZoneLength = timeZone.length(); + return udat_open( + UDAT_PATTERN, + UDAT_PATTERN, + locale8, + timeZoneRes, + timeZoneLength, + bestpattern, + patternLength, + &status); + } else { + return udat_open( + UDAT_PATTERN, + UDAT_PATTERN, + locale8, + 0, + -1, + bestpattern, + patternLength, + &status); + } +} + +std::vector> +DateTimeFormat::formatToParts(double jsTimeValue) noexcept { + std::unordered_map part; + part[u"type"] = u"integer"; + // This isn't right, but I didn't want to do more work for a stub. + std::string s = std::to_string(jsTimeValue); + part[u"value"] = {s.begin(), s.end()}; + return std::vector>{part}; +} + +struct NumberFormat::Impl { + std::u16string locale; +}; + +NumberFormat::NumberFormat() : impl_(std::make_unique()) {} +NumberFormat::~NumberFormat() {} + +vm::CallResult> NumberFormat::supportedLocalesOf( + vm::Runtime &runtime, + const std::vector &locales, + const Options &options) noexcept { + return std::vector{u"en-CA", u"de-DE"}; +} + +vm::ExecutionStatus NumberFormat::initialize( + vm::Runtime &runtime, + const std::vector &locales, + const Options &options) noexcept { + impl_->locale = u"en-US"; + return vm::ExecutionStatus::RETURNED; +} + +Options NumberFormat::resolvedOptions() noexcept { + Options options; + options.emplace(u"locale", Option(impl_->locale)); + options.emplace(u"numeric", Option(false)); + return options; +} + +std::u16string NumberFormat::format(double number) noexcept { + auto s = std::to_string(number); + return std::u16string(s.begin(), s.end()); +} + +std::vector> +NumberFormat::formatToParts(double number) noexcept { + std::unordered_map part; + part[u"type"] = u"integer"; + // This isn't right, but I didn't want to do more work for a stub. + std::string s = std::to_string(number); + part[u"value"] = {s.begin(), s.end()}; + return std::vector>{part}; +} + +} // namespace platform_intl +} // namespace hermes diff --git a/unittests/PlatformIntl/DateTimeFormat.cpp b/unittests/PlatformIntl/DateTimeFormat.cpp new file mode 100644 index 00000000000..033abe76084 --- /dev/null +++ b/unittests/PlatformIntl/DateTimeFormat.cpp @@ -0,0 +1,340 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +#include +#include "hermes/Platform/Intl/PlatformIntl.h" +#include "hermes/VM/Runtime.h" + +#include "gtest/gtest.h" + +namespace { +using namespace hermes; +using namespace hermes::platform_intl; + +// simplest of testcases, tests one locale without any options +TEST(DateTimeFormat, DatesWithoutOptions) { + std::vector AmericanEnglish = + std::vector{u"en-us"}; + std::vector KoreanKorea = + std::vector{u"ko-KR"}; + std::vector french = std::vector{u"fr"}; + std::shared_ptr runtime = hermes::vm::Runtime::create( + hermes::vm::RuntimeConfig::Builder().withIntl(true).build()); + Options testOptions = {}; + + auto dtf = platform_intl::DateTimeFormat(); + dtf.initialize(*runtime.get(), AmericanEnglish, testOptions); + auto result = dtf.format(1620000000000.00); + EXPECT_EQ(result, u"5/2/2021"); + + auto dtf2 = platform_intl::DateTimeFormat(); + dtf2.initialize(*runtime.get(), KoreanKorea, testOptions); + auto result2 = dtf2.format(1620000000000.00); + EXPECT_EQ(result2, u"2021. 5. 2."); + + auto dtf3 = platform_intl::DateTimeFormat(); + dtf3.initialize(*runtime.get(), french, testOptions); + std::u16string result3 = dtf3.format(1620000000000.00); + EXPECT_EQ(result3, u"02/05/2021"); +} + +// tests dateStyle and timeStyle options (full, long, medium, short) +TEST(DateTimeFormat, DatesWithTimeDateStyles) { + std::vector AmericanEnglish = + std::vector{u"en-us"}; + std::vector SpanishPeru = + std::vector{u"es-PE"}; + std::vector french = std::vector{u"fr"}; + std::shared_ptr runtime = hermes::vm::Runtime::create( + hermes::vm::RuntimeConfig::Builder().withIntl(true).build()); + + // dateStyle = full and timeStye = full + Options testOptions = { + {u"dateStyle", Option(std::u16string(u"full"))}, + {u"timeStyle", Option(std::u16string(u"full"))}}; + auto dtf = platform_intl::DateTimeFormat(); + dtf.initialize(*runtime.get(), AmericanEnglish, testOptions); + auto result = dtf.format(1620000000000.00); + EXPECT_EQ(result, u"Sunday, May 2, 2021 at 5:00:00 PM Pacific Daylight Time"); + + auto dtf2 = platform_intl::DateTimeFormat(); + dtf2.initialize(*runtime.get(), french, testOptions); + auto result2 = dtf2.format(1620000000000.00); + EXPECT_EQ( + result2, + u"dimanche 2 mai 2021 \u00E0 17:00:00 heure d\u2019\u00E9t\u00E9 du Pacifique"); // L"dimanche 2 mai 2021 à 17:00:00 heure d’été du Pacifique" + + auto dtf3 = platform_intl::DateTimeFormat(); + dtf3.initialize(*runtime.get(), SpanishPeru, testOptions); + auto result3 = dtf3.format(1620000000000.00); + EXPECT_EQ( + result3, + u"domingo, 2 de mayo de 2021, 17:00:00 hora de verano del Pac\u00EDfico"); // L"domingo, 2 de mayo de 2021, 17:00:00 hora de verano del Pacífico" + + // dateStyle = short and timeStyle = short + Options testOptions2 = { + {u"dateStyle", Option(std::u16string(u"short"))}, + {u"timeStyle", Option(std::u16string(u"short"))}}; + auto dtf4 = platform_intl::DateTimeFormat(); + dtf4.initialize(*runtime.get(), AmericanEnglish, testOptions2); + auto result4 = dtf4.format(1620000000000.00); + EXPECT_EQ(result4, u"5/2/21, 5:00 PM"); + + auto dtf5 = platform_intl::DateTimeFormat(); + dtf5.initialize(*runtime.get(), french, testOptions2); + auto result5 = dtf5.format(1620000000000.00); + EXPECT_EQ(result5, u"02/05/2021 17:00"); + + auto dtf6 = platform_intl::DateTimeFormat(); + dtf6.initialize(*runtime.get(), SpanishPeru, testOptions2); + auto result6 = dtf6.format(1620000000000.00); + EXPECT_EQ(result6, u"2/05/21 17:00"); + + // dateStyle = long and timeStyle = medium + Options testOptions3 = { + {u"dateStyle", Option(std::u16string(u"long"))}, + {u"timeStyle", Option(std::u16string(u"medium"))}}; + auto dtf7 = platform_intl::DateTimeFormat(); + dtf7.initialize(*runtime.get(), AmericanEnglish, testOptions3); + auto result7 = dtf7.format(1620000000000.00); + EXPECT_EQ(result7, u"May 2, 2021 at 5:00:00 PM"); + + auto dtf8 = platform_intl::DateTimeFormat(); + dtf8.initialize(*runtime.get(), french, testOptions3); + auto result8 = dtf8.format(1620000000000.00); + EXPECT_EQ(result8, u"2 mai 2021 \u00E0 17:00:00"); // L"2 mai 2021 à 17:00:00" + + auto dtf9 = platform_intl::DateTimeFormat(); + dtf9.initialize(*runtime.get(), SpanishPeru, testOptions3); + auto result9 = dtf9.format(1620000000000.00); + EXPECT_EQ(result9, u"2 de mayo de 2021, 17:00:00"); +} + +// Tests Date with Month (2-digit, numeric, narrow, short, long), Day (2-digit, +// numeric), and Year (2-digit, numeric) options +TEST(DateTimeFormat, DatesWithMonthDayYearOptions) { + std::vector DutchBelgium = + std::vector{u"nl-BE"}; + std::shared_ptr runtime = hermes::vm::Runtime::create( + hermes::vm::RuntimeConfig::Builder().withIntl(true).build()); + + Options testOptions = { + {u"day", Option(std::u16string(u"numeric"))}, + {u"month", Option(std::u16string(u"long"))}, + {u"year", Option(std::u16string(u"numeric"))}}; + auto dtf = platform_intl::DateTimeFormat(); + dtf.initialize(*runtime.get(), DutchBelgium, testOptions); + auto result = dtf.format(1620000000000.00); + EXPECT_EQ(result, u"2 mei 2021"); + + Options testOptions2 = { + {u"day", Option(std::u16string(u"2-digit"))}, + {u"month", Option(std::u16string(u"narrow"))}, + {u"year", Option(std::u16string(u"2-digit"))}}; + auto dtf2 = platform_intl::DateTimeFormat(); + dtf2.initialize(*runtime.get(), DutchBelgium, testOptions2); + auto result2 = dtf2.format(1620000000000.00); + EXPECT_EQ(result2, u"02 M 21"); + + Options testOptions3 = { + {u"month", Option(std::u16string(u"numeric"))}, + {u"year", Option(std::u16string(u"2-digit"))}}; + auto dtf3 = platform_intl::DateTimeFormat(); + dtf3.initialize(*runtime.get(), DutchBelgium, testOptions3); + auto result3 = dtf3.format(1620000000000.00); + EXPECT_EQ(result3, u"5/21"); +} + +// Tests Date with Weekday ( narrow, short, long), era ( narrow, +// short, long), TimeZoneName (short, long, shortOffset, longOffset, +// shortGeneric, longGeneric) +TEST(DateTimeFormat, DatesWithWeekdayEraTimeZoneNameOptions) { + std::vector ItalianItaly = + std::vector{u"it-IT"}; // it-IT + std::shared_ptr runtime = hermes::vm::Runtime::create( + hermes::vm::RuntimeConfig::Builder().withIntl(true).build()); + + Options testOptions = { + {u"weekday", Option(std::u16string(u"long"))}, + {u"era", Option(std::u16string(u"long"))}, + {u"timeZoneName", Option(std::u16string(u"long"))}}; + auto dtf = platform_intl::DateTimeFormat(); + dtf.initialize(*runtime.get(), ItalianItaly, testOptions); + auto result = dtf.format(1620000000000.00); + EXPECT_EQ(result, u"dopo Cristo domenica, Ora legale del Pacifico USA"); + + Options testOptions2 = { + {u"weekday", Option(std::u16string(u"short"))}, + {u"era", Option(std::u16string(u"narrow"))}, + {u"timeZoneName", Option(std::u16string(u"shortOffset"))}}; + auto dtf2 = platform_intl::DateTimeFormat(); + dtf2.initialize(*runtime.get(), ItalianItaly, testOptions2); + auto result2 = dtf2.format(1620000000000.00); + EXPECT_EQ(result2, u"dC dom, GMT-7"); + + Options testOptions3 = { + {u"weekday", Option(std::u16string(u"narrow"))}, + {u"era", Option(std::u16string(u"short"))}, + {u"timeZoneName", Option(std::u16string(u"longGeneric"))}}; + auto dtf3 = platform_intl::DateTimeFormat(); + dtf3.initialize(*runtime.get(), ItalianItaly, testOptions3); + auto result3 = dtf3.format(1620000000000.00); + EXPECT_EQ(result3, u"d.C. D, Ora del Pacifico USA"); +} + +// Tests Date with Hour (2-digit, numeric), Minute (2-digit, numeric), Second +// (2-digit, numeric) +TEST(DateTimeFormat, DatesWithHourMinuteSecondOptions) { + std::vector AmericanEnglish = + std::vector{u"en-US"}; + std::shared_ptr runtime = hermes::vm::Runtime::create( + hermes::vm::RuntimeConfig::Builder().withIntl(true).build()); + + Options testOptions = { + {u"hour", Option(std::u16string(u"2-digit"))}, + {u"minute", Option(std::u16string(u"2-digit"))}, + {u"second", Option(std::u16string(u"2-digit"))}}; + auto dtf = platform_intl::DateTimeFormat(); + dtf.initialize(*runtime.get(), AmericanEnglish, testOptions); + auto result = dtf.format(1620000303000); + EXPECT_EQ(result, u"05:05:03 PM"); + + Options testOptions2 = { + {u"hour", Option(std::u16string(u"numeric"))}, + {u"minute", Option(std::u16string(u"numeric"))}, + {u"second", Option(std::u16string(u"numeric"))}}; + auto dtf2 = platform_intl::DateTimeFormat(); + dtf2.initialize(*runtime.get(), AmericanEnglish, testOptions2); + auto result2 = dtf2.format(1620000303000); + EXPECT_EQ(result2, u"5:05:03 PM"); + + Options testOptions3 = {{u"minute", Option(std::u16string(u"2-digit"))}}; + auto dtf3 = platform_intl::DateTimeFormat(); + dtf3.initialize(*runtime.get(), AmericanEnglish, testOptions3); + auto result3 = dtf3.format(1620000303000); + EXPECT_EQ(result3, u"05"); + + Options testOptions4 = {{u"hour", Option(std::u16string(u"2-digit"))}}; + auto dtf4 = platform_intl::DateTimeFormat(); + dtf4.initialize(*runtime.get(), AmericanEnglish, testOptions4); + auto result4 = dtf4.format(1620000303000); + EXPECT_EQ(result4, u"05 PM"); + + Options testOptions5 = { + {u"hour", Option(std::u16string(u"2-digit"))}, + {u"second", Option(std::u16string(u"numeric"))}}; + auto dtf5 = platform_intl::DateTimeFormat(); + dtf5.initialize(*runtime.get(), AmericanEnglish, testOptions5); + auto result5 = dtf5.format(1620000303000); + EXPECT_EQ(result5, u"05 PM (second: 3)"); +} + +// Tests Date with HourCycle (h11, h12, h23, h24) +TEST(DateTimeFormat, DatesWithHourCyclesOptions) { + std::vector AmericanEnglish = + std::vector{u"en-US"}; + std::shared_ptr runtime = hermes::vm::Runtime::create( + hermes::vm::RuntimeConfig::Builder().withIntl(true).build()); + + Options testOptions = { + {u"hour", Option(std::u16string(u"numeric"))}, + {u"minute", Option(std::u16string(u"numeric"))}, + {u"hourCycle", Option(std::u16string(u"h12"))}}; + auto dtf = platform_intl::DateTimeFormat(); + dtf.initialize(*runtime.get(), AmericanEnglish, testOptions); + auto result = dtf.format(1620008000000); + EXPECT_EQ(result, u"7:13 PM"); + + Options testOptions2 = { + {u"hour", Option(std::u16string(u"numeric"))}, + {u"minute", Option(std::u16string(u"numeric"))}, + {u"hourCycle", Option(std::u16string(u"h24"))}}; + auto dtf2 = platform_intl::DateTimeFormat(); + dtf2.initialize(*runtime.get(), AmericanEnglish, testOptions2); + auto result2 = dtf2.format(1620008000000); + EXPECT_EQ(result2, u"19:13"); + + Options testOptions3 = { + {u"hour", Option(std::u16string(u"numeric"))}, + {u"minute", Option(std::u16string(u"numeric"))}, + {u"hourCycle", Option(std::u16string(u"h11"))}}; + auto dtf3 = platform_intl::DateTimeFormat(); + dtf3.initialize(*runtime.get(), AmericanEnglish, testOptions3); + auto result3 = dtf3.format(1620008000000); + EXPECT_EQ(result3, u"7:13 PM"); +} + +// Tests Date with specified TimeZone +TEST(DateTimeFormat, DatesWithTimeZone) { + std::vector AmericanEnglish = + std::vector{u"en-US"}; + std::shared_ptr runtime = hermes::vm::Runtime::create( + hermes::vm::RuntimeConfig::Builder().withIntl(true).build()); + + Options testOptions = { + {u"dateStyle", Option(std::u16string(u"long"))}, + {u"timeStyle", Option(std::u16string(u"long"))}, + {u"timeZone", Option(std::u16string(u"UTC"))}}; + auto dtf = platform_intl::DateTimeFormat(); + dtf.initialize(*runtime.get(), AmericanEnglish, testOptions); + auto result = dtf.format(1620000000000.00); + EXPECT_EQ(result, u"May 3, 2021 at 12:00:00 AM UTC"); + + Options testOptions2 = { + {u"dateStyle", Option(std::u16string(u"full"))}, + {u"timeStyle", Option(std::u16string(u"full"))}, + {u"timeZone", Option(std::u16string(u"Australia/Sydney"))}}; + auto dtf2 = platform_intl::DateTimeFormat(); + dtf2.initialize(*runtime.get(), AmericanEnglish, testOptions2); + auto result2 = dtf2.format(1620000000000.00); + EXPECT_EQ( + result2, + u"Monday, May 3, 2021 at 10:00:00 AM Australian Eastern Standard Time"); +} + +// Tests Date with all options +TEST(DateTimeFormat, DatesWithAllOptions) { + std::vector AmericanEnglish = + std::vector{u"en-US"}; + std::shared_ptr runtime = hermes::vm::Runtime::create( + hermes::vm::RuntimeConfig::Builder().withIntl(true).build()); + + Options testOptions = { + {u"day", Option(std::u16string(u"numeric"))}, + {u"month", Option(std::u16string(u"long"))}, + {u"year", Option(std::u16string(u"numeric"))}, + {u"weekday", Option(std::u16string(u"long"))}, + {u"era", Option(std::u16string(u"long"))}, + {u"timeZoneName", Option(std::u16string(u"long"))}, + {u"hour", Option(std::u16string(u"2-digit"))}, + {u"minute", Option(std::u16string(u"2-digit"))}, + {u"second", Option(std::u16string(u"2-digit"))}, + {u"hourCycle", Option(std::u16string(u"h12"))}, + {u"timeZone", Option(std::u16string(u"UTC"))}}; + auto dtf = platform_intl::DateTimeFormat(); + dtf.initialize(*runtime.get(), AmericanEnglish, testOptions); + auto result = dtf.format(1620008000000.00); + EXPECT_EQ( + result, + u"Monday, May 3, 2021 Anno Domini, 02:13:20 AM Coordinated Universal Time"); + + Options testOptions2 = { + {u"day", Option(std::u16string(u"2-digit"))}, + {u"month", Option(std::u16string(u"short"))}, + {u"year", Option(std::u16string(u"2-digit"))}, + {u"weekday", Option(std::u16string(u"short"))}, + {u"era", Option(std::u16string(u"narrow"))}, + {u"timeZoneName", Option(std::u16string(u"longGeneric"))}, + {u"hour", Option(std::u16string(u"2-digit"))}, + {u"minute", Option(std::u16string(u"2-digit"))}, + {u"second", Option(std::u16string(u"2-digit"))}, + {u"hourCycle", Option(std::u16string(u"h24"))}, + {u"timeZone", Option(std::u16string(u"Europe/Madrid"))}}; + auto dtf2 = platform_intl::DateTimeFormat(); + dtf2.initialize(*runtime.get(), AmericanEnglish, testOptions2); + auto result2 = dtf2.format(1620008000000.00); + EXPECT_EQ(result2, u"Mon, May 03, 21 A, 04:13:20 Central European Time"); +} +} // end anonymous namespace \ No newline at end of file From 0963919433bc4e44b204fefd9b22f0a12e309c4a Mon Sep 17 00:00:00 2001 From: TatianaKapos Date: Thu, 8 Sep 2022 16:17:19 -0700 Subject: [PATCH 02/14] adds supportedLocales --- lib/Platform/Intl/PlatformIntlWindows.cpp | 139 +++++++++++++++------- unittests/PlatformIntl/DateTimeFormat.cpp | 112 ++++++++++++----- 2 files changed, 177 insertions(+), 74 deletions(-) diff --git a/lib/Platform/Intl/PlatformIntlWindows.cpp b/lib/Platform/Intl/PlatformIntlWindows.cpp index 9ed51cec346..262357b4d3f 100644 --- a/lib/Platform/Intl/PlatformIntlWindows.cpp +++ b/lib/Platform/Intl/PlatformIntlWindows.cpp @@ -272,7 +272,6 @@ double Collator::compare( return x.compare(y); } - // Implementation of // https://402.ecma-international.org/8.0/#sec-todatetimeoptions Options toDateTimeOptions( @@ -356,36 +355,81 @@ Options toDateTimeOptions( return options; } -/** -namespace { -// Implementation of -// https://402.ecma-international.org/8.0/#datetimeformat-objects -struct DateTimeFormatWindows : DateTimeFormat { - DateTimeFormatWindows(const char16_t *l) : locale(l) {} - // Options used with DateTimeFormat - std::u16string locale_; - std::u16string timeZone_; - std::u16string weekday_; - std::u16string era_; - std::u16string year_; - std::u16string month_; - std::u16string day_; - std::u16string dayPeriod_; // Not Supported - std::u16string hour_; - std::u16string minute_; - std::u16string second_; - std::u16string timeZoneName_; - std::u16string dateStyle_; - std::u16string timeStyle_; - std::u16string hourCycle_; - // Internal use - UDateFormat *dtf_; - const char *locale8_; - UDateFormat *getUDateFormatter(vm::Runtime &runtime); - vm::CallResult getDefaultHourCycle(vm::Runtime &runtime); -}; -} // namespace -**/ +/// https://402.ecma-international.org/8.0/#sec-bestavailablelocale +std::optional bestAvailableLocale( + const std::vector &availableLocales, + const std::u16string &locale) { + // 1. Let candidate be locale + std::u16string candidate = locale; + + // 2. Repeat + while (true) { + // a. If availableLocales contains an element equal to candidate, return + // candidate. + if (llvh::find(availableLocales, candidate) != availableLocales.end()) + return candidate; + // b. Let pos be the character index of the last occurrence of "-" (U+002D) + // within candidate. + size_t pos = candidate.rfind(u'-'); + + // ...If that character does not occur, return undefined. + if (pos == std::u16string::npos) + return std::nullopt; + + // c. If pos ≥ 2 and the character "-" occurs at index pos-2 of candidate, + // decrease pos by 2. + if (pos >= 2 && candidate[pos - 2] == '-') + pos -= 2; + + // d. Let candidate be the substring of candidate from position 0, + // inclusive, to position pos, exclusive. + candidate.resize(pos); + } +} + +/// https://402.ecma-international.org/8.0/#sec-lookupsupportedlocales +std::vector lookupSupportedLocales( + const std::vector &availableLocales, + const std::vector &requestedLocales) { + // 1. Let subset be a new empty List. + std::vector subset; + // 2. For each element locale of requestedLocales in List order, do + for (const std::u16string &locale : requestedLocales) { + // a. Let noExtensionsLocale be the String value that is locale with all + // Unicode locale extension sequences removed. + // We can skip this step, see the comment in lookupMatcher. + // b. Let availableLocale be BestAvailableLocale(availableLocales, + // noExtensionsLocale). + std::optional availableLocale = + bestAvailableLocale(availableLocales, locale); + // c. If availableLocale is not undefined, append locale to the end of + // subset. + if (availableLocale) { + subset.push_back(locale); + } + } + // 3. Return subset. + return subset; +} + +/// https://402.ecma-international.org/8.0/#sec-supportedlocales +std::vector supportedLocales( + const std::vector &availableLocales, + const std::vector &requestedLocales) { + // 1. Set options to ? CoerceOptionsToObject(options). + // 2. Let matcher be ? GetOption(options, "localeMatcher", "string", « + // "lookup", "best fit" », "best fit"). + // 3. If matcher is "best fit", then + // a. Let supportedLocales be BestFitSupportedLocales(availableLocales, + // requestedLocales). + // 4. Else, + // a. Let supportedLocales be LookupSupportedLocales(availableLocales, + // requestedLocales). + // 5. Return CreateArrayFromList(supportedLocales). + + // We do not implement a BestFitMatcher, so we can just use LookupMatcher. + return lookupSupportedLocales(availableLocales, requestedLocales); +} namespace { // Implementation of @@ -394,7 +438,7 @@ class DateTimeFormatWindows : public DateTimeFormat { public: DateTimeFormatWindows() = default; ~DateTimeFormatWindows() = default; - + vm::ExecutionStatus initialize( vm::Runtime &runtime, const std::vector &locales, @@ -407,7 +451,7 @@ class DateTimeFormatWindows : public DateTimeFormat { std::vector formatToParts(double x) noexcept; private: - // Options used with DateTimeFormat + // Options used with DateTimeFormat std::u16string locale_; std::u16string timeZone_; std::u16string weekday_; @@ -415,7 +459,7 @@ class DateTimeFormatWindows : public DateTimeFormat { std::u16string year_; std::u16string month_; std::u16string day_; - std::u16string dayPeriod_; // Not Supported + std::u16string dayPeriod_; // Not yet supported std::u16string hour_; std::u16string minute_; std::u16string second_; @@ -434,17 +478,25 @@ class DateTimeFormatWindows : public DateTimeFormat { DateTimeFormat::DateTimeFormat() = default; DateTimeFormat::~DateTimeFormat() = default; -// get the supported locales of dateTimeFormat +// Implementation of +// https://402.ecma-international.org/8.0/#sec-intl.datetimeformat.supportedlocalesof +// without options vm::CallResult> DateTimeFormat::supportedLocalesOf( vm::Runtime &runtime, const std::vector &locales, const Options &options) noexcept { - std::vector result = {}; + // 1. Let availableLocales be %DateTimeFormat%.[[AvailableLocales]]. + std::vector availableLocales = {}; for (int32_t i = 0; i < uloc_countAvailable(); i++) { auto locale = uloc_getAvailable(i); - result.push_back(UTF8toUTF16(runtime, locale).getValue()); + availableLocales.push_back(UTF8toUTF16(runtime, locale).getValue()); } - return result; + + // 2. Let requestedLocales be ? CanonicalizeLocaleList(locales). + auto requestedLocales = getCanonicalLocales(runtime, locales).getValue(); + + // 3. Return ? SupportedLocales(availableLocales, requestedLocales, options). + return supportedLocales(availableLocales, requestedLocales); } // Implementation of @@ -465,7 +517,7 @@ vm::ExecutionStatus DateTimeFormatWindows::initialize( } const char *locale8 = conversion.getValue().c_str(); locale8_ = locale8; // store the UTF8 version of locale since it is used - // in almost all other functions + // in almost all other functions // 2. Let options be ? ToDateTimeOptions(options, "any", "date"). Options options = toDateTimeOptions(runtime, inputOptions, u"any", u"date"); @@ -754,7 +806,6 @@ vm::ExecutionStatus DateTimeFormatWindows::initialize( return vm::ExecutionStatus::RETURNED; } - vm::CallResult> DateTimeFormat::create( vm::Runtime &runtime, const std::vector &locales, @@ -771,7 +822,6 @@ vm::CallResult> DateTimeFormat::create( Options DateTimeFormatWindows::resolvedOptions() noexcept { Options options; options.emplace(u"locale", Option(locale_)); - options.emplace(u"numeric", Option(false)); options.emplace(u"timeZone", Option(timeZone_)); options.emplace(u"weekday", weekday_); options.emplace(u"era", era_); @@ -804,10 +854,9 @@ std::u16string DateTimeFormatWindows::format(double jsTimeValue) noexcept { status = U_ZERO_ERROR; myString = (UChar *)malloc(sizeof(UChar) * (myStrlen + 1)); udat_format(dtf_, *date, myString, myStrlen + 1, NULL, &status); - return myString; } - return u""; + return myString; } vm::CallResult DateTimeFormatWindows::getDefaultHourCycle( @@ -1063,6 +1112,8 @@ std::u16string DateTimeFormat::format(double jsTimeValue) noexcept { return static_cast(this)->format(jsTimeValue); } +// Not yet implemented. Tracked by +// https://github.com/microsoft/hermes-windows/issues/87 std::vector> DateTimeFormatWindows::formatToParts(double jsTimeValue) noexcept { std::unordered_map part; @@ -1073,6 +1124,8 @@ DateTimeFormatWindows::formatToParts(double jsTimeValue) noexcept { return std::vector>{part}; } +// Not yet implemented. Tracked by +// https://github.com/microsoft/hermes-windows/issues/87 std::vector DateTimeFormat::formatToParts(double x) noexcept { return static_cast(this)->formatToParts(x); } diff --git a/unittests/PlatformIntl/DateTimeFormat.cpp b/unittests/PlatformIntl/DateTimeFormat.cpp index 44ab980e696..577984051ea 100644 --- a/unittests/PlatformIntl/DateTimeFormat.cpp +++ b/unittests/PlatformIntl/DateTimeFormat.cpp @@ -24,19 +24,21 @@ TEST(DateTimeFormat, DatesWithoutOptions) { hermes::vm::RuntimeConfig::Builder().withIntl(true).build()); platform_intl::Options testOptions = {}; - auto dtf = platform_intl::DateTimeFormat::create(*runtime.get(), AmericanEnglish, testOptions); + auto dtf = platform_intl::DateTimeFormat::create( + *runtime.get(), AmericanEnglish, testOptions); // dtf.initialize(*runtime.get(), AmericanEnglish, testOptions); auto result = dtf.getValue().get()->format(1620000000000.00); EXPECT_EQ(result, u"5/2/2021"); - auto dtf2 = platform_intl::DateTimeFormat::create(*runtime.get(), KoreanKorea, testOptions); + auto dtf2 = platform_intl::DateTimeFormat::create( + *runtime.get(), KoreanKorea, testOptions); auto result2 = dtf2.getValue().get()->format(1620000000000.00); EXPECT_EQ(result2, u"2021. 5. 2."); - auto dtf3 = platform_intl::DateTimeFormat::create(*runtime.get(), french, testOptions); + auto dtf3 = platform_intl::DateTimeFormat::create( + *runtime.get(), french, testOptions); std::u16string result3 = dtf3.getValue().get()->format(1620000000000.00); EXPECT_EQ(result3, u"02/05/2021"); - } // tests dateStyle and timeStyle options (full, long, medium, short) @@ -53,17 +55,20 @@ TEST(DateTimeFormat, DatesWithTimeDateStyles) { Options testOptions = { {u"dateStyle", Option(std::u16string(u"full"))}, {u"timeStyle", Option(std::u16string(u"full"))}}; - auto dtf = platform_intl::DateTimeFormat::create(*runtime.get(), AmericanEnglish, testOptions); + auto dtf = platform_intl::DateTimeFormat::create( + *runtime.get(), AmericanEnglish, testOptions); auto result = dtf.getValue().get()->format(1620000000000.00); EXPECT_EQ(result, u"Sunday, May 2, 2021 at 5:00:00 PM Pacific Daylight Time"); - auto dtf2 = platform_intl::DateTimeFormat::create(*runtime.get(), french, testOptions); + auto dtf2 = platform_intl::DateTimeFormat::create( + *runtime.get(), french, testOptions); auto result2 = dtf2.getValue().get()->format(1620000000000.00); EXPECT_EQ( result2, u"dimanche 2 mai 2021 \u00E0 17:00:00 heure d\u2019\u00E9t\u00E9 du Pacifique"); // L"dimanche 2 mai 2021 à 17:00:00 heure d’été du Pacifique" - auto dtf3 = platform_intl::DateTimeFormat::create(*runtime.get(), SpanishPeru, testOptions); + auto dtf3 = platform_intl::DateTimeFormat::create( + *runtime.get(), SpanishPeru, testOptions); auto result3 = dtf3.getValue().get()->format(1620000000000.00); EXPECT_EQ( result3, @@ -73,15 +78,18 @@ TEST(DateTimeFormat, DatesWithTimeDateStyles) { Options testOptions2 = { {u"dateStyle", Option(std::u16string(u"short"))}, {u"timeStyle", Option(std::u16string(u"short"))}}; - auto dtf4 = platform_intl::DateTimeFormat::create(*runtime.get(), AmericanEnglish, testOptions2); + auto dtf4 = platform_intl::DateTimeFormat::create( + *runtime.get(), AmericanEnglish, testOptions2); auto result4 = dtf4.getValue().get()->format(1620000000000.00); EXPECT_EQ(result4, u"5/2/21, 5:00 PM"); - auto dtf5 = platform_intl::DateTimeFormat::create(*runtime.get(), french, testOptions2); + auto dtf5 = platform_intl::DateTimeFormat::create( + *runtime.get(), french, testOptions2); auto result5 = dtf5.getValue().get()->format(1620000000000.00); EXPECT_EQ(result5, u"02/05/2021 17:00"); - auto dtf6 = platform_intl::DateTimeFormat::create(*runtime.get(), SpanishPeru, testOptions2); + auto dtf6 = platform_intl::DateTimeFormat::create( + *runtime.get(), SpanishPeru, testOptions2); auto result6 = dtf6.getValue().get()->format(1620000000000.00); EXPECT_EQ(result6, u"2/05/21 17:00"); @@ -89,15 +97,18 @@ TEST(DateTimeFormat, DatesWithTimeDateStyles) { Options testOptions3 = { {u"dateStyle", Option(std::u16string(u"long"))}, {u"timeStyle", Option(std::u16string(u"medium"))}}; - auto dtf7 = platform_intl::DateTimeFormat::create(*runtime.get(), AmericanEnglish, testOptions3); + auto dtf7 = platform_intl::DateTimeFormat::create( + *runtime.get(), AmericanEnglish, testOptions3); auto result7 = dtf7.getValue().get()->format(1620000000000.00); EXPECT_EQ(result7, u"May 2, 2021 at 5:00:00 PM"); - auto dtf8 = platform_intl::DateTimeFormat::create(*runtime.get(), french, testOptions3); + auto dtf8 = platform_intl::DateTimeFormat::create( + *runtime.get(), french, testOptions3); auto result8 = dtf8.getValue().get()->format(1620000000000.00); EXPECT_EQ(result8, u"2 mai 2021 \u00E0 17:00:00"); // L"2 mai 2021 à 17:00:00" - auto dtf9 = platform_intl::DateTimeFormat::create(*runtime.get(), SpanishPeru, testOptions3); + auto dtf9 = platform_intl::DateTimeFormat::create( + *runtime.get(), SpanishPeru, testOptions3); auto result9 = dtf9.getValue().get()->format(1620000000000.00); EXPECT_EQ(result9, u"2 de mayo de 2021, 17:00:00"); } @@ -114,7 +125,8 @@ TEST(DateTimeFormat, DatesWithMonthDayYearOptions) { {u"day", Option(std::u16string(u"numeric"))}, {u"month", Option(std::u16string(u"long"))}, {u"year", Option(std::u16string(u"numeric"))}}; - auto dtf = platform_intl::DateTimeFormat::create(*runtime.get(), DutchBelgium, testOptions); + auto dtf = platform_intl::DateTimeFormat::create( + *runtime.get(), DutchBelgium, testOptions); auto result = dtf.getValue().get()->format(1620000000000.00); EXPECT_EQ(result, u"2 mei 2021"); @@ -122,14 +134,16 @@ TEST(DateTimeFormat, DatesWithMonthDayYearOptions) { {u"day", Option(std::u16string(u"2-digit"))}, {u"month", Option(std::u16string(u"narrow"))}, {u"year", Option(std::u16string(u"2-digit"))}}; - auto dtf2 = platform_intl::DateTimeFormat::create(*runtime.get(), DutchBelgium, testOptions2); + auto dtf2 = platform_intl::DateTimeFormat::create( + *runtime.get(), DutchBelgium, testOptions2); auto result2 = dtf2.getValue().get()->format(1620000000000.00); EXPECT_EQ(result2, u"02 M 21"); Options testOptions3 = { {u"month", Option(std::u16string(u"numeric"))}, {u"year", Option(std::u16string(u"2-digit"))}}; - auto dtf3 = platform_intl::DateTimeFormat::create(*runtime.get(), DutchBelgium, testOptions3); + auto dtf3 = platform_intl::DateTimeFormat::create( + *runtime.get(), DutchBelgium, testOptions3); auto result3 = dtf3.getValue().get()->format(1620000000000.00); EXPECT_EQ(result3, u"5/21"); } @@ -147,7 +161,8 @@ TEST(DateTimeFormat, DatesWithWeekdayEraTimeZoneNameOptions) { {u"weekday", Option(std::u16string(u"long"))}, {u"era", Option(std::u16string(u"long"))}, {u"timeZoneName", Option(std::u16string(u"long"))}}; - auto dtf = platform_intl::DateTimeFormat::create(*runtime.get(), ItalianItaly, testOptions); + auto dtf = platform_intl::DateTimeFormat::create( + *runtime.get(), ItalianItaly, testOptions); auto result = dtf.getValue().get()->format(1620000000000.00); EXPECT_EQ(result, u"dopo Cristo domenica, Ora legale del Pacifico USA"); @@ -155,7 +170,8 @@ TEST(DateTimeFormat, DatesWithWeekdayEraTimeZoneNameOptions) { {u"weekday", Option(std::u16string(u"short"))}, {u"era", Option(std::u16string(u"narrow"))}, {u"timeZoneName", Option(std::u16string(u"shortOffset"))}}; - auto dtf2 = platform_intl::DateTimeFormat::create(*runtime.get(), ItalianItaly, testOptions2); + auto dtf2 = platform_intl::DateTimeFormat::create( + *runtime.get(), ItalianItaly, testOptions2); auto result2 = dtf2.getValue().get()->format(1620000000000.00); EXPECT_EQ(result2, u"dC dom, GMT-7"); @@ -163,7 +179,8 @@ TEST(DateTimeFormat, DatesWithWeekdayEraTimeZoneNameOptions) { {u"weekday", Option(std::u16string(u"narrow"))}, {u"era", Option(std::u16string(u"short"))}, {u"timeZoneName", Option(std::u16string(u"longGeneric"))}}; - auto dtf3 = platform_intl::DateTimeFormat::create(*runtime.get(), ItalianItaly, testOptions3); + auto dtf3 = platform_intl::DateTimeFormat::create( + *runtime.get(), ItalianItaly, testOptions3); auto result3 = dtf3.getValue().get()->format(1620000000000.00); EXPECT_EQ(result3, u"d.C. D, Ora del Pacifico USA"); } @@ -180,7 +197,8 @@ TEST(DateTimeFormat, DatesWithHourMinuteSecondOptions) { {u"hour", Option(std::u16string(u"2-digit"))}, {u"minute", Option(std::u16string(u"2-digit"))}, {u"second", Option(std::u16string(u"2-digit"))}}; - auto dtf = platform_intl::DateTimeFormat::create(*runtime.get(), AmericanEnglish, testOptions); + auto dtf = platform_intl::DateTimeFormat::create( + *runtime.get(), AmericanEnglish, testOptions); auto result = dtf.getValue().get()->format(1620000303000); EXPECT_EQ(result, u"05:05:03 PM"); @@ -188,24 +206,28 @@ TEST(DateTimeFormat, DatesWithHourMinuteSecondOptions) { {u"hour", Option(std::u16string(u"numeric"))}, {u"minute", Option(std::u16string(u"numeric"))}, {u"second", Option(std::u16string(u"numeric"))}}; - auto dtf2 = platform_intl::DateTimeFormat::create(*runtime.get(), AmericanEnglish, testOptions2); + auto dtf2 = platform_intl::DateTimeFormat::create( + *runtime.get(), AmericanEnglish, testOptions2); auto result2 = dtf2.getValue().get()->format(1620000303000); EXPECT_EQ(result2, u"5:05:03 PM"); Options testOptions3 = {{u"minute", Option(std::u16string(u"2-digit"))}}; - auto dtf3 = platform_intl::DateTimeFormat::create(*runtime.get(), AmericanEnglish, testOptions3); + auto dtf3 = platform_intl::DateTimeFormat::create( + *runtime.get(), AmericanEnglish, testOptions3); auto result3 = dtf3.getValue().get()->format(1620000303000); EXPECT_EQ(result3, u"05"); Options testOptions4 = {{u"hour", Option(std::u16string(u"2-digit"))}}; - auto dtf4 = platform_intl::DateTimeFormat::create(*runtime.get(), AmericanEnglish, testOptions4); + auto dtf4 = platform_intl::DateTimeFormat::create( + *runtime.get(), AmericanEnglish, testOptions4); auto result4 = dtf4.getValue().get()->format(1620000303000); EXPECT_EQ(result4, u"05 PM"); Options testOptions5 = { {u"hour", Option(std::u16string(u"2-digit"))}, {u"second", Option(std::u16string(u"numeric"))}}; - auto dtf5 = platform_intl::DateTimeFormat::create(*runtime.get(), AmericanEnglish, testOptions5); + auto dtf5 = platform_intl::DateTimeFormat::create( + *runtime.get(), AmericanEnglish, testOptions5); auto result5 = dtf5.getValue().get()->format(1620000303000); EXPECT_EQ(result5, u"05 PM (second: 3)"); } @@ -221,7 +243,8 @@ TEST(DateTimeFormat, DatesWithHourCyclesOptions) { {u"hour", Option(std::u16string(u"numeric"))}, {u"minute", Option(std::u16string(u"numeric"))}, {u"hourCycle", Option(std::u16string(u"h12"))}}; - auto dtf = platform_intl::DateTimeFormat::create(*runtime.get(), AmericanEnglish, testOptions); + auto dtf = platform_intl::DateTimeFormat::create( + *runtime.get(), AmericanEnglish, testOptions); auto result = dtf.getValue().get()->format(1620008000000); EXPECT_EQ(result, u"7:13 PM"); @@ -229,7 +252,8 @@ TEST(DateTimeFormat, DatesWithHourCyclesOptions) { {u"hour", Option(std::u16string(u"numeric"))}, {u"minute", Option(std::u16string(u"numeric"))}, {u"hourCycle", Option(std::u16string(u"h24"))}}; - auto dtf2 = platform_intl::DateTimeFormat::create(*runtime.get(), AmericanEnglish, testOptions2); + auto dtf2 = platform_intl::DateTimeFormat::create( + *runtime.get(), AmericanEnglish, testOptions2); auto result2 = dtf2.getValue().get()->format(1620008000000); EXPECT_EQ(result2, u"19:13"); @@ -237,7 +261,8 @@ TEST(DateTimeFormat, DatesWithHourCyclesOptions) { {u"hour", Option(std::u16string(u"numeric"))}, {u"minute", Option(std::u16string(u"numeric"))}, {u"hourCycle", Option(std::u16string(u"h11"))}}; - auto dtf3 = platform_intl::DateTimeFormat::create(*runtime.get(), AmericanEnglish, testOptions3); + auto dtf3 = platform_intl::DateTimeFormat::create( + *runtime.get(), AmericanEnglish, testOptions3); auto result3 = dtf3.getValue().get()->format(1620008000000); EXPECT_EQ(result3, u"7:13 PM"); } @@ -253,7 +278,8 @@ TEST(DateTimeFormat, DatesWithTimeZone) { {u"dateStyle", Option(std::u16string(u"long"))}, {u"timeStyle", Option(std::u16string(u"long"))}, {u"timeZone", Option(std::u16string(u"UTC"))}}; - auto dtf = platform_intl::DateTimeFormat::create(*runtime.get(), AmericanEnglish, testOptions); + auto dtf = platform_intl::DateTimeFormat::create( + *runtime.get(), AmericanEnglish, testOptions); auto result = dtf.getValue().get()->format(1620000000000.00); EXPECT_EQ(result, u"May 3, 2021 at 12:00:00 AM UTC"); @@ -261,7 +287,8 @@ TEST(DateTimeFormat, DatesWithTimeZone) { {u"dateStyle", Option(std::u16string(u"full"))}, {u"timeStyle", Option(std::u16string(u"full"))}, {u"timeZone", Option(std::u16string(u"Australia/Sydney"))}}; - auto dtf2 = platform_intl::DateTimeFormat::create(*runtime.get(), AmericanEnglish, testOptions2); + auto dtf2 = platform_intl::DateTimeFormat::create( + *runtime.get(), AmericanEnglish, testOptions2); auto result2 = dtf2.getValue().get()->format(1620000000000.00); EXPECT_EQ( result2, @@ -287,7 +314,8 @@ TEST(DateTimeFormat, DatesWithAllOptions) { {u"second", Option(std::u16string(u"2-digit"))}, {u"hourCycle", Option(std::u16string(u"h12"))}, {u"timeZone", Option(std::u16string(u"UTC"))}}; - auto dtf = platform_intl::DateTimeFormat::create(*runtime.get(), AmericanEnglish, testOptions); + auto dtf = platform_intl::DateTimeFormat::create( + *runtime.get(), AmericanEnglish, testOptions); auto result = dtf.getValue().get()->format(1620008000000.00); EXPECT_EQ( result, @@ -305,8 +333,30 @@ TEST(DateTimeFormat, DatesWithAllOptions) { {u"second", Option(std::u16string(u"2-digit"))}, {u"hourCycle", Option(std::u16string(u"h24"))}, {u"timeZone", Option(std::u16string(u"Europe/Madrid"))}}; - auto dtf2 = platform_intl::DateTimeFormat::create(*runtime.get(), AmericanEnglish, testOptions2); + auto dtf2 = platform_intl::DateTimeFormat::create( + *runtime.get(), AmericanEnglish, testOptions2); auto result2 = dtf2.getValue().get()->format(1620008000000.00); EXPECT_EQ(result2, u"Mon, May 03, 21 A, 04:13:20 Central European Time"); } + +// Tests DateTimeFormat.supportedLocalesOf +TEST(DateTimeFormat, SupportedLocales) { + std::shared_ptr runtime = hermes::vm::Runtime::create( + hermes::vm::RuntimeConfig::Builder().withIntl(true).build()); + + Options testOptions = {}; + std::vector expected = + std::vector{u"en-US", u"fr"}; + auto result = platform_intl::DateTimeFormat::supportedLocalesOf( + *runtime.get(), {u"en-us", u"fr"}, testOptions); + auto value = result.getValue(); + EXPECT_EQ(value, expected); + + std::vector expected2 = + std::vector{u"en-US", u"fr", u"it-IT"}; + auto result2 = platform_intl::DateTimeFormat::supportedLocalesOf( + *runtime.get(), {u"en-us", u"fr", u"bans", u"it-it"}, testOptions); + auto value2 = result2.getValue(); + EXPECT_EQ(value2, expected2); +} } // end anonymous namespace \ No newline at end of file From 9141dbf27c98ed2216a1e819ffd511ae695d0932 Mon Sep 17 00:00:00 2001 From: TatianaKapos Date: Mon, 19 Sep 2022 17:05:44 -0700 Subject: [PATCH 03/14] simple PR feedback --- lib/Platform/Intl/PlatformIntlWindows.cpp | 66 ++++++++++++----------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/lib/Platform/Intl/PlatformIntlWindows.cpp b/lib/Platform/Intl/PlatformIntlWindows.cpp index 262357b4d3f..577309f181e 100644 --- a/lib/Platform/Intl/PlatformIntlWindows.cpp +++ b/lib/Platform/Intl/PlatformIntlWindows.cpp @@ -186,8 +186,8 @@ vm::CallResult getOptionString( vm::Runtime &runtime, const Options &options, const std::u16string &property, - std::vector values, - std::u16string fallback) { + const std::vector &values, + const std::u16string &fallback) { // 1. Assert type(options) is object // 2. Let value be ? Get(options, property). auto valueIt = options.find(property); @@ -212,24 +212,24 @@ vm::CallResult getOptionString( } // boolean + null option -enum BoolNull { eFalse, eTrue, eNull }; +enum class BoolNull { False, True, Null }; BoolNull getOptionBool( vm::Runtime &runtime, const Options &options, const std::u16string &property, - bool fallback) { + const bool fallback) { // 1. Assert: Type(options) is Object. // 2. Let value be ? Get(options, property). auto value = options.find(property); // 3. If value is undefined, return fallback. if (value == options.end()) { - return eNull; + return BoolNull::Null; } // 8. Return value. if (value->second.getBool()) { - return eTrue; + return BoolNull::True; } - return eFalse; + return BoolNull::False; } // Collator - Not yet implemented. Tracked by @@ -274,11 +274,11 @@ double Collator::compare( // Implementation of // https://402.ecma-international.org/8.0/#sec-todatetimeoptions -Options toDateTimeOptions( +vm::CallResult toDateTimeOptions( vm::Runtime &runtime, Options options, - std::u16string required, - std::u16string defaults) { + const std::u16string &required, + const std::u16string &defaults) { // 1. If options is undefined, let options be null; otherwise let options be ? // ToObject(options). // 2. Let options be OrdinaryObjectCreate(options). @@ -326,10 +326,12 @@ Options toDateTimeOptions( // 9. If required is "date" and timeStyle is not undefined, then if (required == u"date" && timeStyle != options.end()) { // a. Throw a TypeError exception. + return runtime.raiseTypeError("TimeSyle is defined"); } // 10. If required is "time" and dateStyle is not undefined, then if (required == u"time" && dateStyle != options.end()) { // a. Throw a TypeError exception. + return runtime.raiseTypeError("DateSyle is defined"); } // 11. If needDefaults is true and defaults is either "date" or "all", then if (needDefaults && (defaults == u"date" || defaults == u"all")) { @@ -487,7 +489,7 @@ vm::CallResult> DateTimeFormat::supportedLocalesOf( const Options &options) noexcept { // 1. Let availableLocales be %DateTimeFormat%.[[AvailableLocales]]. std::vector availableLocales = {}; - for (int32_t i = 0; i < uloc_countAvailable(); i++) { + for (int32_t i = 0, count = uloc_countAvailable(); i < count; i++) { auto locale = uloc_getAvailable(i); availableLocales.push_back(UTF8toUTF16(runtime, locale).getValue()); } @@ -520,7 +522,7 @@ vm::ExecutionStatus DateTimeFormatWindows::initialize( // in almost all other functions // 2. Let options be ? ToDateTimeOptions(options, "any", "date"). - Options options = toDateTimeOptions(runtime, inputOptions, u"any", u"date"); + Options options = toDateTimeOptions(runtime, inputOptions, u"any", u"date").getValue(); // 3. Let opt be a new Record. std::unordered_map opt; // 4. Let matcher be ? GetOption(options, "localeMatcher", "string", @@ -560,7 +562,7 @@ vm::ExecutionStatus DateTimeFormatWindows::initialize( std::u16string hourCycle = hourCycleRes.getValue(); // 14. If hour12 is not undefined, then a. Set hourCycle to null. - if (!(hour12 == eNull)) { + if (!(hour12 == BoolNull::Null)) { hourCycle = u""; } // 15. Set opt.[[hc]] to hourCycle. @@ -763,9 +765,9 @@ vm::ExecutionStatus DateTimeFormatWindows::initialize( // i. Set hc to hcDefault. hc = hcDefault; // d. If hour12 is not undefined, then - if (!(hour12 == eNull)) { + if (!(hour12 == BoolNull::Null)) { // i. If hour12 is true, then - if ((hour12 == eTrue)) { + if ((hour12 == BoolNull::True)) { // 1. If hcDefault is "h11" or "h23", then if (hcDefault == u"h11" || hcDefault == u"h23") { // a. Set hc to "h11". @@ -846,14 +848,14 @@ std::u16string DateTimeFormatWindows::format(double jsTimeValue) noexcept { auto timeInSeconds = jsTimeValue; UDate *date = new UDate(timeInSeconds); UErrorCode status = U_ZERO_ERROR; - UChar *myString; + std::u16string myString; int32_t myStrlen = 0; - myStrlen = udat_format(dtf_, *date, NULL, myStrlen, NULL, &status); + myStrlen = udat_format(dtf_, *date, 0, myStrlen, 0, &status); if (status == U_BUFFER_OVERFLOW_ERROR) { status = U_ZERO_ERROR; - myString = (UChar *)malloc(sizeof(UChar) * (myStrlen + 1)); - udat_format(dtf_, *date, myString, myStrlen + 1, NULL, &status); + myString.resize(myStrlen); + udat_format(dtf_, *date, &myString[0], myStrlen + 1, 0, &status); } return myString; @@ -862,15 +864,15 @@ std::u16string DateTimeFormatWindows::format(double jsTimeValue) noexcept { vm::CallResult DateTimeFormatWindows::getDefaultHourCycle( vm::Runtime &runtime) { UErrorCode status = U_ZERO_ERROR; - UChar *myString; + std::u16string myString; // open the default UDateFormat and Pattern of locale UDateFormat *defaultDTF = - udat_open(UDAT_DEFAULT, UDAT_DEFAULT, locale8_, 0, -1, NULL, -1, &status); - int32_t size = udat_toPattern(defaultDTF, true, NULL, 0, &status); + udat_open(UDAT_DEFAULT, UDAT_DEFAULT, locale8_, 0, -1, 0, -1, &status); + int32_t size = udat_toPattern(defaultDTF, true, 0, 0, &status); if (status == U_BUFFER_OVERFLOW_ERROR) { status = U_ZERO_ERROR; - myString = (UChar *)malloc(sizeof(UChar) * (size + 1)); - udat_toPattern(defaultDTF, true, myString, 40, &status); + myString.resize(size + 1); + udat_toPattern(defaultDTF, true, &myString[0], 40, &status); // find the default hour cycle and return it for (int32_t i = 0; i < size; i++) { char16_t ch = myString[i]; @@ -945,12 +947,12 @@ UDateFormat *DateTimeFormatWindows::getUDateFormatter(vm::Runtime &runtime) { locale8_, timeZoneRes, timeZoneLength, - NULL, + 0, -1, &status); } return udat_open( - timeStyleRes, dateStyleRes, locale8_, 0, -1, NULL, -1, &status); + timeStyleRes, dateStyleRes, locale8_, 0, -1, 0, -1, &status); } // Else: lets create the skeleton @@ -1055,7 +1057,7 @@ UDateFormat *DateTimeFormatWindows::getUDateFormatter(vm::Runtime &runtime) { UErrorCode status = U_ZERO_ERROR; const UChar *skeleton = reinterpret_cast(customDate.c_str()); - UChar *bestpattern; + std::u16string bestpattern; int32_t patternLength; UDateTimePatternGenerator *dtpGenerator = udatpg_open(locale8_, &status); @@ -1064,19 +1066,19 @@ UDateFormat *DateTimeFormatWindows::getUDateFormatter(vm::Runtime &runtime) { skeleton, -1, UDATPG_MATCH_ALL_FIELDS_LENGTH, - NULL, + 0, 0, &status); if (status == U_BUFFER_OVERFLOW_ERROR) { status = U_ZERO_ERROR; - bestpattern = (UChar *)malloc(sizeof(UChar) * (patternLength + 1)); + bestpattern.resize(patternLength); udatpg_getBestPatternWithOptions( dtpGenerator, skeleton, customDate.length(), UDATPG_MATCH_ALL_FIELDS_LENGTH, - bestpattern, + &bestpattern[0], patternLength, &status); } @@ -1092,7 +1094,7 @@ UDateFormat *DateTimeFormatWindows::getUDateFormatter(vm::Runtime &runtime) { locale8_, timeZoneRes, timeZoneLength, - bestpattern, + &bestpattern[0], patternLength, &status); } else { @@ -1102,7 +1104,7 @@ UDateFormat *DateTimeFormatWindows::getUDateFormatter(vm::Runtime &runtime) { locale8_, 0, -1, - bestpattern, + &bestpattern[0], patternLength, &status); } From 84e76edf4d25c9060b0d16f937daf760ea04fb42 Mon Sep 17 00:00:00 2001 From: TatianaKapos Date: Mon, 19 Sep 2022 17:06:08 -0700 Subject: [PATCH 04/14] format --- lib/Platform/Intl/PlatformIntlWindows.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Platform/Intl/PlatformIntlWindows.cpp b/lib/Platform/Intl/PlatformIntlWindows.cpp index 577309f181e..fd39fa26f50 100644 --- a/lib/Platform/Intl/PlatformIntlWindows.cpp +++ b/lib/Platform/Intl/PlatformIntlWindows.cpp @@ -522,7 +522,8 @@ vm::ExecutionStatus DateTimeFormatWindows::initialize( // in almost all other functions // 2. Let options be ? ToDateTimeOptions(options, "any", "date"). - Options options = toDateTimeOptions(runtime, inputOptions, u"any", u"date").getValue(); + Options options = + toDateTimeOptions(runtime, inputOptions, u"any", u"date").getValue(); // 3. Let opt be a new Record. std::unordered_map opt; // 4. Let matcher be ? GetOption(options, "localeMatcher", "string", From a94b954305f1faaed718b02070bc58b99ec75d6f Mon Sep 17 00:00:00 2001 From: TatianaKapos Date: Wed, 28 Sep 2022 16:36:57 -0700 Subject: [PATCH 05/14] more simple PR feedback --- lib/Platform/Intl/PlatformIntlWindows.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/Platform/Intl/PlatformIntlWindows.cpp b/lib/Platform/Intl/PlatformIntlWindows.cpp index fd39fa26f50..7ed80ae8329 100644 --- a/lib/Platform/Intl/PlatformIntlWindows.cpp +++ b/lib/Platform/Intl/PlatformIntlWindows.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "llvh/Support/ConvertUTF.h" using namespace ::hermes; @@ -417,7 +418,8 @@ std::vector lookupSupportedLocales( /// https://402.ecma-international.org/8.0/#sec-supportedlocales std::vector supportedLocales( const std::vector &availableLocales, - const std::vector &requestedLocales) { + const std::vector &requestedLocales, + const Options &options) { // 1. Set options to ? CoerceOptionsToObject(options). // 2. Let matcher be ? GetOption(options, "localeMatcher", "string", « // "lookup", "best fit" », "best fit"). @@ -498,7 +500,7 @@ vm::CallResult> DateTimeFormat::supportedLocalesOf( auto requestedLocales = getCanonicalLocales(runtime, locales).getValue(); // 3. Return ? SupportedLocales(availableLocales, requestedLocales, options). - return supportedLocales(availableLocales, requestedLocales); + return supportedLocales(availableLocales, requestedLocales, options); } // Implementation of @@ -533,7 +535,7 @@ vm::ExecutionStatus DateTimeFormatWindows::initialize( options, u"localeMatcher", {u"lookup", u"best fit"}, - u"best fit"); + u"lookup"); // 5. Set opt.[[localeMatcher]] to matcher. opt.emplace(u"localeMatcher", matcher.getValue()); // 6. Let calendar be ? GetOption(options, "calendar", "string", undefined, @@ -868,12 +870,13 @@ vm::CallResult DateTimeFormatWindows::getDefaultHourCycle( std::u16string myString; // open the default UDateFormat and Pattern of locale UDateFormat *defaultDTF = - udat_open(UDAT_DEFAULT, UDAT_DEFAULT, locale8_, 0, -1, 0, -1, &status); + udat_open(UDAT_DEFAULT, UDAT_DEFAULT, locale8_, nullptr, -1, nullptr, -1, &status); int32_t size = udat_toPattern(defaultDTF, true, 0, 0, &status); if (status == U_BUFFER_OVERFLOW_ERROR) { status = U_ZERO_ERROR; myString.resize(size + 1); udat_toPattern(defaultDTF, true, &myString[0], 40, &status); + udat_close(defaultDTF); // find the default hour cycle and return it for (int32_t i = 0; i < size; i++) { char16_t ch = myString[i]; @@ -948,12 +951,12 @@ UDateFormat *DateTimeFormatWindows::getUDateFormatter(vm::Runtime &runtime) { locale8_, timeZoneRes, timeZoneLength, - 0, + nullptr, -1, &status); } return udat_open( - timeStyleRes, dateStyleRes, locale8_, 0, -1, 0, -1, &status); + timeStyleRes, dateStyleRes, locale8_, nullptr, -1, nullptr, -1, &status); } // Else: lets create the skeleton @@ -1067,7 +1070,7 @@ UDateFormat *DateTimeFormatWindows::getUDateFormatter(vm::Runtime &runtime) { skeleton, -1, UDATPG_MATCH_ALL_FIELDS_LENGTH, - 0, + nullptr, 0, &status); @@ -1103,7 +1106,7 @@ UDateFormat *DateTimeFormatWindows::getUDateFormatter(vm::Runtime &runtime) { UDAT_PATTERN, UDAT_PATTERN, locale8_, - 0, + nullptr, -1, &bestpattern[0], patternLength, From e989dfbea39cff5e4468b04181aa3cdbca393c95 Mon Sep 17 00:00:00 2001 From: TatianaKapos Date: Wed, 28 Sep 2022 16:40:54 -0700 Subject: [PATCH 06/14] change to nullptr --- lib/Platform/Intl/PlatformIntlWindows.cpp | 25 +++++++++++++---------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/Platform/Intl/PlatformIntlWindows.cpp b/lib/Platform/Intl/PlatformIntlWindows.cpp index 7ed80ae8329..186816826ee 100644 --- a/lib/Platform/Intl/PlatformIntlWindows.cpp +++ b/lib/Platform/Intl/PlatformIntlWindows.cpp @@ -7,9 +7,9 @@ #include #include +#include #include #include -#include #include "llvh/Support/ConvertUTF.h" using namespace ::hermes; @@ -531,11 +531,7 @@ vm::ExecutionStatus DateTimeFormatWindows::initialize( // 4. Let matcher be ? GetOption(options, "localeMatcher", "string", // «"lookup", "best fit" », "best fit"). auto matcher = getOptionString( - runtime, - options, - u"localeMatcher", - {u"lookup", u"best fit"}, - u"lookup"); + runtime, options, u"localeMatcher", {u"lookup", u"best fit"}, u"lookup"); // 5. Set opt.[[localeMatcher]] to matcher. opt.emplace(u"localeMatcher", matcher.getValue()); // 6. Let calendar be ? GetOption(options, "calendar", "string", undefined, @@ -854,11 +850,11 @@ std::u16string DateTimeFormatWindows::format(double jsTimeValue) noexcept { std::u16string myString; int32_t myStrlen = 0; - myStrlen = udat_format(dtf_, *date, 0, myStrlen, 0, &status); + myStrlen = udat_format(dtf_, *date, nullptr, myStrlen, nullptr, &status); if (status == U_BUFFER_OVERFLOW_ERROR) { status = U_ZERO_ERROR; myString.resize(myStrlen); - udat_format(dtf_, *date, &myString[0], myStrlen + 1, 0, &status); + udat_format(dtf_, *date, &myString[0], myStrlen + 1, nullptr, &status); } return myString; @@ -869,8 +865,8 @@ vm::CallResult DateTimeFormatWindows::getDefaultHourCycle( UErrorCode status = U_ZERO_ERROR; std::u16string myString; // open the default UDateFormat and Pattern of locale - UDateFormat *defaultDTF = - udat_open(UDAT_DEFAULT, UDAT_DEFAULT, locale8_, nullptr, -1, nullptr, -1, &status); + UDateFormat *defaultDTF = udat_open( + UDAT_DEFAULT, UDAT_DEFAULT, locale8_, nullptr, -1, nullptr, -1, &status); int32_t size = udat_toPattern(defaultDTF, true, 0, 0, &status); if (status == U_BUFFER_OVERFLOW_ERROR) { status = U_ZERO_ERROR; @@ -956,7 +952,14 @@ UDateFormat *DateTimeFormatWindows::getUDateFormatter(vm::Runtime &runtime) { &status); } return udat_open( - timeStyleRes, dateStyleRes, locale8_, nullptr, -1, nullptr, -1, &status); + timeStyleRes, + dateStyleRes, + locale8_, + nullptr, + -1, + nullptr, + -1, + &status); } // Else: lets create the skeleton From 065d043d7881dff2e0f9214fb0ce04ebbb39e095 Mon Sep 17 00:00:00 2001 From: TatianaKapos Date: Wed, 28 Sep 2022 17:26:55 -0700 Subject: [PATCH 07/14] use std:optional --- lib/Platform/Intl/PlatformIntlWindows.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/lib/Platform/Intl/PlatformIntlWindows.cpp b/lib/Platform/Intl/PlatformIntlWindows.cpp index 186816826ee..196d2b0e95a 100644 --- a/lib/Platform/Intl/PlatformIntlWindows.cpp +++ b/lib/Platform/Intl/PlatformIntlWindows.cpp @@ -213,24 +213,21 @@ vm::CallResult getOptionString( } // boolean + null option -enum class BoolNull { False, True, Null }; -BoolNull getOptionBool( +//enum class BoolNull { False, True, Null }; +std::optional getOptionBool( vm::Runtime &runtime, const Options &options, const std::u16string &property, - const bool fallback) { + const std::optional fallback) { // 1. Assert: Type(options) is Object. // 2. Let value be ? Get(options, property). auto value = options.find(property); // 3. If value is undefined, return fallback. if (value == options.end()) { - return BoolNull::Null; + return fallback; } // 8. Return value. - if (value->second.getBool()) { - return BoolNull::True; - } - return BoolNull::False; + return value->second.getBool(); } // Collator - Not yet implemented. Tracked by @@ -550,7 +547,7 @@ vm::ExecutionStatus DateTimeFormatWindows::initialize( // 12. Let hour12 be ? GetOption(options, "hour12", "boolean", undefined, // undefined). - BoolNull hour12 = getOptionBool(runtime, options, u"hour12", {}); + auto hour12 = getOptionBool(runtime, options, u"hour12", {}); // 13. Let hourCycle be ? GetOption(options, "hourCycle", "string", «"h11", // "h12", "h23", "h24" », undefined). @@ -561,7 +558,7 @@ vm::ExecutionStatus DateTimeFormatWindows::initialize( std::u16string hourCycle = hourCycleRes.getValue(); // 14. If hour12 is not undefined, then a. Set hourCycle to null. - if (!(hour12 == BoolNull::Null)) { + if (hour12.has_value()) { hourCycle = u""; } // 15. Set opt.[[hc]] to hourCycle. @@ -764,9 +761,9 @@ vm::ExecutionStatus DateTimeFormatWindows::initialize( // i. Set hc to hcDefault. hc = hcDefault; // d. If hour12 is not undefined, then - if (!(hour12 == BoolNull::Null)) { + if (hour12.has_value()) { // i. If hour12 is true, then - if ((hour12 == BoolNull::True)) { + if (*hour12 == true) { // 1. If hcDefault is "h11" or "h23", then if (hcDefault == u"h11" || hcDefault == u"h23") { // a. Set hc to "h11". From 61c0b07d8724614941b5019f5d49c3bc6d4351e9 Mon Sep 17 00:00:00 2001 From: TatianaKapos Date: Thu, 29 Sep 2022 14:49:14 -0700 Subject: [PATCH 08/14] Create shared code file and more PR feedback --- lib/Platform/Intl/PlatformIntlShared.cpp | 176 +++++++++++ lib/Platform/Intl/PlatformIntlWindows.cpp | 355 ++++++---------------- 2 files changed, 277 insertions(+), 254 deletions(-) create mode 100644 lib/Platform/Intl/PlatformIntlShared.cpp diff --git a/lib/Platform/Intl/PlatformIntlShared.cpp b/lib/Platform/Intl/PlatformIntlShared.cpp new file mode 100644 index 00000000000..f380f78c31c --- /dev/null +++ b/lib/Platform/Intl/PlatformIntlShared.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// This file includes shared code between Apple and Windows implementation of Intl APIs +#include "hermes/Platform/Intl/PlatformIntl.h" + +using namespace ::hermes; + +namespace hermes { +namespace platform_intl { + +// https://402.ecma-international.org/8.0/#sec-bestavailablelocale +std::optional bestAvailableLocale( + const std::vector &availableLocales, + const std::u16string &locale) { + // 1. Let candidate be locale + std::u16string candidate = locale; + + // 2. Repeat + while (true) { + // a. If availableLocales contains an element equal to candidate, return + // candidate. + if (llvh::find(availableLocales, candidate) != availableLocales.end()) + return candidate; + // b. Let pos be the character index of the last occurrence of "-" (U+002D) + // within candidate. + size_t pos = candidate.rfind(u'-'); + + // ...If that character does not occur, return undefined. + if (pos == std::u16string::npos) + return std::nullopt; + + // c. If pos ≥ 2 and the character "-" occurs at index pos-2 of candidate, + // decrease pos by 2. + if (pos >= 2 && candidate[pos - 2] == '-') + pos -= 2; + + // d. Let candidate be the substring of candidate from position 0, + // inclusive, to position pos, exclusive. + candidate.resize(pos); + } +} + +// https://402.ecma-international.org/8.0/#sec-lookupsupportedlocales +std::vector lookupSupportedLocales( + const std::vector &availableLocales, + const std::vector &requestedLocales) { + // 1. Let subset be a new empty List. + std::vector subset; + // 2. For each element locale of requestedLocales in List order, do + for (const std::u16string &locale : requestedLocales) { + // a. Let noExtensionsLocale be the String value that is locale with all + // Unicode locale extension sequences removed. + // We can skip this step, see the comment in lookupMatcher. + // b. Let availableLocale be BestAvailableLocale(availableLocales, + // noExtensionsLocale). + std::optional availableLocale = + bestAvailableLocale(availableLocales, locale); + // c. If availableLocale is not undefined, append locale to the end of + // subset. + if (availableLocale) { + subset.push_back(locale); + } + } + // 3. Return subset. + return subset; +} + +std::optional getOptionBool( + vm::Runtime &runtime, + const Options &options, + const std::u16string &property, + std::optional fallback) { + // 1. Assert: Type(options) is Object. + // 2. Let value be ? Get(options, property). + auto value = options.find(property); + // 3. If value is undefined, return fallback. + if (value == options.end()) { + return fallback; + } + // 8. Return value. + return value->second.getBool(); +} + +// Implementation of +// https://402.ecma-international.org/8.0/#sec-todatetimeoptions +vm::CallResult toDateTimeOptions( + vm::Runtime &runtime, + Options options, + std::u16string_view required, + std::u16string_view defaults) { + // 1. If options is undefined, let options be null; otherwise let options be ? + // ToObject(options). + // 2. Let options be OrdinaryObjectCreate(options). + // 3. Let needDefaults be true. + bool needDefaults = true; + // 4. If required is "date" or "any", then + if (required == u"date" || required == u"any") { + // a. For each property name prop of « "weekday", "year", "month", "day" », + // do + // TODO(T116352920): Make this a std::u16string props[] once we have + // constexpr std::u16string. + static constexpr std::u16string_view props[] = { + u"weekday", u"year", u"month", u"day"}; + for (const auto &prop : props) { + // i. Let value be ? Get(options, prop). + if (options.find(std::u16string(prop)) != options.end()) { + // ii. If value is not undefined, let needDefaults be false. + needDefaults = false; + } + } + } + // 5. If required is "time" or "any", then + if (required == u"time" || required == u"any") { + // a. For each property name prop of « "dayPeriod", "hour", "minute", + // "second", "fractionalSecondDigits" », do + static constexpr std::u16string_view props[] = { + u"dayPeriod", u"hour", u"minute", u"second", u"fractionalSecondDigits"}; + for (const auto &prop : props) { + // i. Let value be ? Get(options, prop). + if (options.find(std::u16string(prop)) != options.end()) { + // ii. If value is not undefined, let needDefaults be false. + needDefaults = false; + } + } + } + // 6. Let dateStyle be ? Get(options, "dateStyle"). + auto dateStyle = options.find(u"dateStyle"); + // 7. Let timeStyle be ? Get(options, "timeStyle"). + auto timeStyle = options.find(u"timeStyle"); + // 8. If dateStyle is not undefined or timeStyle is not undefined, let + // needDefaults be false. + if (dateStyle != options.end() || timeStyle != options.end()) { + needDefaults = false; + } + // 9. If required is "date" and timeStyle is not undefined, then + if (required == u"date" && timeStyle != options.end()) { + // a. Throw a TypeError exception. + return runtime.raiseTypeError( + "Unexpectedly found timeStyle option for \"date\" property"); + } + // 10. If required is "time" and dateStyle is not undefined, then + if (required == u"time" && dateStyle != options.end()) { + // a. Throw a TypeError exception. + return runtime.raiseTypeError( + "Unexpectedly found dateStyle option for \"time\" property"); + } + // 11. If needDefaults is true and defaults is either "date" or "all", then + if (needDefaults && (defaults == u"date" || defaults == u"all")) { + // a. For each property name prop of « "year", "month", "day" », do + static constexpr std::u16string_view props[] = {u"year", u"month", u"day"}; + for (const auto &prop : props) { + // i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric"). + options.emplace(prop, Option(std::u16string(u"numeric"))); + } + } + // 12. If needDefaults is true and defaults is either "time" or "all", then + if (needDefaults && (defaults == u"time" || defaults == u"all")) { + // a. For each property name prop of « "hour", "minute", "second" », do + static constexpr std::u16string_view props[] = { + u"hour", u"minute", u"second"}; + for (const auto &prop : props) { + // i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric"). + options.emplace(prop, Option(std::u16string(u"numeric"))); + } + } + // 13. return options + return options; +} + +} // namespace platform_intl +} // namespace hermes \ No newline at end of file diff --git a/lib/Platform/Intl/PlatformIntlWindows.cpp b/lib/Platform/Intl/PlatformIntlWindows.cpp index 196d2b0e95a..15819685aeb 100644 --- a/lib/Platform/Intl/PlatformIntlWindows.cpp +++ b/lib/Platform/Intl/PlatformIntlWindows.cpp @@ -3,6 +3,7 @@ * Licensed under the MIT License. */ +#include "./PlatformIntlShared.cpp" #include "hermes/Platform/Intl/PlatformIntl.h" #include @@ -17,6 +18,8 @@ using namespace ::hermes; namespace hermes { namespace platform_intl { +namespace { + // convert utf8 string to utf16 vm::CallResult UTF8toUTF16( vm::Runtime &runtime, @@ -156,31 +159,6 @@ vm::CallResult> CanonicalizeLocaleList( return seen; } -// https://tc39.es/ecma402/#sec-intl.getcanonicallocales -vm::CallResult> getCanonicalLocales( - vm::Runtime &runtime, - const std::vector &locales) { - return CanonicalizeLocaleList(runtime, locales); -} - -// Not yet implemented. Tracked by -// https://github.com/microsoft/hermes-windows/issues/87 -vm::CallResult toLocaleLowerCase( - vm::Runtime &runtime, - const std::vector &locales, - const std::u16string &str) { - return std::u16string(u"lowered"); -} - -// Not yet implemented. Tracked by -// https://github.com/microsoft/hermes-windows/issues/87 -vm::CallResult toLocaleUpperCase( - vm::Runtime &runtime, - const std::vector &locales, - const std::u16string &str) { - return std::u16string(u"uppered"); -} - /// https://402.ecma-international.org/8.0/#sec-getoption /// Split into getOptionString and getOptionBool to help readability vm::CallResult getOptionString( @@ -212,22 +190,50 @@ vm::CallResult getOptionString( return std::u16string(value); } -// boolean + null option -//enum class BoolNull { False, True, Null }; -std::optional getOptionBool( +/// https://402.ecma-international.org/8.0/#sec-supportedlocales +std::vector supportedLocales( + const std::vector &availableLocales, + const std::vector &requestedLocales, + const Options &options) { + // 1. Set options to ? CoerceOptionsToObject(options). + // 2. Let matcher be ? GetOption(options, "localeMatcher", "string", « + // "lookup", "best fit" », "best fit"). + // 3. If matcher is "best fit", then + // a. Let supportedLocales be BestFitSupportedLocales(availableLocales, + // requestedLocales). + // 4. Else, + // a. Let supportedLocales be LookupSupportedLocales(availableLocales, + // requestedLocales). + // 5. Return CreateArrayFromList(supportedLocales). + + // We do not implement a BestFitMatcher, so we can just use LookupMatcher. + return lookupSupportedLocales(availableLocales, requestedLocales); +} +} // namespace + +// https://tc39.es/ecma402/#sec-intl.getcanonicallocales +vm::CallResult> getCanonicalLocales( vm::Runtime &runtime, - const Options &options, - const std::u16string &property, - const std::optional fallback) { - // 1. Assert: Type(options) is Object. - // 2. Let value be ? Get(options, property). - auto value = options.find(property); - // 3. If value is undefined, return fallback. - if (value == options.end()) { - return fallback; - } - // 8. Return value. - return value->second.getBool(); + const std::vector &locales) { + return CanonicalizeLocaleList(runtime, locales); +} + +// Not yet implemented. Tracked by +// https://github.com/microsoft/hermes-windows/issues/87 +vm::CallResult toLocaleLowerCase( + vm::Runtime &runtime, + const std::vector &locales, + const std::u16string &str) { + return std::u16string(u"lowered"); +} + +// Not yet implemented. Tracked by +// https://github.com/microsoft/hermes-windows/issues/87 +vm::CallResult toLocaleUpperCase( + vm::Runtime &runtime, + const std::vector &locales, + const std::u16string &str) { + return std::u16string(u"uppered"); } // Collator - Not yet implemented. Tracked by @@ -270,168 +276,6 @@ double Collator::compare( return x.compare(y); } -// Implementation of -// https://402.ecma-international.org/8.0/#sec-todatetimeoptions -vm::CallResult toDateTimeOptions( - vm::Runtime &runtime, - Options options, - const std::u16string &required, - const std::u16string &defaults) { - // 1. If options is undefined, let options be null; otherwise let options be ? - // ToObject(options). - // 2. Let options be OrdinaryObjectCreate(options). - // 3. Let needDefaults be true. - bool needDefaults = true; - // 4. If required is "date" or "any", then - if (required == u"date" || required == u"any") { - // a. For each property name prop of « "weekday", "year", "month", "day" », - // do - // TODO(T116352920): Make this a std::u16string props[] once we have - // constexpr std::u16string. - static const std::vector props = { - u"weekday", u"year", u"month", u"day"}; - for (const auto &prop : props) { - // i. Let value be ? Get(options, prop). - if (options.find(std::u16string(prop)) != options.end()) { - // ii. If value is not undefined, let needDefaults be false. - needDefaults = false; - } - } - } - // 5. If required is "time" or "any", then - if (required == u"time" || required == u"any") { - // a. For each property name prop of « "dayPeriod", "hour", "minute", - // "second", "fractionalSecondDigits" », do - static const std::vector props = { - u"dayPeriod", u"hour", u"minute", u"second", u"fractionalSecondDigits"}; - for (const auto &prop : props) { - // i. Let value be ? Get(options, prop). - if (options.find(std::u16string(prop)) != options.end()) { - // ii. If value is not undefined, let needDefaults be false. - needDefaults = false; - } - } - } - // 6. Let dateStyle be ? Get(options, "dateStyle"). - auto dateStyle = options.find(u"dateStyle"); - // 7. Let timeStyle be ? Get(options, "timeStyle"). - auto timeStyle = options.find(u"timeStyle"); - // 8. If dateStyle is not undefined or timeStyle is not undefined, let - // needDefaults be false. - if (dateStyle != options.end() || timeStyle != options.end()) { - needDefaults = false; - } - // 9. If required is "date" and timeStyle is not undefined, then - if (required == u"date" && timeStyle != options.end()) { - // a. Throw a TypeError exception. - return runtime.raiseTypeError("TimeSyle is defined"); - } - // 10. If required is "time" and dateStyle is not undefined, then - if (required == u"time" && dateStyle != options.end()) { - // a. Throw a TypeError exception. - return runtime.raiseTypeError("DateSyle is defined"); - } - // 11. If needDefaults is true and defaults is either "date" or "all", then - if (needDefaults && (defaults == u"date" || defaults == u"all")) { - // a. For each property name prop of « "year", "month", "day" », do - static const std::vector props = { - u"year", u"month", u"day"}; - for (const auto &prop : props) { - // i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric"). - options.emplace(prop, Option(std::u16string(u"numeric"))); - } - } - // 12. If needDefaults is true and defaults is either "time" or "all", then - if (needDefaults && (defaults == u"time" || defaults == u"all")) { - // a. For each property name prop of « "hour", "minute", "second" », do - static const std::vector props = { - u"hour", u"minute", u"second"}; - for (const auto &prop : props) { - // i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric"). - options.emplace(prop, Option(std::u16string(u"numeric"))); - } - } - // 13. return options - return options; -} - -/// https://402.ecma-international.org/8.0/#sec-bestavailablelocale -std::optional bestAvailableLocale( - const std::vector &availableLocales, - const std::u16string &locale) { - // 1. Let candidate be locale - std::u16string candidate = locale; - - // 2. Repeat - while (true) { - // a. If availableLocales contains an element equal to candidate, return - // candidate. - if (llvh::find(availableLocales, candidate) != availableLocales.end()) - return candidate; - // b. Let pos be the character index of the last occurrence of "-" (U+002D) - // within candidate. - size_t pos = candidate.rfind(u'-'); - - // ...If that character does not occur, return undefined. - if (pos == std::u16string::npos) - return std::nullopt; - - // c. If pos ≥ 2 and the character "-" occurs at index pos-2 of candidate, - // decrease pos by 2. - if (pos >= 2 && candidate[pos - 2] == '-') - pos -= 2; - - // d. Let candidate be the substring of candidate from position 0, - // inclusive, to position pos, exclusive. - candidate.resize(pos); - } -} - -/// https://402.ecma-international.org/8.0/#sec-lookupsupportedlocales -std::vector lookupSupportedLocales( - const std::vector &availableLocales, - const std::vector &requestedLocales) { - // 1. Let subset be a new empty List. - std::vector subset; - // 2. For each element locale of requestedLocales in List order, do - for (const std::u16string &locale : requestedLocales) { - // a. Let noExtensionsLocale be the String value that is locale with all - // Unicode locale extension sequences removed. - // We can skip this step, see the comment in lookupMatcher. - // b. Let availableLocale be BestAvailableLocale(availableLocales, - // noExtensionsLocale). - std::optional availableLocale = - bestAvailableLocale(availableLocales, locale); - // c. If availableLocale is not undefined, append locale to the end of - // subset. - if (availableLocale) { - subset.push_back(locale); - } - } - // 3. Return subset. - return subset; -} - -/// https://402.ecma-international.org/8.0/#sec-supportedlocales -std::vector supportedLocales( - const std::vector &availableLocales, - const std::vector &requestedLocales, - const Options &options) { - // 1. Set options to ? CoerceOptionsToObject(options). - // 2. Let matcher be ? GetOption(options, "localeMatcher", "string", « - // "lookup", "best fit" », "best fit"). - // 3. If matcher is "best fit", then - // a. Let supportedLocales be BestFitSupportedLocales(availableLocales, - // requestedLocales). - // 4. Else, - // a. Let supportedLocales be LookupSupportedLocales(availableLocales, - // requestedLocales). - // 5. Return CreateArrayFromList(supportedLocales). - - // We do not implement a BestFitMatcher, so we can just use LookupMatcher. - return lookupSupportedLocales(availableLocales, requestedLocales); -} - namespace { // Implementation of // https://402.ecma-international.org/8.0/#datetimeformat-objects @@ -842,16 +686,16 @@ Options DateTimeFormat::resolvedOptions() noexcept { std::u16string DateTimeFormatWindows::format(double jsTimeValue) noexcept { auto timeInSeconds = jsTimeValue; - UDate *date = new UDate(timeInSeconds); + UDate date = UDate(timeInSeconds); UErrorCode status = U_ZERO_ERROR; std::u16string myString; int32_t myStrlen = 0; - myStrlen = udat_format(dtf_, *date, nullptr, myStrlen, nullptr, &status); + myStrlen = udat_format(dtf_, date, nullptr, myStrlen, nullptr, &status); if (status == U_BUFFER_OVERFLOW_ERROR) { status = U_ZERO_ERROR; myString.resize(myStrlen); - udat_format(dtf_, *date, &myString[0], myStrlen + 1, nullptr, &status); + udat_format(dtf_, date, &myString[0], myStrlen, nullptr, &status); } return myString; @@ -864,7 +708,7 @@ vm::CallResult DateTimeFormatWindows::getDefaultHourCycle( // open the default UDateFormat and Pattern of locale UDateFormat *defaultDTF = udat_open( UDAT_DEFAULT, UDAT_DEFAULT, locale8_, nullptr, -1, nullptr, -1, &status); - int32_t size = udat_toPattern(defaultDTF, true, 0, 0, &status); + int32_t size = udat_toPattern(defaultDTF, true, nullptr, 0, &status); if (status == U_BUFFER_OVERFLOW_ERROR) { status = U_ZERO_ERROR; myString.resize(size + 1); @@ -933,12 +777,13 @@ UDateFormat *DateTimeFormatWindows::getUDateFormatter(vm::Runtime &runtime) { } UErrorCode status = U_ZERO_ERROR; + UDateFormat *dtf; // if timezone is specified, use that instead, else use default if (!timeZone_.empty()) { const UChar *timeZoneRes = reinterpret_cast(timeZone_.c_str()); int32_t timeZoneLength = timeZone_.length(); - return udat_open( + dtf = udat_open( timeStyleRes, dateStyleRes, locale8_, @@ -947,127 +792,129 @@ UDateFormat *DateTimeFormatWindows::getUDateFormatter(vm::Runtime &runtime) { nullptr, -1, &status); + } else { + dtf = udat_open( + timeStyleRes, + dateStyleRes, + locale8_, + nullptr, + -1, + nullptr, + -1, + &status); } - return udat_open( - timeStyleRes, - dateStyleRes, - locale8_, - nullptr, - -1, - nullptr, - -1, - &status); + assert(status == U_ZERO_ERROR); + return dtf; } // Else: lets create the skeleton - std::u16string customDate = u""; + std::u16string skeleton = u""; if (!weekday_.empty()) { if (weekday_ == eNarrow) - customDate += u"EEEEE"; + skeleton += u"EEEEE"; else if (weekday_ == eLong) - customDate += u"EEEE"; + skeleton += u"EEEE"; else if (weekday_ == eShort) - customDate += u"EEE"; + skeleton += u"EEE"; } if (!timeZoneName_.empty()) { if (timeZoneName_ == eShort) - customDate += u"z"; + skeleton += u"z"; else if (timeZoneName_ == eLong) - customDate += u"zzzz"; + skeleton += u"zzzz"; else if (timeZoneName_ == eShortOffset) - customDate += u"O"; + skeleton += u"O"; else if (timeZoneName_ == eLongOffset) - customDate += u"OOOO"; + skeleton += u"OOOO"; else if (timeZoneName_ == eShortGeneric) - customDate += u"v"; + skeleton += u"v"; else if (timeZoneName_ == eLongGeneric) - customDate += u"vvvv"; + skeleton += u"vvvv"; } if (!era_.empty()) { if (era_ == eNarrow) - customDate += u"GGGGG"; + skeleton += u"GGGGG"; else if (era_ == eShort) - customDate += u"G"; + skeleton += u"G"; else if (era_ == eLong) - customDate += u"GGGG"; + skeleton += u"GGGG"; } if (!year_.empty()) { if (year_ == eNumeric) - customDate += u"y"; + skeleton += u"y"; else if (year_ == eTwoDigit) - customDate += u"yy"; + skeleton += u"yy"; } if (!month_.empty()) { if (month_ == eTwoDigit) - customDate += u"MM"; + skeleton += u"MM"; else if (month_ == eNumeric) - customDate += u'M'; + skeleton += u'M'; else if (month_ == eNarrow) - customDate += u"MMMMM"; + skeleton += u"MMMMM"; else if (month_ == eShort) - customDate += u"MMM"; + skeleton += u"MMM"; else if (month_ == eLong) - customDate += u"MMMM"; + skeleton += u"MMMM"; } if (!day_.empty()) { if (day_ == eNumeric) - customDate += u"d"; + skeleton += u"d"; else if (day_ == eTwoDigit) - customDate += u"dd"; + skeleton += u"dd"; } if (!hour_.empty()) { if (hourCycle_ == u"h12") { if (hour_ == eNumeric) - customDate += u"h"; + skeleton += u"h"; else if (hour_ == eTwoDigit) - customDate += u"hh"; + skeleton += u"hh"; } else if (hourCycle_ == u"h24") { if (hour_ == eNumeric) - customDate += u"k"; + skeleton += u"k"; else if (hour_ == eTwoDigit) - customDate += u"kk"; + skeleton += u"kk"; } else if (hourCycle_ == u"h23") { if (hour_ == eNumeric) - customDate += u"k"; + skeleton += u"k"; else if (hour_ == eTwoDigit) - customDate += u"KK"; + skeleton += u"KK"; } else { if (hour_ == eNumeric) - customDate += u"h"; + skeleton += u"h"; else if (hour_ == eTwoDigit) - customDate += u"HH"; + skeleton += u"HH"; } } if (!minute_.empty()) { if (minute_ == eNumeric) - customDate += u"m"; + skeleton += u"m"; else if (minute_ == eTwoDigit) - customDate += u"mm"; + skeleton += u"mm"; } if (!second_.empty()) { if (second_ == eNumeric) - customDate += u"s"; + skeleton += u"s"; else if (second_ == eTwoDigit) - customDate += u"ss"; + skeleton += u"ss"; } UErrorCode status = U_ZERO_ERROR; - const UChar *skeleton = reinterpret_cast(customDate.c_str()); std::u16string bestpattern; int32_t patternLength; UDateTimePatternGenerator *dtpGenerator = udatpg_open(locale8_, &status); patternLength = udatpg_getBestPatternWithOptions( dtpGenerator, - skeleton, + &skeleton[0], -1, UDATPG_MATCH_ALL_FIELDS_LENGTH, nullptr, @@ -1079,8 +926,8 @@ UDateFormat *DateTimeFormatWindows::getUDateFormatter(vm::Runtime &runtime) { bestpattern.resize(patternLength); udatpg_getBestPatternWithOptions( dtpGenerator, - skeleton, - customDate.length(), + &skeleton[0], + skeleton.length(), UDATPG_MATCH_ALL_FIELDS_LENGTH, &bestpattern[0], patternLength, From 279b2c25e43254fa3a6a53f899c04e340bcbc625 Mon Sep 17 00:00:00 2001 From: TatianaKapos Date: Thu, 29 Sep 2022 17:07:49 -0700 Subject: [PATCH 09/14] free datetimeformat --- lib/Platform/Intl/PlatformIntlWindows.cpp | 4 +++- unittests/PlatformIntl/DateTimeFormat.cpp | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/Platform/Intl/PlatformIntlWindows.cpp b/lib/Platform/Intl/PlatformIntlWindows.cpp index 15819685aeb..a53d5534418 100644 --- a/lib/Platform/Intl/PlatformIntlWindows.cpp +++ b/lib/Platform/Intl/PlatformIntlWindows.cpp @@ -282,7 +282,9 @@ namespace { class DateTimeFormatWindows : public DateTimeFormat { public: DateTimeFormatWindows() = default; - ~DateTimeFormatWindows() = default; + ~DateTimeFormatWindows() { + udat_close(dtf_); + }; vm::ExecutionStatus initialize( vm::Runtime &runtime, diff --git a/unittests/PlatformIntl/DateTimeFormat.cpp b/unittests/PlatformIntl/DateTimeFormat.cpp index 577984051ea..ff620420118 100644 --- a/unittests/PlatformIntl/DateTimeFormat.cpp +++ b/unittests/PlatformIntl/DateTimeFormat.cpp @@ -26,7 +26,6 @@ TEST(DateTimeFormat, DatesWithoutOptions) { auto dtf = platform_intl::DateTimeFormat::create( *runtime.get(), AmericanEnglish, testOptions); - // dtf.initialize(*runtime.get(), AmericanEnglish, testOptions); auto result = dtf.getValue().get()->format(1620000000000.00); EXPECT_EQ(result, u"5/2/2021"); From f8e219fe3529aab3af2280ad5a6076ab7e780c8b Mon Sep 17 00:00:00 2001 From: TatianaKapos Date: Thu, 13 Oct 2022 16:16:04 -0700 Subject: [PATCH 10/14] store std::string globally --- lib/Platform/Intl/PlatformIntlWindows.cpp | 26 ++++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/Platform/Intl/PlatformIntlWindows.cpp b/lib/Platform/Intl/PlatformIntlWindows.cpp index a53d5534418..1b83aa929e2 100644 --- a/lib/Platform/Intl/PlatformIntlWindows.cpp +++ b/lib/Platform/Intl/PlatformIntlWindows.cpp @@ -316,7 +316,7 @@ class DateTimeFormatWindows : public DateTimeFormat { std::u16string hourCycle_; // Internal use UDateFormat *dtf_; - const char *locale8_; + std::string locale8_; UDateFormat *getUDateFormatter(vm::Runtime &runtime); vm::CallResult getDefaultHourCycle(vm::Runtime &runtime); }; @@ -362,9 +362,8 @@ vm::ExecutionStatus DateTimeFormatWindows::initialize( if (conversion.getStatus() == vm::ExecutionStatus::EXCEPTION) { return conversion.getStatus(); } - const char *locale8 = conversion.getValue().c_str(); - locale8_ = locale8; // store the UTF8 version of locale since it is used - // in almost all other functions + locale8_ = conversion.getValue(); // store the UTF8 version of locale since it + // is used in almost all other functions // 2. Let options be ? ToDateTimeOptions(options, "any", "date"). Options options = @@ -709,7 +708,14 @@ vm::CallResult DateTimeFormatWindows::getDefaultHourCycle( std::u16string myString; // open the default UDateFormat and Pattern of locale UDateFormat *defaultDTF = udat_open( - UDAT_DEFAULT, UDAT_DEFAULT, locale8_, nullptr, -1, nullptr, -1, &status); + UDAT_DEFAULT, + UDAT_DEFAULT, + &locale8_[0], + nullptr, + -1, + nullptr, + -1, + &status); int32_t size = udat_toPattern(defaultDTF, true, nullptr, 0, &status); if (status == U_BUFFER_OVERFLOW_ERROR) { status = U_ZERO_ERROR; @@ -788,7 +794,7 @@ UDateFormat *DateTimeFormatWindows::getUDateFormatter(vm::Runtime &runtime) { dtf = udat_open( timeStyleRes, dateStyleRes, - locale8_, + &locale8_[0], timeZoneRes, timeZoneLength, nullptr, @@ -798,7 +804,7 @@ UDateFormat *DateTimeFormatWindows::getUDateFormatter(vm::Runtime &runtime) { dtf = udat_open( timeStyleRes, dateStyleRes, - locale8_, + &locale8_[0], nullptr, -1, nullptr, @@ -913,7 +919,7 @@ UDateFormat *DateTimeFormatWindows::getUDateFormatter(vm::Runtime &runtime) { std::u16string bestpattern; int32_t patternLength; - UDateTimePatternGenerator *dtpGenerator = udatpg_open(locale8_, &status); + UDateTimePatternGenerator *dtpGenerator = udatpg_open(&locale8_[0], &status); patternLength = udatpg_getBestPatternWithOptions( dtpGenerator, &skeleton[0], @@ -944,7 +950,7 @@ UDateFormat *DateTimeFormatWindows::getUDateFormatter(vm::Runtime &runtime) { return udat_open( UDAT_PATTERN, UDAT_PATTERN, - locale8_, + &locale8_[0], timeZoneRes, timeZoneLength, &bestpattern[0], @@ -954,7 +960,7 @@ UDateFormat *DateTimeFormatWindows::getUDateFormatter(vm::Runtime &runtime) { return udat_open( UDAT_PATTERN, UDAT_PATTERN, - locale8_, + &locale8_[0], nullptr, -1, &bestpattern[0], From 94ed8a6b398cb047fbc3f047b74ab733254c7f11 Mon Sep 17 00:00:00 2001 From: TatianaKapos Date: Tue, 15 Nov 2022 13:31:40 -0800 Subject: [PATCH 11/14] add more error checking --- lib/Platform/Intl/PlatformIntlWindows.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/Platform/Intl/PlatformIntlWindows.cpp b/lib/Platform/Intl/PlatformIntlWindows.cpp index 1b83aa929e2..eeb06da0947 100644 --- a/lib/Platform/Intl/PlatformIntlWindows.cpp +++ b/lib/Platform/Intl/PlatformIntlWindows.cpp @@ -160,12 +160,11 @@ vm::CallResult> CanonicalizeLocaleList( } /// https://402.ecma-international.org/8.0/#sec-getoption -/// Split into getOptionString and getOptionBool to help readability vm::CallResult getOptionString( vm::Runtime &runtime, const Options &options, const std::u16string &property, - const std::vector &values, + const std::vector &validValues, const std::u16string &fallback) { // 1. Assert type(options) is object // 2. Let value be ? Get(options, property). @@ -182,7 +181,7 @@ vm::CallResult getOptionString( // a. Set value to ? ToString(value). // 7. If values is not undefined and values does not contain an element equal // to value, throw a RangeError exception. - if (!values.empty() && llvh::find(values, value) == values.end()) + if (!validValues.empty() && llvh::find(validValues, value) == validValues.end()) return runtime.raiseRangeError( vm::TwineChar16(property.c_str()) + vm::TwineChar16("Value is invalid.")); @@ -699,6 +698,7 @@ std::u16string DateTimeFormatWindows::format(double jsTimeValue) noexcept { udat_format(dtf_, date, &myString[0], myStrlen, nullptr, &status); } + assert(status <= 0); // Check for errors return myString; } @@ -721,6 +721,7 @@ vm::CallResult DateTimeFormatWindows::getDefaultHourCycle( status = U_ZERO_ERROR; myString.resize(size + 1); udat_toPattern(defaultDTF, true, &myString[0], 40, &status); + assert(status <= 0); // Check for errors udat_close(defaultDTF); // find the default hour cycle and return it for (int32_t i = 0; i < size; i++) { @@ -746,7 +747,7 @@ vm::CallResult DateTimeFormatWindows::getDefaultHourCycle( return vm::ExecutionStatus::EXCEPTION; } -// gets the UDateFormat with options set in initalize +// gets the UDateFormat with options set in initialize UDateFormat *DateTimeFormatWindows::getUDateFormatter(vm::Runtime &runtime) { static std::u16string eLong = u"long", eShort = u"short", eNarrow = u"narrow", eMedium = u"medium", eFull = u"full", From 589180dd1fc37808356897bf94e2c41bdef79c39 Mon Sep 17 00:00:00 2001 From: Tatiana Kapos Date: Thu, 17 Nov 2022 15:30:47 -0800 Subject: [PATCH 12/14] Update IntlAPIs.md --- doc/IntlAPIs.md | 98 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 81 insertions(+), 17 deletions(-) diff --git a/doc/IntlAPIs.md b/doc/IntlAPIs.md index 4e8ad3ba704..f0b6b56fcf1 100644 --- a/doc/IntlAPIs.md +++ b/doc/IntlAPIs.md @@ -3,16 +3,18 @@ id: intl title: Internationalization APIs --- -This document describes the current state of Android implementation of the [ECMAScript Internationalization API Specification](https://tc39.es/ecma402/) (ECMA-402, or `Intl`). ECMA-402 is still evolving and the latest iteration is [7th edition](https://402.ecma-international.org/7.0/) which was published in June 2020. Each new edition is built on top of the last one and adds new capabilities typically as, +This document describes the current state of Android/Windows implementation of the [ECMAScript Internationalization API Specification](https://tc39.es/ecma402/) (ECMA-402, or `Intl`). ECMA-402 is still evolving and the latest iteration is [7th edition](https://402.ecma-international.org/7.0/) which was published in June 2020. Each new edition is built on top of the last one and adds new capabilities typically as, - New `Intl` service constructors (e.g. `Intl.Collator`, `Intl.NumberFormat` etc.) or extending existing ones by accepting more parameters - New functions or properties in `Intl` objects (e.g. `Intl.Collator.prototype.compare`) - New locale aware functions in standard Javascript object prototypes (e.g. `String.prototype.localeCompare`) +# Android + One popular implementation strategy followed by other engines, is to bundle an internationalization framework (typically [ICU](http://site.icu-project.org/)) along with the application package. This guarantees deterministic behaviours at the cost of applications package bloat. We decided to consume the Android platform provided facilities for space efficiency, but at the cost of some variance in behaviours across Android platforms. -# ECMA-402 Compliance +## ECMA-402 Compliance -## Supported +### Supported - `Intl.Collator` - `Intl.Collator.supportedLocalesOf` @@ -49,7 +51,7 @@ One popular implementation strategy followed by other engines, is to bundle an i - `toLocaleDateString` - `toLocaleTimeString` -## Not yet supported +### Not yet supported - [`Intl.PluralRules`](https://tc39.es/ecma402/#pluralrules-objects) @@ -67,13 +69,13 @@ One popular implementation strategy followed by other engines, is to bundle an i - [`fractionalSecondDigits`](https://github.com/tc39/ecma402/pull/347) - [`BigInt.prototype.toLocaleString`](https://tc39.es/ecma402/#sup-bigint.prototype.tolocalestring) -## Excluded +### Excluded - `Intl.DateTimeFormat`: [`formatMatcher`](https://tc39.es/ecma402/#sec-basicformatmatcher) parameter is not respected. The parameter enables the implementation to pick the best display format when it supports only a subset of all possible formats. ICU library in Android platform and hence our implementation allows all subsets and formats which makes this `formatMatcher` property unnecessary. -## Limitations across Android SDKs +### Limitations across Android SDKs -### Android 11 +#### Android 11 - The keys of the object returned by `resolvedOptions` function in all `Intl` services are not deterministically ordered as prescribed by spec. - DateFormat: ECMAScript [beginning of time](https://www.ecma-international.org/ecma-262/11.0/index.html#sec-time-values-and-time-range) (-8,640,000,000,000,000), is formatted as `November 271817`, instead of expected `April 271822`. @@ -83,28 +85,28 @@ One popular implementation strategy followed by other engines, is to bundle an i - `signDisplay` - `currencyFormat` -### Android 10 and older (SDK < 30) +#### Android 10 and older (SDK < 30) - `Intl.NumberFormat`: Scientific notation formatting has issues on some cases. e.g. `-Infinity` may get formatted as '-∞E0' instead of expected '-∞'. Another manifestation of the issues is that the formatToParts may return 4 parts instead of 2. - `Intl.NumberFormat`: Compact notation `formatToParts` doesn't identify unit, hence we report unit as 'literal'. For e.g. the second part of "100ac" gets reported as "literal" instead of "compact" -### Android 9 and older (SDK < 29) +#### Android 9 and older (SDK < 29) - There are some failures likely due to older Unicode and CLDR version, which are hard to generalize. Some examples are, - `Intl.NumberFormat`: 'Percent' is not accepted as a unit. - `Intl.NumberFormat`: unit symbols difference, kph vs km/h - Some issue in significant digit precision, which is not yet looked into the details. -### Android 8.0 – 8.1 and older (SDK < 28) +#### Android 8.0 – 8.1 and older (SDK < 28) - `Intl.getCanonicalLocales`: Unicode/CLDR version differences results in some variances. e.g. und-u-tz-utc vs. und-u-tz-gmt. - `Intl.NumberFormat`: CompactFormatter doesn't respect the precision inputs. -### Android 7.0 - 7.1 and older (SDK < 26) +#### Android 7.0 - 7.1 and older (SDK < 26) - `Intl.getCanonicalLocales`: Unicode/CLDR version differences results in some variances. e.g. und-u-ms-imperial vs. und-u-ms-uksystem. -### Android 7.0 - 7.1 and older (SDK < 24) +#### Android 7.0 - 7.1 and older (SDK < 24) - `Intl.Collator`: Doesn't canonically decompose the input strings. Canonically equivalent string with non-identical code points may not match. - `Intl.getCanonicalLocales`: Unicode/CLDR version differences results in some variances. e.g. und-u-ca-ethiopic-amete-alem vs. und-u-ca-ethioaa, und-u-ks-primary vs. und-u-ks-level1. @@ -112,11 +114,11 @@ One popular implementation strategy followed by other engines, is to bundle an i - `Intl.NumberFormat`: There are issues in the precision configuration due to lack of APIs. - `Intl.DateFormat`: There are issues with the calendar configuration which needs to be dug into. -### SDK < 21 and older +#### SDK < 21 and older On platforms before 21, `Locale.forLanguageTag()` is not available, hence we can't construct `java.util.Locale` object from locale tag. Hence, we fallback to English for any locale input. -# Internationalization framework in Android Platform +## Internationalization framework in Android Platform Our implementation is essentially projecting the Android platform provided internationalization facilities through the ECMA-402 specified services. It implies that the results of running the same code may vary between devices running different versions of Android. @@ -124,7 +126,7 @@ Android platform internationalization libraries have been based on [ICU4j projec The following table summarizes ICU, CLDR and Unicode versions available on the Android platforms. -### Platform 24+ where ICU4j APIs are available. +#### Platform 24+ where ICU4j APIs are available. | Android Platform Version | ICU | Unicode | CLDR | --- | --- | --- | --- | @@ -135,7 +137,7 @@ The following table summarizes ICU, CLDR and Unicode versions available on the A | Android 7.0 - 7.1 (API levels 24 - 25) | ICU4j 56 ([ref](https://developer.android.com/guide/topics/resources/internationalization))| CLDR 28 | Unicode 8.0 | -### Pre-24 platforms +#### Pre-24 platforms | Android Platform Version | ICU | Unicode | CLDR | --- | --- | --- | --- | @@ -157,7 +159,7 @@ In summary, 4. Platform 30 has introduced classes under [`android.icu.number`](https://developer.android.com/reference/android/icu/util/package-summary) namespace which will majorly improve our `Intl.NumberFormat` implementation -# Impact on Application Size +## Impact on Application Size The following numbers are measured using a test application which takes dependency on the Hermes library to evaluate a JavaScript snippet. Essentially, enabling Intl APIs adds 57-62K per ABI. @@ -193,3 +195,65 @@ And finally, this is the increase in the final npm package, | **NPM Package** | **NOINTL** | **INTL** | **DIFF** | **PERC** | | --- | --- | --- | --- | --- | | hermes | 214447973 | 219291220 | 4,843,247 | 2.26% | + +# Windows + +The Windows Intl API's are a work in progress and currently very limited in support. We'll keep track of the status of API's here as we work through them. + +## ECMA-402 Compliance +### Supported +- `Intl.DateTimeFormat` + - `Intl.DateTimeFormat.supportedLocalesOf` + - `Intl.DateTimeFormat.prototype.format` + - `Intl.DateTimeFormat.prototype.resolvedOptions` + +- `Intl.getCanonicalLocales` + +### Not yet supported + +- `Intl.DateTimeFormat` + - `Intl.DateTimeFormat.prototype.formatToParts` + +- `Intl.DateTimeFormat` properties + - [`dayPeriod`] + - [`fractionalSecondDigits`] + - [`formatMatcher`] + +- `Intl.Collator` + - `Intl.Collator.supportedLocalesOf` + - `Intl.Collator.prototype.compare` + - `Intl.Collator.prototype.resolvedOptions` + +- `Intl.NumberFormat` + - `Intl.NumberFormat.supportedLocalesOf` + - `Intl.NumberFormat.prototype.format` + - `Intl.NumberFormat.prototype.formatToParts` + - `Intl.NumberFormat.prototype.resolvedOptions` + +- `String.prototype` + - `localeCompare` + - `toLocaleLowerCase` + - `toLocaleUpperCase` + +- `Array.prototype` + - `toLocaleString` + +- `Number.prototype` + - `toLocaleString` + +- `Date.prototype` + - `toLocaleString` + - `toLocaleDateString` + - `toLocaleTimeString` + +- [`Intl.PluralRules`](https://tc39.es/ecma402/#pluralrules-objects) + +- [`Intl.RelativeTimeFormat`](https://tc39.es/ecma402/#relativetimeformat-objects) + +- [`Intl.DisplayNames`](https://tc39.es/proposal-intl-displaynames/#sec-intl-displaynames-constructor) + +- [`Intl.ListFormat`](https://tc39.es/proposal-intl-list-format/#sec-intl-listformat-constructor) + +- [`Intl.Locale`](https://tc39.es/ecma402/#sec-intl-locale-constructor) + +- [`BigInt.prototype.toLocaleString`](https://tc39.es/ecma402/#sup-bigint.prototype.tolocalestring) From 271bdc8d12927e2860d2109bfd5784936c9c77a6 Mon Sep 17 00:00:00 2001 From: TatianaKapos Date: Mon, 28 Nov 2022 12:06:21 -0800 Subject: [PATCH 13/14] fix camelCase --- lib/Platform/Intl/PlatformIntlWindows.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/Platform/Intl/PlatformIntlWindows.cpp b/lib/Platform/Intl/PlatformIntlWindows.cpp index eeb06da0947..0b8d6b824ef 100644 --- a/lib/Platform/Intl/PlatformIntlWindows.cpp +++ b/lib/Platform/Intl/PlatformIntlWindows.cpp @@ -120,7 +120,7 @@ vm::CallResult NormalizeLanguageTag( } // https://tc39.es/ecma402/#sec-canonicalizelocalelist -vm::CallResult> CanonicalizeLocaleList( +vm::CallResult> canonicalizeLocaleList( vm::Runtime &runtime, const std::vector &locales) { // 1. If locales is undefined, then a. Return a new empty list @@ -214,7 +214,7 @@ std::vector supportedLocales( vm::CallResult> getCanonicalLocales( vm::Runtime &runtime, const std::vector &locales) { - return CanonicalizeLocaleList(runtime, locales); + return canonicalizeLocaleList(runtime, locales); } // Not yet implemented. Tracked by @@ -351,7 +351,7 @@ vm::ExecutionStatus DateTimeFormatWindows::initialize( vm::Runtime &runtime, const std::vector &locales, const Options &inputOptions) noexcept { - auto requestedLocalesRes = CanonicalizeLocaleList(runtime, locales); + auto requestedLocalesRes = canonicalizeLocaleList(runtime, locales); if (requestedLocalesRes.getStatus() == vm::ExecutionStatus::EXCEPTION) { return requestedLocalesRes.getStatus(); } @@ -644,6 +644,8 @@ vm::ExecutionStatus DateTimeFormatWindows::initialize( // 42. Set dateTimeFormat.[[RangePatterns]] to rangePatterns. // 43. Return dateTimeFormat + //UDateFormat *test = getUDateFormatter(runtime); + //dtf_ = std::make_unique(test); dtf_ = getUDateFormatter(runtime); return vm::ExecutionStatus::RETURNED; } From c97f3115ea3305d1e051e332fa79b9da2c52cbb4 Mon Sep 17 00:00:00 2001 From: TatianaKapos Date: Mon, 28 Nov 2022 15:16:15 -0800 Subject: [PATCH 14/14] remove old comment --- lib/Platform/Intl/PlatformIntlWindows.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/Platform/Intl/PlatformIntlWindows.cpp b/lib/Platform/Intl/PlatformIntlWindows.cpp index 0b8d6b824ef..a6a84f9e8b3 100644 --- a/lib/Platform/Intl/PlatformIntlWindows.cpp +++ b/lib/Platform/Intl/PlatformIntlWindows.cpp @@ -643,9 +643,7 @@ vm::ExecutionStatus DateTimeFormatWindows::initialize( // 41. Set dateTimeFormat.[[Pattern]] to pattern. // 42. Set dateTimeFormat.[[RangePatterns]] to rangePatterns. // 43. Return dateTimeFormat - - //UDateFormat *test = getUDateFormatter(runtime); - //dtf_ = std::make_unique(test); + dtf_ = getUDateFormatter(runtime); return vm::ExecutionStatus::RETURNED; }