Skip to content

Commit

Permalink
Revert "Disable DateTimeFormat::formatToParts for Apple platform (#1155
Browse files Browse the repository at this point in the history
…)"

This reverts commit c5a633f.
  • Loading branch information
cortinico authored and blakef committed Nov 12, 2024
1 parent db6d12e commit 5b4aa20
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 8 deletions.
2 changes: 1 addition & 1 deletion doc/IntlAPIs.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ One popular implementation strategy followed by other engines, is to bundle an i
- `Intl.DateTimeFormat`*
- `Intl.DateTimeFormat.supportedLocalesOf`
- `Intl.DateTimeFormat.prototype.format`
- `Intl.DateTimeFormat.prototype.formatToParts`
- `Intl.DateTimeFormat.prototype.resolvedOptions`

- `Intl.getCanonicalLocales`
Expand All @@ -50,7 +51,6 @@ One popular implementation strategy followed by other engines, is to bundle an i
## Supported on Android only
- `Intl.NumberFormat`
- `Intl.NumberFormat.prototype.formatToParts`
- `Intl.DateTimeFormat.prototype.formatToParts`

## * Limitations on property support

Expand Down
72 changes: 71 additions & 1 deletion lib/Platform/Intl/PlatformIntlApple.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1229,6 +1229,8 @@ uint8_t getCurrencyDigits(std::u16string_view code) {

std::u16string format(double jsTimeValue) noexcept;

std::vector<Part> formatToParts(double x) noexcept;

private:
void initializeNSDateFormatter(NSLocale *nsLocale) noexcept;

Expand Down Expand Up @@ -1895,8 +1897,76 @@ uint8_t getCurrencyDigits(std::u16string_view code) {
return static_cast<DateTimeFormatApple *>(this)->format(jsTimeValue);
}

static std::u16string returnTypeOfDate(const char16_t &c16) {
if (c16 == u'a')
return u"dayPeriod";
if (c16 == u'z' || c16 == u'v' || c16 == u'O')
return u"timeZoneName";
if (c16 == u'G')
return u"era";
if (c16 == u'y')
return u"year";
if (c16 == u'M')
return u"month";
if (c16 == u'E')
return u"weekday";
if (c16 == u'd')
return u"day";
if (c16 == u'h' || c16 == u'k' || c16 == u'K' || c16 == u'H')
return u"hour";
if (c16 == u'm')
return u"minute";
if (c16 == u's')
return u"second";
if (c16 == u'S')
return u"fractionalSecond";
return u"literal";
}

// Implementer note: This method corresponds roughly to
// https://402.ecma-international.org/8.0/#sec-formatdatetimetoparts
std::vector<Part> DateTimeFormatApple::formatToParts(double x) noexcept {
// NOTE: We dont have access to localeData.patterns. Instead we use
// NSDateFormatter's foramt string, and break it into components.
// 1. Let parts be ? PartitionDateTimePattern(dateTimeFormat, x).
auto fmt = nsStringToU16String(nsDateFormatter_.dateFormat);
std::unique(fmt.begin(), fmt.end());
auto formattedDate = format(x);
// 2. Let result be ArrayCreate(0).
std::vector<Part> result;
// 3. Let n be 0.
// 4. For each Record { [[Type]], [[Value]] } part in parts, do
// a. Let O be OrdinaryObjectCreate(%Object.prototype%).
// b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]).
// c. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]).
// d. Perform ! CreateDataProperty(result, ! ToString(n), O).
// e. Increment n by 1.
std::u16string currentPart;
unsigned n = 0;
static auto alphanumerics = NSCharacterSet.alphanumericCharacterSet;
for (char16_t c16 : formattedDate) {
if ([alphanumerics characterIsMember:c16]) {
currentPart += c16;
continue;
}
if (currentPart != u"") {
result.push_back(
{{u"type", returnTypeOfDate(fmt[n])}, {u"value", currentPart}});
currentPart = u"";
n++;
}
result.push_back({{u"type", u"literal"}, {u"value", {c16}}});
n++;
}
// Last format string component.
result.push_back(
{{u"type", returnTypeOfDate(fmt[n])}, {u"value", currentPart}});
// 5. Return result.
return result;
}

std::vector<Part> DateTimeFormat::formatToParts(double x) noexcept {
llvm_unreachable("formatToParts is unimplemented on Apple platforms");
return static_cast<DateTimeFormatApple *>(this)->formatToParts(x);
}

class NumberFormatApple : public NumberFormat {
Expand Down
2 changes: 0 additions & 2 deletions lib/VM/JSLib/Intl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -929,15 +929,13 @@ void defineIntlDateTimeFormat(Runtime &runtime, Handle<JSObject> intl) {
false,
true);

#ifndef __APPLE__
defineMethod(
runtime,
prototype,
Predefined::getSymbolID(Predefined::formatToParts),
nullptr,
intlDateTimeFormatPrototypeFormatToParts,
1);
#endif

defineMethod(
runtime,
Expand Down
19 changes: 19 additions & 0 deletions test/hermes/intl/date-time-format-apple.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,5 +150,24 @@ print(new Intl.DateTimeFormat('en-US').resolvedOptions().numberingSystem);
print(new Intl.DateTimeFormat('en-US', { timeZone: 'SGT'}).resolvedOptions().timeZone);
// CHECK-NEXT: SGT

print(JSON.stringify(new Intl.DateTimeFormat('en-US').formatToParts(date)));
// CHECK-NEXT: [{"value":"1","type":"month"},{"value":"/","type":"literal"},{"value":"2","type":"day"},{"value":"/","type":"literal"},{"value":"2020","type":"year"}]

print(JSON.stringify(new Intl.DateTimeFormat('en-GB').formatToParts(date)));
// CHECK-NEXT: [{"value":"02","type":"day"},{"value":"/","type":"literal"},{"value":"01","type":"month"},{"value":"/","type":"literal"},{"value":"2020","type":"year"}]

print(JSON.stringify(new Intl.DateTimeFormat('en-US', {weekday: 'long',
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
fractionalSecondDigits: 3,
hour12: true,
timeZone: 'UTC'
}).formatToParts(new Date(Date.UTC(2020, 0, 2, 3, 45, 00, 30)))));
// CHECK-NEXT: [{"value":"Thursday","type":"weekday"},{"value":",","type":"literal"},{"value":" ","type":"literal"},{"value":"1","type":"month"},{"value":"/","type":"literal"},{"value":"2","type":"day"},{"value":"/","type":"literal"},{"value":"2020","type":"year"},{"value":",","type":"literal"},{"value":" ","type":"literal"},{"value":"3","type":"hour"},{"value":":","type":"literal"},{"value":"45","type":"minute"},{"value":":","type":"literal"},{"value":"00","type":"second"},{"value":".","type":"literal"},{"value":"030","type":"fractionalSecond"},{"value":" ","type":"literal"},{"value":"AM","type":"dayPeriod"}]

print(new Date(Date.UTC(2020, 0, 2)).toLocaleString("en-US", {weekday: "short", timeZone: "UTC"}))
// CHECK-NEXT: Thu
4 changes: 1 addition & 3 deletions test/hermes/intl/intl.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,7 @@ testServiceGetterTypes(Intl.DateTimeFormat, 'format');
testServiceMethodTypes(Intl.DateTimeFormat, 'formatToParts');
testServiceMethodTypes(Intl.DateTimeFormat, 'resolvedOptions');
assert(typeof Intl.DateTimeFormat().format() === 'string');
if(Intl.DateTimeFormat.prototype.formatToParts) {
testParts(Intl.DateTimeFormat().formatToParts());
}
testParts(Intl.DateTimeFormat().formatToParts());

testServiceTypes(Intl.NumberFormat);
testServiceGetterTypes(Intl.NumberFormat, 'format');
Expand Down
7 changes: 6 additions & 1 deletion utils/testsuite/testsuite_skiplist.py
Original file line number Diff line number Diff line change
Expand Up @@ -1115,7 +1115,11 @@
"test262/test/intl402/DateTimeFormat/prototype/resolvedOptions/order-dayPeriod.js",
"test262/test/intl402/DateTimeFormat/prototype/resolvedOptions/hourCycle-timeStyle.js",
"test262/test/intl402/DateTimeFormat/prototype/resolvedOptions/order-style.js",
"test262/test/intl402/DateTimeFormat/prototype/formatToParts",
"test262/test/intl402/DateTimeFormat/prototype/formatToParts/related-year-zh.js",
"test262/test/intl402/DateTimeFormat/prototype/formatToParts/dayPeriod-narrow-en.js",
"test262/test/intl402/DateTimeFormat/prototype/formatToParts/dayPeriod-long-en.js",
"test262/test/intl402/DateTimeFormat/prototype/formatToParts/dayPeriod-short-en.js",
"test262/test/intl402/DateTimeFormat/prototype/formatToParts/fractionalSecondDigits.js",
"test262/test/intl402/DateTimeFormat/prototype/format/timedatestyle-en.js",
"test262/test/intl402/DateTimeFormat/prototype/format/dayPeriod-long-en.js",
"test262/test/intl402/DateTimeFormat/prototype/format/dayPeriod-narrow-en.js",
Expand All @@ -1125,6 +1129,7 @@
# This test assumes that "year" has some default value. That is an implementation-defined behavior.
# In our case it remains undefined, which causes this test to fail.
"test262/test/intl402/DateTimeFormat/default-options-object-prototype.js",
"test262/test/intl402/DateTimeFormat/prototype/formatToParts/related-year.js",
"test262/test/intl402/DateTimeFormat/prototype/format/proleptic-gregorian-calendar.js",
"test262/test/intl402/DateTimeFormat/prototype/formatRange",
"test262/test/intl402/DateTimeFormat/prototype/formatRangeToParts",
Expand Down

0 comments on commit 5b4aa20

Please sign in to comment.