From bf3fce1766635451daab9bef487dada4fa806105 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Tue, 7 Nov 2023 11:16:03 -0500 Subject: [PATCH] LibJS: Add Date.parse formats for the output of Date.prototype.to*String We currently cannot parse the output of `toString` and `toUTCString`. While the spec does not require such support, test262 expects it, and all major engines support it. --- Userland/Libraries/LibJS/CMakeLists.txt | 2 +- .../LibJS/Runtime/DateConstructor.cpp | 14 ++-- .../LibJS/Tests/builtins/Date/Date.parse.js | 64 +++++++++++++++++++ 3 files changed, 73 insertions(+), 7 deletions(-) diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index 68d93adbec..c523e8ee8e 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -267,7 +267,7 @@ set(SOURCES ) serenity_lib(LibJS js) -target_link_libraries(LibJS PRIVATE LibCore LibCrypto LibFileSystem LibRegex LibSyntax LibLocale LibUnicode LibJIT) +target_link_libraries(LibJS PRIVATE LibCore LibCrypto LibFileSystem LibRegex LibSyntax LibLocale LibUnicode LibTimeZone LibJIT) if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64") target_link_libraries(LibJS PRIVATE LibX86) endif() diff --git a/Userland/Libraries/LibJS/Runtime/DateConstructor.cpp b/Userland/Libraries/LibJS/Runtime/DateConstructor.cpp index ce37a970d8..4fb41013e5 100644 --- a/Userland/Libraries/LibJS/Runtime/DateConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/DateConstructor.cpp @@ -159,12 +159,14 @@ static double parse_date_string(DeprecatedString const& date_string) // Both Chrome and Firefox seem to support "4/17/2019 11:08 PM +0000" with most parts // being optional, however this is not clearly documented anywhere. static constexpr auto extra_formats = AK::Array { - "%a %b %e %T %z %Y"sv, // "Wed Apr 17 23:08:53 +0000 2019" - "%m/%e/%Y"sv, // "4/17/2019" - "%m/%e/%Y %R %z"sv, // "12/05/2022 10:00 -0800" - "%Y/%m/%e %R"sv, // "2014/11/14 13:05" - "%Y-%m-%e %R"sv, // "2014-11-14 13:05" - "%B %e, %Y %T"sv, // "June 5, 2023 17:00:00" + "%a %b %d %Y %T GMT%z (%+)"sv, // "Tue Nov 07 2023 10:05:55 GMT-0500 (Eastern Standard Time)" + "%a, %d %b %Y %T %Z"sv, // "Tue, 07 Nov 2023 15:05:55 GMT" + "%a %b %e %T %z %Y"sv, // "Wed Apr 17 23:08:53 +0000 2019" + "%m/%e/%Y"sv, // "4/17/2019" + "%m/%e/%Y %R %z"sv, // "12/05/2022 10:00 -0800" + "%Y/%m/%e %R"sv, // "2014/11/14 13:05" + "%Y-%m-%e %R"sv, // "2014-11-14 13:05" + "%B %e, %Y %T"sv, // "June 5, 2023 17:00:00" }; for (auto const& format : extra_formats) { diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.parse.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.parse.js index 8c464a5c0d..f8b71a9363 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.parse.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.parse.js @@ -119,3 +119,67 @@ test("Month dd, yy hh:mm:ss extension", () => { expectStringToGiveDate("May 30, 2023 17:00:00", 2023, 5, 30, 17, 0, 0); expectStringToGiveDate("June 5, 2023 17:00:00", 2023, 6, 5, 17, 0, 0); }); + +test("Date.prototype.toString extension", () => { + function expectStringToGiveDate(input, fullYear, month, dayInMonth, hours) { + const date = new Date(Date.parse(input)); + expect(date.getFullYear()).toBe(fullYear); + expect(date.getMonth() + 1).toBe(month); + expect(date.getDate()).toBe(dayInMonth); + expect(date.getHours()).toBe(hours); + } + + const time1 = "Tue Nov 07 2023 10:00:00 GMT-0500 (Eastern Standard Time)"; + const time2 = "Tue Nov 07 2023 10:00:00 GMT+0100 (Central European Standard Time)"; + const time3 = "Tue Nov 07 2023 10:00:00 GMT+0800 (Australian Western Standard Time)"; + + const originalTimeZone = setTimeZone("UTC"); + expectStringToGiveDate(time1, 2023, 11, 7, 15); + expectStringToGiveDate(time2, 2023, 11, 7, 9); + expectStringToGiveDate(time3, 2023, 11, 7, 2); + + setTimeZone("America/New_York"); + expectStringToGiveDate(time1, 2023, 11, 7, 10); + expectStringToGiveDate(time2, 2023, 11, 7, 4); + expectStringToGiveDate(time3, 2023, 11, 6, 21); + + setTimeZone("Australia/Perth"); + expectStringToGiveDate(time1, 2023, 11, 7, 23); + expectStringToGiveDate(time2, 2023, 11, 7, 17); + expectStringToGiveDate(time3, 2023, 11, 7, 10); + + // FIXME: Create a scoped time zone helper when bytecode supports the `using` declaration. + setTimeZone(originalTimeZone); +}); + +test("Date.prototype.toUTCString extension", () => { + function expectStringToGiveDate(input, fullYear, month, dayInMonth, hours) { + const date = new Date(Date.parse(input)); + expect(date.getFullYear()).toBe(fullYear); + expect(date.getMonth() + 1).toBe(month); + expect(date.getDate()).toBe(dayInMonth); + expect(date.getHours()).toBe(hours); + } + + const time = "Tue, 07 Nov 2023 15:00:00 GMT"; + + const originalTimeZone = setTimeZone("UTC"); + expectStringToGiveDate(time, 2023, 11, 7, 15); + + setTimeZone("America/New_York"); + expectStringToGiveDate(time, 2023, 11, 7, 10); + + setTimeZone("Australia/Perth"); + expectStringToGiveDate(time, 2023, 11, 7, 23); + + // FIXME: Create a scoped time zone helper when bytecode supports the `using` declaration. + setTimeZone(originalTimeZone); +}); + +test("Round trip Date.prototype.to*String", () => { + const epoch = new Date(0); + + expect(Date.parse(epoch.toString())).toBe(epoch.valueOf()); + expect(Date.parse(epoch.toISOString())).toBe(epoch.valueOf()); + expect(Date.parse(epoch.toUTCString())).toBe(epoch.valueOf()); +});