From ee7ab5d053bb0cc5d90b460a252ad7df65fb6d14 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Tue, 7 Nov 2023 09:52:37 -0500 Subject: [PATCH] LibCore: Support wildcard-skipping characters in Core::DateTime::parse For example, LibJS will need to parse date strings of the form: Wed Dec 31 1969 19:00:00 GMT-0500 (Eastern Standard Time) This string contains both the time zone offset (-05:00) and a display name for the time zone (Eastern Standard Time). Because we will already handle time zone adjustments when we handle the offsets, we will want to just skip the time zone name. This patch will let us use a format string of the form "GMT%z (%+)" to do so. --- Tests/LibCore/TestLibCoreDateTime.cpp | 23 +++++++++++++++++++++++ Userland/Libraries/LibCore/DateTime.cpp | 18 ++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/Tests/LibCore/TestLibCoreDateTime.cpp b/Tests/LibCore/TestLibCoreDateTime.cpp index 0fde0d3065..d412ce6b1d 100644 --- a/Tests/LibCore/TestLibCoreDateTime.cpp +++ b/Tests/LibCore/TestLibCoreDateTime.cpp @@ -83,3 +83,26 @@ TEST_CASE(parse_time_zone_name) test("%Y/%m/%d %R %Z"sv, "2023/01/23 10:50 Europe/Paris"sv, 2023, 01, 23, 17, 50); test("%Y/%m/%d %R %Z"sv, "2023/01/23 10:50 Australia/Perth"sv, 2023, 01, 23, 10, 50); } + +TEST_CASE(parse_wildcard_characters) +{ + EXPECT(!Core::DateTime::parse("%+"sv, ""sv).has_value()); + EXPECT(!Core::DateTime::parse("foo%+"sv, "foo"sv).has_value()); + EXPECT(!Core::DateTime::parse("[%*]"sv, "[foo"sv).has_value()); + EXPECT(!Core::DateTime::parse("[%*]"sv, "foo]"sv).has_value()); + EXPECT(!Core::DateTime::parse("%+%b"sv, "fooJan"sv).has_value()); + + auto test = [](auto format, auto time, u32 year, u32 month, u32 day) { + auto result = Core::DateTime::parse(format, time); + VERIFY(result.has_value()); + + EXPECT_EQ(year, result->year()); + EXPECT_EQ(month, result->month()); + EXPECT_EQ(day, result->day()); + }; + + test("%Y %+ %m %d"sv, "2023 whf 01 23"sv, 2023, 01, 23); + test("%Y %m %d %+"sv, "2023 01 23 whf"sv, 2023, 01, 23); + test("%Y [%+] %m %d"sv, "2023 [well hello friends!] 01 23"sv, 2023, 01, 23); + test("%Y %m %d [%+]"sv, "2023 01 23 [well hello friends!]"sv, 2023, 01, 23); +} diff --git a/Userland/Libraries/LibCore/DateTime.cpp b/Userland/Libraries/LibCore/DateTime.cpp index 11ecba36e2..000d684e0d 100644 --- a/Userland/Libraries/LibCore/DateTime.cpp +++ b/Userland/Libraries/LibCore/DateTime.cpp @@ -512,6 +512,24 @@ Optional DateTime::parse(StringView format, StringView string) tm_represents_utc_time = true; break; + case '+': { + Optional next_format_character; + + if (format_pos + 1 < format.length()) { + next_format_character = format[format_pos + 1]; + + // Disallow another formatter directly after %+. This is to avoid ambiguity when parsing a string like + // "ignoreJan" with "%+%b", as it would be non-trivial to know that where the %b field begins. + if (next_format_character == '%') + return {}; + } + + auto discarded = string_lexer.consume_until([&](auto ch) { return ch == next_format_character; }); + if (discarded.is_empty()) + return {}; + + break; + } case '%': consume('%'); break;