Diff
Modified: branches/safari-612-branch/JSTests/ChangeLog (283973 => 283974)
--- branches/safari-612-branch/JSTests/ChangeLog 2021-10-12 08:40:24 UTC (rev 283973)
+++ branches/safari-612-branch/JSTests/ChangeLog 2021-10-12 09:30:55 UTC (rev 283974)
@@ -1,3 +1,14 @@
+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-10-06 Russell Epstein <[email protected]>
Cherry-pick r283556. rdar://problem/83956477
Added: branches/safari-612-branch/JSTests/stress/intl-date-pattern-includes-literal-text.js (0 => 283974)
--- branches/safari-612-branch/JSTests/stress/intl-date-pattern-includes-literal-text.js (rev 0)
+++ branches/safari-612-branch/JSTests/stress/intl-date-pattern-includes-literal-text.js 2021-10-12 09:30:55 UTC (rev 283974)
@@ -0,0 +1,10 @@
+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');
Modified: branches/safari-612-branch/Source/_javascript_Core/ChangeLog (283973 => 283974)
--- branches/safari-612-branch/Source/_javascript_Core/ChangeLog 2021-10-12 08:40:24 UTC (rev 283973)
+++ branches/safari-612-branch/Source/_javascript_Core/ChangeLog 2021-10-12 09:30:55 UTC (rev 283974)
@@ -1,3 +1,27 @@
+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-10-06 Russell Epstein <[email protected]>
Cherry-pick r283632. rdar://problem/83942704
Modified: branches/safari-612-branch/Source/_javascript_Core/runtime/IntlDateTimeFormat.cpp (283973 => 283974)
--- branches/safari-612-branch/Source/_javascript_Core/runtime/IntlDateTimeFormat.cpp 2021-10-12 08:40:24 UTC (rev 283973)
+++ branches/safari-612-branch/Source/_javascript_Core/runtime/IntlDateTimeFormat.cpp 2021-10-12 09:30:55 UTC (rev 283974)
@@ -310,13 +310,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;
@@ -453,9 +495,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':
@@ -472,7 +521,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':
@@ -503,7 +561,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: branches/safari-612-branch/Source/_javascript_Core/runtime/IntlDateTimeFormat.h (283973 => 283974)
--- branches/safari-612-branch/Source/_javascript_Core/runtime/IntlDateTimeFormat.h 2021-10-12 08:40:24 UTC (rev 283973)
+++ branches/safari-612-branch/Source/_javascript_Core/runtime/IntlDateTimeFormat.h 2021-10-12 09:30:55 UTC (rev 283974)
@@ -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);