Title: [281374] trunk
Revision
281374
Author
ysuz...@apple.com
Date
2021-08-21 07:33:08 -0700 (Sat, 21 Aug 2021)

Log Message

[JSC] Intl Locale Info
https://bugs.webkit.org/show_bug.cgi?id=227830

Reviewed by Ross Kirsling.

JSTests:

* stress/intl-locale-info.js: Added.
(shouldBe):
(throw.new.Error):
(let.enGB.new.Intl.Locale.shouldBe):
(let.l.new.Intl.Locale.shouldBe):
* test262/config.yaml:

Source/_javascript_Core:

This patch implements Intl.Locale's extension (Intl Locale Info proposal)[1], which is already stage 3.
Intl.Locale#{calendars,collations,hourCycles,numberingSystems,timeZones} can return array of preferred
configuration for the given locale. And Intl.Locale#textInfo can return text layout direction and Intl.Locale#weekInfo
can return weekday information (e.g. when weekend starts).

[1]: https://github.com/tc39/proposal-intl-locale-info

* runtime/IntlLocale.cpp:
(JSC::createArrayFromStringVector):
(JSC::IntlLocale::calendars):
(JSC::IntlLocale::collations):
(JSC::IntlLocale::hourCycles):
(JSC::IntlLocale::numberingSystems):
(JSC::IntlLocale::timeZones):
(JSC::IntlLocale::textInfo):
(JSC::IntlLocale::weekInfo):
* runtime/IntlLocale.h:
* runtime/IntlLocalePrototype.cpp:
(JSC::JSC_DEFINE_CUSTOM_GETTER):

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (281373 => 281374)


--- trunk/JSTests/ChangeLog	2021-08-21 13:49:32 UTC (rev 281373)
+++ trunk/JSTests/ChangeLog	2021-08-21 14:33:08 UTC (rev 281374)
@@ -1,5 +1,19 @@
 2021-08-21  Yusuke Suzuki  <ysuz...@apple.com>
 
+        [JSC] Intl Locale Info
+        https://bugs.webkit.org/show_bug.cgi?id=227830
+
+        Reviewed by Ross Kirsling.
+
+        * stress/intl-locale-info.js: Added.
+        (shouldBe):
+        (throw.new.Error):
+        (let.enGB.new.Intl.Locale.shouldBe):
+        (let.l.new.Intl.Locale.shouldBe):
+        * test262/config.yaml:
+
+2021-08-21  Yusuke Suzuki  <ysuz...@apple.com>
+
         [JSC] Extend Intl TimeZoneName Option
         https://bugs.webkit.org/show_bug.cgi?id=227831
 

Added: trunk/JSTests/stress/intl-locale-info.js (0 => 281374)


