Search code examples
ocamlocaml-core

Converting strings to Core.Time.t from format string


I have to to convert strings of the form %d/%m/%Y %H:%M:%S to Time.t. Is there a Core equivalent of Calendar's Printer.Time.from_fstring function?


Solution

  • Basic version

    As far as I know there is no such function in Core library. You can implement this easily using scanf:

    open Core.Std
    
    let of_parts d m y hr min sec =
      let time_of_day = Time.Ofday.create ~hr ~min ~sec () in
      let m = Month.of_int_exn m in
      let date = Date.create_exn ~y ~m ~d in
      Time.of_date_ofday date time_of_day ~zone:Time.Zone.utc
    
    let strptime0 data =
      Scanf.sscanf data "%d/%d/%d %d:%d:%d" of_parts
    

    Full version

    The strptime0 is a first approximation, that will parse input with a fixed format. It is not very hard to implement a true strptime function, that will accept format. To do this, we need to implement the following steps:

    Transform format string

    First of all we need to transform the format string from strptime language to format language, e.g., transform %Y -> %4d, etc, and then use Scanf.format_from_string to get the instance of format object. The return value of this function should be a format, suitable for scanf, and a permutation matrix, encoded as an array.

    Rearrange arguments

    You can use an array to specify the order of elements:

    (** [rearrage f p a0 a1 a2 a3 a4 a5] call function [f] with
        provided arguments passed in the order specified by the
        permutation [p]
    
        The [i]th element of the permutation [p] specifies the subscript
        of the [i]'th argument to function [f]. Effectively [f] is called
        like this: $f a_{p[0]} ... a_{p[i]} ... a_{p[5]}$ *)
    
    let rearrange f arr a1 a2 a3 a4 a5 a6 =
      let args = [| a1; a2; a3; a4; a5; a6 |] in
      f args.(arr.(0)) args.(arr.(1)) args.(arr.(2))
        args.(arr.(3)) args.(arr.(4)) args.(arr.(5))
    

    (this will work fine, if we will represent all parts with integers, once we will introduce floating point arguments (for %S we need to lift arguments into our own numbering type).

    Glue together

    So that finally, you will get something like this (you still need to fill in stubs)

    (* d m y hr min sec *)
    let canonical_format = format_of_string "%d%d%d%d%d%d"
    
    (* this is a stub, that doesn't support rearrangment
       and works incorrectly for most of inputs *)
    let fmt_of_time p = function
      | 'm' | 'Y' | 'H' | 'M' | 'S' -> 'd'
      | x -> x
    
    let transform_format fmt =
      let p = Array.init 6 ~f:ident in (* stub: identity permutation *)
      let fmt = String.map fmt ~f:(fmt_of_time p) in
      let fmt = Scanf.format_from_string fmt canonical_format in
      p, fmt
    
    let strptime data fmt =
      let (p,fmt) = transform_format fmt in
      let of_parts = rearrange of_parts p in
      Scanf.sscanf data fmt of_parts
    

    So, as a result, we can do the following:

    # strptime "09/05/1945 12:04:32" "%d/%m/%Y %H:%M:%S";;
    - : Core.Std.Time.t = 1945-05-09 08:04:32.000000-04:00