]> Matthias Wimmer (溫雅石)

Content from 2022-04

Handling time zones in Elm

posted on

When programming a single page application, especially if it is designed to run within different time zones, you often use UTC on the REST interfaces to the backend and let the single page application handle the time zone calculation.

When I first tried to implement this in Elm, I first tried to do this using the tools at hand when using the standard package for time handling: elm/time:

On the start-up of my SPA I send a Cmd to the Elm runtime, requesting that it generated a SetTimezone event with the local time zone of the user (here).

getTimezone : Cmd Msg
getTimezone =
    Task.perform SetTimezone Time.here

When we give the Elm architecture this command, we get back this message:

type Msg
    = SetTimezone Time.Zone

Then I stored the provided Zone in my model and used it within a helper function to format timestamps:

formatTime : Zone -> Posix -> String
formatTime zone timestamp =
    let
        hour =
            toHour zone timestamp

        minute =
            toMinute zone timestamp
    in
    (padLeft 2 '0' <| fromInt hour) ++ ":" ++ (padLeft 2 '0' <| fromInt minute)

formatTimeOfIsoTimestamp : Zone -> String -> String
formatTimeOfIsoTimestamp zone isoTimestamp =
    toTime isoTimestamp |> Result.map (formatTime zone) |> Result.withDefault isoTimestamp

The problem with this approach is, that actually here does not tell the user's actual time zone, that would take into account the shifts between standard time and daylight saving time, but just something generic for the current offset to UTC. So the time zone we get will never be Europe/Berlin but Etc/GMT+1 or Etc/GMT+2 depending on whether we currently have standard time or daylight saving time.

The problem with that is, that you can display “current” timestamps with that zone, but it fails for example as soon as you try to show timestamps within the winter while it is still summer.

The solution to this is to use the functionality of justinmimbs/timezone-data. Instead of using the here Task as in the code above, this Elm package provides a different task, to get the time zone of the web browser the application is running in: getZone

The function, that generates the Cmd to request the time zone gets this:

getTimezone : Cmd Msg
getTimezone =
    Task.attempt SetTimezone TimeZone.getZone

With this change, also the definition of the SetTimezone message changes a bit as the retrieval of the time zone may fail:

type Msg
    = SetTimezone (Result TimeZone.Error ( String, Time.Zone ))

When we handle the SetTimezone message we can unwrap this result and use a default zone, if the correct zone couldn't be determined:

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case ( msg, model ) of
        ( SetTimezone result ) ->
            let
                zone = case result of
                     Ok ( _, timezone ) ->
                         timezone
                     _ ->
                         TimeZone.europe__berlin()
            in
                -- store the retrieved zone in the model

Unless otherwise credited all material Creative Commons License by Matthias Wimmer