Search code examples
f#extension-methods

How can I extend System.DateTime in F#?


I would like to extend DateTime in the following fashion:

[<AutoOpen>]
type System.DateTime with
  member this.floor (interval: TimeSpan) =
    this.AddTicks(-(this.Ticks % interval.Ticks))

  member this.ceiling (interval: TimeSpan) =
    let overflow = this.Ticks % interval.Ticks
    if overflow = 0 then this else this.AddTicks(interval.Ticks - overflow)

  member this.round (interval: TimeSpan) =
    let halfIntervalTicks = (interval.Ticks + 1) >>> 1
    this.AddTicks(halfIntervalTicks - ((this.Ticks + halfIntervalTicks) % interval.Ticks))

based on aj.toulan's C# answer at: DateTime Round Up and Down

but this won't work; apparently I should be using a module, but then how do I get the 'this' part? what would be the right syntax?

I get this error:

[FS0644] Namespaces cannot contain extension members except in the same file and namespace declaration group where the type is defined. Consider using a module to hold declarations of extension members.


Solution

  • Judging by the error message, I'm assuming you have that declaration inside a namespace, like this:

    namespace N
    
    type System.DateTime with
        ...
    

    If so, then my first question is: why do you need a namespace in the first place? Use modules instead! Modules are more idiomatic in F# and let you do more stuff:

    module N
    
    type System.DateTime with
        ...
    

    But if you must have a namespace for some reason, you can still make this work by using the advice that is given to you in the error message itself: put the extensions inside a module!

    namespace N
    
    module M =    
        type System.DateTime with
            ...
    

    Of course, now you would also have to open that module at use sites:

    open N
    open M  // <-- extra open
    
    ... DateTime.Now.floor ...
    

    But you can avoid that as well by giving that module an [<AutoOpen>] attribute:

    namespace N
    
    [<AutoOpen>]
    module M =    
        type System.DateTime with
            ...
    

    Now the use site can open just the namespace:

    open N
    
    ... DateTime.Now.floor ...
    

    Also, note that [<AutoOpen>] on the type extension is non-sensical. Type extensions are always open, that's their very point, you don't need to open them explicitly or have an AutoOpen attribute.