convert string to System.DateTime in F#

11,532

Solution 1

You could do it as simply as this:

let dateTime = System.DateTime.Parse "1-1-2011"

Solution 2

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;}]

Solution 3

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.

Share:
11,532

Related videos on Youtube

Ramy
Author by

Ramy

Updated on February 11, 2020

Comments

  • Ramy
    Ramy about 4 years

    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#?

    • Sebastian Paaske Tørholm
      Sebastian Paaske Tørholm about 13 years
      possible duplicate of F# - Parse dates
  • Coding Edgar
    Coding Edgar about 3 years
    didn't know it was possible to do this with the out argument, nice

Related