<!-- LLM_VERSION_INFO
FORMAT: text/markdown
CONTENT_TYPE: article
ORIGINAL_URL: https://fly.io/phoenix-files/dates-formatting-with-hooks
ALTERNATE_VERSION: phoenix-files/dates-formatting-with-hooks/index.html (text/html)
EXTRACTION_DATE: 2026-04-18T22:12:21.399Z

This is the markdown version with text-only content (images converted to alt-text).
For rich formatting with images, request the HTML version at: phoenix-files/dates-formatting-with-hooks/index.html
-->

# Problem

When we’re developing an application for users around the world, we are bound to hit problems with timezones.

Often we decide to store the dates in UTC format to avoid storing the timezone of each user. This brings us to the real problem, “How can we display the UTC time in the user’s local timezone?”

## Solution

We need to convert the date to the user’s local timezone and display it. We can use the user’s locale information in the browser, add a few lines of Javascript in a [Client Hook](https://hexdocs.pm/phoenix_live_view/js-interop.html#client-hooks-via-phx-hook) and update the DOM to display the right time!

### Converting a UTC date to a local date in Javascript

First we’ll create a `Date` object, passing our UTC date as a parameter to the constructor. This lets us use the `Date` class methods to manipulate dates.

```javascript
let dt = new Date("2016-05-24T13:26:08.003Z");
```

Through a `Date` class object we can use the `toLocaleString` method. Based on the user’s default locale and timezone, this method returns the local date as a string.

```javascript
dt.toLocaleString()
//   5/24/2016, 8:26:08 AM
```

We can also extract and display the timezone that is used internally for the conversion as follows:

```javascript
Intl.DateTimeFormat().resolvedOptions().timeZone;
//   America/Mexico_City
```

Concatenating the results of the previous functions, we put our string into a more friendly format:

```javascript
let dt = new Date("2016-05-24T13:26:08.003Z");
let dateString = dt.toLocaleString() +
                 " " +
                 Intl.DateTimeFormat().resolvedOptions().timeZone;
//    5/24/2016, 8:26:08 AM America/Mexico_City
```

### Configuring and defining a Hook

We’ve figured out how to get a new representation of a date from the user locale with JavaScript. Now we want to show the date in its new format from our LiveView application.

We’ve stored the [date](https://hexdocs.pm/elixir/1.13/DateTime.html) we want to show in the `@utc` assign, and we render its content inside a `time` tag:

```elixir
<time><%= @utc %></time>
```

To change this to the new format, we’ll use a client hook to execute a variant of the lines of Javascript we used above.

First we need to define our `Hooks` object inside `assets/js/app.js` and add the hook, which we name `LocalTime`. This hook is responsible for taking the content of the `time` tag, reformatting it and updating its content.

```javascript
let Hooks = {}

Hooks.LocalTime = {
  mounted() {
    let dt = new Date(this.el.textContent);
    this.el.textContent =
      dt.toLocaleString() +
      " " +
      Intl.DateTimeFormat().resolvedOptions().timeZone;
  }
}
```

A hook can be executed at different life stages of an HTML element. Above, we defined the `mounted` callback, so the transformation will happen when the `time` tag is added to the DOM and the component has finished mounting.

All callbacks in a hook object have in-scope access to the `el` attribute, which is a reference to the DOM element on which the hook is running: here, the `time` tag. We get the UTC date from its `textContent` attribute, and store it in a Date object called `dt`:

```javascript
let dt = new Date(this.el.textContent);
```

Then we replace the content of the HTML element with the new string we’ve created.

```javascript
this.el.textContent = dt.toLocaleString() +
                      " " +
                      Intl.DateTimeFormat().resolvedOptions().timeZone;
```

The conversion also needs to be redone whenever the server updates the element, so we add the `updated` callback, with a small refactor to avoid duplicating code:

```javascript
Hooks.LocalTime = {
  mounted(){
    this.updated()
  },
  updated() {
    let dt = new Date(this.el.textContent);
    this.el.textContent =
      dt.toLocaleString() +
      " " +
      Intl.DateTimeFormat().resolvedOptions().timeZone;
  }
}
```

Now that our `LocalTime` hook is defined, we pass the `Hooks` object to the socket:

```javascript
let liveSocket = new LiveSocket("/live", Socket, {hooks: Hooks})
```

### Executing a Hook from an HTML element

The only missing piece is to tie our `LocalTime` hook to the `time` tag we defined. For this, we’ll use the `phx-hook` option, passing the name of our `LocalTime` hook.

```elixir
<time phx-hook="LocalTime" id="my-local-time">
  <%= @utc %>
</time>
```

Note that, when using `phx-hook`, we must always define a unique DOM ID for the HTML element.

Let’s see our work in action (slowed down for visibility):

When the `time` tag is first rendered, the date is in UTC format, but then our hook is executed and the content of the tag is replaced.

We can make a little improvement and hide the initial content of the time tag using CSS. Here we’re using the TailwindCSS `invisible` class.

```elixir
<time phx-hook="LocalTime" id="my-local-time" class="invisible">
  <%= @utc %>
</time>
```

This way the time tag still exists in the DOM, but its content will be hidden.

Once we’ve modified the date format, we remove the class with JavaScript to display the content of the time tag:

```javascript
updated() {
    let dt = new Date(this.el.textContent);
    this.el.textContent =
      dt.toLocaleString() +
      " " +
      Intl.DateTimeFormat().resolvedOptions().timeZone;
    this.el.classList.remove("invisible")
  }
```

Let’s see what has changed:

The content of the time tag is not displayed until the date is reformatted and replaced.

### Creating a reusable component

If this is not the only date we’ll show in our application, we can go one step further and define a function component that takes a unique DOM ID and a date as part of its assigns:

```elixir
def local_time(%{date: date, id: id} = assigns) do
  ~H"""
    <time phx-hook="LocalTime" id={@id} class="invisible">
      <%= @date %>
    </time>
  """
end
```

We use our component as follows:

```elixir
<.local_time id="my-date" date="2016-05-24T13:26:08.003Z"/>
```

Or passing the content of an assign:

```elixir
<.local_time id="my-date" date={@date}/>
```

## Discussion

We could get the user’s locale and timezone from the browser and use server-side libraries like [Timex](https://hexdocs.pm/timex/Timex.html) to manipulate datetimes, which would involve writing lines of code on both, the client and server side, and storing those values in some part of our application.

With the Phoenix client Hooks, we can format dates using a few lines of Javascript and entirely client-side.