--- trunk/JSTests/stress/intl-locale-info.js	                        (rev 0)
+++ trunk/JSTests/stress/intl-locale-info.js	2021-08-21 14:33:08 UTC (rev 281374)
@@ -0,0 +1,106 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error(`expected ${expected} but got ${actual}`);
+}
+
+{
+    let he = new Intl.Locale("he")
+    shouldBe(JSON.stringify(he.weekInfo), `{"firstDay":7,"weekendStart":5,"weekendEnd":6,"minimalDays":1}`);
+    let af = new Intl.Locale("af")
+    shouldBe(JSON.stringify(af.weekInfo), `{"firstDay":7,"weekendStart":6,"weekendEnd":7,"minimalDays":1}`);
+    let enGB = new Intl.Locale("en-GB")
+    shouldBe(JSON.stringify(enGB.weekInfo), `{"firstDay":1,"weekendStart":6,"weekendEnd":7,"minimalDays":4}`);
+}
+{
+    let l = new Intl.Locale("ar")
+    shouldBe(JSON.stringify(l.textInfo), `{"direction":"rtl"}`);
+}
+{
+    let locale = new Intl.Locale("ar")
+    shouldBe(JSON.stringify(locale.calendars), `["gregory","coptic","islamic","islamicc","islamic-tbla"]`);
+    shouldBe(JSON.stringify(locale.collations), `["compat","emoji","eor"]`);
+    shouldBe(JSON.stringify(locale.hourCycles), `["h12"]`);
+    let ns = JSON.stringify(locale.numberingSystems);
+    shouldBe(ns === `["arab"]` || ns === `["latn"]`, true);
+    shouldBe(locale.timeZones, undefined);
+}
+{
+    let locale = new Intl.Locale("ar-EG")
+    shouldBe(JSON.stringify(locale.calendars), `["gregory","coptic","islamic","islamicc","islamic-tbla"]`);
+    shouldBe(JSON.stringify(locale.collations), `["compat","emoji","eor"]`);
+    shouldBe(JSON.stringify(locale.hourCycles), `["h12"]`);
+    shouldBe(JSON.stringify(locale.numberingSystems), `["arab"]`);
+    shouldBe(JSON.stringify(locale.timeZones), `["Africa/Cairo"]`);
+}
+{
+    let locale = new Intl.Locale("ar-SA")
+    shouldBe(JSON.stringify(locale.calendars), `["islamic-umalqura","islamic-rgsa","islamic","gregory"]`);
+    shouldBe(JSON.stringify(locale.collations), `["compat","emoji","eor"]`);
+    shouldBe(JSON.stringify(locale.hourCycles), `["h12"]`);
+    shouldBe(JSON.stringify(locale.numberingSystems), `["arab"]`);
+    shouldBe(JSON.stringify(locale.timeZones), `["Asia/Riyadh"]`);
+}
+{
+    let locale = new Intl.Locale("ja")
+    shouldBe(JSON.stringify(locale.calendars), `["gregory","japanese"]`);
+    shouldBe(JSON.stringify(locale.collations), `["unihan","emoji","eor"]`);
+    shouldBe(JSON.stringify(locale.hourCycles), `["h23"]`);
+    shouldBe(JSON.stringify(locale.numberingSystems), `["latn"]`);
+    shouldBe(locale.timeZones, undefined);
+}
+{
+    let locale = new Intl.Locale("ja-JP")
+    shouldBe(JSON.stringify(locale.calendars), `["gregory","japanese"]`);
+    shouldBe(JSON.stringify(locale.collations), `["unihan","emoji","eor"]`);
+    shouldBe(JSON.stringify(locale.hourCycles), `["h23"]`);
+    shouldBe(JSON.stringify(locale.numberingSystems), `["latn"]`);
+    shouldBe(JSON.stringify(locale.timeZones), `["Asia/Tokyo"]`);
+}
+{
+    let locale = new Intl.Locale("en-US")
+    shouldBe(JSON.stringify(locale.calendars), `["gregory"]`);
+    shouldBe(JSON.stringify(locale.collations), `["emoji","eor"]`);
+    shouldBe(JSON.stringify(locale.hourCycles), `["h12"]`);
+    shouldBe(JSON.stringify(locale.numberingSystems), `["latn"]`);
+    shouldBe(JSON.stringify(locale.timeZones), `["America/Adak","America/Anchorage","America/Boise","America/Chicago","America/Denver","America/Detroit","America/Indiana/Knox","America/Indiana/Marengo","America/Indiana/Petersburg","America/Indiana/Tell_City","America/Indiana/Vevay","America/Indiana/Vincennes","America/Indiana/Winamac","America/Indianapolis","America/Juneau","America/Kentucky/Monticello","America/Los_Angeles","America/Louisville","America/Menominee","America/Metlakatla","America/New_York","America/Nome","America/North_Dakota/Beulah","America/North_Dakota/Center","America/North_Dakota/New_Salem","America/Phoenix","America/Sitka","America/Yakutat","Pacific/Honolulu"]`);
+}
+{
+    let locale = new Intl.Locale("en-NZ")
+    shouldBe(JSON.stringify(locale.calendars), `["gregory"]`);
+    shouldBe(JSON.stringify(locale.collations), `["emoji","eor"]`);
+    shouldBe(JSON.stringify(locale.hourCycles), `["h12"]`);
+    shouldBe(JSON.stringify(locale.numberingSystems), `["latn"]`);
+    shouldBe(JSON.stringify(locale.timeZones), `["Pacific/Auckland","Pacific/Chatham"]`);
+}
+{
+    let locale = new Intl.Locale("zh")
+    shouldBe(JSON.stringify(locale.calendars), `["gregory","chinese"]`);
+    shouldBe(JSON.stringify(locale.collations), `["pinyin","big5han","gb2312","stroke","unihan","zhuyin","emoji","eor"]`);
+    shouldBe(JSON.stringify(locale.hourCycles), `["h12"]`);
+    shouldBe(JSON.stringify(locale.numberingSystems), `["latn"]`);
+    shouldBe(locale.timeZones, undefined);
+}
+{
+    let locale = new Intl.Locale("zh-TW")
+    shouldBe(JSON.stringify(locale.calendars), `["gregory","roc","chinese"]`);
+    shouldBe(JSON.stringify(locale.collations), `["stroke","big5han","gb2312","pinyin","unihan","zhuyin","emoji","eor"]`);
+    shouldBe(JSON.stringify(locale.hourCycles), `["h12"]`);
+    shouldBe(JSON.stringify(locale.numberingSystems), `["latn"]`);
+    shouldBe(JSON.stringify(locale.timeZones), `["Asia/Taipei"]`);
+}
+{
+    let locale = new Intl.Locale("zh-HK")
+    shouldBe(JSON.stringify(locale.calendars), `["gregory","chinese"]`);
+    shouldBe(JSON.stringify(locale.collations), `["stroke","big5han","gb2312","pinyin","unihan","zhuyin","emoji","eor"]`);
+    shouldBe(JSON.stringify(locale.hourCycles), `["h12"]`);
+    shouldBe(JSON.stringify(locale.numberingSystems), `["latn"]`);
+    shouldBe(JSON.stringify(locale.timeZones), `["Asia/Hong_Kong"]`);
+}
+{
+    let locale = new Intl.Locale("fa")
+    shouldBe(JSON.stringify(locale.calendars), `["persian","gregory","islamic","islamicc","islamic-tbla"]`);
+    shouldBe(JSON.stringify(locale.collations), `["emoji","eor"]`);
+    shouldBe(JSON.stringify(locale.hourCycles), `["h23"]`);
+    shouldBe(JSON.stringify(locale.numberingSystems), `["arabext"]`);
+    shouldBe(locale.timeZones, undefined);
+}

Modified: trunk/JSTests/test262/config.yaml (281373 => 281374)


--- trunk/JSTests/test262/config.yaml	2021-08-21 13:49:32 UTC (rev 281373)
+++ trunk/JSTests/test262/config.yaml	2021-08-21 14:33:08 UTC (rev 281374)
@@ -24,7 +24,6 @@
     # https://bugs.webkit.org/show_bug.cgi?id=174931
     - regexp-lookbehind
     - cleanupSome
-    - Intl.Locale-info
     - resizable-arraybuffer
     - Object.hasOwn
     - Temporal

