diff --git a/src/fable-library-ts/Date.ts b/src/fable-library-ts/Date.ts index ccf50fce6..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,6 +611,10 @@ export function parseRaw(input: string): [Date, Offset] { fail(); } + if ((input.match(/[a-z]+/gi) ?? []).some(word => !recognizedDateWords.has(word.toLowerCase()))) { + 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..0cdc25741 100644 --- a/tests/Js/Main/DateTimeTests.fs +++ b/tests/Js/Main/DateTimeTests.fs @@ -876,6 +876,31 @@ 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 () -> + // 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 + 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 equal 0 d.Hour