From a61424a62bbde229f69ab52c13826a31d699a66a Mon Sep 17 00:00:00 2001 From: davidot Date: Wed, 9 Mar 2022 13:46:12 +0100 Subject: [PATCH] LibJS: Be more lenient when parsing milliseconds for Date Other engines don't give NaN if there is at least one digit after the dot for milliseconds. We were much stricter and required exactly three digits. But there is real world usage of different amounts of digits such as discord having three extra trailing zeros. --- .../LibJS/Runtime/DateConstructor.cpp | 28 ++++++++++++++++++- .../LibJS/Tests/builtins/Date/Date.parse.js | 26 +++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/Userland/Libraries/LibJS/Runtime/DateConstructor.cpp b/Userland/Libraries/LibJS/Runtime/DateConstructor.cpp index f465c89391..34b78ffa2b 100644 --- a/Userland/Libraries/LibJS/Runtime/DateConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/DateConstructor.cpp @@ -79,7 +79,33 @@ static Value parse_simplified_iso8601(GlobalObject& global_object, const String& return false; }; auto lex_seconds = [&]() { return lex_n_digits(2, seconds) && *seconds >= 0 && *seconds <= 59; }; - auto lex_milliseconds = [&]() { return lex_n_digits(3, milliseconds); }; + auto lex_milliseconds = [&]() { + // Date.parse() is allowed to accept an arbitrary number of implementation-defined formats. + // Milliseconds are parsed slightly different as other engines allow effectively any number of digits here. + // We require at least one digit and only use the first three. + + auto digits_read = 0; + int result = 0; + while (!lexer.is_eof() && is_ascii_digit(lexer.peek())) { + char ch = lexer.consume(); + if (digits_read < 3) + result = 10 * result + ch - '0'; + + ++digits_read; + } + + if (digits_read == 0) + return false; + + // If we got less than three digits pretend we have trailing zeros. + while (digits_read < 3) { + result *= 10; + ++digits_read; + } + + milliseconds = result; + return true; + }; auto lex_seconds_milliseconds = [&]() { return lex_seconds() && (!lexer.consume_specific('.') || lex_milliseconds()); }; auto lex_timezone = [&]() { if (lexer.consume_specific('+')) { diff --git a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.parse.js b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.parse.js index 6b42f7d829..3ace60e830 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Date/Date.parse.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Date/Date.parse.js @@ -36,3 +36,29 @@ test("time clip", () => { expect(Date.parse("+999999")).toBeNaN(); expect(Date.parse("-999999")).toBeNaN(); }); + +test("extra micro seconds extension", () => { + expect(Date.parse("2021-04-30T15:19:02.937+00:00")).toBe(1619795942937); + expect(Date.parse("2021-04-30T15:19:02.9370+00:00")).toBe(1619795942937); + expect(Date.parse("2021-04-30T15:19:02.93700+00:00")).toBe(1619795942937); + expect(Date.parse("2021-04-30T15:19:02.937000+00:00")).toBe(1619795942937); + + expect(Date.parse("2021-04-30T15:19:02.93+00:00")).toBe(1619795942930); + expect(Date.parse("2021-04-30T15:19:02.9+00:00")).toBe(1619795942900); + + // These values are just checked against NaN since they don't have a specified timezone. + expect(Date.parse("2021-04-30T15:19:02.93")).not.toBe(NaN); + expect(Date.parse("2021-04-30T15:19:02.9")).not.toBe(NaN); + + expect(Date.parse("2021-04-30T15:19:02.+00:00")).toBe(NaN); + expect(Date.parse("2021-04-30T15:19:02.")).toBe(NaN); + expect(Date.parse("2021-04-30T15:19:02.a")).toBe(NaN); + expect(Date.parse("2021-04-30T15:19:02.000a")).toBe(NaN); + + expect(Date.parse("2021-04-30T15:19:02.937001+00:00")).toBe(1619795942937); + expect(Date.parse("2021-04-30T15:19:02.937999+00:00")).toBe(1619795942937); + + expect(Date.parse("2021-06-26T07:24:40.007000+00:00")).toBe(1624692280007); + expect(Date.parse("2021-06-26T07:24:40.0079999999999999999+00:00")).toBe(1624692280007); + expect(Date.parse("2021-04-15T18:47:25.606000+00:00")).toBe(1618512445606); +});