Fable icon indicating copy to clipboard operation
Fable copied to clipboard

DateTime.ParseExact breaks REPL

Open forki opened this issue 5 years ago • 4 comments

open System
open System.Globalization

let date = DateTime.ParseExact("21.10.2020", "dd.MM.yyyy", CultureInfo.InvariantCulture)

printfn "Date %O" date

forki avatar Mar 25 '20 13:03 forki

DateTime.ParseExact is not implemented at the moment. We should add a compiler error for it, or it should be implemented.

ncave avatar Mar 25 '20 17:03 ncave

Typically my need for using DateTime.PaseExact is because I want to parse a string to a date using some specific date format, and I am guessing the same is true for most other people. Presently, I want to parse a string in "dd/MM/yyyy" format, on the client side. I am hoping this can be done using DateTime.TryParse and providing a custom implementation of IFormatProvider.

However, the example I linked to is (to me anyway) a mess of OOP inheritance and stuff which I can't currently understand. The interface defines as single method called GetFormat(type) but I can't figure out if the type I need to cater for is DateTime or (as in the example) ICustomFormatter.

Anyway, I think an example of how to do this (if indeed it is possible) would be a good thing for the documentation so that it becomes a widely known work around. Anyone know how to do this?

travis-leith avatar Aug 27 '20 16:08 travis-leith

@travis-leith The Elmish Book has a section on Parsing Date Segments where if you ignore the parts related to the router, you can parse like this

[<RequireQualifiesAccess>]
module Date

open System

let (|Between|_|) (x: int) (y: int) (input: int) =
    if input >= x && input <= y
    then Some()
    else None

let isLeapYear (year: int) = DateTime.IsLeapYear(year)

type DateParts = { day: int; month: int; year: int }

let buildDate (parts: DateParts) : Option<DateTime> =
  let day, month, year = parts.day, parts.month, parts.year
  if year <= 0 then
      None
  else
    match month, day with
    | 2, Between 1 29 when isLeapYear year -> Some (DateTime(year, month, day))
    | 2, Between 1 28 when not (isLeapYear year) -> Some (DateTime(year, month, day))
    | (1 | 3 | 5 | 7 | 8 | 10 | 12), Between 1 31 -> Some (DateTime(year, month, day))
    | (4 | 6 | 9 | 11), Between 1 30 -> Some (DateTime(year, month, day))
    | _ -> None

let (|Int|_|) (number: string) = 
  match Int32.TryParse number with 
  | (true, value) -> Some value
  | _ -> None

let parse (date: string) : Option<DateTime> = 
  match date.Split '/' with
  | [| Int day; Int month; Int year |] -> buildDate { day = day; month = month; year = year }
  | _ -> None

Then you can the module like this

match Date.parse inputDate with 
| Some parsedDate -> (* do stuff *)
| None -> (* invalid *)

Notice the function parse will split the input date string on / but you can change that to whatever you like and in read the parts in the order you want

Zaid-Ajaj avatar Aug 27 '20 17:08 Zaid-Ajaj

@Zaid-Ajaj this works well, thank you.

travis-leith avatar Aug 28 '20 09:08 travis-leith