How to: Display Client’s Local Time in Your Elixir/Phoenix App (1.6+)

Mark
Geek Culture
Published in
6 min readNov 22, 2021
Undraw’s time management

Background

As we head toward the end of 2021, I have been pushing to get the first few Early Access invite codes sent out to people who’ve signed up for Metamorphic’s Early Access launch— and in doing so I wanted to find a quick and easy way to display the current time for someone no matter where they are in the world.

For those just tuning in, Metamorphic is a transformation for how we connect and share online. I designed it, with my family in mind, to provide a safe, fun, and easy way to connect and share; all while being insulated from the destructive forces of surveillance capitalism that powers and drives the major social network, video chat, and search companies.

Typically, displaying local time in development is incredibly easy as your “server” is in the same location as the “client” (you and your computer). In production however, it tends to get more complicated as the client is no longer located where the server is.

Anyone working with time, or as a developer, knows that time is an incredibly complicated programming concept (problem, philosophy, etc.). We won’t get into those nitty gritty details, thankfully we can rely on wonderful libraries to do the brunt of that work for us.

But, even with those wonderful libraries, displaying the client’s time in their local time zone is still complicated in production.

So, I’m going to show you the quick and easy way that I’m doing it with Metamorphic.

Prerequisites

For this post, we’re going to assume the following:

  • You have a Phoenix app up and running (1.6+)
  • You have Phoenix LiveView 17.5+
  • You have Elixir 1.12+ and OTP 24+
  • You have the Timex library installed (or understand how to work with the equivalent time related functions in Elixir or another library)

Okay, let’s dive in.

Approach

Let’s imagine that we have a dashboard or home page for signed in people, and that on that page we display a live clock to them in their local time zone.

There are so many different ways to do this, but let’s say that we don’t want to bother them with any of the nitty gritty and we don’t want to persist people’s time zones in the database (for privacy’s or simplicity’s sake).

To achieve this, we’re going to utilize Phoenix LiveView’s incredible on_mount/1 and attach_hook/4 functions to grab the client’s local time zone with a simple JavaScript hook and assign it to our socket as the LiveView page is mounted.

Let’s go.

Step 1

First, let’s create our on_mount/4 function that we will call with our on_mount/1 callback (we’re defining how our on_mount/1 callback hook will work):

# your_app_web/hooks/local_time_zone_hook.ex
# You can organize your code however you like.
defmodule YourAppWeb.Hooks.LocalTimeZoneHook do
@moduledoc false
import Phoenix.LiveView

def on_mount(:default, _params, _session, socket) do
{:cont,
socket
|> attach_hook(:local_tz, :handle_event, fn
"local-timezone", %{"local-timezone" => local_timezone},
socket ->
# Handle our "local-timezone" event and detach hook
socket =
socket
|> assign(:local_timezone, local_timezone)
{:halt, detach_hook(socket, :local_tz, :handle_event)}
_event, _params, socket ->
{:cont, socket}
end)
}
end
end

Okay, so essentially what we did is call attach_hook/4 on our on_mount/4 callback that we will be calling later with our on_mount/1 to hook into the mount process of our LiveView.

The hook that we are attaching is called local_tz (or LocalTz in our JavaScript file).

Without knowing much about our hook, we can already tell that it is going to send an event called “local-timezone” and that we are going to pattern match on that event and assign it to our socket as local_timezone.

Let’s write our JavaScript hook now.

Step 2

For simplicity’s sake, we’re going to write our hook in our app.js file but you can organize your code to meet the needs of your application.

In your app.js you should already have something like this in it:

# your_app_web/assets/js/app.js...
let liveSocket = new LiveSocket('/live', Socket, {
hooks: Hooks,
...

This line is wiring up any JavaScript hooks that we write and adding them to our socket.

So, just above that let’s add in our LocalTz hook:

# your_app_web/assets/js/app.js...
Hooks.LocalTz = {
mounted() {
let localTz =
Intl.DateTimeFormat().resolvedOptions().timeZone
this.pushEvent("local-timezone", {local_timezone: localTz})
}
}
let liveSocket = ...

And that’s it for our hook!

As you can see, we are using some awesome JavaScript to (1) get the client’s local time zone, (2) assign it to localTz , and (3) send an event to the server called “local-timezone” with the relevant data that we want.

With that done, we can move on to our final step.

Step 3

In this last step, we are going to handle the event as well as enable our live clock. There is a lot of room for customization and expansion based on your application’s needs (we won’t go too deep into the woods here).

What we do need to do is call our hook with the on_mount/1 function, wire up our clock, and display our clock (we’ll assume you have authentication or whatever else is needed for your application).

Let’s take a look:

your_app_web/dashboard_live/index.ex

So, what’s going on here?

On line 5, we use the on_mount/1 callback to call our hook and pop the client’s local time zone into an assign on our socket. Now, this doesn’t necessarily beat the rest of our mount function, so we start off our @date assign without any reference to the client’s local time zone.

Then, if the socket connects, we start sending a message to ourselves every second and update our clock: Process.send_after(self(), :tick, 1000). And we handle that in our handle_info/2 function on line 41 (inside that function we again send a message to ourselves in order to create the recurring “loop” or “tick” of our clock). Lastly, before our handle_info/2 is finished, we call our put_date/1 function, which simply updates our @date assign.

The special thing to note is that now, inside our put_date/1 function, we can call Timex.now(socket.assigns.local_timezone) and we will be inserting the local time zone from the client that our hook put into the socket for us under the local_timezone assign.

Lastly, since we are using a phx-hook, we need to make sure we assign a unique DOM id in our HTML, id="local-timezone", as well as the phx-hook="LocalTimeZone".

Bonus (Step 6)

Now, there is most likely a slight delay between the initial mount and the first “tick” of our clock (so you’ll see UTC time for a moment before the clock switches over to the client’s local time).

So, let’s modify things a bit to lay the groundwork for improving the UX:

Nice! Let’s look at what we changed:

  • assign(:timezone_loaded, false)— on line 17, we replace our @date assign with a flag to indicate whether or not we’ve loaded the client’s local time zone info.
  • lines 28 — 32 — here we add a simple if else block in our HTML to display information before and after our client’s local time zone has been loaded.
  • maybe_update_assigns_for_clock(socket) — on line 47, we call a new maybe_update_assigns_for_clock/1 function in our handle_info/2 callback to update our socket once the local time zone info has been loaded from our hook.
  • maybe_update_assigns_for_clock/1 — on lines 50–61, we implement a simple if else do end block where we check the socket.assigns map for the :date key and proceed accordingly. Both branches of this function end by calling our existing put_date/1 function to ensure our clock keeps ticking.

With that, we have replaced seeing the UTC time with a loading message prior to our local time zone info being loaded. You could essentially expand on this to improve and further customize the UX based on the needs of your application.

Conclusion

And we’re done! With just a few simple lines of JavaScript and Phoenix LiveView’s new on_mount/1 function, we can easily display time for each client in their local time zone.

Thanks for following along and I hope this helps you if you were scratching your head about how to handle client’s local time, or inspires you to wire up something even more awesome.

If you’re interested in learning more about Metamorphic, then follow along with our company podcast and sign up for invite codes to our Early Access launch — a transformation to our online life is coming soon!

Always open to improvements and thoughts, thank you!

💚 Mark

--

--

Mark
Geek Culture

Creator @ Metamorphic | Co-founder @ Moss Piglet