| Differences between
and this patch
- a/Source/JavaScriptCore/ChangeLog +19 lines
Lines 1-3 a/Source/JavaScriptCore/ChangeLog_sec1
1
2020-09-14  Yusuke Suzuki  <ysuzuki@apple.com>
2
3
        [JSC] Apply Intl.DateTimeFormat hour-cycle correctly when timeStyle is used
4
        https://bugs.webkit.org/show_bug.cgi?id=216521
5
6
        Reviewed by NOBODY (OOPS!).
7
8
        * runtime/IntlDateTimeFormat.cpp:
9
        (JSC::IntlDateTimeFormat::setFormatsFromPattern):
10
        (JSC::IntlDateTimeFormat::parseHourCycle):
11
        (JSC::IntlDateTimeFormat::hourCycleFromPattern):
12
        (JSC::IntlDateTimeFormat::replaceHourCycleInSkeleton):
13
        (JSC::IntlDateTimeFormat::replaceHourCycleInPattern):
14
        (JSC::IntlDateTimeFormat::initializeDateTimeFormat):
15
        (JSC::IntlDateTimeFormat::hourCycleString):
16
        (JSC::IntlDateTimeFormat::resolvedOptions const):
17
        (JSC::IntlDateTimeFormat::createDateIntervalFormatIfNecessary):
18
        * runtime/IntlDateTimeFormat.h:
19
1
2020-09-11  Yusuke Suzuki  <ysuzuki@apple.com>
20
2020-09-11  Yusuke Suzuki  <ysuzuki@apple.com>
2
21
3
        [JSC] Canonicalize "true" unicode extension type value to ""
22
        [JSC] Canonicalize "true" unicode extension type value to ""
- a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp -121 / +222 lines
Lines 329-346 void IntlDateTimeFormat::setFormatsFromPattern(const StringView& pattern) a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp_sec1
329
            ++i;
329
            ++i;
330
        }
330
        }
331
331
332
        // If hourCycle was null, this sets it to the locale default.
333
        if (m_hourCycle.isNull()) {
334
            if (currentCharacter == 'h')
335
                m_hourCycle = "h12"_s;
336
            else if (currentCharacter == 'H')
337
                m_hourCycle = "h23"_s;
338
            else if (currentCharacter == 'k')
339
                m_hourCycle = "h24"_s;
340
            else if (currentCharacter == 'K')
341
                m_hourCycle = "h11"_s;
342
        }
343
344
        switch (currentCharacter) {
332
        switch (currentCharacter) {
345
        case 'G':
333
        case 'G':
346
            if (count <= 3)
334
            if (count <= 3)
Lines 398-409 void IntlDateTimeFormat::setFormatsFromPattern(const StringView& pattern) a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp_sec2
398
        case 'h':
386
        case 'h':
399
        case 'H':
387
        case 'H':
400
        case 'k':
388
        case 'k':
401
        case 'K':
389
        case 'K': {
390
            // Populate hourCycle from actually generated patterns. It is possible that locale or option is specifying hourCycle explicitly,
391
            // but the generated pattern does not include related part since the pattern does not include hours.
392
            // This is tested in test262/test/intl402/DateTimeFormat/prototype/resolvedOptions/hourCycle-dateStyle.js and our stress tests.
393
            // Example:
394
            //     new Intl.DateTimeFormat(`de-u-hc-h11`, {
395
            //         dateStyle: "full"
396
            //     }).resolvedOptions().hourCycle === undefined
397
            switch (currentCharacter) {
398
            case 'h':
399
                m_hourCycle = HourCycle::H12;
400
                break;
401
            case 'H':
402
                m_hourCycle = HourCycle::H23;
403
                break;
404
            case 'k':
405
                m_hourCycle = HourCycle::H24;
406
                break;
407
            case 'K':
408
                m_hourCycle = HourCycle::H11;
409
                break;
410
            }
402
            if (count == 1)
411
            if (count == 1)
403
                m_hour = Hour::Numeric;
412
                m_hour = Hour::Numeric;
404
            else if (count == 2)
413
            else if (count == 2)
405
                m_hour = Hour::TwoDigit;
414
                m_hour = Hour::TwoDigit;
406
            break;
415
            break;
416
        }
407
        case 'm':
417
        case 'm':
408
            if (count == 1)
418
            if (count == 1)
409
                m_minute = Minute::Numeric;
419
                m_minute = Minute::Numeric;
Lines 431-436 void IntlDateTimeFormat::setFormatsFromPattern(const StringView& pattern) a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp_sec3
431
    }
441
    }