Modified: trunk/Source/_javascript_Core/ChangeLog (281373 => 281374)


--- trunk/Source/_javascript_Core/ChangeLog	2021-08-21 13:49:32 UTC (rev 281373)
+++ trunk/Source/_javascript_Core/ChangeLog	2021-08-21 14:33:08 UTC (rev 281374)
@@ -1,5 +1,32 @@
 2021-08-21  Yusuke Suzuki  <ysuz...@apple.com>
 
+        [JSC] Intl Locale Info
+        https://bugs.webkit.org/show_bug.cgi?id=227830
+
+        Reviewed by Ross Kirsling.
+
+        This patch implements Intl.Locale's extension (Intl Locale Info proposal)[1], which is already stage 3.
+        Intl.Locale#{calendars,collations,hourCycles,numberingSystems,timeZones} can return array of preferred
+        configuration for the given locale. And Intl.Locale#textInfo can return text layout direction and Intl.Locale#weekInfo
+        can return weekday information (e.g. when weekend starts).
+
+        [1]: https://github.com/tc39/proposal-intl-locale-info
+
+        * runtime/IntlLocale.cpp:
+        (JSC::createArrayFromStringVector):
+        (JSC::IntlLocale::calendars):
+        (JSC::IntlLocale::collations):
+        (JSC::IntlLocale::hourCycles):
+        (JSC::IntlLocale::numberingSystems):
+        (JSC::IntlLocale::timeZones):
+        (JSC::IntlLocale::textInfo):
+        (JSC::IntlLocale::weekInfo):
+        * runtime/IntlLocale.h:
+        * runtime/IntlLocalePrototype.cpp:
+        (JSC::JSC_DEFINE_CUSTOM_GETTER):
+
+2021-08-21  Yusuke Suzuki  <ysuz...@apple.com>
+
         [JSC] Extend Intl TimeZoneName Option
         https://bugs.webkit.org/show_bug.cgi?id=227831
 

Modified: trunk/Source/_javascript_Core/runtime/IntlCollator.cpp (281373 => 281374)


--- trunk/Source/_javascript_Core/runtime/IntlCollator.cpp	2021-08-21 13:49:32 UTC (rev 281373)
+++ trunk/Source/_javascript_Core/runtime/IntlCollator.cpp	2021-08-21 14:33:08 UTC (rev 281374)
@@ -90,23 +90,17 @@
         UErrorCode status = U_ZERO_ERROR;
         auto enumeration = std::unique_ptr<UEnumeration, ICUDeleter<uenum_close>>(ucol_getKeywordValuesForLocale("collation", locale.utf8().data(), false, &status));
         if (U_SUCCESS(status)) {
-            const char* collation;
-            while ((collation = uenum_next(enumeration.get(), nullptr, &status)) && U_SUCCESS(status)) {
+            const char* pointer;
+            int32_t length = 0;
+            while ((pointer = uenum_next(enumeration.get(), &length, &status)) && U_SUCCESS(status)) {
                 // 10.2.3 "The values "standard" and "search" must not be used as elements in any [[sortLocaleData]][locale].co and [[searchLocaleData]][locale].co array."
-                if (!strcmp(collation, "standard") || !strcmp(collation, "search"))
+                String collation(pointer, length);
+                if (collation == "standard"_s || collation == "search"_s)
                     continue;
-
-                // Map keyword values to BCP 47 equivalents.
-                if (!strcmp(collation, "dictionary"))
-                    keyLocaleData.append("dict"_s);
-                else if (!strcmp(collation, "gb2312han"))
-                    keyLocaleData.append("gb2312"_s);
-                else if (!strcmp(collation, "phonebook"))
-                    keyLocaleData.append("phonebk"_s);
-                else if (!strcmp(collation, "traditional"))
-                    keyLocaleData.append("trad"_s);
+                if (auto mapped = mapICUCollationKeywordToBCP47(collation))
+                    keyLocaleData.append(WTFMove(mapped.value()));
                 else
-                    keyLocaleData.append(collation);
+                    keyLocaleData.append(WTFMove(collation));
             }
         }
         break;

Modified: trunk/Source/_javascript_Core/runtime/IntlDateTimeFormat.cpp (281373 => 281374)


--- trunk/Source/_javascript_Core/runtime/IntlDateTimeFormat.cpp	2021-08-21 13:49:32 UTC (rev 281373)
+++ trunk/Source/_javascript_Core/runtime/IntlDateTimeFormat.cpp	2021-08-21 14:33:08 UTC (rev 281374)
@@ -165,13 +165,8 @@
             ASSERT(U_SUCCESS(status));
             String calendar = String(availableName, nameLength);
             keyLocaleData.append(calendar);
-            // Ensure aliases used in language tag are allowed.
-            if (calendar == "gregorian")
-                keyLocaleData.append("gregory"_s);
-            else if (calendar == "islamic-civil")
-                keyLocaleData.append("islamicc"_s);
-            else if (calendar == "ethiopic-amete-alem")
-                keyLocaleData.append("ethioaa"_s);
+            if (auto mapped = mapICUCalendarKeywordToBCP47(calendar))
+                keyLocaleData.append(WTFMove(mapped.value()));
         }
         uenum_close(calendars);
         break;

Modified: trunk/Source/_javascript_Core/runtime/IntlLocale.cpp (281373 => 281374)


--- trunk/Source/_javascript_Core/runtime/IntlLocale.cpp	2021-08-21 13:49:32 UTC (rev 281373)
+++ trunk/Source/_javascript_Core/runtime/IntlLocale.cpp	2021-08-21 14:33:08 UTC (rev 281374)
@@ -29,7 +29,12 @@
 
 #include "IntlObjectInlines.h"
 #include "JSCInlines.h"
