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.