Skip to content

Commit

Permalink
Adjust parse / tryParse (#558)
Browse files Browse the repository at this point in the history
  • Loading branch information
gusty authored Oct 13, 2023
1 parent 3962979 commit cc644dc
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 18 deletions.
89 changes: 81 additions & 8 deletions src/FSharpPlus/Control/Converter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -111,26 +111,47 @@ type TryParse =
static member TryParse (_: string , _: TryParse) = fun x -> Some x : option<string>
static member TryParse (_: StringBuilder , _: TryParse) = fun x -> Some (new StringBuilder (x: string)) : option<StringBuilder>
#if !FABLE_COMPILER
static member TryParse (_: DateTime , _: TryParse) = fun (x:string) -> DateTime.TryParseExact (x, [|"yyyy-MM-ddTHH:mm:ss.fffZ"; "yyyy-MM-ddTHH:mm:ssZ"|], null, DateTimeStyles.RoundtripKind) |> tupleToOption : option<DateTime>
static member TryParse (_: DateTimeOffset, _: TryParse) = fun (x:string) -> DateTimeOffset.TryParseExact (x, [|"yyyy-MM-ddTHH:mm:ss.fffK"; "yyyy-MM-ddTHH:mm:ssK"|], null, DateTimeStyles.RoundtripKind) |> tupleToOption : option<DateTimeOffset>

static member TryParse (_: DateTime , _: TryParse) = fun (x:string) ->
match DateTime.TryParseExact (x, [|"yyyy-MM-ddTHH:mm:ss.fffZ"; "yyyy-MM-ddTHH:mm:ssZ"|], null, DateTimeStyles.RoundtripKind) with
| true, x -> Some x
| _ ->
match DateTime.TryParse (x, CultureInfo.InvariantCulture, DateTimeStyles.None) with
| true, x -> Some x
| _ -> None

static member TryParse (_: DateTimeOffset, _: TryParse) = fun (x:string) ->
match DateTimeOffset.TryParseExact (x, [|"yyyy-MM-ddTHH:mm:ss.fffK"; "yyyy-MM-ddTHH:mm:ssK"|], null, DateTimeStyles.AssumeUniversal) with
| true, x -> Some x
| _ ->
match DateTimeOffset.TryParse (x, CultureInfo.InvariantCulture, DateTimeStyles.None) with
| true, x -> Some x
| _ -> None
#endif

static member inline Invoke (value: string) =
let inline call_2 (a: ^a, b: ^b) = ((^a or ^b) : (static member TryParse : _*_ -> _) b, a)
let inline call (a: 'a) = fun (x: 'x) -> call_2 (a, Unchecked.defaultof<'r>) x : 'r option
call Unchecked.defaultof<TryParse> value

type TryParse with
static member inline TryParse (_: 'R, _: Default2) = fun x ->
/// The F# signature
static member inline InvokeOnInstance (value: string) = (^R: (static member TryParse : string -> 'R option) value)

/// The .Net signature
static member inline InvokeOnConvention (value: string) =
let mutable r = Unchecked.defaultof< ^R>
if (^R: (static member TryParse : _ * _ -> _) (x, &r)) then Some r else None
if (^R: (static member TryParse : _ * _ -> _) (value, &r)) then Some r else None

#if NET7_0
/// IParsable<'T>
static member InvokeOnInterface<'T when 'T :> IParsable<'T>> (value: string) =
let mutable r = Unchecked.defaultof<'T>
if ('T.TryParse(value, CultureInfo.InvariantCulture, &r)) then Some r else None
#endif

static member inline TryParse (_: ^t when ^t: null and ^t: struct, _: Default1) = id
static member inline TryParse (_: 'R, _: Default1) = fun x -> (^R: (static member TryParse : string -> 'R option) x)

type Parse =
inherit Default1
static member inline Parse (_: ^R , _: Default1) = fun (x:string) -> (^R: (static member Parse : _ -> ^R) x)
static member inline Parse (_: ^R , _: Parse ) = fun (x:string) -> (^R: (static member Parse : _ * _ -> ^R) (x, CultureInfo.InvariantCulture))

static member inline Parse (_: 'T when 'T : enum<_>, _: Parse ) = fun (x:string) ->
Expand All @@ -139,6 +160,16 @@ type Parse =
| _ -> invalidArg "value" ("Requested value '" + x + "' was not found.")
) : 'enum

#if !FABLE_COMPILER
static member Parse (_: DateTime , _: Parse) = fun (x:string) ->
match DateTime.TryParseExact (x, [|"yyyy-MM-ddTHH:mm:ss.fffZ"; "yyyy-MM-ddTHH:mm:ssZ"|], null, DateTimeStyles.RoundtripKind) with
| true, x -> x
| _ -> DateTime.Parse (x, CultureInfo.InvariantCulture)

static member Parse (_: DateTimeOffset, _: Parse) = fun (x:string) ->
try DateTimeOffset.ParseExact (x, [|"yyyy-MM-ddTHH:mm:ss.fffK"; "yyyy-MM-ddTHH:mm:ssK"|], null, DateTimeStyles.AssumeUniversal)
with _ -> DateTimeOffset.Parse (x, CultureInfo.InvariantCulture)
#endif

static member Parse (_: bool , _: Parse) = fun (x:string) -> Boolean.Parse (x)

Expand All @@ -151,4 +182,46 @@ type Parse =
let inline call (a: 'a) = fun (x: 'x) -> call_2 (a, Unchecked.defaultof<'r>) x : 'r
call Unchecked.defaultof<Parse> value

static member inline InvokeOnInstance (value: string) = (^R: (static member Parse : _ -> ^R) value)


type Parse with

static member inline Parse (_: ^R , _: Default4) = fun (value: string) ->
match TryParse.InvokeOnConvention value with
| Some x -> x : ^R
| None -> invalidArg "value" ("Error parsing value '" + value + "'.")

static member inline Parse (_: ^R , _: Default3) = fun (value: string) ->
match TryParse.InvokeOnInstance value with
| Some x -> x : ^R
| None -> invalidArg "value" ("Error parsing value '" + value + "'.")

static member inline Parse (_: ^R , _: Default2) : string -> ^R = Parse.InvokeOnInstance

#if NET7_0
static member Parse<'T when 'T :> IParsable<'T>> (_: 'T, _: Default1) = fun (x: string) -> 'T.Parse (x, CultureInfo.InvariantCulture)
static member inline Parse (_: ^t when ^t: null and ^t: struct, _: Default1) = id
#else
static member inline Parse (_: ^t when ^t: null and ^t: struct, _: Default2) = id
#endif

type TryParse with

static member inline TryParse (_: 'R, _: Default4) : string -> 'R option = fun (value: string) ->
try Some (Parse.InvokeOnInstance value) with
| :? ArgumentNullException | :? FormatException -> None
| _ -> reraise ()

static member inline TryParse (_: 'R, _: Default3) : string -> 'R option = TryParse.InvokeOnConvention

static member inline TryParse (_: 'R, _: Default2) : string -> 'R option = TryParse.InvokeOnInstance

#if NET7_0
static member inline TryParse (_: 'R, _: Default1) : string -> 'R option = TryParse.InvokeOnInterface
static member inline TryParse (_: ^t when ^t: null and ^t: struct, _: Default1) = id
#else
static member inline TryParse (_: ^t when ^t: null and ^t: struct, _: Default2) = id
#endif

#endif
35 changes: 25 additions & 10 deletions tests/FSharpPlus.Tests/Parsing.fs
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,33 @@ module Parsing =

[<Test>]
let parseDateTime () =
#if MONO
let v1 : DateTime = parse "2011-03-04T15:42:19+03:00"
Assert.IsTrue((v1 = DateTime(2011,3,4,12,42,19)))
#else
Assert.Ignore ("Depends on how it's executed...")
#endif

let t = DateTime(2011,3,14,12,42,19)
let u = DateTimeOffset(2011,3,14,15,42,19, TimeSpan.FromHours 3.)
let u0 = DateTimeOffset(2011,3,14,15,42,19, TimeSpan.FromHours 0.)

let t1 = parse<DateTime> "2011-03-14T15:42:19+03:00" in Assert.AreEqual( t, t1, nameof t1)
let t2 = tryParse<DateTime> "2011-03-14T15:42:19+03:00" in Assert.AreEqual(Some t, t2, nameof t2)

let u1 = parse<DateTimeOffset> "2011-03-14T15:42:19+03:00" in Assert.AreEqual( u, u1, nameof u1)
let u2 = tryParse<DateTimeOffset> "2011-03-14T15:42:19+03:00" in Assert.AreEqual(Some u, u2, nameof u2)

let t3 = parse<DateTime> "Mon, 14 Mar 2011 12:42:19 GMT" in Assert.AreEqual( t, t3, nameof t3)
let t4 = tryParse<DateTime> "Mon, 14 Mar 2011 12:42:19 GMT" in Assert.AreEqual(Some t, t4, nameof t4)

let u3 = parse<DateTimeOffset> "Mon, 14 Mar 2011 15:42:19 GMT" in Assert.AreEqual( u0, u3, nameof u3)
let u4 = tryParse<DateTimeOffset> "Mon, 14 Mar 2011 15:42:19 GMT" in Assert.AreEqual(Some u0, u4, nameof u4)

let u5 = parse<DateTimeOffset> "2011-03-14T15:42:19" in Assert.AreEqual( u0, u5, nameof u5)
let u6 = tryParse<DateTimeOffset> "2011-03-14T15:42:19" in Assert.AreEqual(Some u0, u6, nameof u6)

let u7 = parse<DateTimeOffset> "2011-03-14T15:42:19Z" in Assert.AreEqual( u0, u7, nameof u7)
let u8 = tryParse<DateTimeOffset> "2011-03-14T15:42:19Z" in Assert.AreEqual(Some u0, u8, nameof u8)



[<Test>]
let parse () =
let v2 : DateTimeOffset = parse "2011-03-04T15:42:19+03:00"

Assert.IsTrue((v2 = DateTimeOffset(2011,3,4,15,42,19, TimeSpan.FromHours 3.)))

let _101 = tryParse "10.1.0.1" : Net.IPAddress option
let _102 = tryParse "102" : string option
Expand All @@ -50,7 +65,7 @@ module Parsing =
areStEqual r66 (Some 66.0)

let r123: WrappedListA<int> option = tryParse "[1;2;3]"
areStEqual r123 (Some (WrappedListA [1; 2; 3]))
areStEqual r123 (Some (WrappedListA [1; 2; 3]))

[<Test>]
let parseCustomType () =
Expand Down

0 comments on commit cc644dc

Please sign in to comment.