+#include <unicode/ucal.h>
+#include <unicode/ucol.h>
+#include <unicode/udat.h>
+#include <unicode/udatpg.h>
 #include <unicode/uloc.h>
+#include <unicode/unumsys.h>
 #include <wtf/unicode/icu/ICUHelpers.h>
 
 namespace JSC {
@@ -532,4 +537,333 @@
     return m_numeric;
 }
 
+static inline JSArray* createArrayFromStringVector(JSGlobalObject* globalObject, Vector<String, 1>&& elements)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    JSArray* result = JSArray::tryCreate(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), elements.size());
+    if (!result) {
+        throwOutOfMemoryError(globalObject, scope);
+        return nullptr;
+    }
+    for (unsigned index = 0; index < elements.size(); ++index) {
+        result->putDirectIndex(globalObject, index, jsString(vm, WTFMove(elements[index])));
+        RETURN_IF_EXCEPTION(scope, { });
+    }
+    return result;
+}
+
+JSArray* IntlLocale::calendars(JSGlobalObject* globalObject)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    Vector<String, 1> elements;
+
+    String preferred = calendar();
+    if (!preferred.isEmpty()) {
+        elements.append(WTFMove(preferred));
+        RELEASE_AND_RETURN(scope, createArrayFromStringVector(globalObject, WTFMove(elements)));
+    }
+
+    UErrorCode status = U_ZERO_ERROR;
+    constexpr bool commonlyUsed = true;
+    auto calendars = std::unique_ptr<UEnumeration, ICUDeleter<uenum_close>>(ucal_getKeywordValuesForLocale("calendar", m_localeID.data(), commonlyUsed, &status));
+    if (!U_SUCCESS(status)) {
+        throwTypeError(globalObject, scope, "invalid locale"_s);
+        return nullptr;
+    }
+
+    const char* pointer;
+    int32_t length = 0;
+    while ((pointer = uenum_next(calendars.get(), &length, &status)) && U_SUCCESS(status)) {
+        String calendar(pointer, length);
+        if (auto mapped = mapICUCalendarKeywordToBCP47(calendar))
+            elements.append(WTFMove(mapped.value()));
+        else
+            elements.append(WTFMove(calendar));
+    }
+    if (!U_SUCCESS(status)) {
+        throwTypeError(globalObject, scope, "invalid locale"_s);
+        return nullptr;
+    }
+
+    RELEASE_AND_RETURN(scope, createArrayFromStringVector(globalObject, WTFMove(elements)));
+}
+
+JSArray* IntlLocale::collations(JSGlobalObject* globalObject)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    Vector<String, 1> elements;
+
+    String preferred = collation();
+    if (!preferred.isEmpty()) {
+        elements.append(WTFMove(preferred));
+        RELEASE_AND_RETURN(scope, createArrayFromStringVector(globalObject, WTFMove(elements)));
+    }
+
+    UErrorCode status = U_ZERO_ERROR;
+    constexpr bool commonlyUsed = true;
+    auto enumeration = std::unique_ptr<UEnumeration, ICUDeleter<uenum_close>>(ucol_getKeywordValuesForLocale("collation", m_localeID.data(), commonlyUsed, &status));
+    if (!U_SUCCESS(status)) {
+        throwTypeError(globalObject, scope, "invalid locale"_s);
+        return nullptr;
+    }
+
+    const char* pointer;
+    int32_t length = 0;
+    while ((pointer = uenum_next(enumeration.get(), &length, &status)) && U_SUCCESS(status)) {
+        String collation(pointer, length);
+        // 1.1.3 step 4, The values "standard" and "search" must be excluded from list.
+        if (collation == "standard"_s || collation == "search"_s)
+            continue;
+        if (auto mapped = mapICUCollationKeywordToBCP47(collation))
+            elements.append(WTFMove(mapped.value()));
+        else
+            elements.append(WTFMove(collation));
+    }
+    if (!U_SUCCESS(status)) {
+        throwTypeError(globalObject, scope, "invalid locale"_s);
+        return nullptr;
+    }
+
+    RELEASE_AND_RETURN(scope, createArrayFromStringVector(globalObject, WTFMove(elements)));
+}
+
+JSArray* IntlLocale::hourCycles(JSGlobalObject* globalObject)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    Vector<String, 1> elements;
+
+    String preferred = hourCycle();
+    if (!preferred.isEmpty()) {
+        elements.append(WTFMove(preferred));
+        RELEASE_AND_RETURN(scope, createArrayFromStringVector(globalObject, WTFMove(elements)));
+    }
+
+    UErrorCode status = U_ZERO_ERROR;
+    auto generator = std::unique_ptr<UDateTimePatternGenerator, ICUDeleter<udatpg_close>>(udatpg_open(m_localeID.data(), &status));
+    if (U_FAILURE(status))
+        return nullptr;
+
+    // Use "j" skeleton and parse pattern to retrieve the configured hour-cycle information.
+    constexpr const UChar skeleton[] = { 'j', 0 };
+    Vector<UChar, 32> pattern;
+    status = callBufferProducingFunction(udatpg_getBestPatternWithOptions, generator.get(), skeleton, 1, UDATPG_MATCH_HOUR_FIELD_LENGTH, pattern);
+    if (!U_SUCCESS(status)) {
+        throwTypeError(globalObject, scope, "invalid locale"_s);
+        return nullptr;
+    }
+
+    for (unsigned i = 0; i < pattern.size(); ++i) {
+        UChar currentCharacter = pattern[i];
+        if (!isASCIIAlpha(currentCharacter))
+            continue;
+
+        while (i + 1 < pattern.size() && pattern[i + 1] == currentCharacter)
+            ++i;
+
+        switch (currentCharacter) {
+        case 'h': {
+            elements.append("h12"_s);
+            RELEASE_AND_RETURN(scope, createArrayFromStringVector(globalObject, WTFMove(elements)));
+        }
+        case 'H': {
+            elements.append("h23"_s);
+            RELEASE_AND_RETURN(scope, createArrayFromStringVector(globalObject, WTFMove(elements)));
+        }
+        case 'k': {
+            elements.append("h24"_s);
+            RELEASE_AND_RETURN(scope, createArrayFromStringVector(globalObject, WTFMove(elements)));
+        }
+        case 'K': {
+            elements.append("h11"_s);
+            RELEASE_AND_RETURN(scope, createArrayFromStringVector(globalObject, WTFMove(elements)));
+        }
+        default:
+            break;
+        }
+    }
+
+    RELEASE_AND_RETURN(scope, createArrayFromStringVector(globalObject, WTFMove(elements)));
+}
+
+JSArray* IntlLocale::numberingSystems(JSGlobalObject* globalObject)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    Vector<String, 1> elements;
+    String preferred = numberingSystem();
+    if (!preferred.isEmpty()) {
+        elements.append(WTFMove(preferred));
+        RELEASE_AND_RETURN(scope, createArrayFromStringVector(globalObject, WTFMove(elements)));
+    }
+
+    UErrorCode status = U_ZERO_ERROR;
+    auto numberingSystem = std::unique_ptr<UNumberingSystem, ICUDeleter<unumsys_close>>(unumsys_open(m_localeID.data(), &status));
+    if (!U_SUCCESS(status)) {
+        throwTypeError(globalObject, scope, "invalid locale"_s);
+        return nullptr;
+    }
+    elements.append(unumsys_getName(numberingSystem.get()));
+
+    RELEASE_AND_RETURN(scope, createArrayFromStringVector(globalObject, WTFMove(elements)));
+}
+
+JSValue IntlLocale::timeZones(JSGlobalObject* globalObject)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    Vector<String, 1> elements;
+
+    // 11.6-3 Let region be the substring of locale corresponding to the unicode_region_subtag production of the unicode_language_id.
+    String region = this->region();
+    if (region.isEmpty())
+        return jsUndefined();
+
+    UErrorCode status = U_ZERO_ERROR;
+    auto enumeration = std::unique_ptr<UEnumeration, ICUDeleter<uenum_close>>(ucal_openTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, region.utf8().data(), nullptr, &status));
+    if (!U_SUCCESS(status)) {
+        throwTypeError(globalObject, scope, "invalid locale"_s);
+        return { };
+    }
+
+    int32_t length;
+    const char* collation;
+    while ((collation = uenum_next(enumeration.get(), &length, &status)) && U_SUCCESS(status))
+        elements.constructAndAppend(collation, length);
+    if (!U_SUCCESS(status)) {
+        throwTypeError(globalObject, scope, "invalid locale"_s);
+        return { };
+    }
+
+    RELEASE_AND_RETURN(scope, createArrayFromStringVector(globalObject, WTFMove(elements)));
+}
+
+JSObject* IntlLocale::textInfo(JSGlobalObject* globalObject)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    UErrorCode status = U_ZERO_ERROR;
+    ULayoutType layout = uloc_getCharacterOrientation(m_localeID.data(), &status);
+    if (!U_SUCCESS(status)) {
+        throwTypeError(globalObject, scope, "invalid locale"_s);
+        return nullptr;
+    }
+
+    JSString* layoutString = nullptr;
+    switch (layout) {
+    default:
+    case ULOC_LAYOUT_LTR:
+        layoutString = jsString(vm, "ltr"_s);
+        break;
+    case ULOC_LAYOUT_RTL:
+        layoutString = jsString(vm, "rtl"_s);
+        break;
+    case ULOC_LAYOUT_TTB:
+        layoutString = jsString(vm, "ttb"_s);
+        break;
+    case ULOC_LAYOUT_BTT:
+        layoutString = jsString(vm, "btt"_s);
+        break;
+    }
+
+    JSObject* result = constructEmptyObject(globalObject);
+    result->putDirect(vm, Identifier::fromString(vm, "direction"), layoutString);
+    return result;
+}
+
+JSObject* IntlLocale::weekInfo(JSGlobalObject* globalObject)
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    UErrorCode status = U_ZERO_ERROR;
+    auto calendar = std::unique_ptr<UCalendar, ICUDeleter<ucal_close>>(ucal_open(nullptr, 0, m_localeID.data(), UCAL_DEFAULT, &status));
+    if (!U_SUCCESS(status)) {
+        throwTypeError(globalObject, scope, "invalid locale"_s);
+        return nullptr;
+    }
+
+    int32_t firstDayOfWeek = ucal_getAttribute(calendar.get(), UCAL_FIRST_DAY_OF_WEEK);
+    int32_t minimalDays = ucal_getAttribute(calendar.get(), UCAL_MINIMAL_DAYS_IN_FIRST_WEEK);
+
+    auto canonicalizeDayOfWeekType = [](UCalendarWeekdayType type) {
+        switch (type) {
+        // UCAL_WEEKEND_ONSET is a day that starts as a weekday and transitions to the weekend. It means this is WeekDay.
+        case UCAL_WEEKEND_ONSET:
+        case UCAL_WEEKDAY:
+            return UCAL_WEEKDAY;
+        // UCAL_WEEKEND_CEASE is a day that starts as the weekend and transitions to a weekday. It means this is WeekEnd.
+        case UCAL_WEEKEND_CEASE:
+        case UCAL_WEEKEND:
+            return UCAL_WEEKEND;
+        default:
+            return UCAL_WEEKEND;
+        }
+    };
+
+    static_assert(UCAL_SUNDAY == 1);
+    static_assert(UCAL_SATURDAY == 7);
+    UCalendarWeekdayType previous = canonicalizeDayOfWeekType(ucal_getDayOfWeekType(calendar.get(), UCAL_SATURDAY, &status));
+    if (!U_SUCCESS(status)) {
+        throwTypeError(globalObject, scope, "invalid locale"_s);
+        return nullptr;
+    }
+
+    int32_t weekendStart = 0;
+    int32_t weekendEnd = 0;
+    for (int32_t day = UCAL_SUNDAY; day <= UCAL_SATURDAY; ++day) {
+        UCalendarWeekdayType type = canonicalizeDayOfWeekType(ucal_getDayOfWeekType(calendar.get(), static_cast<UCalendarDaysOfWeek>(day), &status));
+        if (!U_SUCCESS(status)) {
+            throwTypeError(globalObject, scope, "invalid locale"_s);
+            return nullptr;
+        }
+        if (previous != type) {
+            switch (type) {
+            case UCAL_WEEKDAY: // WeekEnd => WeekDay
+                if (day == UCAL_SUNDAY)
+                    weekendEnd = UCAL_SATURDAY;
+                else
+                    weekendEnd = day - 1;
+                break;
+            case UCAL_WEEKEND: // WeekDay => WeekEnd
+                weekendStart = day;
+                break;
+            default:
+                ASSERT_NOT_REACHED();
+                break;
+            }
+        }
+        previous = type;
+    }
+
+    auto convertUCalendarDaysOfWeekToMondayBasedDay = [](int32_t day) -> int32_t {
+        // Convert from
+        //     Sunday => 1
+        //     Saturday => 7
+        // to
+        //     Monday => 1
+        //     Sunday => 7
+        if (day == UCAL_SUNDAY)
+            return 7;
+        return day - 1;
+    };
+
+    JSObject* result = constructEmptyObject(globalObject);
+    result->putDirect(vm, Identifier::fromString(vm, "firstDay"), jsNumber(convertUCalendarDaysOfWeekToMondayBasedDay(firstDayOfWeek)));
+    result->putDirect(vm, Identifier::fromString(vm, "weekendStart"), jsNumber(convertUCalendarDaysOfWeekToMondayBasedDay(weekendStart)));
+    result->putDirect(vm, Identifier::fromString(vm, "weekendEnd"), jsNumber(convertUCalendarDaysOfWeekToMondayBasedDay(weekendEnd)));
+    result->putDirect(vm, Identifier::fromString(vm, "minimalDays"), jsNumber(minimalDays));
+    return result;
+}
+
 } // namespace JSC

