Diff
Modified: trunk/JSTests/ChangeLog (271223 => 271224)
--- trunk/JSTests/ChangeLog 2021-01-07 06:11:14 UTC (rev 271223)
+++ trunk/JSTests/ChangeLog 2021-01-07 06:11:42 UTC (rev 271224)
@@ -1,3 +1,21 @@
+2021-01-06 Yusuke Suzuki <[email protected]>
+
+ [JSC] DateTimeFormat#formatRange should generate the same output to DateTimeFormat#format if startDate and endDate are "practically-equal"
+ https://bugs.webkit.org/show_bug.cgi?id=220395
+
+ Reviewed by Ross Kirsling.
+
+ Imported some test262 tests. Updated, fixed some issues (`formatToParts` test was using `format`, we should upstream it to test262), and added more tests.
+
+ * stress/intl-datetimeformat-format-range-should-check-practically-equal.js: Added.
+ (shouldBe):
+ (vm.icuVersion):
+ * stress/intl-datetimeformat-format-range-to-parts-should-check-practically-equal.js: Added.
+ (shouldBe):
+ (zip):
+ (compare):
+ (vm.icuVersion):
+
2021-01-05 Yusuke Suzuki <[email protected]>
[WASM] [BigInt] Add I64 to BigInt conversion
Added: trunk/JSTests/stress/intl-datetimeformat-format-range-should-check-practically-equal.js (0 => 271224)
--- trunk/JSTests/stress/intl-datetimeformat-format-range-should-check-practically-equal.js (rev 0)
+++ trunk/JSTests/stress/intl-datetimeformat-format-range-should-check-practically-equal.js 2021-01-07 06:11:42 UTC (rev 271224)
@@ -0,0 +1,73 @@
+// The << Software identified by reference to the Ecma Standard* ("Software)">> is protected by copyright and is being
+// made available under the "BSD License", included below. This Software may be subject to third party rights (rights
+// from parties other than Ecma International), including patent rights, and no licenses under such third party rights
+// are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA
+// CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT http://www.ecma-international.org/memento/codeofconduct.htm FOR
+// INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS*.
+//
+// Copyright (C) 2012-2013 Ecma International
+// Copyright (C) 2021 Google Inc. All rights reserved.
+// Copyright (C) 2021 Apple Inc. All rights reserved.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+// following conditions are met:
+// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+// disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
+// following disclaimer in the documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the authors nor Ecma International may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+// SHALL ECMA INTERNATIONAL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+// DAMAGE.
+//
+// * Ecma International Standards hereafter means Ecma International Standards as well as Ecma Technical Reports
+
+function shouldBe(actual, expected) {
+ if (actual !== expected)
+ throw new Error('bad value: ' + actual);
+}
+
+if ($vm.icuVersion() >= 64) {
+ {
+ const date = new Date(2019, 7, 10, 1, 2, 3, 234);
+
+ let dtf = new Intl.DateTimeFormat("en", { year: "numeric", month: "short", day: "numeric" });
+ shouldBe(dtf.formatRange(date, date), dtf.format(date));
+
+ dtf = new Intl.DateTimeFormat("en", { minute: "numeric", second: "numeric" });
+ shouldBe(dtf.formatRange(date, date), dtf.format(date));
+
+ dtf = new Intl.DateTimeFormat("en", { month: "short", day: "numeric", minute: "numeric" });
+ shouldBe(dtf.formatRange(date, date), dtf.format(date));
+
+ dtf = new Intl.DateTimeFormat("en", { dateStyle: "long", timeStyle: "short" });
+ shouldBe(dtf.formatRange(date, date), dtf.format(date));
+ }
+
+ {
+ // date1 and date2 are practically equal since the following formats do not show milliseconds.
+ const date1 = new Date(2019, 7, 10, 1, 2, 3, 234);
+ const date2 = new Date(2019, 7, 10, 1, 2, 3, 235);
+
+ shouldBe(date1.getTime() !== date2.getTime(), true);
+
+ let dtf = new Intl.DateTimeFormat("en", { year: "numeric", month: "short", day: "numeric" });
+ shouldBe(dtf.formatRange(date1, date2), dtf.format(date1));
+
+ dtf = new Intl.DateTimeFormat("en", { minute: "numeric", second: "numeric" });
+ shouldBe(dtf.formatRange(date1, date2), dtf.format(date1));
+
+ dtf = new Intl.DateTimeFormat("en", { month: "short", day: "numeric", minute: "numeric" });
+ shouldBe(dtf.formatRange(date1, date2), dtf.format(date1));
+
+ dtf = new Intl.DateTimeFormat("en", { dateStyle: "long", timeStyle: "short" });
+ shouldBe(dtf.formatRange(date1, date2), dtf.format(date1));
+ }
+}
Added: trunk/JSTests/stress/intl-datetimeformat-format-range-to-parts-should-check-practically-equal.js (0 => 271224)
--- trunk/JSTests/stress/intl-datetimeformat-format-range-to-parts-should-check-practically-equal.js (rev 0)
+++ trunk/JSTests/stress/intl-datetimeformat-format-range-to-parts-should-check-practically-equal.js 2021-01-07 06:11:42 UTC (rev 271224)
@@ -0,0 +1,84 @@
+// The << Software identified by reference to the Ecma Standard* ("Software)">> is protected by copyright and is being
+// made available under the "BSD License", included below. This Software may be subject to third party rights (rights
+// from parties other than Ecma International), including patent rights, and no licenses under such third party rights
+// are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA
+// CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT http://www.ecma-international.org/memento/codeofconduct.htm FOR
+// INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS*.
+//
+// Copyright (C) 2012-2013 Ecma International
+// Copyright (C) 2021 Google Inc. All rights reserved.
+// Copyright (C) 2021 Apple Inc. All rights reserved.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+// following conditions are met:
+// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+// disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
+// following disclaimer in the documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the authors nor Ecma International may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+// SHALL ECMA INTERNATIONAL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+// DAMAGE.
+//
+// * Ecma International Standards hereafter means Ecma International Standards as well as Ecma Technical Reports
+
+function shouldBe(actual, expected) {
+ if (actual !== expected)
+ throw new Error('bad value: ' + actual);
+}
+
+function* zip(a, b) {
+ shouldBe(a.length, b.length);
+ for (let i = 0; i < a.length; ++i) {
+ yield [i, a[i], b[i]];
+ }
+}
+
+function compare(actual, expected) {
+ for (const [i, actualEntry, expectedEntry] of zip(actual, expected)) {
+ shouldBe(actualEntry.type, expectedEntry.type);
+ shouldBe(actualEntry.value, expectedEntry.value);
+ }
+}
+
+if ($vm.icuVersion() >= 64) {
+ {
+ const date = new Date(2019, 7, 10, 1, 2, 3, 234);
+
+ let dtf = new Intl.DateTimeFormat("en", { year: "numeric", month: "short", day: "numeric" });
+ compare(dtf.formatRangeToParts(date, date), dtf.formatToParts(date));
+
+ dtf = new Intl.DateTimeFormat("en", { minute: "numeric", second: "numeric" });
+ compare(dtf.formatRangeToParts(date, date), dtf.formatToParts(date));
+
+ dtf = new Intl.DateTimeFormat("en", { month: "short", day: "numeric", minute: "numeric" });
+ compare(dtf.formatRangeToParts(date, date), dtf.formatToParts(date));
+
+ dtf = new Intl.DateTimeFormat("en", { dateStyle: "long", timeStyle: "short" });
+ compare(dtf.formatRangeToParts(date, date), dtf.formatToParts(date));
+ }
+ {
+ // date1 and date2 are practically equal since the following formats do not show milliseconds.
+ const date1 = new Date(2019, 7, 10, 1, 2, 3, 234);
+ const date2 = new Date(2019, 7, 10, 1, 2, 3, 235);
+
+ let dtf = new Intl.DateTimeFormat("en", { year: "numeric", month: "short", day: "numeric" });
+ compare(dtf.formatRangeToParts(date1, date2), dtf.formatToParts(date1));
+
+ dtf = new Intl.DateTimeFormat("en", { minute: "numeric", second: "numeric" });
+ compare(dtf.formatRangeToParts(date1, date2), dtf.formatToParts(date1));
+
+ dtf = new Intl.DateTimeFormat("en", { month: "short", day: "numeric", minute: "numeric" });
+ compare(dtf.formatRangeToParts(date1, date2), dtf.formatToParts(date1));
+
+ dtf = new Intl.DateTimeFormat("en", { dateStyle: "long", timeStyle: "short" });
+ compare(dtf.formatRangeToParts(date1, date2), dtf.formatToParts(date1));
+ }
+}
Modified: trunk/Source/_javascript_Core/ChangeLog (271223 => 271224)
--- trunk/Source/_javascript_Core/ChangeLog 2021-01-07 06:11:14 UTC (rev 271223)
+++ trunk/Source/_javascript_Core/ChangeLog 2021-01-07 06:11:42 UTC (rev 271224)
@@ -1,5 +1,35 @@
2021-01-06 Yusuke Suzuki <[email protected]>
+ [JSC] DateTimeFormat#formatRange should generate the same output to DateTimeFormat#format if startDate and endDate are "practically-equal"
+ https://bugs.webkit.org/show_bug.cgi?id=220395
+
+ Reviewed by Ross Kirsling.
+
+ Intl.DateTimeFormat.formatRange(startDate, endDate) also needs to generate the same formatted string to the Intl.DateTimeFormat.format
+ if startDate and endDate are *practically-equal* (spec term). However, due to CLDR, just using udtitvfmt_format generates different
+ formatted string to udat_format's result even though startDate and endDate are the same.
+
+ new Intl.DateTimeFormat("en", { dateStyle: "long", timeStyle: "short" }).format(new Date())
+ // "December 12, 2019 at 11:48 AM"
+ new Intl.DateTimeFormat("en", { dateStyle: "long", timeStyle: "short" }).formatRange(new Date(), new Date())
+ // "December 12, 2019, 11:48 AM"
+
+ In Intl.DateTimeFormat#formatRangeToParts, we deploys *practically-equal* checking to avoid this issue. The same thing should be done in
+ Intl.DateTimeFormat#formatRange too.
+
+ In this patch, we stop using udtitvfmt_format if ICU version is 64 or later to perform *practically-equal* checking.
+
+ [1]: https://github.com/tc39/proposal-intl-DateTimeFormat-formatRange/issues/19
+
+ * runtime/IntlDateTimeFormat.cpp:
+ (JSC::formattedValueFromDateRange):
+ (JSC::dateFieldsPracticallyEqual):
+ (JSC::IntlDateTimeFormat::formatRange):
+ (JSC::IntlDateTimeFormat::formatRangeToParts):
+ (JSC::definitelyAfterGregorianCalendarChangeDate): Deleted.
+
+2021-01-06 Yusuke Suzuki <[email protected]>
+
[JSC] Replace JSBigInt::toUint64 with JSBigInt::toBigUInt64
https://bugs.webkit.org/show_bug.cgi?id=220378
Modified: trunk/Source/_javascript_Core/runtime/IntlDateTimeFormat.cpp (271223 => 271224)
--- trunk/Source/_javascript_Core/runtime/IntlDateTimeFormat.cpp 2021-01-07 06:11:14 UTC (rev 271223)
+++ trunk/Source/_javascript_Core/runtime/IntlDateTimeFormat.cpp 2021-01-07 06:11:42 UTC (rev 271224)
@@ -1382,16 +1382,6 @@
#if HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS)
-// If a date is after Oct 15, 1582, the configuration of gregorian calendar change date in UCalendar does not affect
-// on the formatted string. To ensure that it is after Oct 15 in all timezones, we add one day to gregorian calendar
-// change date in UTC, so that this check can conservatively answer whether the date is definitely after gregorian
-// calendar change date.
-static inline bool definitelyAfterGregorianCalendarChangeDate(double millisecondsFromEpoch)
-{
- constexpr double gregorianCalendarReformDateInUTC = -12219292800000.0;
- return millisecondsFromEpoch >= (gregorianCalendarReformDateInUTC + msPerDay);
-}
-
static std::unique_ptr<UFormattedDateInterval, ICUDeleter<udtitvfmt_closeResult>> formattedValueFromDateRange(UDateIntervalFormat& dateIntervalFormat, UDateFormat& dateFormat, double startDate, double endDate, UErrorCode& status)
{
auto result = std::unique_ptr<UFormattedDateInterval, ICUDeleter<udtitvfmt_closeResult>>(udtitvfmt_openResult(&status));
@@ -1400,6 +1390,15 @@
// After ICU 67, udtitvfmt_formatToResult's signature is changed.
#if U_ICU_VERSION_MAJOR_NUM >= 67
+ // If a date is after Oct 15, 1582, the configuration of gregorian calendar change date in UCalendar does not affect
+ // on the formatted string. To ensure that it is after Oct 15 in all timezones, we add one day to gregorian calendar
+ // change date in UTC, so that this check can conservatively answer whether the date is definitely after gregorian
+ // calendar change date.
+ auto definitelyAfterGregorianCalendarChangeDate = [](double millisecondsFromEpoch) {
+ constexpr double gregorianCalendarReformDateInUTC = -12219292800000.0;
+ return millisecondsFromEpoch >= (gregorianCalendarReformDateInUTC + msPerDay);
+ };
+
// UFormattedDateInterval does not have a way to configure gregorian calendar change date while ECMAScript requires that
// gregorian calendar change should not have effect (we are setting ucal_setGregorianChange(cal, minECMAScriptTime, &status) explicitly).
// As a result, if the input date is older than gregorian calendar change date (Oct 15, 1582), the formatted string becomes
@@ -1443,6 +1442,24 @@
return result;
}
+static bool dateFieldsPracticallyEqual(const UFormattedValue* formattedValue, UErrorCode& status)
+{
+ auto iterator = std::unique_ptr<UConstrainedFieldPosition, ICUDeleter<ucfpos_close>>(ucfpos_open(&status));
+ if (U_FAILURE(status))
+ return false;
+
+ // We only care about UFIELD_CATEGORY_DATE_INTERVAL_SPAN category.
+ ucfpos_constrainCategory(iterator.get(), UFIELD_CATEGORY_DATE_INTERVAL_SPAN, &status);
+ if (U_FAILURE(status))
+ return false;
+
+ bool hasSpan = ufmtval_nextPosition(formattedValue, iterator.get(), &status);
+ if (U_FAILURE(status))
+ return false;
+
+ return !hasSpan;
+}
+
#endif // HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS)
JSValue IntlDateTimeFormat::formatRange(JSGlobalObject* globalObject, double startDate, double endDate)
@@ -1464,35 +1481,42 @@
RETURN_IF_EXCEPTION(scope, { });
#if HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS)
- // If the date is older than gregorian calendar change date, we need to explicitly pass configured UCalendar to
- // udtitvfmt_formatCalendarToResult to generate a correct formatted string.
- // The comment in formattedValueFromDateRange describes the details.
- if (!definitelyAfterGregorianCalendarChangeDate(startDate)) {
- UErrorCode status = U_ZERO_ERROR;
- auto result = formattedValueFromDateRange(*dateIntervalFormat, *m_dateFormat, startDate, endDate, status);
- if (U_FAILURE(status)) {
- throwTypeError(globalObject, scope, "Failed to format date interval"_s);
- return { };
- }
+ UErrorCode status = U_ZERO_ERROR;
+ auto result = formattedValueFromDateRange(*dateIntervalFormat, *m_dateFormat, startDate, endDate, status);
+ if (U_FAILURE(status)) {
+ throwTypeError(globalObject, scope, "Failed to format date interval"_s);
+ return { };
+ }
- // UFormattedValue is owned by UFormattedDateInterval. We do not need to close it.
- auto formattedValue = udtitvfmt_resultAsValue(result.get(), &status);
- if (U_FAILURE(status)) {
- throwTypeError(globalObject, scope, "Failed to format date interval"_s);
- return { };
- }
+ // UFormattedValue is owned by UFormattedDateInterval. We do not need to close it.
+ auto formattedValue = udtitvfmt_resultAsValue(result.get(), &status);
+ if (U_FAILURE(status)) {
+ throwTypeError(globalObject, scope, "Failed to format date interval"_s);
+ return { };
+ }
- int32_t formattedStringLength = 0;
- const UChar* formattedStringPointer = ufmtval_getString(formattedValue, &formattedStringLength, &status);
- if (U_FAILURE(status)) {
- throwTypeError(globalObject, scope, "Failed to format date interval"_s);
- return { };
- }
+ // If the formatted parts of startDate and endDate are the same, it is possible that the resulted string does not look like range.
+ // For example, if the requested format only includes "year" and startDate and endDate are the same year, the result just contains one year.
+ // In that case, startDate and endDate are *practically-equal* (spec term), and we generate parts as we call `formatToParts(startDate)` with
+ // `source: "shared"` additional fields.
+ bool equal = dateFieldsPracticallyEqual(formattedValue, status);
+ if (U_FAILURE(status)) {
+ throwTypeError(globalObject, scope, "Failed to format date interval"_s);
+ return { };
+ }
- return jsString(vm, String(formattedStringPointer, formattedStringLength));
+ if (equal)
+ RELEASE_AND_RETURN(scope, format(globalObject, startDate));
+
+ int32_t formattedStringLength = 0;
+ const UChar* formattedStringPointer = ufmtval_getString(formattedValue, &formattedStringLength, &status);
+ if (U_FAILURE(status)) {
+ throwTypeError(globalObject, scope, "Failed to format date interval"_s);
+ return { };
}
-#endif
+ return jsString(vm, String(formattedStringPointer, formattedStringLength));
+#else
Vector<UChar, 32> buffer;
auto status = callBufferProducingFunction(udtitvfmt_format, dateIntervalFormat, startDate, endDate, buffer, nullptr);
if (U_FAILURE(status)) {
@@ -1501,6 +1525,7 @@
}
return jsString(vm, String(buffer));
+#endif
}
JSValue IntlDateTimeFormat::formatRangeToParts(JSGlobalObject* globalObject, double startDate, double endDate)
@@ -1522,23 +1547,6 @@
auto* dateIntervalFormat = createDateIntervalFormatIfNecessary(globalObject);
RETURN_IF_EXCEPTION(scope, { });
- auto dateFieldsPracticallyEqual = [](const UFormattedValue* formattedValue, UErrorCode& status) {
- auto iterator = std::unique_ptr<UConstrainedFieldPosition, ICUDeleter<ucfpos_close>>(ucfpos_open(&status));
- if (U_FAILURE(status))
- return false;
-
- // We only care about UFIELD_CATEGORY_DATE_INTERVAL_SPAN category.
- ucfpos_constrainCategory(iterator.get(), UFIELD_CATEGORY_DATE_INTERVAL_SPAN, &status);
- if (U_FAILURE(status))
- return false;
-
- bool hasSpan = ufmtval_nextPosition(formattedValue, iterator.get(), &status);
- if (U_FAILURE(status))
- return false;
-
- return !hasSpan;
- };
-
UErrorCode status = U_ZERO_ERROR;
auto result = formattedValueFromDateRange(*dateIntervalFormat, *m_dateFormat, startDate, endDate, status);
if (U_FAILURE(status)) {