Changeset 266033 in webkit


Ignore:
Timestamp:
Aug 22, 2020 1:16:58 PM (4 years ago)
Author:
ysuzuki@apple.com
Message:

[ECMA-402] Implement Intl.DateTimeFormat.prototype.formatRange
https://bugs.webkit.org/show_bug.cgi?id=209778

Reviewed by Ross Kirsling.

JSTests:

test262 failures are due to either of

  1. formatRangeToParts is missing
  2. ICU version difference
  • stress/intl-datetimeformat-formatrange-relevant-extensions-ja.js: Added.

(shouldBe):
(shouldThrow):
(vm.icuVersion):

  • stress/intl-datetimeformat-formatrange-relevant-extensions.js: Added.

(shouldBe):
(shouldThrow):

  • stress/intl-datetimeformat-formatrange.js: Added.

(shouldBe):
(shouldThrow):
(test):

  • test262/config.yaml:
  • test262/expectations.yaml:

Source/JavaScriptCore:

This patch adds Intl.DateTimeFormat#formatRange. It takes two dates, and
generates formatted text which represents interval between these two dates.
We skip the implementation of Intl.DateTimeFormat#formatRangeToParts since
ICU udtitvfmt_formatToResult API is not getting stable state yet. We retrieve
pattern from UDateFormat, get skeleton from that pattern, and construct
UDateIntervalFormat from this skeleton.

  • runtime/IntlDateTimeFormat.cpp:

(JSC::IntlDateTimeFormat::initializeDateTimeFormat):
(JSC::IntlDateTimeFormat::createDateIntervalFormatIfNecessary):
(JSC::IntlDateTimeFormat::formatRange):
(JSC::IntlDateTimeFormat::UDateFormatDeleter::operator() const): Deleted.

  • runtime/IntlDateTimeFormat.h:
  • runtime/IntlDateTimeFormatPrototype.cpp:

(JSC::IntlDateTimeFormatPrototypeFuncFormatRange):