Modified: trunk/Source/_javascript_Core/runtime/IntlLocale.h (281373 => 281374)


--- trunk/Source/_javascript_Core/runtime/IntlLocale.h	2021-08-21 13:49:32 UTC (rev 281373)
+++ trunk/Source/_javascript_Core/runtime/IntlLocale.h	2021-08-21 14:33:08 UTC (rev 281374)
@@ -69,6 +69,14 @@
     const String& numberingSystem();
     TriState numeric();
 
+    JSArray* calendars(JSGlobalObject*);
+    JSArray* collations(JSGlobalObject*);
+    JSArray* hourCycles(JSGlobalObject*);
+    JSArray* numberingSystems(JSGlobalObject*);
+    JSValue timeZones(JSGlobalObject*);
+    JSObject* textInfo(JSGlobalObject*);
+    JSObject* weekInfo(JSGlobalObject*);
+
 private:
     IntlLocale(VM&, Structure*);
     void finishCreation(VM&);

Modified: trunk/Source/_javascript_Core/runtime/IntlLocalePrototype.cpp (281373 => 281374)


--- trunk/Source/_javascript_Core/runtime/IntlLocalePrototype.cpp	2021-08-21 13:49:32 UTC (rev 281373)
+++ trunk/Source/_javascript_Core/runtime/IntlLocalePrototype.cpp	2021-08-21 14:33:08 UTC (rev 281374)
@@ -36,14 +36,21 @@
 static JSC_DECLARE_HOST_FUNCTION(IntlLocalePrototypeFuncToString);
 static JSC_DECLARE_CUSTOM_GETTER(IntlLocalePrototypeGetterBaseName);
 static JSC_DECLARE_CUSTOM_GETTER(IntlLocalePrototypeGetterCalendar);
