Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/FSharp.Data.Http/Http.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 26 additions & 8 deletions src/FSharp.Data.Runtime.Utilities/TextConversions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions tests/FSharp.Data.Core.Tests/TextConversions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading