Skip to content

Commit

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

Summary:
This reverts commit c5a633f.

That commit removed the Intl.DateTimeFormat.formatToParts for Apple platforms from Hermes.
However, the community started depending on it, and we ended up manually reverting that commit in the release branch for the past 3 releases.

This is like reverting the commit in main, so we are proceeding with that approach to simplify the release process of React Native and Hermes to the OSS.

Pull Request resolved: #1567

Test Plan: CI

Reviewed By: blakef

Differential Revision: D66099176

Pulled By: cipolleschi

fbshipit-source-id: fe42bab7308bbd2294911be694f0bef8d1652566
  • Loading branch information
cortinico authored and facebook-github-bot committed Nov 22, 2024
1 parent 6076857 commit ac557bb
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 8 deletions.
1 change: 0 additions & 1 deletion doc/IntlAPIs.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,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 @@ -1262,6 +1262,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 @@ -1932,8 +1934,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
9 changes: 8 additions & 1 deletion utils/testsuite/testsuite_skiplist.py
Original file line number Diff line number Diff line change
Expand Up @@ -1113,7 +1113,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 @@ -1123,6 +1127,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 Expand Up @@ -1954,11 +1959,13 @@
"test262/test/intl402/NumberFormat",
"test262/test/intl402/String/prototype/toLocaleLowerCase",
"test262/test/intl402/String/prototype/toLocaleUpperCase",
"test262/test/intl402/DateTimeFormat/prototype/formatToParts",
],
"darwin": [
# Intl implementation issues on Apple.
"test262/test/intl402/Collator/ignore-invalid-unicode-ext-values.js",
"test262/test/intl402/Collator/unicode-ext-value-collation.js",
"test262/test/intl402/DateTimeFormat/prototype/formatToParts/offset-timezone-correct.js",
],
}

Expand Down

0 comments on commit ac557bb

Please sign in to comment.