if I get a string from the command line and it looks like this:
'1-1-2011'
how can I convert that string to a DateTime object in F#?
if I get a string from the command line and it looks like this:
'1-1-2011'
how can I convert that string to a DateTime object in F#?
Depending on your specific need, .NET's DateTime class has several static methods for converting strings to DateTime
instances, these are DateTime.Parse
, DateTime.ParseExact
, and DateTime.TryParse
and their several overloads.
@7sharp9 demonstrated the most basic way to perform date parsing, with a direct method call to DateTime.Parse
. But where things get interesting in F# is with DateTime.TryParse
. Whereas DateTime.Parse
will throw an exception if the parse fails, the simplest overload of DateTime.TryParse
has the signature string * byref<DateTime> -> bool
which will return whether the parse succeeds setting the byref
argument to the parsed date if true, or to it's default value (null
in this case) otherwise. However, the syntax for using this in F# is cumbersome (and indeed it's not pleasant from any .NET language), so the F# language was designed with a special feature which allows a much nicer calling convention for methods like these as @Thomas Petricek pointed out.
But even F#'s (bool, result) return type pattern here is not ideal. Most of the time you don't need the default value if a parse fails. A nicer signature for DateTime.TryParse
would be string -> option<DateTime>
. Luckily, we can easily extend DateTime
as we like:
type System.DateTime with
static member TryParseOption str =
match DateTime.TryParse str with
| true, r -> Some(r)
| _ -> None
We use the above extension like so:
match System.DateTime.TryParseOption "11/11/11" with
| Some r -> stdout.WriteLine r
| None -> stdout.WriteLine "none"
Which is more consistent with F# conventions (like List.tryFind
, for example). But even this is can get "better". Notice how we are matching on the result of the try parse. Using Partial Active Patterns (of course!), we can wrap a whole class of try parses and move the match to the match case for greater flexibility. Take the following
open System
let (|DateTime|_|) str =
match DateTime.TryParse str with
| true, dt -> Some(dt)
| _ -> None
let (|Int|_|) str =
match Int32.TryParse str with
| true, num -> Some(num)
| _ -> None
let (|Float|_|) str =
match Double.TryParse str with
| true, num -> Some(num)
| _ -> None
Using these, we can write a neat little console application:
let rec loop() =
stdout.WriteLine "
Please select an option:
1) Parse something
2) Exit
"
match stdin.ReadLine() with
| Int 1 ->
stdout.WriteLine "Enter something to parse: "
match stdin.ReadLine() with
| Int num -> stdout.WriteLine("Successfully parsed int: {0}", num)
| Float num -> stdout.WriteLine("Successfully parsed float: {0}", num)
| DateTime dt -> stdout.WriteLine("Successfully parsed DateTime: {0}", dt)
| _ -> stdout.WriteLine "Parse Failed!"
loop()
| Int 2 ->
stdout.WriteLine "Now exiting"
| _ ->
stdout.WriteLine "Invalid option, please try again"
loop()
The key thing to notice is the nested match, where Int
, Float
, DateTime
perform their try parses within the same match expression.
There are other neat applications of these active patterns too, for example, we can succinctly simultaneously filter and map a list of date strings
> ["11/23/2003"; "not a date"; "1/1/23 23:23pm"] |> Seq.choose(|DateTime|_|);;
val it : seq<DateTime> =
seq
[11/23/2003 12:00:00 AM {Date = 11/23/2003 12:00:00 AM;
Day = 23;
DayOfWeek = Sunday;
DayOfYear = 327;
Hour = 0;
Kind = Unspecified;
Millisecond = 0;
Minute = 0;
Month = 11;
Second = 0;
Ticks = 632051424000000000L;
TimeOfDay = 00:00:00;
Year = 2003;};
1/1/2023 11:23:00 PM {Date = 1/1/2023 12:00:00 AM;
Day = 1;
DayOfWeek = Sunday;
DayOfYear = 1;
Hour = 23;
Kind = Unspecified;
Millisecond = 0;
Minute = 23;
Month = 1;
Second = 0;
Ticks = 638082121800000000L;
TimeOfDay = 23:23:00;
Year = 2023;}]
To add one nice thing to what 7sharp9 wrote, if you also want to handle failures, you can write:
match System.DateTime.TryParse "1-1-2011" with
| true, date -> printfn "Success: %A" date
| false, _ -> printfn "Failed!"
This is not obvious, because the TryParse
method has a byref<DateTime>
as the last argument (and it is used using out
in C#), but F# allows you to call the method like this.
You could do it as simply as this:
let dateTime = System.DateTime.Parse "1-1-2011"