An approach to local date time formatting with Phoenix LiveView
Handling timezone(s) when building applications is almost always a frustration. Often developers resort (sensibly) to storing all Date / Time information in UTC.
Storing everything in UTC solves a whole bunch of problems, but creates a few more when it comes to presenting the information to users.
Prior Art
I've read a number of articles on this specific topic, each proposing different ways to solve the problem.
There's an approach from Berenice Medel which uses client side JS to facilitate this.
Alex-Min, uses an approach that is much more server side focused, whilst Masatoshi Nishiguchi uses an approach that combines client and server side handling.
All of these approaches have pros and cons.
For my purposes, Berenice's approach fits more with what I want to achieve. The main issue I have with this approach, is the use of CSS to hide the text in the DOM and programmatically show it when the client side JavaScript executes.
A modified approach
One of the advantages of using the HTML <time>
element is the ability to use the datetime
attribute to present machine readable content. By setting a value here, we can use the JS to put the user readable content in place without needing to hide content to prevent a 'jump' when we convert the date to the user's local time and format.
I prefer to avoid needing to pass an id
to every instance of these types of components, so I make it optional and use a UUID when no default is provided,though you could also use something like System.unique_integer([:positive])
too.
def local_datetime(assigns) do
assigns = assigns |> assign_new(:id, &Ecto.UUID.generate/0)
~H"""
<time datetime={@datetime} id={@id} phx-hook="LocalTime"></time>
"""
end
The main advantage of this approach, in my opinion, is that we've provided the browser with all the necessary information for the next step in a non-destructive way (e.g. we will not replace the datetime
attribute) and we won't present the user with a flickering by changing the information in the next step.
The JS Hook ends up being very similar to Berenice's approach. Main difference here is I'm using TypeScript (because I don't trust myself to get things right).
So I first tell the compiler that el
is an HTMLTimeElement
. This is very handy because if we looked at the HTML markup it would be fair to expect that the attribute we want to target is datetime
, but alas, it's actually dateTime
!
We then use the parsed date with a template string to create the textContent
the user will actually see.
Hooks.LocalTime = {
mounted() {
this.updated();
},
updated() {
const el = this.el as HTMLTimeElement;
const date: new Date(el.dateTime);
this.el.textContent = `${date.toLocaleString()} ${Intl.DateTimeFormat().resolvedOptions().timeZone}`;
},
};
With this in place we can now present the information in the users local date time format and don't need to hide things with CSS.