From e898af671a92ce5ef2aa57a5205d344fc1527c19 Mon Sep 17 00:00:00 2001 From: Gang Zhao Date: Thu, 30 Jan 2025 10:39:34 -0800 Subject: [PATCH] Revert "Disable DateTimeFormat::formatToParts for Apple platform (#1155)" (#1567) (#1567) Summary: Original Author: ncor@meta.com Original Git: ac557bb00fa815946c87732c7b81174a73cde5be Original Reviewed By: blakef Original Revision: D66099176 This reverts commit c5a633f6452902cd0b5c9392a1e4f51a33933284. 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: https://github.com/facebook/hermes/pull/1567 Pulled By: cipolleschi Reviewed By: neildhar Differential Revision: D68868242 fbshipit-source-id: c06b9821feb57507c7380358a7181747b91b0de5 --- doc/IntlAPIs.md | 1 - lib/Platform/Intl/PlatformIntlApple.mm | 72 +++++++++++++++++++++- lib/VM/JSLib/Intl.cpp | 2 - test/hermes/intl/date-time-format-apple.js | 19 ++++++ test/hermes/intl/intl.js | 4 +- utils/testsuite/skiplist.json | 8 ++- 6 files changed, 98 insertions(+), 8 deletions(-) diff --git a/doc/IntlAPIs.md b/doc/IntlAPIs.md index 6798a0f1f97..5f3a4158756 100644 --- a/doc/IntlAPIs.md +++ b/doc/IntlAPIs.md @@ -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 diff --git a/lib/Platform/Intl/PlatformIntlApple.mm b/lib/Platform/Intl/PlatformIntlApple.mm index e6f3b265836..9be1b29e44b 100644 --- a/lib/Platform/Intl/PlatformIntlApple.mm +++ b/lib/Platform/Intl/PlatformIntlApple.mm @@ -1229,6 +1229,8 @@ uint8_t getCurrencyDigits(std::u16string_view code) { std::u16string format(double jsTimeValue) noexcept; + std::vector formatToParts(double x) noexcept; + private: void initializeNSDateFormatter(NSLocale *nsLocale) noexcept; @@ -1895,8 +1897,76 @@ uint8_t getCurrencyDigits(std::u16string_view code) { return static_cast(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 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 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 DateTimeFormat::formatToParts(double x) noexcept { - llvm_unreachable("formatToParts is unimplemented on Apple platforms"); + return static_cast(this)->formatToParts(x); } class NumberFormatApple : public NumberFormat { diff --git a/lib/VM/JSLib/Intl.cpp b/lib/VM/JSLib/Intl.cpp index 289c6855b9d..33864968104 100644 --- a/lib/VM/JSLib/Intl.cpp +++ b/lib/VM/JSLib/Intl.cpp @@ -910,7 +910,6 @@ void defineIntlDateTimeFormat(Runtime &runtime, Handle intl) { false, true); -#ifndef __APPLE__ defineMethod( runtime, prototype, @@ -918,7 +917,6 @@ void defineIntlDateTimeFormat(Runtime &runtime, Handle intl) { nullptr, intlDateTimeFormatPrototypeFormatToParts, 1); -#endif defineMethod( runtime, diff --git a/test/hermes/intl/date-time-format-apple.js b/test/hermes/intl/date-time-format-apple.js index ea3a3c94bf9..70e3b14d90e 100644 --- a/test/hermes/intl/date-time-format-apple.js +++ b/test/hermes/intl/date-time-format-apple.js @@ -151,5 +151,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 diff --git a/test/hermes/intl/intl.js b/test/hermes/intl/intl.js index c5eb5086ad6..8e13358dee2 100644 --- a/test/hermes/intl/intl.js +++ b/test/hermes/intl/intl.js @@ -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'); diff --git a/utils/testsuite/skiplist.json b/utils/testsuite/skiplist.json index d0d5e25001c..73bb527935d 100644 --- a/utils/testsuite/skiplist.json +++ b/utils/testsuite/skiplist.json @@ -27,7 +27,13 @@ }, { "paths": [ - "test262/test/intl402/DateTimeFormat/prototype/formatToParts/", + "test262/test/intl402/DateTimeFormat/prototype/formatToParts/fractionalSecondDigits.js", + "test262/test/intl402/DateTimeFormat/prototype/formatToParts/dayPeriod-long-en.js", + "test262/test/intl402/DateTimeFormat/prototype/formatToParts/dayPeriod-narrow-en.js", + "test262/test/intl402/DateTimeFormat/prototype/formatToParts/dayPeriod-short-en.js", + "test262/test/intl402/DateTimeFormat/prototype/formatToParts/offset-timezone-correct.js", + "test262/test/intl402/DateTimeFormat/prototype/formatToParts/related-year.js", + "test262/test/intl402/DateTimeFormat/prototype/formatToParts/related-year-zh.js", "test262/test/intl402/DateTimeFormat/prototype/formatRange/", "test262/test/intl402/DateTimeFormat/prototype/formatRangeToParts/", "test262/test/intl402/NumberFormat/prototype/formatToParts/"