Title: [281688] trunk
Revision
281688
Author
[email protected]
Date
2021-08-27 00:15:06 -0700 (Fri, 27 Aug 2021)

Log Message

Intl.DateTimeFormat incorrectly parses patterns with 'h' literal
https://bugs.webkit.org/show_bug.cgi?id=229313
rdar://82414310

Reviewed by Ross Kirsling.

JSTests:

* stress/intl-date-pattern-includes-literal-text.js: Added.
(shouldBe):

Source/_javascript_Core:

While DateTimeFormat pattern and skeleton can include single-quoted literal texts,
we are not respecting that when parsing them to extract information. As a result,
we are incorrectly extracting hour-cycle information for "fr" locale since it can
include "HH 'h'" pattern text. This patch fixes that by skipping literal text
correctly.

* runtime/IntlDateTimeFormat.cpp:
(JSC::skipLiteralText):
(JSC::IntlDateTimeFormat::setFormatsFromPattern):
(JSC::IntlDateTimeFormat::hourCycleFromPattern):
(JSC::IntlDateTimeFormat::replaceHourCycleInSkeleton):
(JSC::IntlDateTimeFormat::replaceHourCycleInPattern):
* runtime/IntlDateTimeFormat.h:
* runtime/IntlLocale.cpp:
(JSC::IntlLocale::hourCycles):

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (281687 => 281688)


--- trunk/JSTests/ChangeLog	2021-08-27 06:18:56 UTC (rev 281687)
+++ trunk/JSTests/ChangeLog	2021-08-27 07:15:06 UTC (rev 281688)
@@ -1,5 +1,16 @@
 2021-08-26  Yusuke Suzuki  <[email protected]>
 
+        Intl.DateTimeFormat incorrectly parses patterns with 'h' literal
+        https://bugs.webkit.org/show_bug.cgi?id=229313
+        rdar://82414310
+
+        Reviewed by Ross Kirsling.
+
+        * stress/intl-date-pattern-includes-literal-text.js: Added.
+        (shouldBe):
+
+2021-08-26  Yusuke Suzuki  <[email protected]>
+
         Float32Arrays.sort() return undefined when length < 2
         https://bugs.webkit.org/show_bug.cgi?id=229575
 

Added: trunk/JSTests/stress/intl-date-pattern-includes-literal-text.js (0 => 281688)


--- trunk/JSTests/stress/intl-date-pattern-includes-literal-text.js	                        (rev 0)
+++ trunk/JSTests/stress/intl-date-pattern-includes-literal-text.js	2021-08-27 07:15:06 UTC (rev 281688)
@@ -0,0 +1,14 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error(`expected ${expected} but got ${actual}`);
+}
+
+shouldBe(new Intl.DateTimeFormat("fr", {hour: "numeric", hour12: false}).resolvedOptions().hour12, false);
+shouldBe(new Intl.DateTimeFormat("fr", {hour: "numeric", hour12: false}).format(new Date(2021, 2, 3, 23)), `23 h`);
+
+shouldBe(new Intl.DateTimeFormat("fr", {hour: "numeric", hourCycle: 'h24'}).format(new Date(2021, 2, 3, 23)), '23 h');
+shouldBe(new Intl.DateTimeFormat("fr", {hour: "numeric", hourCycle: 'h23'}).format(new Date(2021, 2, 3, 23)), '23 h');
+
+shouldBe(JSON.stringify(new Intl.Locale("fr", {hourCycle: 'h24'}).hourCycles), `["h24"]`);
+shouldBe(JSON.stringify(new Intl.Locale("fr", {hourCycle: 'h23'}).hourCycles), `["h23"]`);
+shouldBe(JSON.stringify(new Intl.Locale("fr").hourCycles), `["h23"]`);

Modified: trunk/Source/_javascript_Core/ChangeLog (281687 => 281688)


--- trunk/Source/_javascript_Core/ChangeLog	2021-08-27 06:18:56 UTC (rev 281687)
+++ trunk/Source/_javascript_Core/ChangeLog	2021-08-27 07:15:06 UTC (rev 281688)
@@ -1,5 +1,29 @@
 2021-08-26  Yusuke Suzuki  <[email protected]>
 