+static JSC_DECLARE_CUSTOM_GETTER(IntlLocalePrototypeGetterCalendars);
 static JSC_DECLARE_CUSTOM_GETTER(IntlLocalePrototypeGetterCaseFirst);
 static JSC_DECLARE_CUSTOM_GETTER(IntlLocalePrototypeGetterCollation);
+static JSC_DECLARE_CUSTOM_GETTER(IntlLocalePrototypeGetterCollations);
 static JSC_DECLARE_CUSTOM_GETTER(IntlLocalePrototypeGetterHourCycle);
+static JSC_DECLARE_CUSTOM_GETTER(IntlLocalePrototypeGetterHourCycles);
 static JSC_DECLARE_CUSTOM_GETTER(IntlLocalePrototypeGetterNumeric);
 static JSC_DECLARE_CUSTOM_GETTER(IntlLocalePrototypeGetterNumberingSystem);
+static JSC_DECLARE_CUSTOM_GETTER(IntlLocalePrototypeGetterNumberingSystems);
 static JSC_DECLARE_CUSTOM_GETTER(IntlLocalePrototypeGetterLanguage);
 static JSC_DECLARE_CUSTOM_GETTER(IntlLocalePrototypeGetterScript);
 static JSC_DECLARE_CUSTOM_GETTER(IntlLocalePrototypeGetterRegion);
