How to Format Dates in a Phoenix Application

Formatting dates in Phoenix isn’t as straightforward as it is in other frameworks. There isn’t a built-in method to do strftime, but by using the timex library, we can add it in easily. Let’s look at how.

Creating a Sample Application

First, let’s create a new Phoenix application called cookbook which we’ll use to tinker around with dates.

$ mix phoenix.new cookbook

Then change to the cookbook directory, create the development database, and generate a new table of recipes:

$ cd cookbook
$ mix ecto.create
$ mix phoenix.gen.html Recipe recipes title:string ngredients:text instructions:text

Modify the web/router.ex file to include the new route for recipes:

  scope "/", Cookbook do
    pipe_through :browser # Use the default browser stack

    ## add this line
    resources "/recipes", RecipeController
    
    get "/", PageController, :index
  end

Save the file. Then migrate the database:

$ mix ecto.migrate

The new table will be created:

The basic app is now configured. Now add a couple of new dependencies. Open mix.exs and add timex and timex_ecto to both the deps section and your applications section. First, update deps so it looks like this:

  defp deps do
    [{:phoenix, "~> 1.2.1"},
     {:phoenix_pubsub, "~> 1.0"},
     {:phoenix_ecto, "~> 3.0"},
     {:postgrex, ">= 0.0.0"},
     {:phoenix_html, "~> 2.6"},
     {:phoenix_live_reload, "~> 1.0", only: :dev},
     {:gettext, "~> 0.11"},
     
     {:timex, "~> 3.0"},
     {:timex_ecto, "~> 3.0"},
     
     {:cowboy, "~> 1.0"}]
  end

Then update the applications:

  def application do
    [mod: {Cookbook, []},
     applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy, :logger, :gettext,
                    :phoenix_ecto, :postgrex, :timex, :timex_ecto]]
  end

Save the file, fetch dependencies, and recompile.

$ mix do deps.get, compile

The timex_ecto module automatically hooks into the timestamps feature of our schema, so the automatically-created inserted_at and updated_at fields can be converted into nicely formatted timestamps with ease.

Let’s display the creation date of a record. Open web/templates/recipes/show.html.eex and add this code:

  <li>
    <strong>Created at: </strong>
    <%= Timex.format!(@recipe.inserted_at, "%m-%d-%Y", :strftime) %>
  </li>

We call Timex.format! which takes in a Timex.Datetime object, a formatting pattern, and the type of pattern. We’ll use strftime here since it’s well-known.

That’s all you have to do to get the default timestamps to format.

But you probably want to add your own additional time fields, so open web/web.ex, locate the model section and modify it to include Timex.Ecto.Timestamps:

def model do
  quote do
    use Ecto.Schema
    use Timex.Ecto.Timestamps
  end
end

Let’s add a published_at field to the recipes table and schema to demonstrate this. First create a migration to define this new field:

mix ecto.gen.migration add_published_at_to_recipes

Open the new file and alter it so it looks like the following:

defmodule Cookbook.Repo.Migrations.AddPublishedAtToRecipes do
  use Ecto.Migration

  def change do
    alter table(:recipes) do
      add :published_at, :datetime
    end
  end
end

Then run migrations to add the new field to the table:

$ mix ecto.migrate

This adds the new field to the database.

Then modify the web/models/recipe.ex file to include the new field in the schema, but specify Timex.Ecto.DateTime instead of :datetime:

  schema "recipes" do
    field :title, :string
    field :ingredients, :string
    field :instructions, :string
    field :published_at, Timex.Ecto.DateTime

    timestamps()
  end

Let’s add a controller action to publish recipes. We’ll fetch the ID from the URL, use it to grab the record, create a new changeset that only alters the published_at field, and then save the record to the database:

  def publish(conn, %{"id" => id}) do
    recipe = Repo.get!(Recipe, id)
    changeset = Recipe.changeset(recipe)
    |> Ecto.Changeset.change(published_at: Timex.now)
    IO.inspect changeset

    case Repo.update(changeset) do
      {:ok, recipe} ->
        conn
        |> put_flash(:info, "Recipe published successfully.")
        |> redirect(to: recipe_path(conn, :show, recipe))
      {:error, _} ->
        conn
        |> put_flash(:info, "Recipe could not be published")
        |> redirect(to: recipe_path(conn, :show, recipe))
    end
  end

If it errors, you’ll probably want to do more than just say there was a problem. But we’re just testing here.

Now add a route to make this action fire when a put request is sent to the app:

    put "/recipes/:id/publish", RecipeController, :publish
I

On the “show” page, add a link to publish a recipe and the block of code to show the publication date:

  <%= if @recipe.published_at  do %>
    <li>
      <strong>Published at: </strong>
      <%= Timex.format!(@recipe.published_at, "%m-%d-%Y", :strftime) %>
    </li>
  <% end %>

  <%= if !@recipe.published_at do %>
    <%= link "Publish", to: recipe_path(@conn, :publish, @recipe), method: :put,  class: "btn btn-danger btn-xs" %>
  <% end %>

We only show the publish button if the recipe isn’t published.

Try it out. Create a new recipe, visit its show page, and press the publish button. Your time should be nicely formatted.