+        Intl.DateTimeFormat incorrectly parses patterns with 'h' literal
+        https://bugs.webkit.org/show_bug.cgi?id=229313
+        rdar://82414310
+
+        Reviewed by Ross Kirsling.
+
+        While DateTimeFormat pattern and skeleton can include single-quoted literal texts,
+        we are not respecting that when parsing them to extract information. As a result,
+        we are incorrectly extracting hour-cycle information for "fr" locale since it can
+        include "HH 'h'" pattern text. This patch fixes that by skipping literal text
+        correctly.
+
+        * runtime/IntlDateTimeFormat.cpp:
+        (JSC::skipLiteralText):
+        (JSC::IntlDateTimeFormat::setFormatsFromPattern):
+        (JSC::IntlDateTimeFormat::hourCycleFromPattern):
+        (JSC::IntlDateTimeFormat::replaceHourCycleInSkeleton):
+        (JSC::IntlDateTimeFormat::replaceHourCycleInPattern):
+        * runtime/IntlDateTimeFormat.h:
+        * runtime/IntlLocale.cpp:
+        (JSC::IntlLocale::hourCycles):
+
+2021-08-26  Yusuke Suzuki  <[email protected]>
+
         Float32Arrays.sort() return undefined when length < 2
         https://bugs.webkit.org/show_bug.cgi?id=229575
 

Modified: trunk/Source/_javascript_Core/runtime/IntlDateTimeFormat.cpp (281687 => 281688)


--- trunk/Source/_javascript_Core/runtime/IntlDateTimeFormat.cpp	2021-08-27 06:18:56 UTC (rev 281687)
+++ trunk/Source/_javascript_Core/runtime/IntlDateTimeFormat.cpp	2021-08-27 07:15:06 UTC (rev 281688)
@@ -306,13 +306,55 @@
     return options;
 }
 
