Changeset 269706 in webkit
- Timestamp:
- Nov 11, 2020 4:25:17 PM (21 months ago)
- Location:
- trunk
- Files:
-
- 5 added
- 13 edited
-
JSTests/ChangeLog (modified) (1 diff)
-
JSTests/stress/intl-datetimeformat-formatrange-relevant-extensions.js (modified) (1 diff)
-
JSTests/stress/intl-datetimeformat-formatrange-should-not-handle-gregorian-change-date.js (added)
-
JSTests/stress/intl-datetimeformat-formatrange.js (modified) (7 diffs)
-
JSTests/stress/intl-datetimeformat-formatrangetoparts-relevant-extensions-ja.js (added)
-
JSTests/stress/intl-datetimeformat-formatrangetoparts-relevant-extensions.js (added)
-
JSTests/stress/intl-datetimeformat-formatrangetoparts-should-not-handle-gregorian-change-date.js (added)
-
JSTests/stress/intl-datetimeformat-formatrangetoparts.js (added)
-
JSTests/test262/config.yaml (modified) (1 diff)
-
JSTests/test262/expectations.yaml (modified) (1 diff)
-
Source/JavaScriptCore/ChangeLog (modified) (1 diff)
-
Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj (modified) (2 diffs)
-
Source/JavaScriptCore/Sources.txt (modified) (2 diffs)
-
Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp (modified) (7 diffs)
-
Source/JavaScriptCore/runtime/IntlDateTimeFormat.h (modified) (4 diffs)
-
Source/JavaScriptCore/runtime/IntlDateTimeFormatPrototype.cpp (modified) (4 diffs)
-
Source/JavaScriptCore/runtime/IntlDateTimeFormatPrototype.h (modified) (1 diff)
-
Source/JavaScriptCore/runtime/OptionsList.h (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/JSTests/ChangeLog
r269670 r269706 1 2020-11-11 Yusuke Suzuki <ysuzuki@apple.com> 2 3 [JSC] Implement Intl.DateTimeFormat.formatRangeToParts 4 https://bugs.webkit.org/show_bug.cgi?id=213822 5 <rdar://problem/69328711> 6 7 Reviewed by Ross Kirsling. 8 9 * stress/intl-datetimeformat-formatrange-relevant-extensions.js: 10 * stress/intl-datetimeformat-formatrange-should-not-handle-gregorian-change-date.js: Added. 11 (shouldBe): 12 (vm.icuHeaderVersion): 13 * stress/intl-datetimeformat-formatrange.js: 14 (shouldThrow): 15 (test): 16 * stress/intl-datetimeformat-formatrangetoparts-relevant-extensions-ja.js: Added. 17 (shouldBe): 18 (compareParts): 19 (shouldThrow): 20 (Intl.DateTimeFormat.formatRangeToParts.compareParts.fmt1.formatRangeToParts): 21 (Intl.DateTimeFormat.formatRangeToParts.compareParts.fmt2.formatRangeToParts): 22 (Intl.DateTimeFormat.formatRangeToParts.compareParts.fmt3.formatRangeToParts): 23 (Intl.DateTimeFormat.formatRangeToParts.compareParts.fmt4.formatRangeToParts): 24 (Intl.DateTimeFormat.formatRangeToParts.compareParts.fmt5.formatRangeToParts): 25 (Intl.DateTimeFormat.formatRangeToParts.vm.icuVersion): 26 (Intl.DateTimeFormat.formatRangeToParts.compareParts.fmt6.formatRangeToParts): 27 (Intl.DateTimeFormat.formatRangeToParts.compareParts.fmt7.formatRangeToParts): 28 (Intl.DateTimeFormat.formatRangeToParts.compareParts.fmt8.formatRangeToParts): 29 (Intl.DateTimeFormat.formatRangeToParts.compareParts.fmt9.formatRangeToParts): 30 (Intl.DateTimeFormat.formatRangeToParts.compareParts.fmt10.formatRangeToParts): 31 (Intl.DateTimeFormat.formatRangeToParts.compareParts.fmt11.formatRangeToParts): 32 (Intl.DateTimeFormat.formatRangeToParts.compareParts.fmt12.formatRangeToParts): 33 (Intl.DateTimeFormat.formatRangeToParts.compareParts.fmt13.formatRangeToParts): 34 (Intl.DateTimeFormat.formatRangeToParts.compareParts.fmt14.formatRangeToParts): 35 (Intl.DateTimeFormat.formatRangeToParts.compareParts.fmt15.formatRangeToParts): 36 (Intl.DateTimeFormat.formatRangeToParts.compareParts.fmt16.formatRangeToParts): 37 * stress/intl-datetimeformat-formatrangetoparts-relevant-extensions.js: Added. 38 (shouldBe): 39 (compareParts): 40 (shouldThrow): 41 * stress/intl-datetimeformat-formatrangetoparts-should-not-handle-gregorian-change-date.js: Added. 42 (shouldBe): 43 (compareParts): 44 (vm.icuHeaderVersion): 45 * stress/intl-datetimeformat-formatrangetoparts.js: Added. 46 (shouldBe): 47 (compareParts): 48 (shouldThrow): 49 * test262/config.yaml: 50 * test262/expectations.yaml: This failure is because of CLDR data inside ICU. ICU 67 will fix them. 51 1 52 2020-11-10 Ross Kirsling <ross.kirsling@sony.com> 2 53 -
trunk/JSTests/stress/intl-datetimeformat-formatrange-relevant-extensions.js
r269667 r269706 21 21 function test() { 22 22 let range = " – "; // This is not usual space unfortuantely in older ICU versions. 23 if ($vm.icuVersion() >= 6 5)23 if ($vm.icuVersion() >= 67) 24 24 range = " – "; 25 25 -
trunk/JSTests/stress/intl-datetimeformat-formatrange.js
r269667 r269706 32 32 }, `TypeError: startDate or endDate is undefined`); 33 33 34 shouldThrow(() => { 35 fmt0.formatRange(new Date(Date.UTC(2008, 0, 20, 10, 0, 0)), new Date(Date.UTC(2007, 0, 10, 10, 0, 0))); 36 }, `RangeError: startDate is larger than endDate`); 34 37 35 38 function test() { 36 let range = " – "; // This is not usual space unfortuantely in older ICU versions.37 if ($vm.icuVersion() >= 6 5)39 let range = " – "; 40 if ($vm.icuVersion() >= 67) 38 41 range = " – "; 39 42 … … 53 56 }); 54 57 shouldBe(fmt1.format(date1), `1/10/07, 2:00 AM`); 58 shouldBe(fmt1.formatRange(date1, date1), `1/10/07, 2:00 AM`); 55 59 shouldBe(fmt1.formatRange(date1, date2), `1/10/07, 2:00${range}3:00 AM`); 56 60 shouldBe(fmt1.formatRange(date1, date3), `1/10/07, 2:00 AM${range}1/20/07, 2:00 AM`); … … 65 69 }); 66 70 shouldBe(fmt1.format(date1), `1/10/07`); 71 shouldBe(fmt1.formatRange(date1, date1), `1/10/07`); 67 72 shouldBe(fmt1.formatRange(date1, date2), `1/10/07`); 68 73 shouldBe(fmt1.formatRange(date1, date3), `1/10/07${range}1/20/07`); … … 76 81 }); 77 82 shouldBe(fmt1.format(date1), `2007`); 83 shouldBe(fmt1.formatRange(date1, date1), `2007`); 78 84 shouldBe(fmt1.formatRange(date1, date2), `2007`); 79 85 shouldBe(fmt1.formatRange(date1, date4), `2007${range}2008`); … … 90 96 }); 91 97 shouldBe(fmt1.format(date1), `1/10/07, 10:00 AM`); 98 shouldBe(fmt1.formatRange(date1, date1), `1/10/07, 10:00 AM`); 92 99 shouldBe(fmt1.formatRange(date1, date2), `1/10/07, 10:00${range}11:00 AM`); 93 100 shouldBe(fmt1.formatRange(date1, date3), `1/10/07, 10:00 AM${range}1/20/07, 10:00 AM`); … … 102 109 }); 103 110 shouldBe(fmt2.format(date1), `Jan 10, 2007`); 111 shouldBe(fmt2.formatRange(date1, date1), `Jan 10, 2007`); 104 112 shouldBe(fmt2.formatRange(date1, date2), `Jan 10, 2007`); 105 113 shouldBe(fmt2.formatRange(date1, date3), `Jan 10${range}20, 2007`); … … 131 139 } 132 140 133 if ( $vm.icuVersion() >= 64)141 if (Intl.DateTimeFormat.formatRange) 134 142 test(); -
trunk/JSTests/test262/config.yaml
r269531 r269706 95 95 - test/built-ins/Atomics/wait/bigint 96 96 - test/built-ins/Atomics/xor/bigint 97 98 # requires ICU APIs implementation99 # https://bugs.webkit.org/show_bug.cgi?id=213822100 - test/intl402/DateTimeFormat/prototype/formatRangeToParts101 97 files: 102 98 # https://bugs.webkit.org/show_bug.cgi?id=190800 -
trunk/JSTests/test262/expectations.yaml
r269670 r269706 956 956 default: 'Test262Error: no fractionalSecondDigits Expected SameValue(«02:03 – 02:13», «02:03 – 02:13») to be true' 957 957 strict mode: 'Test262Error: no fractionalSecondDigits Expected SameValue(«02:03 – 02:13», «02:03 – 02:13») to be true' 958 test/intl402/DateTimeFormat/prototype/formatRangeToParts/en-US.js: 959 default: 'Test262Error: value for entry 5 Expected SameValue(« – », « – ») to be true' 960 strict mode: 'Test262Error: value for entry 5 Expected SameValue(« – », « – ») to be true' 961 test/intl402/DateTimeFormat/prototype/formatRangeToParts/fractionalSecondDigits.js: 962 default: 'Test262Error: value for entry 3 Expected SameValue(« – », « – ») to be true' 963 strict mode: 'Test262Error: value for entry 3 Expected SameValue(« – », « – ») to be true' 958 964 test/intl402/DateTimeFormat/prototype/resolvedOptions/hourCycle-default.js: 959 965 default: 'Test262Error: Expected SameValue(«h24», «h23») to be true' -
trunk/Source/JavaScriptCore/ChangeLog
r269694 r269706 1 2020-11-11 Yusuke Suzuki <ysuzuki@apple.com> 2 3 [JSC] Implement Intl.DateTimeFormat.formatRangeToParts 4 https://bugs.webkit.org/show_bug.cgi?id=213822 5 <rdar://problem/69328711> 6 7 Reviewed by Ross Kirsling. 8 9 This patch implements Intl.DateTimeFormat.formatRangeToParts. It is already stage-4 (included in the spec). 10 The inputs are date interval, and this function generates array of parts of formatted string of date interval. 11 Currently, required ICU APIs are draft status. So, for now, we track ABI changes, and use APIs with careful version checks. 12 13 However, currently, OpenSource macOS WebKit is built with specific ICU header (ICU 62 headers). So for now, we disable it 14 in OpenSource macOS WebKit build. But we enable it for Apple Internal SDK WebKit build. We can enable it if we include 15 multiple ICU header sets and select appropriate one against the linked ICU version. In the other platforms, they are using 16 corresponding ICU headers so that we can just enable it. 17 18 There are two interesting implementation topics. 19 20 1. From ICU 67, the signature of udtitvfmt_formatToResult is changed. We need to switch the implementation with fine grained ICU version checks. 21 2. udtitvfmt_formatToResult does not have an ability to configure gregorian calendar change date: before that date, the calendar is julian. 22 In ECMAScript spec, we need to ignore this gregorian calendar change date, and we should handle all gregorian calendar dates as is even if 23 the dates are older than gregorian calendar change date. However, since udtitvfmt_formatToResult does not offer the above ability, 24 ICU automatically switches the calendar between gregorian and julian. To fix this issue, ICU 67 introduced udtitvfmt_formatCalendarToResult, 25 which can take an explicit calendar for each input date so that we configure gregorian calendar change date. But this only exists after ICU 67. 26 In the implementations using ICU 64-66, we just use udtitvfmt_formatToResult. 27 28 * JavaScriptCore.xcodeproj/project.pbxproj: 29 * Sources.txt: 30 * runtime/IntlDateTimeFormat.cpp: 31 (JSC::UDateIntervalFormatDeleter::operator()): 32 (JSC::IntlDateTimeFormat::formatToParts const): 33 (JSC::definitelyAfterGregorianCalendarChangeDate): 34 (JSC::formattedValueFromDateRange): 35 (JSC::IntlDateTimeFormat::formatRange): 36 (JSC::IntlDateTimeFormat::formatRangeToParts): 37 * runtime/IntlDateTimeFormat.h: 38 * runtime/IntlDateTimeFormatPrototype.cpp: 39 (JSC::IntlDateTimeFormatPrototype::create): 40 (JSC::IntlDateTimeFormatPrototype::finishCreation): 41 (JSC::JSC_DEFINE_HOST_FUNCTION): 42 * runtime/IntlDateTimeFormatPrototype.h: 43 * runtime/OptionsList.h: 44 1 45 2020-11-11 Yusuke Suzuki <ysuzuki@apple.com> 2 46 -
trunk/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj
r269380 r269706 1866 1866 E392E6F924D25FA900B20767 /* B3BottomTupleValue.h in Headers */ = {isa = PBXBuildFile; fileRef = E392E6F724D25FA600B20767 /* B3BottomTupleValue.h */; }; 1867 1867 E393ADD81FE702D00022D681 /* WeakMapImplInlines.h in Headers */ = {isa = PBXBuildFile; fileRef = E393ADD71FE702CC0022D681 /* WeakMapImplInlines.h */; }; 1868 E399AEC32559457F00B78485 /* IntlDateTimeFormat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A1587D671B4DC14100D69849 /* IntlDateTimeFormat.cpp */; }; 1868 1869 E39BF39922A2288B00BD183E /* SymbolTableInlines.h in Headers */ = {isa = PBXBuildFile; fileRef = E39BF39822A2288B00BD183E /* SymbolTableInlines.h */; }; 1869 1870 E39D45F51D39005600B3B377 /* InterpreterInlines.h in Headers */ = {isa = PBXBuildFile; fileRef = E39D9D841D39000600667282 /* InterpreterInlines.h */; settings = {ATTRIBUTES = (Private, ); }; }; … … 11393 11394 33B2A548226543BF005A0F79 /* FTLLowerDFGToB3.cpp in Sources */, 11394 11395 5C4196622270E0000047B7CD /* InspectorBackendDispatcherCompatibility.cpp in Sources */, 11396 E399AEC32559457F00B78485 /* IntlDateTimeFormat.cpp in Sources */, 11395 11397 E366441E254409B30001876F /* IntlListFormat.cpp in Sources */, 11396 11398 E38E8790254B978400F6F9E4 /* JSDateMath.cpp in Sources */, -
trunk/Source/JavaScriptCore/Sources.txt
r269320 r269706 814 814 runtime/IntlCollatorConstructor.cpp 815 815 runtime/IntlCollatorPrototype.cpp 816 runtime/IntlDateTimeFormat.cpp 816 runtime/IntlDateTimeFormat.cpp @no-unify // Confine U_HIDE_DRAFT_API's effect to this file. 817 817 runtime/IntlDateTimeFormatConstructor.cpp 818 818 runtime/IntlDateTimeFormatPrototype.cpp … … 820 820 runtime/IntlDisplayNamesConstructor.cpp 821 821 runtime/IntlDisplayNamesPrototype.cpp 822 // Confine U_HIDE_DRAFT_API's effect in this file. 823 runtime/IntlListFormat.cpp @no-unify 822 runtime/IntlListFormat.cpp @no-unify // Confine U_HIDE_DRAFT_API's effect in this file. 824 823 runtime/IntlListFormatConstructor.cpp 825 824 runtime/IntlListFormatPrototype.cpp -
trunk/Source/JavaScriptCore/runtime/IntlDateTimeFormat.cpp
r269341 r269706 35 35 #include <unicode/ucal.h> 36 36 #include <unicode/uenum.h> 37 #include <wtf/Range.h> 37 38 #include <wtf/text/StringBuilder.h> 38 39 #include <wtf/unicode/icu/ICUHelpers.h> 39 40 41 #if HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS) 42 #include <unicode/uformattedvalue.h> 43 #ifdef U_HIDE_DRAFT_API 44 #undef U_HIDE_DRAFT_API 45 #endif 46 #endif // HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS) 47 #include <unicode/udateintervalformat.h> 48 #if HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS) 49 #define U_HIDE_DRAFT_API 1 50 #endif // HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS) 51 40 52 namespace JSC { 41 53 42 static const double minECMAScriptTime = -8.64E15; 54 // We do not use ICUDeleter<udtitvfmt_close> because we do not want to include udateintervalformat.h in IntlDateTimeFormat.h. 55 // udateintervalformat.h needs to be included with #undef U_HIDE_DRAFT_API, and we would like to minimize this effect in IntlDateTimeFormat.cpp. 56 void UDateIntervalFormatDeleter::operator()(UDateIntervalFormat* formatter) 57 { 58 if (formatter) 59 udtitvfmt_close(formatter); 60 } 61 62 static constexpr double minECMAScriptTime = -8.64E15; 43 63 44 64 const ClassInfo IntlDateTimeFormat::s_info = { "Object", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(IntlDateTimeFormat) }; … … 1249 1269 1250 1270 // https://tc39.es/ecma402/#sec-formatdatetimetoparts 1251 JSValue IntlDateTimeFormat::formatToParts(JSGlobalObject* globalObject, double value ) const1271 JSValue IntlDateTimeFormat::formatToParts(JSGlobalObject* globalObject, double value, JSString* sourceType) const 1252 1272 { 1253 1273 ASSERT(m_dateFormat); … … 1290 1310 part->putDirect(vm, vm.propertyNames->type, literalString); 1291 1311 part->putDirect(vm, vm.propertyNames->value, value); 1312 if (sourceType) 1313 part->putDirect(vm, vm.propertyNames->source, sourceType); 1292 1314 parts->push(globalObject, part); 1293 1315 RETURN_IF_EXCEPTION(scope, { }); … … 1301 1323 part->putDirect(vm, vm.propertyNames->type, type); 1302 1324 part->putDirect(vm, vm.propertyNames->value, value); 1325 if (sourceType) 1326 part->putDirect(vm, vm.propertyNames->source, sourceType); 1303 1327 parts->push(globalObject, part); 1304 1328 RETURN_IF_EXCEPTION(scope, { }); 1305 1329 } 1306 1330 } 1307 1308 1331 1309 1332 return parts; … … 1358 1381 } 1359 1382 1383 #if HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS) 1384 1385 // If a date is after Oct 15, 1582, the configuration of gregorian calendar change date in UCalendar does not affect 1386 // on the formatted string. To ensure that it is after Oct 15 in all timezones, we add one day to gregorian calendar 1387 // change date in UTC, so that this check can conservatively answer whether the date is definitely after gregorian 1388 // 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 static std::unique_ptr<UFormattedDateInterval, ICUDeleter<udtitvfmt_closeResult>> formattedValueFromDateRange(UDateIntervalFormat& dateIntervalFormat, UDateFormat& dateFormat, double startDate, double endDate, UErrorCode& status) 1396 { 1397 auto result = std::unique_ptr<UFormattedDateInterval, ICUDeleter<udtitvfmt_closeResult>>(udtitvfmt_openResult(&status)); 1398 if (U_FAILURE(status)) 1399 return nullptr; 1400 1401 // After ICU 67, udtitvfmt_formatToResult's signature is changed. 1402 #if U_ICU_VERSION_MAJOR_NUM >= 67 1403 // UFormattedDateInterval does not have a way to configure gregorian calendar change date while ECMAScript requires that 1404 // gregorian calendar change should not have effect (we are setting ucal_setGregorianChange(cal, minECMAScriptTime, &status) explicitly). 1405 // As a result, if the input date is older than gregorian calendar change date (Oct 15, 1582), the formatted string becomes 1406 // julian calendar date. 1407 // udtitvfmt_formatCalendarToResult API offers the way to set calendar to each date of the input, so that we can use UDateFormat's 1408 // calendar which is already configured to meet ECMAScript's requirement (effectively clearing gregorian calendar change date). 1409 // 1410 // If we can ensure that startDate is after gregorian calendar change date, we can just use udtitvfmt_formatToResult since gregorian 1411 // calendar change date does not affect on the formatted string. 1412 // 1413 // https://unicode-org.atlassian.net/browse/ICU-20705 1414 if (definitelyAfterGregorianCalendarChangeDate(startDate)) 1415 udtitvfmt_formatToResult(&dateIntervalFormat, startDate, endDate, result.get(), &status); 1416 else { 1417 auto createCalendarForDate = [](const UCalendar* calendar, double date, UErrorCode& status) -> std::unique_ptr<UCalendar, ICUDeleter<ucal_close>> { 1418 auto result = std::unique_ptr<UCalendar, ICUDeleter<ucal_close>>(ucal_clone(calendar, &status)); 1419 if (U_FAILURE(status)) 1420 return nullptr; 1421 ucal_setMillis(result.get(), date, &status); 1422 if (U_FAILURE(status)) 1423 return nullptr; 1424 return result; 1425 }; 1426 1427 auto calendar = udat_getCalendar(&dateFormat); 1428 1429 auto startCalendar = createCalendarForDate(calendar, startDate, status); 1430 if (U_FAILURE(status)) 1431 return nullptr; 1432 1433 auto endCalendar = createCalendarForDate(calendar, endDate, status); 1434 if (U_FAILURE(status)) 1435 return nullptr; 1436 1437 udtitvfmt_formatCalendarToResult(&dateIntervalFormat, startCalendar.get(), endCalendar.get(), result.get(), &status); 1438 } 1439 #else 1440 UNUSED_PARAM(dateFormat); 1441 udtitvfmt_formatToResult(&dateIntervalFormat, result.get(), startDate, endDate, &status); 1442 #endif 1443 return result; 1444 } 1445 1446 #endif // HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS) 1447 1360 1448 JSValue IntlDateTimeFormat::formatRange(JSGlobalObject* globalObject, double startDate, double endDate) 1361 1449 { … … 1376 1464 RETURN_IF_EXCEPTION(scope, { }); 1377 1465 1466 #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 1378 1496 Vector<UChar, 32> buffer; 1379 1497 auto status = callBufferProducingFunction(udtitvfmt_format, dateIntervalFormat, startDate, endDate, buffer, nullptr); … … 1386 1504 } 1387 1505 1506 JSValue IntlDateTimeFormat::formatRangeToParts(JSGlobalObject* globalObject, double startDate, double endDate) 1507 { 1508 ASSERT(m_dateFormat); 1509 1510 VM& vm = globalObject->vm(); 1511 auto scope = DECLARE_THROW_SCOPE(vm); 1512 1513 #if HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS) 1514 // http://tc39.es/proposal-intl-DateTimeFormat-formatRange/#sec-partitiondatetimerangepattern 1515 startDate = timeClip(startDate); 1516 endDate = timeClip(endDate); 1517 if (std::isnan(startDate) || std::isnan(endDate)) { 1518 throwRangeError(globalObject, scope, "Passed date is out of range"_s); 1519 return { }; 1520 } 1521 1522 auto* dateIntervalFormat = createDateIntervalFormatIfNecessary(globalObject); 1523 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 1542 UErrorCode status = U_ZERO_ERROR; 1543 auto result = formattedValueFromDateRange(*dateIntervalFormat, *m_dateFormat, startDate, endDate, status); 1544 if (U_FAILURE(status)) { 1545 throwTypeError(globalObject, scope, "Failed to format date interval"_s); 1546 return { }; 1547 } 1548 1549 // UFormattedValue is owned by UFormattedDateInterval. We do not need to close it. 1550 auto formattedValue = udtitvfmt_resultAsValue(result.get(), &status); 1551 if (U_FAILURE(status)) { 1552 throwTypeError(globalObject, scope, "Failed to format date interval"_s); 1553 return { }; 1554 } 1555 1556 auto sharedString = jsNontrivialString(vm, "shared"_s); 1557 1558 // If the formatted parts of startDate and endDate are the same, it is possible that the resulted string does not look like range. 1559 // For example, if the requested format only includes "year" and startDate and endDate are the same year, the result just contains one year. 1560 // In that case, startDate and endDate are *practically-equal* (spec term), and we generate parts as we call `formatToParts(startDate)` with 1561 // `source: "shared"` additional fields. 1562 bool equal = dateFieldsPracticallyEqual(formattedValue, status); 1563 if (U_FAILURE(status)) { 1564 throwTypeError(globalObject, scope, "Failed to format date interval"_s); 1565 return { }; 1566 } 1567 1568 if (equal) 1569 RELEASE_AND_RETURN(scope, formatToParts(globalObject, startDate, sharedString)); 1570 1571 // ICU produces ranges for the formatted string, and we construct parts array from that. 1572 // For example, startDate = Jan 3, 2019, endDate = Jan 5, 2019 with en-US locale is, 1573 // 1574 // Formatted string: "1/3/2019 – 1/5/2019" 1575 // | | | | | | | | 1576 // B C | | F G | | 1577 // | +-D+ | +-H+ 1578 // | | | | 1579 // +--A---+ +--E---+ 1580 // 1581 // Ranges ICU generates: 1582 // A: (0, 8) UFIELD_CATEGORY_DATE_INTERVAL_SPAN startRange 1583 // B: (0, 1) UFIELD_CATEGORY_DATE month 1584 // C: (2, 3) UFIELD_CATEGORY_DATE day 1585 // D: (4, 8) UFIELD_CATEGORY_DATE year 1586 // E: (11, 19) UFIELD_CATEGORY_DATE_INTERVAL_SPAN endRange 1587 // F: (11, 12) UFIELD_CATEGORY_DATE month 1588 // G: (13, 14) UFIELD_CATEGORY_DATE day 1589 // H: (15, 19) UFIELD_CATEGORY_DATE year 1590 // 1591 // We use UFIELD_CATEGORY_DATE_INTERVAL_SPAN range to determine each part is either "startRange", "endRange", or "shared". 1592 // It is gurarnteed that UFIELD_CATEGORY_DATE_INTERVAL_SPAN comes first before any other parts including that range. 1593 // For example, in the above formatted string, " – " is "shared" part. For UFIELD_CATEGORY_DATE ranges, we generate corresponding 1594 // part object with types such as "month". And non populated parts (e.g. "/") become "literal" parts. 1595 // In the above case, expected parts are, 1596 // 1597 // { type: "month", value: "1", source: "startRange" }, 1598 // { type: "literal", value: "/", source: "startRange" }, 1599 // { type: "day", value: "3", source: "startRange" }, 1600 // { type: "literal", value: "/", source: "startRange" }, 1601 // { type: "year", value: "2019", source: "startRange" }, 1602 // { type: "literal", value: " - ", source: "shared" }, 1603 // { type: "month", value: "1", source: "endRange" }, 1604 // { type: "literal", value: "/", source: "endRange" }, 1605 // { type: "day", value: "5", source: "endRange" }, 1606 // { type: "literal", value: "/", source: "endRange" }, 1607 // { type: "year", value: "2019", source: "endRange" }, 1608 // 1609 1610 JSArray* parts = JSArray::tryCreate(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), 0); 1611 if (!parts) { 1612 throwOutOfMemoryError(globalObject, scope); 1613 return { }; 1614 } 1615 1616 int32_t formattedStringLength = 0; 1617 const UChar* formattedStringPointer = ufmtval_getString(formattedValue, &formattedStringLength, &status); 1618 if (U_FAILURE(status)) { 1619 throwTypeError(globalObject, scope, "Failed to format date interval"_s); 1620 return { }; 1621 } 1622 String resultString(formattedStringPointer, formattedStringLength); 1623 1624 // We care multiple categories (UFIELD_CATEGORY_DATE and UFIELD_CATEGORY_DATE_INTERVAL_SPAN). 1625 // So we do not constraint iterator. 1626 auto iterator = std::unique_ptr<UConstrainedFieldPosition, ICUDeleter<ucfpos_close>>(ucfpos_open(&status)); 1627 if (U_FAILURE(status)) { 1628 throwTypeError(globalObject, scope, "Failed to format date interval"_s); 1629 return { }; 1630 } 1631 1632 auto startRangeString = jsNontrivialString(vm, "startRange"_s); 1633 auto endRangeString = jsNontrivialString(vm, "endRange"_s); 1634 auto literalString = jsNontrivialString(vm, "literal"_s); 1635 1636 WTF::Range<int32_t> startRange { -1, -1 }; 1637 WTF::Range<int32_t> endRange { -1, -1 }; 1638 1639 auto createPart = [&] (JSString* type, int32_t beginIndex, int32_t length) { 1640 auto sourceType = [&](int32_t index) -> JSString* { 1641 if (startRange.contains(index)) 1642 return startRangeString; 1643 if (endRange.contains(index)) 1644 return endRangeString; 1645 return sharedString; 1646 }; 1647 1648 auto value = jsString(vm, resultString.substring(beginIndex, length)); 1649 JSObject* part = constructEmptyObject(globalObject); 1650 part->putDirect(vm, vm.propertyNames->type, type); 1651 part->putDirect(vm, vm.propertyNames->value, value); 1652 part->putDirect(vm, vm.propertyNames->source, sourceType(beginIndex)); 1653 return part; 1654 }; 1655 1656 int32_t resultLength = resultString.length(); 1657 int32_t previousEndIndex = 0; 1658 while (true) { 1659 bool next = ufmtval_nextPosition(formattedValue, iterator.get(), &status); 1660 if (U_FAILURE(status)) { 1661 throwTypeError(globalObject, scope, "Failed to format date interval"_s); 1662 return { }; 1663 } 1664 if (!next) 1665 break; 1666 1667 int32_t category = ucfpos_getCategory(iterator.get(), &status); 1668 if (U_FAILURE(status)) { 1669 throwTypeError(globalObject, scope, "Failed to format date interval"_s); 1670 return { }; 1671 } 1672 1673 int32_t fieldType = ucfpos_getField(iterator.get(), &status); 1674 if (U_FAILURE(status)) { 1675 throwTypeError(globalObject, scope, "Failed to format date interval"_s); 1676 return { }; 1677 } 1678 1679 int32_t beginIndex = 0; 1680 int32_t endIndex = 0; 1681 ucfpos_getIndexes(iterator.get(), &beginIndex, &endIndex, &status); 1682 if (U_FAILURE(status)) { 1683 throwTypeError(globalObject, scope, "Failed to format date interval"_s); 1684 return { }; 1685 } 1686 1687 dataLogLnIf(IntlDateTimeFormatInternal::verbose, category, " ", fieldType, " (", beginIndex, ", ", endIndex, ")"); 1688 1689 if (category != UFIELD_CATEGORY_DATE && category != UFIELD_CATEGORY_DATE_INTERVAL_SPAN) 1690 continue; 1691 if (category == UFIELD_CATEGORY_DATE && fieldType < 0) 1692 continue; 1693 1694 if (previousEndIndex < beginIndex) { 1695 JSObject* part = createPart(literalString, previousEndIndex, beginIndex - previousEndIndex); 1696 parts->push(globalObject, part); 1697 RETURN_IF_EXCEPTION(scope, { }); 1698 previousEndIndex = beginIndex; 1699 } 1700 1701 if (category == UFIELD_CATEGORY_DATE_INTERVAL_SPAN) { 1702 // > The special field category UFIELD_CATEGORY_DATE_INTERVAL_SPAN is used to indicate which datetime 1703 // > primitives came from which arguments: 0 means fromCalendar, and 1 means toCalendar. The span category 1704 // > will always occur before the corresponding fields in UFIELD_CATEGORY_DATE in the nextPosition() iterator. 1705 // from ICU comment. So, field 0 is startRange, field 1 is endRange. 1706 if (!fieldType) 1707 startRange = WTF::Range<int32_t>(beginIndex, endIndex); 1708 else { 1709 ASSERT(fieldType == 1); 1710 endRange = WTF::Range<int32_t>(beginIndex, endIndex); 1711 } 1712 continue; 1713 } 1714 1715 ASSERT(category == UFIELD_CATEGORY_DATE); 1716 1717 auto type = jsString(vm, partTypeString(UDateFormatField(fieldType))); 1718 JSObject* part = createPart(type, beginIndex, endIndex - beginIndex); 1719 parts->push(globalObject, part); 1720 RETURN_IF_EXCEPTION(scope, { }); 1721 previousEndIndex = endIndex; 1722 } 1723 1724 if (previousEndIndex < resultLength) { 1725 JSObject* part = createPart(literalString, previousEndIndex, resultLength - previousEndIndex); 1726 parts->push(globalObject, part); 1727 RETURN_IF_EXCEPTION(scope, { }); 1728 } 1729 1730 return parts; 1731 #else 1732 UNUSED_PARAM(startDate); 1733 UNUSED_PARAM(endDate); 1734 throwTypeError(globalObject, scope, "Failed to format date interval"_s); 1735 return { }; 1736 #endif 1737 } 1738 1739 1388 1740 } // namespace JSC -
trunk/Source/JavaScriptCore/runtime/IntlDateTimeFormat.h
r267108 r269706 28 28 #include "JSObject.h" 29 29 #include <unicode/udat.h> 30 #include <unicode/udateintervalformat.h>31 30 #include <wtf/unicode/icu/ICUHelpers.h> 31 32 struct UDateIntervalFormat; 33 34 #if !defined(HAVE_ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS) 35 // ICU header is up-to-date if the build is non-Darwin or using Apple Internal SDK. 36 #if (USE(APPLE_INTERNAL_SDK) || !OS(DARWIN)) && U_ICU_VERSION_MAJOR_NUM >= 64 37 #define HAVE_ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS 1 38 #endif 39 #endif 32 40 33 41 namespace JSC { … … 36 44 37 45 class JSBoundFunction; 46 47 struct UDateIntervalFormatDeleter { 48 JS_EXPORT_PRIVATE void operator()(UDateIntervalFormat*); 49 }; 38 50 39 51 class IntlDateTimeFormat final : public JSNonFinalObject { … … 61 73 void initializeDateTimeFormat(JSGlobalObject*, JSValue locales, JSValue options); 62 74 JSValue format(JSGlobalObject*, double value) const; 63 JSValue formatToParts(JSGlobalObject*, double value ) const;75 JSValue formatToParts(JSGlobalObject*, double value, JSString* sourceType = nullptr) const; 64 76 JSValue formatRange(JSGlobalObject*, double startDate, double endDate); 77 JSValue formatRangeToParts(JSGlobalObject*, double startDate, double endDate); 65 78 JSObject* resolvedOptions(JSGlobalObject*) const; 66 79 … … 113 126 114 127 using UDateFormatDeleter = ICUDeleter<udat_close>; 115 using UDateIntervalFormatDeleter = ICUDeleter<udtitvfmt_close>;116 128 117 129 WriteBarrier<JSBoundFunction> m_boundFormat; -
trunk/Source/JavaScriptCore/runtime/IntlDateTimeFormatPrototype.cpp
r267594 r269706 39 39 static JSC_DECLARE_HOST_FUNCTION(IntlDateTimeFormatPrototypeGetterFormat); 40 40 static JSC_DECLARE_HOST_FUNCTION(IntlDateTimeFormatPrototypeFuncFormatRange); 41 static JSC_DECLARE_HOST_FUNCTION(IntlDateTimeFormatPrototypeFuncFormatRangeToParts); 41 42 static JSC_DECLARE_HOST_FUNCTION(IntlDateTimeFormatPrototypeFuncFormatToParts); 42 43 static JSC_DECLARE_HOST_FUNCTION(IntlDateTimeFormatPrototypeFuncResolvedOptions); … … 60 61 */ 61 62 62 IntlDateTimeFormatPrototype* IntlDateTimeFormatPrototype::create(VM& vm, JSGlobalObject* , Structure* structure)63 IntlDateTimeFormatPrototype* IntlDateTimeFormatPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure) 63 64 { 64 65 IntlDateTimeFormatPrototype* object = new (NotNull, allocateCell<IntlDateTimeFormatPrototype>(vm.heap)) IntlDateTimeFormatPrototype(vm, structure); 65 object->finishCreation(vm );66 object->finishCreation(vm, globalObject); 66 67 return object; 67 68 } … … 77 78 } 78 79 79 void IntlDateTimeFormatPrototype::finishCreation(VM& vm )80 void IntlDateTimeFormatPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) 80 81 { 81 82 Base::finishCreation(vm); 82 83 ASSERT(inherits(vm, info())); 84 #if HAVE(ICU_U_DATE_INTERVAL_FORMAT_FORMAT_RANGE_TO_PARTS) 85 if (Options::useIntlDateTimeFormatRangeToParts()) 86 JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("formatRangeToParts", IntlDateTimeFormatPrototypeFuncFormatRangeToParts, static_cast<unsigned>(PropertyAttribute::DontEnum), 2); 87 #else 88 UNUSED_PARAM(globalObject); 89 UNUSED_PARAM(&IntlDateTimeFormatPrototypeFuncFormatRangeToParts); 90 #endif 83 91 JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); 84 92 } … … 189 197 } 190 198 199 // http://tc39.es/proposal-intl-DateTimeFormat-formatRange/#sec-intl.datetimeformat.prototype.formatRangeToParts 200 JSC_DEFINE_HOST_FUNCTION(IntlDateTimeFormatPrototypeFuncFormatRangeToParts, (JSGlobalObject* globalObject, CallFrame* callFrame)) 201 { 202 VM& vm = globalObject->vm(); 203 auto scope = DECLARE_THROW_SCOPE(vm); 204 205 // Do not use unwrapForOldFunctions. 206 auto* dateTimeFormat = jsDynamicCast<IntlDateTimeFormat*>(vm, callFrame->thisValue()); 207 if (UNLIKELY(!dateTimeFormat)) 208 return JSValue::encode(throwTypeError(globalObject, scope, "Intl.DateTimeFormat.prototype.formatRangeToParts called on value that's not an object initialized as a DateTimeFormat"_s)); 209 210 JSValue startDateValue = callFrame->argument(0); 211 JSValue endDateValue = callFrame->argument(1); 212 213 if (startDateValue.isUndefined() || endDateValue.isUndefined()) 214 return throwVMTypeError(globalObject, scope, "startDate or endDate is undefined"_s); 215 216 double startDate = startDateValue.toNumber(globalObject); 217 RETURN_IF_EXCEPTION(scope, { }); 218 double endDate = endDateValue.toNumber(globalObject); 219 RETURN_IF_EXCEPTION(scope, { }); 220 if (startDate > endDate) 221 return throwVMRangeError(globalObject, scope, "startDate is larger than endDate"_s); 222 223 RELEASE_AND_RETURN(scope, JSValue::encode(dateTimeFormat->formatRangeToParts(globalObject, startDate, endDate))); 224 } 225 191 226 JSC_DEFINE_HOST_FUNCTION(IntlDateTimeFormatPrototypeFuncResolvedOptions, (JSGlobalObject* globalObject, CallFrame* callFrame)) 192 227 { -
trunk/Source/JavaScriptCore/runtime/IntlDateTimeFormatPrototype.h
r260415 r269706 50 50 private: 51 51 IntlDateTimeFormatPrototype(VM&, Structure*); 52 void finishCreation(VM& );52 void finishCreation(VM&, JSGlobalObject*); 53 53 }; 54 54 -
trunk/Source/JavaScriptCore/runtime/OptionsList.h
r269531 r269706 494 494 v(Bool, useWeakRefs, true, Normal, "Expose the WeakRef constructor.") \ 495 495 v(Bool, useIntlDateTimeFormatDayPeriod, true, Normal, "Expose the Intl.DateTimeFormat dayPeriod feature.") \ 496 v(Bool, useIntlDateTimeFormatRangeToParts, true, Normal, "Expose the Intl.DateTimeFormat#formatRangeToParts feature.") \ 496 497 v(Bool, useAtMethod, false, Normal, "Expose the at() method on Array and %TypedArray%.") \ 497 498 v(Bool, useSharedArrayBuffer, false, Normal, nullptr) \
Note: See TracChangeset
for help on using the changeset viewer.