432
}
442
}
433
443
444
IntlDateTimeFormat::HourCycle IntlDateTimeFormat::parseHourCycle(const String& hourCycle)
445
{
446
    if (hourCycle == "h11"_s)
447
        return HourCycle::H11;
448
    if (hourCycle == "h12"_s)
449
        return HourCycle::H12;
450
    if (hourCycle == "h23"_s)
451
        return HourCycle::H23;
452
    if (hourCycle == "h24"_s)
453
        return HourCycle::H24;
454
    return HourCycle::None;
455
}
456
457
IntlDateTimeFormat::HourCycle IntlDateTimeFormat::hourCycleFromPattern(const Vector<UChar, 32>& pattern)
458
{
459
    for (auto character : pattern) {
460
        switch (character) {
461
        case 'K':
462
            return HourCycle::H11;
463
        case 'h':
464
            return HourCycle::H12;
465
        case 'H':
466
            return HourCycle::H23;
467
        case 'k':
468
            return HourCycle::H24;
469
        }
470
    }
471
    return HourCycle::None;
472
}
473
474
void IntlDateTimeFormat::replaceHourCycleInSkeleton(Vector<UChar, 32>& skeleton, bool isHour12)
475
{
476
    UChar skeletonCharacter = 'H';
477
    if (isHour12)
478
        skeletonCharacter = 'h';
479
    for (auto& character : skeleton) {
480
        switch (character) {
481
        case 'h':
482
        case 'H':
483
        case 'j':
484
            character = skeletonCharacter;
485
            break;
486
        }
487
    }
488
}
489
490
void IntlDateTimeFormat::replaceHourCycleInPattern(Vector<UChar, 32>& pattern, HourCycle hourCycle)
491
{
492
    UChar hourFromHourCycle = 'H';
493
    switch (hourCycle) {
494
    case HourCycle::H11:
495
        hourFromHourCycle = 'K';
496
        break;
497
    case HourCycle::H12:
498
        hourFromHourCycle = 'h';
499
        break;
500
    case HourCycle::H23:
501
        hourFromHourCycle = 'H';
502
        break;
503
    case HourCycle::H24:
504
        hourFromHourCycle = 'k';
505
        break;
506
    case HourCycle::None:
507
        return;
508
    }
509
510
    for (auto& character : pattern) {
511
        switch (character) {
512
        case 'K':
513
        case 'h':
514
        case 'H':
515
        case 'k':
516
            character = hourFromHourCycle;
517
            break;
518
        }
519
    }
520
}
521
434
// https://tc39.github.io/ecma402/#sec-initializedatetimeformat
522
// https://tc39.github.io/ecma402/#sec-initializedatetimeformat
435
void IntlDateTimeFormat::initializeDateTimeFormat(JSGlobalObject* globalObject, JSValue locales, JSValue originalOptions)
523
void IntlDateTimeFormat::initializeDateTimeFormat(JSGlobalObject* globalObject, JSValue locales, JSValue originalOptions)
436
{
524
{
Lines 470-486 void IntlDateTimeFormat::initializeDateTimeFormat(JSGlobalObject* globalObject, a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp_sec4
470
558
471
    TriState hour12 = intlBooleanOption(globalObject, options, vm.propertyNames->hour12);
559
    TriState hour12 = intlBooleanOption(globalObject, options, vm.propertyNames->hour12);
472
    RETURN_IF_EXCEPTION(scope, void());
560
    RETURN_IF_EXCEPTION(scope, void());
473
    bool isHour12Undefined = (hour12 == TriState::Indeterminate);
474
561
475
    String hourCycle = intlStringOption(globalObject, options, vm.propertyNames->hourCycle, { "h11", "h12", "h23", "h24" }, "hourCycle must be \"h11\", \"h12\", \"h23\", or \"h24\"", nullptr);
562
    HourCycle hourCycle = intlOption<HourCycle>(globalObject, options, vm.propertyNames->hourCycle, { { "h11"_s, HourCycle::H11 }, { "h12"_s, HourCycle::H12 }, { "h23"_s, HourCycle::H23 }, { "h24"_s, HourCycle::H24 } }, "hourCycle must be \"h11\", \"h12\", \"h23\", or \"h24\""_s, HourCycle::None);
476
    RETURN_IF_EXCEPTION(scope, void());
563
    RETURN_IF_EXCEPTION(scope, void());
477
    if (isHour12Undefined) {
564
    if (hour12 == TriState::Indeterminate) {
478
        // Set hour12 here to simplify hour logic later.
565
        if (hourCycle != HourCycle::None)
479
        hour12 = triState(hourCycle == "h11" || hourCycle == "h12");
566
            localeOptions[static_cast<unsigned>(RelevantExtensionKey::Hc)] = String(hourCycleString(hourCycle));
480
        if (!hourCycle.isNull())
567
    } else {
481
            localeOptions[static_cast<unsigned>(RelevantExtensionKey::Hc)] = hourCycle;
568
        // If there is hour12, hourCycle is ignored.
482
    } else
483
        localeOptions[static_cast<unsigned>(RelevantExtensionKey::Hc)] = String();
569
        localeOptions[static_cast<unsigned>(RelevantExtensionKey::Hc)] = String();
570
    }
484
571
485
    const HashSet<String>& availableLocales = intlDateTimeFormatAvailableLocales();
572
    const HashSet<String>& availableLocales = intlDateTimeFormatAvailableLocales();
486
    auto resolved = resolveLocale(globalObject, availableLocales, requestedLocales, localeMatcher, localeOptions, { RelevantExtensionKey::Ca, RelevantExtensionKey::Hc, RelevantExtensionKey::Nu }, localeData);
573
    auto resolved = resolveLocale(globalObject, availableLocales, requestedLocales, localeMatcher, localeOptions, { RelevantExtensionKey::Ca, RelevantExtensionKey::Hc, RelevantExtensionKey::Nu }, localeData);
Lines 499-505 void IntlDateTimeFormat::initializeDateTimeFormat(JSGlobalObject* globalObject, a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp_sec5
499
    else if (m_calendar == "ethioaa")
586
    else if (m_calendar == "ethioaa")
500
        m_calendar = "ethiopic-amete-alem"_s;
587
        m_calendar = "ethiopic-amete-alem"_s;
501
588
502
    m_hourCycle = resolved.extensions[static_cast<unsigned>(RelevantExtensionKey::Hc)];
589
    hourCycle = parseHourCycle(resolved.extensions[static_cast<unsigned>(RelevantExtensionKey::Hc)]);
503
    m_numberingSystem = resolved.extensions[static_cast<unsigned>(RelevantExtensionKey::Nu)];
590
    m_numberingSystem = resolved.extensions[static_cast<unsigned>(RelevantExtensionKey::Nu)];
504
    m_dataLocale = resolved.dataLocale;
591
    m_dataLocale = resolved.dataLocale;
505
    CString dataLocaleWithExtensions = makeString(m_dataLocale, "-u-ca-", m_calendar, "-nu-", m_numberingSystem).utf8();
592
    CString dataLocaleWithExtensions = makeString(m_dataLocale, "-u-ca-", m_calendar, "-nu-", m_numberingSystem).utf8();
Lines 609-633 void IntlDateTimeFormat::initializeDateTimeFormat(JSGlobalObject* globalObject, a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp_sec6
609
696
610
    Hour hour = intlOption<Hour>(globalObject, options, vm.propertyNames->hour, { { "2-digit"_s, Hour::TwoDigit }, { "numeric"_s, Hour::Numeric } }, "hour must be \"2-digit\" or \"numeric\""_s, Hour::None);
697
    Hour hour = intlOption<Hour>(globalObject, options, vm.propertyNames->hour, { { "2-digit"_s, Hour::TwoDigit }, { "numeric"_s, Hour::Numeric } }, "hour must be \"2-digit\" or \"numeric\""_s, Hour::None);
611
    RETURN_IF_EXCEPTION(scope, void());
698
    RETURN_IF_EXCEPTION(scope, void());
612
    switch (hour) {
699
    {
613
    case Hour::TwoDigit:
700
        // In skeleton construction, there is no way to specify h11 v.s. h12, h23 v.s. h24. They are enforced after generating pattern.
614
        if (isHour12Undefined && m_hourCycle.isNull())
701
        UChar skeletonCharacter = 'j';
615
            skeletonBuilder.appendLiteral("jj");
702
        if (hour12 == TriState::Indeterminate) {
616
        else if (hour12 == TriState::True)
703
            switch (hourCycle) {
617
            skeletonBuilder.appendLiteral("hh");
704
            case HourCycle::None:
618
        else
705
                skeletonCharacter = 'j';
619
            skeletonBuilder.appendLiteral("HH");
706
                break;
620
        break;
707
            case HourCycle::H11:
621
    case Hour::Numeric:
708
            case HourCycle::H12:
622
        if (isHour12Undefined && m_hourCycle.isNull())
709
                skeletonCharacter = 'h';
623
            skeletonBuilder.append('j');
710
                break;
624
        else if (hour12 == TriState::True)
711
            case HourCycle::H23:
625
            skeletonBuilder.append('h');
712
            case HourCycle::H24:
626
        else
713
                skeletonCharacter = 'H';
627
            skeletonBuilder.append('H');
714
                break;
628
        break;
715
            }
629
    case Hour::None:
716
        } else {
630
        break;
717
            // We prefer hour12 over hourCycle. If hour12 is specified, we ignore hourCycle.
718
            if (hour12 == TriState::True)
719
                skeletonCharacter = 'h';
720
            else
721
                skeletonCharacter = 'H';
722
        }
723
724
        switch (hour) {
725
        case Hour::TwoDigit:
726
            skeletonBuilder.append(skeletonCharacter);
727
            skeletonBuilder.append(skeletonCharacter);
728
            break;
729
        case Hour::Numeric:
730
            skeletonBuilder.append(skeletonCharacter);
731
            break;
732
        case Hour::None:
733
            break;
734
        }
631
    }
735
    }
632
736
633
    if (Options::useIntlDateTimeFormatDayPeriod()) {
737
    if (Options::useIntlDateTimeFormatDayPeriod()) {
Lines 701-707 void IntlDateTimeFormat::initializeDateTimeFormat(JSGlobalObject* globalObject, a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp_sec7
701
    m_timeStyle = intlOption<DateTimeStyle>(globalObject, options, vm.propertyNames->timeStyle, { { "full"_s, DateTimeStyle::Full }, { "long"_s, DateTimeStyle::Long }, { "medium"_s, DateTimeStyle::Medium }, { "short"_s, DateTimeStyle::Short } }, "timeStyle must be \"full\", \"long\", \"medium\", or \"short\""_s, DateTimeStyle::None);
805
    m_timeStyle = intlOption<DateTimeStyle>(globalObject, options, vm.propertyNames->timeStyle, { { "full"_s, DateTimeStyle::Full }, { "long"_s, DateTimeStyle::Long }, { "medium"_s, DateTimeStyle::Medium }, { "short"_s, DateTimeStyle::Short } }, "timeStyle must be \"full\", \"long\", \"medium\", or \"short\""_s, DateTimeStyle::None);
702
    RETURN_IF_EXCEPTION(scope, void());
806
    RETURN_IF_EXCEPTION(scope, void());
703
807
704
    bool canIncludeHour = true;
808
809
    auto patternFromSkeleton = [&](const UChar* skeleton, unsigned skeletonSize, Vector<UChar, 32>& patternBuffer, UErrorCode& status) {
810
        // Always use ICU date format generator, rather than our own pattern list and matcher.
811
        auto generator = std::unique_ptr<UDateTimePatternGenerator, ICUDeleter<udatpg_close>>(udatpg_open(dataLocaleWithExtensions.data(), &status));
812
        if (U_FAILURE(status))
813
            return;
814
815
        status = callBufferProducingFunction(udatpg_getBestPatternWithOptions, generator.get(), skeleton, skeletonSize, UDATPG_MATCH_HOUR_FIELD_LENGTH, patternBuffer);
816
        if (U_FAILURE(status))
817
            return;
818
    };
819
705
    Vector<UChar, 32> patternBuffer;
820
    Vector<UChar, 32> patternBuffer;
706
    if (m_dateStyle != DateTimeStyle::None || m_timeStyle != DateTimeStyle::None) {
821
    if (m_dateStyle != DateTimeStyle::None || m_timeStyle != DateTimeStyle::None) {
707
        // 30. For each row in Table 1, except the header row, do
822
        // 30. For each row in Table 1, except the header row, do
Lines 714-725 void IntlDateTimeFormat::initializeDateTimeFormat(JSGlobalObject* globalObject, a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp_sec8
714
            return;
829
            return;
715
        }
830
        }
716
831
717
        // If timeStyle is not specified, m_hourCycle does not matter. Let's skip enforcing step.
718
        if (m_timeStyle == DateTimeStyle::None) {
719
            canIncludeHour = false;
720
            m_hourCycle = String();
721
        }
722
723
        auto parseUDateFormatStyle = [](DateTimeStyle style) {
832
        auto parseUDateFormatStyle = [](DateTimeStyle style) {
724
            switch (style) {
833
            switch (style) {
725
            case DateTimeStyle::Full:
834
            case DateTimeStyle::Full:
Lines 738-744 void IntlDateTimeFormat::initializeDateTimeFormat(JSGlobalObject* globalObject, a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp_sec9
738
847
739
        // We cannot use this UDateFormat directly yet because we need to enforce specified hourCycle.
848
        // We cannot use this UDateFormat directly yet because we need to enforce specified hourCycle.
740
        // First, we create UDateFormat via dateStyle and timeStyle. And then convert it to pattern string.
849
        // First, we create UDateFormat via dateStyle and timeStyle. And then convert it to pattern string.
741
        // After updating this pattern string with m_hourCycle, we create a final UDateFormat with the updated pattern string.
850
        // After updating this pattern string with hourCycle, we create a final UDateFormat with the updated pattern string.
742
        UErrorCode status = U_ZERO_ERROR;
851
        UErrorCode status = U_ZERO_ERROR;
743
        StringView timeZoneView(m_timeZone);
852
        StringView timeZoneView(m_timeZone);
744
        auto dateFormatFromStyle = std::unique_ptr<UDateFormat, UDateFormatDeleter>(udat_open(parseUDateFormatStyle(m_timeStyle), parseUDateFormatStyle(m_dateStyle), dataLocaleWithExtensions.data(), timeZoneView.upconvertedCharacters(), timeZoneView.length(), nullptr, -1, &status));
853
        auto dateFormatFromStyle = std::unique_ptr<UDateFormat, UDateFormatDeleter>(udat_open(parseUDateFormatStyle(m_timeStyle), parseUDateFormatStyle(m_dateStyle), dataLocaleWithExtensions.data(), timeZoneView.upconvertedCharacters(), timeZoneView.length(), nullptr, -1, &status));
Lines 752-833 void IntlDateTimeFormat::initializeDateTimeFormat(JSGlobalObject* globalObject, a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp_sec10
752
            throwTypeError(globalObject, scope, "failed to initialize DateTimeFormat"_s);
861
            throwTypeError(globalObject, scope, "failed to initialize DateTimeFormat"_s);
753
            return;
862
            return;
754
        }
863
        }
755
    } else {
756
        // If hour is not specified, m_hourCycle does not matter. Let's skip enforcing step.
757
        if (hour == Hour::None) {
758
            canIncludeHour = false;
759
            m_hourCycle = String();
760
        }
761
864
762
        // Always use ICU date format generator, rather than our own pattern list and matcher.
865
        // It is possible that timeStyle includes dayPeriod, which is sensitive to hour-cycle.
763
        UErrorCode status = U_ZERO_ERROR;
866
        // If dayPeriod is included, just replacing hour based on hourCycle / hour12 produces strange results.
764
        auto* generator = udatpg_open(dataLocaleWithExtensions.data(), &status);
867
        // Let's consider about the example. The formatted result looks like "02:12:47 PM Coordinated Universal Time"
765
        if (U_FAILURE(status)) {
868
        // If we simply replace 02 to 14, this becomes "14:12:47 PM Coordinated Universal Time", this looks strange.
766
            throwTypeError(globalObject, scope, "failed to initialize DateTimeFormat"_s);
869
        // We should retrieve skeleton from the pattern, enforcing hour-cycle, and re-generating the best pattern from the modified skeleton to get non-strange result.
767
            return;
870
        if (m_timeStyle != DateTimeStyle::None) {
871
            if (hourCycle != HourCycle::None || hour12 != TriState::Indeterminate) {
872
                auto isHour12 = [](HourCycle hourCycle) {
873
                    return hourCycle == HourCycle::H11 || hourCycle == HourCycle::H12;
874
                };
875
                bool specifiedHour12 = false;
876
                // If hour12 is specified, we prefer it and dignore hourCycle.
877
                if (hour12 != TriState::Indeterminate)
878
                    specifiedHour12 = hour12 == TriState::True;
879
                else
880
                    specifiedHour12 = isHour12(hourCycle);
881
                if (isHour12(hourCycleFromPattern(patternBuffer)) != specifiedHour12) {
882
                    Vector<UChar, 32> skeleton;
883
                    auto status = callBufferProducingFunction(udatpg_getSkeleton, nullptr, patternBuffer.data(), patternBuffer.size(), skeleton);
884
                    if (U_FAILURE(status)) {
885
                        throwTypeError(globalObject, scope, "failed to initialize DateIntervalFormat"_s);
886
                        return;
887
                    }
888
                    replaceHourCycleInSkeleton(skeleton, specifiedHour12);
889
                    dataLogLnIf(IntlDateTimeFormatInternal::verbose, "replaced:(", StringView(skeleton.data(), skeleton.size()), ")");
890
                    patternBuffer.clear();
891
                    patternFromSkeleton(skeleton.data(), skeleton.size(), patternBuffer, status);
892
                    if (U_FAILURE(status)) {
893
                        throwTypeError(globalObject, scope, "failed to initialize DateIntervalFormat"_s);
894
                        return;
895
                    }
896
                }
897
            }
768
        }
898
        }
769
899
    } else {
900
        UErrorCode status = U_ZERO_ERROR;
770
        String skeleton = skeletonBuilder.toString();
901
        String skeleton = skeletonBuilder.toString();
771
        StringView skeletonView(skeleton);
902
        patternFromSkeleton(StringView(skeleton).upconvertedCharacters().get(), skeleton.length(), patternBuffer, status);
772
        status = callBufferProducingFunction(udatpg_getBestPatternWithOptions, generator, skeletonView.upconvertedCharacters().get(), skeletonView.length(), UDATPG_MATCH_HOUR_FIELD_LENGTH, patternBuffer);
773
        udatpg_close(generator);
774
        if (U_FAILURE(status)) {
903
        if (U_FAILURE(status)) {
775
            throwTypeError(globalObject, scope, "failed to initialize DateTimeFormat"_s);
904
            throwTypeError(globalObject, scope, "failed to initialize DateTimeFormat"_s);
776
            return;
905
            return;
777
        }
906
        }
778
    }
907
    }
779
908
780
    // Enforce our hourCycle, replacing hour characters in pattern. ICU can pick format which does not agree with specified hourCycle.
909
    // In skeleton construction, there is no way to specify h11 v.s. h12, or h23 v.s. h24.
781
    // This step modifies generated pattern to use specified hourCycle, and it is specified in 1.1.1 InitializeDateTimeFormat step 33.
910
    // After generating pattern from skeleton, we need to change h11 and h12 / h23 and h24 if hourCycle is specified.
782
    // FIXME: We should cache `hourCycleDefault` value obtained by generating "jjmm" pattern from UDateTimePatternGenerator.
911
    if (hourCycle != HourCycle::None)
783
    // https://bugs.webkit.org/show_bug.cgi?id=213459
912
        replaceHourCycleInPattern(patternBuffer, hourCycle);
784
    if (canIncludeHour) {
785
        bool hasHour = false;
786
        if (!m_hourCycle.isNull() || !isHour12Undefined) {
787
            UChar hourFromHourCycle = 'H'; // H23
788
            if (m_hourCycle == "h11")
789
                hourFromHourCycle = 'K';
790
            else if (m_hourCycle == "h12")
791
                hourFromHourCycle = 'h';
792
            else if (m_hourCycle == "h24")
793
                hourFromHourCycle = 'k';
794
795
            bool isEscaped = false;
796
            for (auto& c : patternBuffer) {
797
                if (c == '\'')
798
                    isEscaped = !isEscaped;
799
                else if (!isEscaped && (c == 'h' || c == 'H' || c == 'k' || c == 'K')) {
800
                    switch (c) {
801
                    case 'K':
802
                    case 'H': {
803
                        if (isHour12Undefined)
804
                            c = hourFromHourCycle;
805
                        else if (hour12 == TriState::True)
806
                            c = 'K';
807
                        else
808
                            c = 'H';
809
                        break;
810
                    }
811
                    case 'h':
812
                    case 'k': {
813
                        if (isHour12Undefined)
814
                            c = hourFromHourCycle;
815
                        else if (hour12 == TriState::True)
816
                            c = 'h';
817
                        else
818
                            c = 'k';
819
                        break;
820
                    }
821
                    default:
822
                        ASSERT_NOT_REACHED();
823
                    }
824
                    hasHour = true;
825
                }
826
            }
827
        }
828
        if (!hasHour)
829
            m_hourCycle = String();
830
    }
831
913
832
    StringView pattern(patternBuffer.data(), patternBuffer.size());
914
    StringView pattern(patternBuffer.data(), patternBuffer.size());
833
    setFormatsFromPattern(pattern);
915
    setFormatsFromPattern(pattern);
Lines 848-853 void IntlDateTimeFormat::initializeDateTimeFormat(JSGlobalObject* globalObject, a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp_sec11
848
    ucal_setGregorianChange(cal, minECMAScriptTime, &status);
930
    ucal_setGregorianChange(cal, minECMAScriptTime, &status);
849
}
931
}
850
932
933
ASCIILiteral IntlDateTimeFormat::hourCycleString(HourCycle hourCycle)
934
{
935
    switch (hourCycle) {
936
    case HourCycle::H11:
937
        return "h11"_s;
938
    case HourCycle::H12:
939
        return "h12"_s;
940
    case HourCycle::H23:
941
        return "h23"_s;
942
    case HourCycle::H24:
943
        return "h24"_s;
944
    case HourCycle::None:
945
        ASSERT_NOT_REACHED();
946
        return ASCIILiteral::null();
947
    }
948
    ASSERT_NOT_REACHED();
949
    return ASCIILiteral::null();
950
}
951
851
ASCIILiteral IntlDateTimeFormat::weekdayString(Weekday weekday)
952
ASCIILiteral IntlDateTimeFormat::weekdayString(Weekday weekday)
852
{
953
{
853
    switch (weekday) {
954
    switch (weekday) {
Lines 1040-1048 JSObject* IntlDateTimeFormat::resolvedOptions(JSGlobalObject* globalObject) cons a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp_sec12
1040
    options->putDirect(vm, vm.propertyNames->numberingSystem, jsNontrivialString(vm, m_numberingSystem));
1141
    options->putDirect(vm, vm.propertyNames->numberingSystem, jsNontrivialString(vm, m_numberingSystem));
1041
    options->putDirect(vm, vm.propertyNames->timeZone, jsNontrivialString(vm, m_timeZone));
1142
    options->putDirect(vm, vm.propertyNames->timeZone, jsNontrivialString(vm, m_timeZone));
1042
1143
1043
    if (!m_hourCycle.isNull()) {
1144
    if (m_hourCycle != HourCycle::None) {
1044
        options->putDirect(vm, vm.propertyNames->hourCycle, jsNontrivialString(vm, m_hourCycle));
1145
        options->putDirect(vm, vm.propertyNames->hourCycle, jsNontrivialString(vm, hourCycleString(m_hourCycle)));
1045
        options->putDirect(vm, vm.propertyNames->hour12, jsBoolean(m_hourCycle == "h11" || m_hourCycle == "h12"));
1146
        options->putDirect(vm, vm.propertyNames->hour12, jsBoolean(m_hourCycle == HourCycle::H11 || m_hourCycle == HourCycle::H12));
1046
    }
1147
    }
1047
1148
1048
    if (m_weekday != Weekday::None)
1149
    if (m_weekday != Weekday::None)
Lines 1267-1274 UDateIntervalFormat* IntlDateTimeFormat::createDateIntervalFormatIfNecessary(JSG a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp_sec13
1267
    // We need to enforce HourCycle by setting "hc" extension if it is specified.
1368
    // We need to enforce HourCycle by setting "hc" extension if it is specified.
1268
    StringBuilder localeBuilder;
1369
    StringBuilder localeBuilder;
1269
    localeBuilder.append(m_dataLocale, "-u-ca-", m_calendar, "-nu-", m_numberingSystem);
1370
    localeBuilder.append(m_dataLocale, "-u-ca-", m_calendar, "-nu-", m_numberingSystem);
1270
    if (!m_hourCycle.isNull())
1371
    if (m_hourCycle != HourCycle::None)
1271
        localeBuilder.append("-hc-", m_hourCycle);
1372
        localeBuilder.append("-hc-", hourCycleString(m_hourCycle));
1272
    CString dataLocaleWithExtensions = localeBuilder.toString().utf8();
1373
    CString dataLocaleWithExtensions = localeBuilder.toString().utf8();
1273
1374
1274
    UErrorCode status = U_ZERO_ERROR;
1375
    UErrorCode status = U_ZERO_ERROR;
- a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.h -1 / +8 lines
Lines 78-83 class IntlDateTimeFormat final : public JSNonFinalObject { a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.h_sec1
78
78
79
    UDateIntervalFormat* createDateIntervalFormatIfNecessary(JSGlobalObject*);
79
    UDateIntervalFormat* createDateIntervalFormatIfNecessary(JSGlobalObject*);
80
80
81
    enum class HourCycle : uint8_t { None, H11, H12, H23, H24 };
81
    enum class Weekday : uint8_t { None, Narrow, Short, Long };
82
    enum class Weekday : uint8_t { None, Narrow, Short, Long };
82
    enum class Era : uint8_t { None, Narrow, Short, Long };
83
    enum class Era : uint8_t { None, Narrow, Short, Long };
83
    enum class Year : uint8_t { None, TwoDigit, Numeric };
84
    enum class Year : uint8_t { None, TwoDigit, Numeric };
Lines 91-96 class IntlDateTimeFormat final : public JSNonFinalObject { a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.h_sec2
91
    enum class DateTimeStyle : uint8_t { None, Full, Long, Medium, Short };
92
    enum class DateTimeStyle : uint8_t { None, Full, Long, Medium, Short };
92
93
93
    void setFormatsFromPattern(const StringView&);
94
    void setFormatsFromPattern(const StringView&);
95
    static ASCIILiteral hourCycleString(HourCycle);
94
    static ASCIILiteral weekdayString(Weekday);
96
    static ASCIILiteral weekdayString(Weekday);
95
    static ASCIILiteral eraString(Era);
97
    static ASCIILiteral eraString(Era);
96
    static ASCIILiteral yearString(Year);
98
    static ASCIILiteral yearString(Year);
Lines 103-108 class IntlDateTimeFormat final : public JSNonFinalObject { a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.h_sec3
103
    static ASCIILiteral timeZoneNameString(TimeZoneName);
105
    static ASCIILiteral timeZoneNameString(TimeZoneName);
104
    static ASCIILiteral formatStyleString(DateTimeStyle);
106
    static ASCIILiteral formatStyleString(DateTimeStyle);
105
107
108
    static HourCycle parseHourCycle(const String&);
109
    static HourCycle hourCycleFromPattern(const Vector<UChar, 32>&);
110
    static void replaceHourCycleInSkeleton(Vector<UChar, 32>&, bool hour12);
111
    static void replaceHourCycleInPattern(Vector<UChar, 32>&, HourCycle);
112
106
    using UDateFormatDeleter = ICUDeleter<udat_close>;
113
    using UDateFormatDeleter = ICUDeleter<udat_close>;
107
    using UDateIntervalFormatDeleter = ICUDeleter<udtitvfmt_close>;
114
    using UDateIntervalFormatDeleter = ICUDeleter<udtitvfmt_close>;
108
115
Lines 115-121 class IntlDateTimeFormat final : public JSNonFinalObject { a/Source/JavaScriptCore/runtime/IntlDateTimeFormat.h_sec4
115
    String m_calendar;
122
    String m_calendar;
116
    String m_numberingSystem;
123
    String m_numberingSystem;
117
    String m_timeZone;
124
    String m_timeZone;
118
    String m_hourCycle;
125
    HourCycle m_hourCycle { HourCycle::None };
119
    Weekday m_weekday { Weekday::None };
126
    Weekday m_weekday { Weekday::None };
120
    Era m_era { Era::None };
127
    Era m_era { Era::None };
121
    Year m_year { Year::None };
128
    Year m_year { Year::None };
- a/JSTests/ChangeLog +10 lines
Lines 1-3 a/JSTests/ChangeLog_sec1
1
2020-09-14  Yusuke Suzuki  <ysuzuki@apple.com>
2
3
        [JSC] Apply Intl.DateTimeFormat hour-cycle correctly when timeStyle is used
4
        https://bugs.webkit.org/show_bug.cgi?id=216521
5
6
        Reviewed by NOBODY (OOPS!).
7
8
        * stress/intl-datetimeformat.js:
9
        * test262/expectations.yaml:
10
1
2020-09-11  Yusuke Suzuki  <ysuzuki@apple.com>
11
2020-09-11  Yusuke Suzuki  <ysuzuki@apple.com>
2
12
3
        [JSC] Canonicalize "true" unicode extension type value to ""
13
        [JSC] Canonicalize "true" unicode extension type value to ""
- a/JSTests/stress/intl-datetimeformat.js +7 lines
Lines 748-750 get formatMatcher() { a/JSTests/stress/intl-datetimeformat.js_sec1
748
{
748
{
749
    shouldBe(new Date(0).toLocaleTimeString('zh-Hans-CN', { timeZone: 'UTC', numberingSystem: 'hanidec', hour: "numeric", minute: "numeric", second: "numeric", fractionalSecondDigits: 2 }), "上午一二:〇〇:〇〇.〇〇");
749
    shouldBe(new Date(0).toLocaleTimeString('zh-Hans-CN', { timeZone: 'UTC', numberingSystem: 'hanidec', hour: "numeric", minute: "numeric", second: "numeric", fractionalSecondDigits: 2 }), "上午一二:〇〇:〇〇.〇〇");
750
}
750
}
751
{
752
    let options = new Intl.DateTimeFormat(`de-u-hc-h11`, {
753
        dateStyle: "short"
754
    }).resolvedOptions();
755
    shouldBe(options.hourCycle, undefined);
756
    shouldBe(options.hour12, undefined);
757
}
- a/JSTests/test262/expectations.yaml -2 / +2 lines
Lines 1453-1460 test/built-ins/TypedArrayConstructors/of/new-instance-using-custom-ctor.js: a/JSTests/test262/expectations.yaml_sec1
1453
  default: 'TypeError: TypedArray.of requires its this argument to subclass a TypedArray constructor (Testing with Float64Array.)'
1453
  default: 'TypeError: TypedArray.of requires its this argument to subclass a TypedArray constructor (Testing with Float64Array.)'
1454
  strict mode: 'TypeError: TypedArray.of requires its this argument to subclass a TypedArray constructor (Testing with Float64Array.)'
1454
  strict mode: 'TypeError: TypedArray.of requires its this argument to subclass a TypedArray constructor (Testing with Float64Array.)'
1455
test/intl402/DateTimeFormat/prototype/format/timedatestyle-en.js:
1455
test/intl402/DateTimeFormat/prototype/format/timedatestyle-en.js:
1456
  default: 'Test262Error: Result for full with {} Expected SameValue(«14:12:47 PM Coordinated Universal Time», «14:12:47 Coordinated Universal Time») to be true'
1456
  default: 'Test262Error: Result for date=medium and time=full Expected SameValue(«May 1, 1886 at 2:12:47 PM Coordinated Universal Time», «May 1, 1886, 2:12:47 PM Coordinated Universal Time») to be true'
1457
  strict mode: 'Test262Error: Result for full with {} Expected SameValue(«14:12:47 PM Coordinated Universal Time», «14:12:47 Coordinated Universal Time») to be true'
1457
  strict mode: 'Test262Error: Result for date=medium and time=full Expected SameValue(«May 1, 1886 at 2:12:47 PM Coordinated Universal Time», «May 1, 1886, 2:12:47 PM Coordinated Universal Time») to be true'
1458
test/intl402/DateTimeFormat/prototype/formatRange/en-US.js:
1458
test/intl402/DateTimeFormat/prototype/formatRange/en-US.js:
1459
  default: 'Test262Error: Expected SameValue(«1/3/2019 – 1/5/2019», «1/3/2019 – 1/5/2019») to be true'
1459
  default: 'Test262Error: Expected SameValue(«1/3/2019 – 1/5/2019», «1/3/2019 – 1/5/2019») to be true'
1460
  strict mode: 'Test262Error: Expected SameValue(«1/3/2019 – 1/5/2019», «1/3/2019 – 1/5/2019») to be true'
1460
  strict mode: 'Test262Error: Expected SameValue(«1/3/2019 – 1/5/2019», «1/3/2019 – 1/5/2019») to be true'

Return to Bug 216521