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;