diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 3c7b3733e..121d622b9 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,10 @@ # Release Notes +## 8.1.2 - Mar 17 2026 + +- Fix `TextConversions.AsDateTime` and `TextConversions.AsDateTimeOffset` to return `None` (instead of throwing `ArgumentOutOfRangeException`) when a `/Date(...)/` value overflows the valid `DateTime`/`DateTimeOffset` range; use `InvariantCulture` explicitly when parsing the milliseconds value +- Fix error message in Range header validation: show the original header value (not the internal byte array representation) when the range format is invalid + ## 8.1.1 - Mar 14 2026 - Fix `CombinedStream` to throw `NotSupportedException` (instead of `Exception`) for unsupported operations (write, seek, set length); fixes a typo in the length error message diff --git a/src/FSharp.Data.Http/Http.fs b/src/FSharp.Data.Http/Http.fs index 52c67d53c..10056cbaf 100644 --- a/src/FSharp.Data.Http/Http.fs +++ b/src/FSharp.Data.Http/Http.fs @@ -1745,7 +1745,7 @@ module internal HttpHelpers = let bytes = value.Substring("bytes=".Length).Split('-') if bytes.Length <> 2 then - failwithf "Invalid value for the Range header (%O)" bytes + failwithf "Invalid value for the Range header (%s)" value req.AddRange(int64 bytes.[0], int64 bytes.[1]) | "proxy-authorization" -> req.Headers.[HeaderEnum.ProxyAuthorization] <- value diff --git a/src/FSharp.Data.Runtime.Utilities/TextConversions.fs b/src/FSharp.Data.Runtime.Utilities/TextConversions.fs index 0226a0cce..2170532f3 100644 --- a/src/FSharp.Data.Runtime.Utilities/TextConversions.fs +++ b/src/FSharp.Data.Runtime.Utilities/TextConversions.fs @@ -171,10 +171,19 @@ type TextConversions private () = let matchesMS = msDateRegex.Value.Match(text.Trim()) if matchesMS.Success then - matchesMS.Groups.[1].Value - |> Double.Parse - |> DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds - |> Some + match + Double.TryParse( + matchesMS.Groups.[1].Value, + Globalization.NumberStyles.Integer, + Globalization.CultureInfo.InvariantCulture + ) + with + | true, ms when not (Double.IsInfinity ms) -> + try + DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds ms |> Some + with :? ArgumentOutOfRangeException -> + None + | _ -> None else // Parse ISO 8601 format, fixing time zone if needed match ParseISO8601FormattedDateTime text cultureInfo with @@ -208,10 +217,19 @@ type TextConversions private () = // e.g. 1231456+1000, 123123+0000, 123123-0500, etc. match offset matchesMS.Groups.[2].Value with | ValueSome ofst -> - matchesMS.Groups.[1].Value - |> Double.Parse - |> DateTimeOffset(1970, 1, 1, 0, 0, 0, ofst).AddMilliseconds - |> Some + match + Double.TryParse( + matchesMS.Groups.[1].Value, + Globalization.NumberStyles.Integer, + Globalization.CultureInfo.InvariantCulture + ) + with + | true, ms when not (Double.IsInfinity ms) -> + try + DateTimeOffset(1970, 1, 1, 0, 0, 0, ofst).AddMilliseconds ms |> Some + with :? ArgumentOutOfRangeException -> + None + | _ -> None | ValueNone -> None else match ParseISO8601FormattedDateTime text cultureInfo with diff --git a/tests/FSharp.Data.Core.Tests/TextConversions.fs b/tests/FSharp.Data.Core.Tests/TextConversions.fs index 8ae3d0041..4e34501cf 100644 --- a/tests/FSharp.Data.Core.Tests/TextConversions.fs +++ b/tests/FSharp.Data.Core.Tests/TextConversions.fs @@ -163,6 +163,10 @@ let ``DateTime conversions with various formats``() = TextConversions.AsDateTime culture "/Date(1577836800000)/" |> Option.isSome |> should equal true TextConversions.AsDateTime culture "/Date(1577836800000+0000)/" |> Option.isSome |> should equal true + // Overflow-safe: astronomically large ms-Date values should return None rather than throwing + TextConversions.AsDateTime culture ("/Date(" + System.String('9', 500) + ")/") |> should equal None + TextConversions.AsDateTime culture ("/Date(-" + System.String('9', 500) + ")/") |> should equal None + // Invalid values TextConversions.AsDateTime culture "not-a-date" |> should equal None TextConversions.AsDateTime culture "" |> should equal None @@ -190,6 +194,9 @@ let ``DateTimeOffset conversions with additional formats``() = TextConversions.AsDateTimeOffset culture "2020-01-01T12:00:00+05:30" |> Option.isSome |> should equal true TextConversions.AsDateTimeOffset culture "2020-01-01T12:00:00-08:00" |> Option.isSome |> should equal true + // Overflow-safe: astronomically large ms-Date+offset values should return None rather than throwing + TextConversions.AsDateTimeOffset culture ("/Date(" + System.String('9', 500) + "+0000)/") |> should equal None + // Invalid values TextConversions.AsDateTimeOffset culture "2020-01-01" |> should equal None TextConversions.AsDateTimeOffset culture "invalid" |> should equal None