+static JSC_DECLARE_CUSTOM_GETTER(IntlLocalePrototypeGetterTimeZones);
+static JSC_DECLARE_CUSTOM_GETTER(IntlLocalePrototypeGetterTextInfo);
+static JSC_DECLARE_CUSTOM_GETTER(IntlLocalePrototypeGetterWeekInfo);
 
 }
 
@@ -60,14 +67,21 @@
   toString         IntlLocalePrototypeFuncToString           DontEnum|Function 0
   baseName         IntlLocalePrototypeGetterBaseName         DontEnum|ReadOnly|CustomAccessor
   calendar         IntlLocalePrototypeGetterCalendar         DontEnum|ReadOnly|CustomAccessor
+  calendars        IntlLocalePrototypeGetterCalendars        DontEnum|ReadOnly|CustomAccessor
   caseFirst        IntlLocalePrototypeGetterCaseFirst        DontEnum|ReadOnly|CustomAccessor
   collation        IntlLocalePrototypeGetterCollation        DontEnum|ReadOnly|CustomAccessor
+  collations       IntlLocalePrototypeGetterCollations       DontEnum|ReadOnly|CustomAccessor
   hourCycle        IntlLocalePrototypeGetterHourCycle        DontEnum|ReadOnly|CustomAccessor
+  hourCycles       IntlLocalePrototypeGetterHourCycles       DontEnum|ReadOnly|CustomAccessor
   numeric          IntlLocalePrototypeGetterNumeric          DontEnum|ReadOnly|CustomAccessor
   numberingSystem  IntlLocalePrototypeGetterNumberingSystem  DontEnum|ReadOnly|CustomAccessor
+  numberingSystems IntlLocalePrototypeGetterNumberingSystems DontEnum|ReadOnly|CustomAccessor
   language         IntlLocalePrototypeGetterLanguage         DontEnum|ReadOnly|CustomAccessor
   script           IntlLocalePrototypeGetterScript           DontEnum|ReadOnly|CustomAccessor
   region           IntlLocalePrototypeGetterRegion           DontEnum|ReadOnly|CustomAccessor
+  timeZones        IntlLocalePrototypeGetterTimeZones        DontEnum|ReadOnly|CustomAccessor
+  textInfo         IntlLocalePrototypeGetterTextInfo         DontEnum|ReadOnly|CustomAccessor
+  weekInfo         IntlLocalePrototypeGetterWeekInfo         DontEnum|ReadOnly|CustomAccessor
 @end
 */
 
@@ -169,6 +183,19 @@
     RELEASE_AND_RETURN(scope, JSValue::encode(calendar.isNull() ? jsUndefined() : jsString(vm, calendar)));
 }
 
+// https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale.prototype.calendars
+JSC_DEFINE_CUSTOM_GETTER(IntlLocalePrototypeGetterCalendars, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName))
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* locale = jsDynamicCast<IntlLocale*>(vm, JSValue::decode(thisValue));
+    if (!locale)
+        return throwVMTypeError(globalObject, scope, "Intl.Locale.prototype.calendars called on value that's not an object initialized as a Locale"_s);
+
+    RELEASE_AND_RETURN(scope, JSValue::encode(locale->calendars(globalObject)));
+}
+
 // https://tc39.es/ecma402/#sec-Intl.Locale.prototype.caseFirst
 JSC_DEFINE_CUSTOM_GETTER(IntlLocalePrototypeGetterCaseFirst, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName))
 {
@@ -197,6 +224,19 @@
     RELEASE_AND_RETURN(scope, JSValue::encode(collation.isNull() ? jsUndefined() : jsString(vm, collation)));
 }
 
+// https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale.prototype.collations
+JSC_DEFINE_CUSTOM_GETTER(IntlLocalePrototypeGetterCollations, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName))
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* locale = jsDynamicCast<IntlLocale*>(vm, JSValue::decode(thisValue));
+    if (!locale)
+        return throwVMTypeError(globalObject, scope, "Intl.Locale.prototype.collations called on value that's not an object initialized as a Locale"_s);
+
+    RELEASE_AND_RETURN(scope, JSValue::encode(locale->collations(globalObject)));
+}
+
 // https://tc39.es/ecma402/#sec-Intl.Locale.prototype.hourCycle
 JSC_DEFINE_CUSTOM_GETTER(IntlLocalePrototypeGetterHourCycle, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName))
 {
@@ -211,6 +251,19 @@
     RELEASE_AND_RETURN(scope, JSValue::encode(hourCycle.isNull() ? jsUndefined() : jsString(vm, hourCycle)));
 }
 