Location:
trunk
Files:
3 added
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/JSTests/ChangeLog

    r266032 r266033  
     12020-08-22  Yusuke Suzuki  <ysuzuki@apple.com>
     2
     3        [ECMA-402] Implement Intl.DateTimeFormat.prototype.formatRange
     4        https://bugs.webkit.org/show_bug.cgi?id=209778
     5
     6        Reviewed by Ross Kirsling.
     7
     8        test262 failures are due to either of
     9
     10            1. formatRangeToParts is missing
     11            2. ICU version difference
     12
     13        * stress/intl-datetimeformat-formatrange-relevant-extensions-ja.js: Added.
     14        (shouldBe):
     15        (shouldThrow):
     16        (vm.icuVersion):
     17        * stress/intl-datetimeformat-formatrange-relevant-extensions.js: Added.
     18        (shouldBe):
     19        (shouldThrow):
     20        * stress/intl-datetimeformat-formatrange.js: Added.
     21        (shouldBe):
     22        (shouldThrow):
     23        (test):
     24        * test262/config.yaml:
     25        * test262/expectations.yaml:
     26
    1272020-08-22  Yusuke Suzuki  <ysuzuki@apple.com>
    228
  • trunk/JSTests/test262/config.yaml

    r266032 r266033  
    2727    - Intl.DateTimeFormat-datetimestyle
    2828    - Intl.DateTimeFormat-dayPeriod
    29     - Intl.DateTimeFormat-formatRange
    3029    - Intl.DateTimeFormat-fractionalSecondDigits
    3130    - Intl.ListFormat
     
    7776    - test/built-ins/TypedArrayConstructors/internals/Set/BigInt
    7877    - test/built-ins/TypedArrayConstructors/of/BigInt
     78
     79    # requires ICU APIs implementation
     80    # https://bugs.webkit.org/show_bug.cgi?id=213822
     81    - test/intl402/DateTimeFormat/prototype/formatRangeToParts
    7982  files:
    8083    # https://bugs.webkit.org/show_bug.cgi?id=186749
  • trunk/JSTests/test262/expectations.yaml

    r266032 r266033  
    15641564  default: "Test262Error: \"kn-true\" is returned in locale, but shouldn't be. Expected SameValue(«7», «-1») to be true"
    15651565  strict mode: "Test262Error: \"kn-true\" is returned in locale, but shouldn't be. Expected SameValue(«7», «-1») to be true"
     1566test/intl402/DateTimeFormat/prototype/formatRange/en-US.js:
     1567  default: 'Test262Error: Expected SameValue(«1/3/2019 – 1/5/2019», «1/3/2019 – 1/5/2019») to be true'
     1568  strict mode: 'Test262Error: Expected SameValue(«1/3/2019 – 1/5/2019», «1/3/2019 – 1/5/2019») to be true'
    15661569test/intl402/DateTimeFormat/prototype/resolvedOptions/hourCycle-default.js:
    15671570  default: 'Test262Error: Expected SameValue(«h24», «h23») to be true'
  • trunk/Source/JavaScriptCore/ChangeLog

    r266032 r266033  
     12020-08-22  Yusuke Suzuki  <ysuzuki@apple.com>
     2
     3        [ECMA-402] Implement Intl.DateTimeFormat.prototype.formatRange
     4        https://bugs.webkit.org/show_bug.cgi?id=209778
     5
     6        Reviewed by Ross Kirsling.
     7
     8        This patch adds Intl.DateTimeFormat#formatRange. It takes two dates, and
     9        generates formatted text which represents interval between these two dates.
     10        We skip the implementation of Intl.DateTimeFormat#formatRangeToParts since
     11        ICU udtitvfmt_formatToResult API is not getting stable state yet. We retrieve
     12        pattern from UDateFormat, get skeleton from that pattern, and construct
     13        UDateIntervalFormat from this skeleton.
     14
     15        * runtime/IntlDateTimeFormat.cpp:
     16        (JSC::IntlDateTimeFormat::initializeDateTimeFormat):
     17        (JSC::IntlDateTimeFormat::createDateIntervalFormatIfNecessary):
     18        (JSC::IntlDateTimeFormat::formatRange):
     19        (JSC::IntlDateTimeFormat::UDateFormatDeleter::operator() const): Deleted.
     20        * runtime/IntlDateTimeFormat.h:
     21        * runtime/IntlDateTimeFormatPrototype.cpp:
     22        (JSC::IntlDateTimeFormatPrototypeFuncFormatRange):
     23
    1242020-08-22  Yusuke Suzuki  <ysuzuki@apple.com>
    225
  • trunk/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp

    r266031 r266033  
    4545
    4646namespace IntlDateTimeFormatInternal {
     47static constexpr bool verbose = false;
    4748}
    4849
     
    468469    m_hourCycle = resolved.extensions[static_cast<unsigned>(RelevantExtensionKey::Hc)];
    469470    m_numberingSystem = resolved.extensions[static_cast<unsigned>(RelevantExtensionKey::Nu)];
    470     CString dataLocaleWithExtensions = makeString(resolved.dataLocale, "-u-ca-", m_calendar, "-nu-", m_numberingSystem).utf8();
     471    m_dataLocale = resolved.dataLocale;
     472    CString dataLocaleWithExtensions = makeString(m_dataLocale, "-u-ca-", m_calendar, "-nu-", m_numberingSystem).utf8();
    471473
    472474    JSValue tzValue = options->get(globalObject, vm.propertyNames->timeZone);
     
    677679    StringView pattern(patternBuffer.data(), patternBuffer.size());
    678680    setFormatsFromPattern(pattern);
     681
     682    dataLogLnIf(IntlDateTimeFormatInternal::verbose, "locale:(", m_locale, "),dataLocale:(", dataLocaleWithExtensions, "),pattern:(", pattern, ")");
    679683
    680684    status = U_ZERO_ERROR;
     
    10261030}
    10271031
     1032UDateIntervalFormat* IntlDateTimeFormat::createDateIntervalFormatIfNecessary(JSGlobalObject* globalObject)
     1033{
     1034    ASSERT(m_dateFormat);
     1035
     1036    VM& vm = globalObject->vm();
     1037    auto scope = DECLARE_THROW_SCOPE(vm);
     1038
     1039    if (m_dateIntervalFormat)
     1040        return m_dateIntervalFormat.get();
     1041
     1042    Vector<UChar, 32> pattern;
     1043    {
     1044        auto status = callBufferProducingFunction(udat_toPattern, m_dateFormat.get(), false, pattern);
     1045        if (U_FAILURE(status)) {
     1046            throwTypeError(globalObject, scope, "failed to initialize DateIntervalFormat"_s);
     1047            return nullptr;
     1048        }
     1049    }
     1050
     1051    Vector<UChar, 32> skeleton;
     1052    {
     1053        auto status = callBufferProducingFunction(udatpg_getSkeleton, nullptr, pattern.data(), pattern.size(), skeleton);
     1054        if (U_FAILURE(status)) {
     1055            throwTypeError(globalObject, scope, "failed to initialize DateIntervalFormat"_s);
     1056            return nullptr;
     1057        }
     1058    }
     1059
     1060    dataLogLnIf(IntlDateTimeFormatInternal::verbose, "interval format pattern:(", String(pattern), "),skeleton:(", String(skeleton), ")");
     1061
     1062    // While the pattern is including right HourCycle patterns, UDateIntervalFormat does not follow.
     1063    // We need to enforce HourCycle by setting "hc" extension if it is specified.
     1064    StringBuilder localeBuilder;
     1065    localeBuilder.append(m_dataLocale, "-u-ca-", m_calendar, "-nu-", m_numberingSystem);
     1066    if (!m_hourCycle.isNull())
     1067        localeBuilder.append("-hc-", m_hourCycle);
     1068    CString dataLocaleWithExtensions = localeBuilder.toString().utf8();
     1069
     1070    UErrorCode status = U_ZERO_ERROR;
     1071    StringView timeZoneView(m_timeZone);
     1072    m_dateIntervalFormat = std::unique_ptr<UDateIntervalFormat, UDateIntervalFormatDeleter>(udtitvfmt_open(dataLocaleWithExtensions.data(), skeleton.data(), skeleton.size(), timeZoneView.upconvertedCharacters(), timeZoneView.length(), &status));
     1073    if (U_FAILURE(status)) {
     1074        throwTypeError(globalObject, scope, "failed to initialize DateIntervalFormat"_s);
     1075        return nullptr;
     1076    }
     1077    return m_dateIntervalFormat.get();
     1078}
     1079
     1080JSValue IntlDateTimeFormat::formatRange(JSGlobalObject* globalObject, double startDate, double endDate)
     1081{
     1082    ASSERT(m_dateFormat);
     1083
     1084    VM& vm = globalObject->vm();
     1085    auto scope = DECLARE_THROW_SCOPE(vm);
     1086
     1087    // http://tc39.es/proposal-intl-DateTimeFormat-formatRange/#sec-partitiondatetimerangepattern
     1088    startDate = timeClip(startDate);
     1089    endDate = timeClip(endDate);
     1090    if (std::isnan(startDate) || std::isnan(endDate)) {
     1091        throwRangeError(globalObject, scope, "Passed date is out of range"_s);
     1092        return { };
     1093    }
     1094
     1095    auto* dateIntervalFormat = createDateIntervalFormatIfNecessary(globalObject);
     1096    RETURN_IF_EXCEPTION(scope, { });
     1097
     1098    Vector<UChar, 32> buffer;
     1099    auto status = callBufferProducingFunction(udtitvfmt_format, dateIntervalFormat, startDate, endDate, buffer, nullptr);
     1100    if (U_FAILURE(status)) {
     1101        throwTypeError(globalObject, scope, "Failed to format date interval"_s);
     1102        return { };
     1103    }
     1104
     1105    return jsString(vm, String(buffer));
     1106}
     1107
    10281108} // namespace JSC
  • trunk/Source/JavaScriptCore/runtime/IntlDateTimeFormat.h

    r266031 r266033  
    2828#include "JSObject.h"
    2929#include <unicode/udat.h>
     30#include <unicode/udateintervalformat.h>
    3031#include <wtf/unicode/icu/ICUHelpers.h>
    3132
     
    6162    JSValue format(JSGlobalObject*, double value) const;
    6263    JSValue formatToParts(JSGlobalObject*, double value) const;
     64    JSValue formatRange(JSGlobalObject*, double startDate, double endDate);
    6365    JSObject* resolvedOptions(JSGlobalObject*) const;
    6466
     
    7375    static Vector<String> localeData(const String&, RelevantExtensionKey);
    7476
     77    UDateIntervalFormat* createDateIntervalFormatIfNecessary(JSGlobalObject*);
     78
    7579    enum class Weekday : uint8_t { None, Narrow, Short, Long };
    7680    enum class Era : uint8_t { None, Narrow, Short, Long };
     
    8286    enum class Second : uint8_t { None, TwoDigit, Numeric };
    8387    enum class TimeZoneName : uint8_t { None, Short, Long };
    84 
    85     using UDateFormatDeleter = ICUDeleter<udat_close>;
    8688
    8789    void setFormatsFromPattern(const StringView&);
     
    9698    static ASCIILiteral timeZoneNameString(TimeZoneName);
    9799
     100    using UDateFormatDeleter = ICUDeleter<udat_close>;
     101    using UDateIntervalFormatDeleter = ICUDeleter<udtitvfmt_close>;
     102
    98103    WriteBarrier<JSBoundFunction> m_boundFormat;
    99104    std::unique_ptr<UDateFormat, UDateFormatDeleter> m_dateFormat;
     105    std::unique_ptr<UDateIntervalFormat, UDateIntervalFormatDeleter> m_dateIntervalFormat;
    100106
    101107    String m_locale;
     108    String m_dataLocale;
    102109    String m_calendar;
    103110    String m_numberingSystem;
  • trunk/Source/JavaScriptCore/runtime/IntlDateTimeFormatPrototype.cpp

    r262568 r266033  
    3838
    3939static EncodedJSValue JSC_HOST_CALL IntlDateTimeFormatPrototypeGetterFormat(JSGlobalObject*, CallFrame*);
     40static EncodedJSValue JSC_HOST_CALL IntlDateTimeFormatPrototypeFuncFormatRange(JSGlobalObject*, CallFrame*);
    4041static EncodedJSValue JSC_HOST_CALL IntlDateTimeFormatPrototypeFuncFormatToParts(JSGlobalObject*, CallFrame*);
    4142static EncodedJSValue JSC_HOST_CALL IntlDateTimeFormatPrototypeFuncResolvedOptions(JSGlobalObject*, CallFrame*);
     
    5152/* Source for IntlDateTimeFormatPrototype.lut.h
    5253@begin dateTimeFormatPrototypeTable
    53   format           IntlDateTimeFormatPrototypeGetterFormat         DontEnum|Accessor
    54   formatToParts    IntlDateTimeFormatPrototypeFuncFormatToParts    DontEnum|Function 1
    55   resolvedOptions  IntlDateTimeFormatPrototypeFuncResolvedOptions  DontEnum|Function 0
     54  format                IntlDateTimeFormatPrototypeGetterFormat              DontEnum|Accessor
     55  formatRange           IntlDateTimeFormatPrototypeFuncFormatRange           DontEnum|Function 2
     56  formatToParts         IntlDateTimeFormatPrototypeFuncFormatToParts         DontEnum|Function 1
     57  resolvedOptions       IntlDateTimeFormatPrototypeFuncResolvedOptions       DontEnum|Function 0
    5658@end
    5759*/
     
    166168}
    167169
     170// http://tc39.es/proposal-intl-DateTimeFormat-formatRange/#sec-intl.datetimeformat.prototype.formatRange
     171EncodedJSValue JSC_HOST_CALL IntlDateTimeFormatPrototypeFuncFormatRange(JSGlobalObject* globalObject, CallFrame* callFrame)
     172{
     173    VM& vm = globalObject->vm();
     174    auto scope = DECLARE_THROW_SCOPE(vm);
     175
     176    IntlDateTimeFormat* dateTimeFormat = jsDynamicCast<IntlDateTimeFormat*>(vm, callFrame->thisValue());
     177    if (!dateTimeFormat)
     178        return JSValue::encode(throwTypeError(globalObject, scope, "Intl.DateTimeFormat.prototype.formatRange called on value that's not an object initialized as a DateTimeFormat"_s));
     179
     180    JSValue startDateValue = callFrame->argument(0);
     181    JSValue endDateValue = callFrame->argument(1);
     182
     183    if (startDateValue.isUndefined() || endDateValue.isUndefined())
     184        return throwVMTypeError(globalObject, scope, "startDate or endDate is undefined"_s);
     185
     186    double startDate = startDateValue.toNumber(globalObject);
     187    RETURN_IF_EXCEPTION(scope, { });
     188    double endDate = endDateValue.toNumber(globalObject);
     189    RETURN_IF_EXCEPTION(scope, { });
     190    if (startDate > endDate)
     191        return throwVMRangeError(globalObject, scope, "startDate is larger than endDate"_s);
     192
     193    RELEASE_AND_RETURN(scope, JSValue::encode(dateTimeFormat->formatRange(globalObject, startDate, endDate)));
     194}
     195
    168196EncodedJSValue JSC_HOST_CALL IntlDateTimeFormatPrototypeFuncResolvedOptions(JSGlobalObject* globalObject, CallFrame* callFrame)
    169197{
Note: See TracChangeset for help on using the changeset viewer.