United Kingdom: +44 (0)208 088 8978

Mastering Fable: Simplifying Your Journey by Knowing What’s Available

This blog showcases how you can achieve locale-based date formatting without diving into JS interop. Discover the simplicity and elegance of writing JavaScript with Fable!

We're hiring Software Developers

Click here to find out more

Earlier this week, a customer asked a seemingly simple question:

I have a front end using Fable, and need to display dates, formatted correctly based on the user's browser settings

I was not sure about the best way to approach it, so I decided to do some research. In this blog post, I don't just want to share my final findings; I want to take you on my journey to the answer, including some sidetracks!

My gut feeling: this is a JS question, without an out-of-the-box solution

Perhaps somewhat clouded by the clause "based on the user's browser settings" I set off on a quest to find a way to format dates in JS based on a locale. I soon landed on the Intl.DateTimeFormat type. It's a type you can construct based on a locale (confusingly referred to as a language in JS) and format a dateTime.

I spun up the Fable REPL and built a quick wrapper:

open Fable.Core.JsInterop

type DateTimeFormat = {
    format: DateTime -> string
}
module DateTimeFormat =

    let create language: DateTimeFormat =
        emitJsExpr (language) "new Intl.DateTimeFormat($0)"

Now I just needed a language to provide to the constructor, which was easily done using the global navigator object. Unfortunately, there are no bindings for the navigator types, so we use some unsafe access using ?:

open Fable.Core.JsInterop
open Browser

let language: string = window?navigator?language

Simple enough to use:

let dateTimeFormat = DateTimeFormat.create language
printfn $"now formatted in {language} is {dateTimeFormat.format(DateTime.Now)}"

There must be an easier way!?

That does not feel right. Surely, we can just format these dates without explicitly having to create a formatter if the browser knows the language it needs to render in?

I did some more digging and came across a set of functions that format dates in JS. I wrote a small extension for the method Date.toLocaleDateString():

type DateTime with

    [<Emit("$0.toLocaleDateString()")>]
    member this.toLocaleDateString() = jsNative

That's a lot less convoluted, and as long as we're happy to stick to the browser-provided locale, we are good!

printfn $"now formatted is {DateTime.Now.toLocaleDateString()}"

Back to the basics

Now that we got rid of the platform-specific use of window.navigator.language, I got a feeling that we should be able to make things a bit simpler still. What if we just try the standard methods on the DateTime object? I gave the .NET function DateTime.ToShortDateString() a go inside the Fable REPL... and it did exactly what I expected. A quick look under the hood reveals that it simply calls Date.toLocaleDateString():

export function toShortDateString(d: IDateTime) {
  return d.toLocaleDateString();
}

Takeaway

Once you start working with Fable and get comfortable with its JS interop, you might assume that you need to emit custom JavaScript to do what you want. However, it's worth bearing in mind that the Fable compiler has already done a lot of this work for you; in particular, the F# System module is extensively covered to emit the necessary JavaScript to get the behaviour that you want.

When in doubt, have a play around with the Fable online REPL; it's an easy way of running Fable in isolation from a bigger project, and the ability to see the JS output can be very helpful in understanding the relation between the F# you write and the JavaScript it produces.