From e8f04f054fbc58ddd500e55b001df88b1d22b494 Mon Sep 17 00:00:00 2001 From: "fable-repo-assist[bot]" <279220273+fable-repo-assist[bot]@users.noreply.github.com> Date: Sun, 3 May 2026 01:31:58 +0000 Subject: [PATCH 1/2] fix(js/ts): reject JS-permissive date strings that .NET TryParse rejects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit V8's Date constructor accepts strings like "ABC 6" by treating the letter sequence as a timezone abbreviation. .NET DateTime.TryParse rejects such strings as invalid. Add a pre-validation check in parseRaw() that requires the input to start with a digit or a recognised month-name prefix (Jan–Dec). This matches .NET behaviour while preserving all existing valid formats. Closes #3858 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/fable-library-ts/Date.ts | 9 +++++++++ tests/Js/Main/DateTimeTests.fs | 14 ++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/fable-library-ts/Date.ts b/src/fable-library-ts/Date.ts index ccf50fce6..cec74789e 100644 --- a/src/fable-library-ts/Date.ts +++ b/src/fable-library-ts/Date.ts @@ -599,6 +599,15 @@ export function parseRaw(input: string): [Date, Offset] { fail(); } + // Pre-validate: reject strings that JavaScript's Date constructor accepts but .NET does not. + // JS Date (V8) is overly permissive — e.g. "ABC 6" is accepted because V8 treats "ABC" as a + // timezone abbreviation. .NET DateTime.TryParse requires a recognisable date format. + // Valid date strings must start with a digit, or with a recognised month name (Jan–Dec). + // See #3858. + if (!/^\s*(?:\d|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)/i.test(input)) { + fail(); + } + // ISO dates without TZ are parsed as UTC. Adding time without TZ keeps them local. if (input.length === 10 && input[4] === "-" && input[7] === "-") { input += "T00:00:00"; diff --git a/tests/Js/Main/DateTimeTests.fs b/tests/Js/Main/DateTimeTests.fs index 8fd2a1572..9f96744da 100644 --- a/tests/Js/Main/DateTimeTests.fs +++ b/tests/Js/Main/DateTimeTests.fs @@ -876,6 +876,20 @@ let tests = let r, _date = DateTime.TryParse(invalidAmericanDate, CultureInfo.InvariantCulture, DateTimeStyles.None) r |> equal false + testCase "DateTime.TryParse rejects JS-permissive strings that .NET rejects" <| fun () -> + // V8's Date constructor accepts "ABC 6" by treating "ABC" as a timezone abbreviation. + // .NET DateTime.TryParse must reject it. See #3858. + let r1, _ = DateTime.TryParse("ABC 6") + r1 |> equal false + + let r2, _ = DateTime.TryParse("XYZ 2024") + r2 |> equal false + + // Valid dates must still parse + let r3, d = DateTime.TryParse("9/10/2014 1:50:34 PM") + r3 |> equal true + d.Year |> equal 2014 + testCase "DateTime.Today works" <| fun () -> let d = DateTime.Today equal 0 d.Hour From 64c98acc40d444b9877346393ecaf858ed849e79 Mon Sep 17 00:00:00 2001 From: Mangel Maxime Date: Fri, 29 May 2026 19:13:27 +0200 Subject: [PATCH 2/2] fix: implementation --- src/fable-library-ts/Date.ts | 19 +++++++++++++------ tests/Js/Main/DateTimeTests.fs | 33 ++++++++++++++++++++++----------- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/src/fable-library-ts/Date.ts b/src/fable-library-ts/Date.ts index cec74789e..968dcbb68 100644 --- a/src/fable-library-ts/Date.ts +++ b/src/fable-library-ts/Date.ts @@ -590,6 +590,18 @@ export function maxValue() { return DateTime(253402300799999, DateTimeKind.Utc); } +// The only date words .NET's invariant parser recognises: month names, weekday names, +// meridiem designators and zone markers (plus the ISO "T" separator). Anything else is +// rejected. Used to reject JS-permissive inputs (see `parseRaw`). +const recognizedDateWords = new Set([ + "january", "february", "march", "april", "may", "june", + "july", "august", "september", "october", "november", "december", + "jan", "feb", "mar", "apr", "jun", "jul", "aug", "sep", "sept", "oct", "nov", "dec", + "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday", + "mon", "tue", "wed", "thu", "fri", "sat", "sun", + "am", "pm", "gmt", "utc", "ut", "t", "z", +]); + export function parseRaw(input: string): [Date, Offset] { function fail() { throw new Exception(`The string is not a valid Date: ${input}`); @@ -599,12 +611,7 @@ export function parseRaw(input: string): [Date, Offset] { fail(); } - // Pre-validate: reject strings that JavaScript's Date constructor accepts but .NET does not. - // JS Date (V8) is overly permissive — e.g. "ABC 6" is accepted because V8 treats "ABC" as a - // timezone abbreviation. .NET DateTime.TryParse requires a recognisable date format. - // Valid date strings must start with a digit, or with a recognised month name (Jan–Dec). - // See #3858. - if (!/^\s*(?:\d|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)/i.test(input)) { + if ((input.match(/[a-z]+/gi) ?? []).some(word => !recognizedDateWords.has(word.toLowerCase()))) { fail(); } diff --git a/tests/Js/Main/DateTimeTests.fs b/tests/Js/Main/DateTimeTests.fs index 9f96744da..0cdc25741 100644 --- a/tests/Js/Main/DateTimeTests.fs +++ b/tests/Js/Main/DateTimeTests.fs @@ -877,18 +877,29 @@ let tests = r |> equal false testCase "DateTime.TryParse rejects JS-permissive strings that .NET rejects" <| fun () -> - // V8's Date constructor accepts "ABC 6" by treating "ABC" as a timezone abbreviation. - // .NET DateTime.TryParse must reject it. See #3858. - let r1, _ = DateTime.TryParse("ABC 6") - r1 |> equal false - - let r2, _ = DateTime.TryParse("XYZ 2024") - r2 |> equal false - - // Valid dates must still parse - let r3, d = DateTime.TryParse("9/10/2014 1:50:34 PM") + // JS's Date constructor treats unrecognised words as timezone abbreviations, so it + // accepts strings .NET rejects: "ABC 6" (ABC), "XYZ 2024" (XYZ), and words that merely + // start with a month name like "Maybe 6" (May) or "Junk 2024" (Jun). + [ "ABC 6"; "XYZ 2024"; "Maybe 6"; "Junk 2024" ] + |> List.map (fun s -> fst (DateTime.TryParse s)) + |> equal [ false; false; false; false ] + + // Recognised date words (weekday / month names, GMT) must still parse, matching .NET. + let r1, d1 = DateTime.TryParse("Sun, 06 Nov 1994 08:49:37 GMT") + r1 |> equal true + d1.Year |> equal 1994 + + let r2, d2 = DateTime.TryParse("Mon, 15 Jan 2024") + r2 |> equal true + d2.Year |> equal 2024 + + let r3, d3 = DateTime.TryParse("January 15, 2024") r3 |> equal true - d.Year |> equal 2014 + d3.Year |> equal 2024 + + let r4, d4 = DateTime.TryParse("9/10/2014 1:50:34 PM") + r4 |> equal true + d4.Year |> equal 2014 testCase "DateTime.Today works" <| fun () -> let d = DateTime.Today