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)));
}