+// https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale.prototype.hourcycles
+JSC_DEFINE_CUSTOM_GETTER(IntlLocalePrototypeGetterHourCycles, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName))
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* locale = jsDynamicCast<IntlLocale*>(vm, JSValue::decode(thisValue));
+    if (!locale)
+        return throwVMTypeError(globalObject, scope, "Intl.Locale.prototype.hourCycles called on value that's not an object initialized as a Locale"_s);
+
+    RELEASE_AND_RETURN(scope, JSValue::encode(locale->hourCycles(globalObject)));
+}
+
 // https://tc39.es/ecma402/#sec-Intl.Locale.prototype.numeric
 JSC_DEFINE_CUSTOM_GETTER(IntlLocalePrototypeGetterNumeric, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName))
 {
@@ -238,6 +291,19 @@
     RELEASE_AND_RETURN(scope, JSValue::encode(numberingSystem.isNull() ? jsUndefined() : jsString(vm, numberingSystem)));
 }
 
+// https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale.prototype.numberingSystems
+JSC_DEFINE_CUSTOM_GETTER(IntlLocalePrototypeGetterNumberingSystems, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName))
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* locale = jsDynamicCast<IntlLocale*>(vm, JSValue::decode(thisValue));
+    if (!locale)
+        return throwVMTypeError(globalObject, scope, "Intl.Locale.prototype.numberingSystems called on value that's not an object initialized as a Locale"_s);
+
+    RELEASE_AND_RETURN(scope, JSValue::encode(locale->numberingSystems(globalObject)));
+}
+
 // https://tc39.es/ecma402/#sec-Intl.Locale.prototype.language
 JSC_DEFINE_CUSTOM_GETTER(IntlLocalePrototypeGetterLanguage, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName))
 {
@@ -280,4 +346,43 @@
     RELEASE_AND_RETURN(scope, JSValue::encode(region.isEmpty() ? jsUndefined() : jsString(vm, region)));
 }
 
+// https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale.prototype.timezones
+JSC_DEFINE_CUSTOM_GETTER(IntlLocalePrototypeGetterTimeZones, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName))
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* locale = jsDynamicCast<IntlLocale*>(vm, JSValue::decode(thisValue));
+    if (!locale)
+        return throwVMTypeError(globalObject, scope, "Intl.Locale.prototype.timeZones called on value that's not an object initialized as a Locale"_s);
+
+    RELEASE_AND_RETURN(scope, JSValue::encode(locale->timeZones(globalObject)));
+}
+
+// https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale.prototype.textInfo
+JSC_DEFINE_CUSTOM_GETTER(IntlLocalePrototypeGetterTextInfo, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName))
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* locale = jsDynamicCast<IntlLocale*>(vm, JSValue::decode(thisValue));
+    if (!locale)
+        return throwVMTypeError(globalObject, scope, "Intl.Locale.prototype.textInfo called on value that's not an object initialized as a Locale"_s);
+
+    RELEASE_AND_RETURN(scope, JSValue::encode(locale->textInfo(globalObject)));
+}
+
+// https://tc39.es/proposal-intl-locale-info/#sec-Intl.Locale.prototype.weekInfo
+JSC_DEFINE_CUSTOM_GETTER(IntlLocalePrototypeGetterWeekInfo, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName))
+{
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    auto* locale = jsDynamicCast<IntlLocale*>(vm, JSValue::decode(thisValue));
+    if (!locale)
+        return throwVMTypeError(globalObject, scope, "Intl.Locale.prototype.weekInfo called on value that's not an object initialized as a Locale"_s);
+
+    RELEASE_AND_RETURN(scope, JSValue::encode(locale->weekInfo(globalObject)));
+}
+
 } // namespace JSC

Modified: trunk/Source/_javascript_Core/runtime/IntlObject.cpp (281373 => 281374)


--- trunk/Source/_javascript_Core/runtime/IntlObject.cpp	2021-08-21 13:49:32 UTC (rev 281373)
+++ trunk/Source/_javascript_Core/runtime/IntlObject.cpp	2021-08-21 14:33:08 UTC (rev 281374)
@@ -1458,6 +1458,31 @@
 #endif
 }
 
+std::optional<String> mapICUCalendarKeywordToBCP47(const String& calendar)
+{
+    if (calendar == "gregorian"_s)
+        return "gregory"_s;
+    if (calendar == "islamic-civil"_s)
+        return "islamicc"_s;
+    if (calendar == "ethiopic-amete-alem"_s)
+        return "ethioaa"_s;
+    return std::nullopt;
+}
+
+std::optional<String> mapICUCollationKeywordToBCP47(const String& collation)
+{
+    // Map keyword values to BCP 47 equivalents.
+    if (collation == "dictionary"_s)
+        return "dict"_s;
+    if (collation == "gb2312han"_s)
+        return "gb2312"_s;
+    if (collation == "phonebook"_s)
+        return "phonebk"_s;
+    if (collation == "traditional"_s)
+        return "trad"_s;
+    return std::nullopt;
+}
+
 JSC_DEFINE_HOST_FUNCTION(intlObjectFuncGetCanonicalLocales, (JSGlobalObject* globalObject, CallFrame* callFrame))
 {
     // Intl.getCanonicalLocales(locales)

Modified: trunk/Source/_javascript_Core/runtime/IntlObject.h (281373 => 281374)


--- trunk/Source/_javascript_Core/runtime/IntlObject.h	2021-08-21 13:49:32 UTC (rev 281373)
+++ trunk/Source/_javascript_Core/runtime/IntlObject.h	2021-08-21 14:33:08 UTC (rev 281374)
@@ -136,4 +136,7 @@
     void operator()(UFieldPositionIterator*) const;
 };
 
+std::optional<String> mapICUCollationKeywordToBCP47(const String&);
+std::optional<String> mapICUCalendarKeywordToBCP47(const String&);
+
 } // namespace JSC
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to