+template<typename Container>
+static inline unsigned skipLiteralText(const Container& container, unsigned start, unsigned length)
+{
+    // Skip literal text. We do not recognize '' single quote specially.
+    // `'ICU''s change'` is `ICU's change` literal text, but even if we split this text into two literal texts,
+    // we can anyway skip the same thing.
+    // This function returns the last character index which can be considered as a literal text.
+    ASSERT(length);
+    ASSERT(start < length);
+    ASSERT(container[start] == '\'');
+    unsigned index = start;
+    ++index;
+    if (!(index < length))
+        return length - 1;
+    for (; index < length; ++index) {
+        if (container[index] == '\'')
+            return index;
+    }
+    return length - 1;
+}
+
 void IntlDateTimeFormat::setFormatsFromPattern(const StringView& pattern)
 {
     // Get all symbols from the pattern, and set format fields accordingly.
     // http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
+    //
+    // A date pattern is a character string consisting of two types of elements:
+    // 1. Pattern fields, which repeat a specific pattern character one or more times.
+    //    These fields are replaced with date and time data from a calendar when formatting,
+    //    or used to generate data for a calendar when parsing. Currently, A..Z and a..z are
+    //    reserved for use as pattern characters (unless they are quoted, see next item).
+    //    The pattern characters currently defined, and the meaning of different fields
+    //    lengths for then, are listed in the Date Field Symbol Table below.
+    // 2. Literal text, which is output as-is when formatting, and must closely match when
+    //    parsing. Literal text can include:
+    //      1. Any characters other than A..Z and a..z, including spaces and punctuation.
+    //      2. Any text between single vertical quotes ('xxxx'), which may include A..Z and
+    //         a..z as literal text.
+    //      3. Two adjacent single vertical quotes (''), which represent a literal single quote,
+    //         either inside or outside quoted text.
     unsigned length = pattern.length();
     for (unsigned i = 0; i < length; ++i) {
-        UChar currentCharacter = pattern[i];
+        auto currentCharacter = pattern[i];
+
+        if (currentCharacter == '\'') {
+            i = skipLiteralText(pattern, i, length);
+            continue;
+        }
+
         if (!isASCIIAlpha(currentCharacter))
             continue;
 
@@ -460,9 +502,16 @@
     return HourCycle::None;
 }
 
-inline IntlDateTimeFormat::HourCycle IntlDateTimeFormat::hourCycleFromPattern(const Vector<UChar, 32>& pattern)
+IntlDateTimeFormat::HourCycle IntlDateTimeFormat::hourCycleFromPattern(const Vector<UChar, 32>& pattern)
 {
-    for (auto character : pattern) {
+    for (unsigned i = 0, length = pattern.size(); i < length; ++i) {
+        auto character = pattern[i];
+
+        if (character == '\'') {
+            i = skipLiteralText(pattern, i, length);
+            continue;
+        }
+
         switch (character) {
         case 'K':
         case 'h':
@@ -479,7 +528,16 @@
     UChar skeletonCharacter = 'H';
     if (isHour12)
         skeletonCharacter = 'h';
-    for (auto& character : skeleton) {
+    for (unsigned i = 0, length = skeleton.size(); i < length; ++i) {
+        auto& character = skeleton[i];
+
+        // ICU DateTimeFormat skeleton also has single-quoted literal text.
+        // https://github.com/unicode-org/icu/blob/main/icu4c/source/i18n/dtptngen.cpp
+        if (character == '\'') {
+            i = skipLiteralText(skeleton, i, length);
+            continue;
+        }
+
         switch (character) {
         case 'h':
         case 'H':
@@ -510,7 +568,14 @@
         return;
     }
 
-    for (auto& character : pattern) {
+    for (unsigned i = 0, length = pattern.size(); i < length; ++i) {
+        auto& character = pattern[i];
+
+        if (character == '\'') {
+            i = skipLiteralText(pattern, i, length);
+            continue;
+        }
+
         switch (character) {
         case 'K':
         case 'h':

Modified: trunk/Source/_javascript_Core/runtime/IntlDateTimeFormat.h (281687 => 281688)


--- trunk/Source/_javascript_Core/runtime/IntlDateTimeFormat.h	2021-08-27 06:18:56 UTC (rev 281687)
+++ trunk/Source/_javascript_Core/runtime/IntlDateTimeFormat.h	2021-08-27 07:15:06 UTC (rev 281688)
@@ -83,6 +83,9 @@
 
     static IntlDateTimeFormat* unwrapForOldFunctions(JSGlobalObject*, JSValue);
 
+    enum class HourCycle : uint8_t { None, H11, H12, H23, H24 };
+    static HourCycle hourCycleFromPattern(const Vector<UChar, 32>&);
+
 private:
     IntlDateTimeFormat(VM&, Structure*);
     void finishCreation(VM&);
@@ -92,7 +95,6 @@
 
     UDateIntervalFormat* createDateIntervalFormatIfNecessary(JSGlobalObject*);
 
-    enum class HourCycle : uint8_t { None, H11, H12, H23, H24 };
     enum class Weekday : uint8_t { None, Narrow, Short, Long };
     enum class Era : uint8_t { None, Narrow, Short, Long };
     enum class Year : uint8_t { None, TwoDigit, Numeric };
@@ -121,7 +123,6 @@
 
     static HourCycle hourCycleFromSymbol(UChar);
     static HourCycle parseHourCycle(const String&);
-    static HourCycle hourCycleFromPattern(const Vector<UChar, 32>&);
     static void replaceHourCycleInSkeleton(Vector<UChar, 32>&, bool hour12);
     static void replaceHourCycleInPattern(Vector<UChar, 32>&, HourCycle);
 

Modified: trunk/Source/_javascript_Core/runtime/IntlLocale.cpp (281687 => 281688)


--- trunk/Source/_javascript_Core/runtime/IntlLocale.cpp	2021-08-27 06:18:56 UTC (rev 281687)
+++ trunk/Source/_javascript_Core/runtime/IntlLocale.cpp	2021-08-27 07:15:06 UTC (rev 281688)
@@ -27,6 +27,7 @@
 #include "config.h"
 #include "IntlLocale.h"
 
+#include "IntlDateTimeFormat.h"
 #include "IntlObjectInlines.h"
 #include "JSCInlines.h"
 #include <unicode/ucal.h>
@@ -41,6 +42,10 @@
 
 const ClassInfo IntlLocale::s_info = { "Object", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(IntlLocale) };
 
+namespace IntlLocaleInternal {
+static constexpr bool verbose = false;
+}
+
 IntlLocale* IntlLocale::create(VM& vm, Structure* structure)
 {
     auto* object = new (NotNull, allocateCell<IntlLocale>(vm.heap)) IntlLocale(vm, structure);
@@ -643,35 +648,28 @@
         return nullptr;
     }
 
-    for (unsigned i = 0; i < pattern.size(); ++i) {
-        UChar currentCharacter = pattern[i];
-        if (!isASCIIAlpha(currentCharacter))
-            continue;
+    dataLogLnIf(IntlLocaleInternal::verbose, "pattern:(", StringView(pattern.data(), pattern.size()), ")");
 
-        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;
-        }
+    switch (IntlDateTimeFormat::hourCycleFromPattern(pattern)) {
+    case IntlDateTimeFormat::HourCycle::None:
+        break;
+    case IntlDateTimeFormat::HourCycle::H11: {
+        elements.append("h11"_s);
+        RELEASE_AND_RETURN(scope, createArrayFromStringVector(globalObject, WTFMove(elements)));
     }
+    case IntlDateTimeFormat::HourCycle::H12: {
+        elements.append("h12"_s);
+        RELEASE_AND_RETURN(scope, createArrayFromStringVector(globalObject, WTFMove(elements)));
+    }
+    case IntlDateTimeFormat::HourCycle::H23: {
+        elements.append("h23"_s);
+        RELEASE_AND_RETURN(scope, createArrayFromStringVector(globalObject, WTFMove(elements)));
+    }
+    case IntlDateTimeFormat::HourCycle::H24: {
+        elements.append("h24"_s);
+        RELEASE_AND_RETURN(scope, createArrayFromStringVector(globalObject, WTFMove(elements)));
+    }
+    }
 
     RELEASE_AND_RETURN(scope, createArrayFromStringVector(globalObject, WTFMove(elements)));
 }
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to