Changeset 271224 in webkit
- Timestamp:
- Jan 6, 2021 10:11:42 PM (19 months ago)
- Location:
- trunk
- Files:
-
- 2 added
- 3 edited
-
JSTests/ChangeLog (modified) (1 diff)
-
JSTests/stress/intl-datetimeformat-format-range-should-check-practically-equal.js (added)
-
JSTests/stress/intl-datetimeformat-format-range-to-parts-should-check-practically-equal.js (added)
-
Source/JavaScriptCore/ChangeLog (modified) (1 diff)
-
Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp (modified) (6 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/JSTests/ChangeLog
r271168 r271224 1 2021-01-06 Yusuke Suzuki <ysuzuki@apple.com> 2 3 [JSC] DateTimeFormat#formatRange should generate the same output to DateTimeFormat#format if startDate and endDate are "practically-equal" 4 https://bugs.webkit.org/show_bug.cgi?id=220395 5 6 Reviewed by Ross Kirsling. 7 8 Imported some test262 tests. Updated, fixed some issues (`formatToParts` test was using `format`, we should upstream it to test262), and added more tests. 9 10 * stress/intl-datetimeformat-format-range-should-check-practically-equal.js: Added. 11 (shouldBe): 12 (vm.icuVersion): 13 * stress/intl-datetimeformat-format-range-to-parts-should-check-practically-equal.js: Added. 14 (shouldBe): 15 (zip): 16 (compare): 17 (vm.icuVersion): 18 1 19 2021-01-05 Yusuke Suzuki <ysuzuki@apple.com> 2 20 -
trunk/Source/JavaScriptCore/ChangeLog
r271217 r271224 1 2021-01-06 Yusuke Suzuki <ysuzuki@apple.com> 2 3 [JSC] DateTimeFormat#formatRange should generate the same output to DateTimeFormat#format if startDate and endDate are "practically-equal" 4 https://bugs.webkit.org/show_bug.cgi?id=220395 5 6 Reviewed by Ross Kirsling. 7 8 Intl.DateTimeFormat.formatRange(startDate, endDate) also needs to generate the same formatted string to the Intl.DateTimeFormat.format 9 if startDate and endDate are *practically-equal* (spec term). However, due to CLDR, just using udtitvfmt_format generates different 10 formatted string to udat_format's result even though startDate and endDate are the same. 11 12 new Intl.DateTimeFormat("en", { dateStyle: "long", timeStyle: "short" }).format(new Date()) 13 // "December 12, 2019 at 11:48 AM" 14 new Intl.DateTimeFormat("en", { dateStyle: "long", timeStyle: "short" }).formatRange(new Date(), new Date()) 15 // "December 12, 2019, 11:48 AM" 16 17 In Intl.DateTimeFormat#formatRangeToParts, we deploys *practically-equal* checking to avoid this issue. The same thing should be done in 18 Intl.DateTimeFormat#formatRange too. 19 20 In this patch, we stop using udtitvfmt_format if ICU version is 64 or later to perform *practically-equal* checking. 21 22 [1]: https://github.com/tc39/proposal-intl-DateTimeFormat-formatRange/issues/19 23 24 * runtime/IntlDateTimeFormat.cpp: 25 (JSC::formattedValueFromDateRange): 26 (JSC::dateFieldsPracticallyEqual): 27 (JSC::IntlDateTimeFormat::formatRange): 28 (JSC::IntlDateTimeFormat::formatRangeToParts): 29 (JSC::definitelyAfterGregorianCalendarChangeDate): Deleted. 30 1 31 2021-01-06 Yusuke Suzuki <ysuzuki@apple.com> 2 32 -
trunk/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp
r270861 r271224 1383 1383 #if HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS) 1384 1384 1385 // If a date is after Oct 15, 1582, the configuration of gregorian calendar change date in UCalendar does not affect1386 // on the formatted string. To ensure that it is after Oct 15 in all timezones, we add one day to gregorian calendar1387 // change date in UTC, so that this check can conservatively answer whether the date is definitely after gregorian1388 // calendar change date.1389 static inline bool definitelyAfterGregorianCalendarChangeDate(double millisecondsFromEpoch)1390 {1391 constexpr double gregorianCalendarReformDateInUTC = -12219292800000.0;1392 return millisecondsFromEpoch >= (gregorianCalendarReformDateInUTC + msPerDay);1393 }1394 1395 1385 static std::unique_ptr<UFormattedDateInterval, ICUDeleter<udtitvfmt_closeResult>> formattedValueFromDateRange(UDateIntervalFormat& dateIntervalFormat, UDateFormat& dateFormat, double startDate, double endDate, UErrorCode& status) 1396 1386 { … … 1401 1391 // After ICU 67, udtitvfmt_formatToResult's signature is changed. 1402 1392 #if U_ICU_VERSION_MAJOR_NUM >= 67 1393 // If a date is after Oct 15, 1582, the configuration of gregorian calendar change date in UCalendar does not affect 1394 // on the formatted string. To ensure that it is after Oct 15 in all timezones, we add one day to gregorian calendar 1395 // change date in UTC, so that this check can conservatively answer whether the date is definitely after gregorian 1396 // calendar change date. 1397 auto definitelyAfterGregorianCalendarChangeDate = [](double millisecondsFromEpoch) { 1398 constexpr double gregorianCalendarReformDateInUTC = -12219292800000.0; 1399 return millisecondsFromEpoch >= (gregorianCalendarReformDateInUTC + msPerDay); 1400 }; 1401 1403 1402 // UFormattedDateInterval does not have a way to configure gregorian calendar change date while ECMAScript requires that 1404 1403 // gregorian calendar change should not have effect (we are setting ucal_setGregorianChange(cal, minECMAScriptTime, &status) explicitly). … … 1444 1443 } 1445 1444 1445 static bool dateFieldsPracticallyEqual(const UFormattedValue* formattedValue, UErrorCode& status) 1446 { 1447 auto iterator = std::unique_ptr<UConstrainedFieldPosition, ICUDeleter<ucfpos_close>>(ucfpos_open(&status)); 1448 if (U_FAILURE(status)) 1449 return false; 1450 1451 // We only care about UFIELD_CATEGORY_DATE_INTERVAL_SPAN category. 1452 ucfpos_constrainCategory(iterator.get(), UFIELD_CATEGORY_DATE_INTERVAL_SPAN, &status); 1453 if (U_FAILURE(status)) 1454 return false; 1455 1456 bool hasSpan = ufmtval_nextPosition(formattedValue, iterator.get(), &status); 1457 if (U_FAILURE(status)) 1458 return false; 1459 1460 return !hasSpan; 1461 } 1462 1446 1463 #endif // HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS) 1447 1464 … … 1465 1482 1466 1483 #if HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS) 1467 // If the date is older than gregorian calendar change date, we need to explicitly pass configured UCalendar to 1468 // udtitvfmt_formatCalendarToResult to generate a correct formatted string. 1469 // The comment in formattedValueFromDateRange describes the details. 1470 if (!definitelyAfterGregorianCalendarChangeDate(startDate)) { 1471 UErrorCode status = U_ZERO_ERROR; 1472 auto result = formattedValueFromDateRange(*dateIntervalFormat, *m_dateFormat, startDate, endDate, status); 1473 if (U_FAILURE(status)) { 1474 throwTypeError(globalObject, scope, "Failed to format date interval"_s); 1475 return { }; 1476 } 1477 1478 // UFormattedValue is owned by UFormattedDateInterval. We do not need to close it. 1479 auto formattedValue = udtitvfmt_resultAsValue(result.get(), &status); 1480 if (U_FAILURE(status)) { 1481 throwTypeError(globalObject, scope, "Failed to format date interval"_s); 1482 return { }; 1483 } 1484 1485 int32_t formattedStringLength = 0; 1486 const UChar* formattedStringPointer = ufmtval_getString(formattedValue, &formattedStringLength, &status); 1487 if (U_FAILURE(status)) { 1488 throwTypeError(globalObject, scope, "Failed to format date interval"_s); 1489 return { }; 1490 } 1491 1492 return jsString(vm, String(formattedStringPointer, formattedStringLength)); 1493 } 1494 #endif 1495 1484 UErrorCode status = U_ZERO_ERROR; 1485 auto result = formattedValueFromDateRange(*dateIntervalFormat, *m_dateFormat, startDate, endDate, status); 1486 if (U_FAILURE(status)) { 1487 throwTypeError(globalObject, scope, "Failed to format date interval"_s); 1488 return { }; 1489 } 1490 1491 // UFormattedValue is owned by UFormattedDateInterval. We do not need to close it. 1492 auto formattedValue = udtitvfmt_resultAsValue(result.get(), &status); 1493 if (U_FAILURE(status)) { 1494 throwTypeError(globalObject, scope, "Failed to format date interval"_s); 1495 return { }; 1496 } 1497 1498 // If the formatted parts of startDate and endDate are the same, it is possible that the resulted string does not look like range. 1499 // For example, if the requested format only includes "year" and startDate and endDate are the same year, the result just contains one year. 1500 // In that case, startDate and endDate are *practically-equal* (spec term), and we generate parts as we call `formatToParts(startDate)` with 1501 // `source: "shared"` additional fields. 1502 bool equal = dateFieldsPracticallyEqual(formattedValue, status); 1503 if (U_FAILURE(status)) { 1504 throwTypeError(globalObject, scope, "Failed to format date interval"_s); 1505 return { }; 1506 } 1507 1508 if (equal) 1509 RELEASE_AND_RETURN(scope, format(globalObject, startDate)); 1510 1511 int32_t formattedStringLength = 0; 1512 const UChar* formattedStringPointer = ufmtval_getString(formattedValue, &formattedStringLength, &status); 1513 if (U_FAILURE(status)) { 1514 throwTypeError(globalObject, scope, "Failed to format date interval"_s); 1515 return { }; 1516 } 1517 1518 return jsString(vm, String(formattedStringPointer, formattedStringLength)); 1519 #else 1496 1520 Vector<UChar, 32> buffer; 1497 1521 auto status = callBufferProducingFunction(udtitvfmt_format, dateIntervalFormat, startDate, endDate, buffer, nullptr); … … 1502 1526 1503 1527 return jsString(vm, String(buffer)); 1528 #endif 1504 1529 } 1505 1530 … … 1522 1547 auto* dateIntervalFormat = createDateIntervalFormatIfNecessary(globalObject); 1523 1548 RETURN_IF_EXCEPTION(scope, { }); 1524 1525 auto dateFieldsPracticallyEqual = [](const UFormattedValue* formattedValue, UErrorCode& status) {1526 auto iterator = std::unique_ptr<UConstrainedFieldPosition, ICUDeleter<ucfpos_close>>(ucfpos_open(&status));1527 if (U_FAILURE(status))1528 return false;1529 1530 // We only care about UFIELD_CATEGORY_DATE_INTERVAL_SPAN category.1531 ucfpos_constrainCategory(iterator.get(), UFIELD_CATEGORY_DATE_INTERVAL_SPAN, &status);1532 if (U_FAILURE(status))1533 return false;1534 1535 bool hasSpan = ufmtval_nextPosition(formattedValue, iterator.get(), &status);1536 if (U_FAILURE(status))1537 return false;1538 1539 return !hasSpan;1540 };1541 1549 1542 1550 UErrorCode status = U_ZERO_ERROR;
Note: See TracChangeset
for help on using the changeset viewer.