I have a application built on Phoenix 1.3. I use timex
and timex_ecto
in order to deal with datetimes with time zone.
defp deps do
[
{:phoenix, "~> 1.3.3"},
{:phoenix_pubsub, "~> 1.0"},
{:phoenix_ecto, "~> 3.2"},
{:postgrex, ">= 0.0.0"},
{:phoenix_html, "~> 2.10"},
{:phoenix_live_reload, "~> 1.0", only: :dev},
{:gettext, "~> 0.11"},
{:cowboy, "~> 1.0"},
{:timex, "~> 3.1"},
{:timex_ecto, "~> 3.2"}
]
end
The schema module MyApp.Schedule.Item
is defined as follows:
defmodule MyApp.Schedule.Item do
use Ecto.Schema
use Timex.Ecto.Timestamps
schema "items" do
field(:name, :string)
field(:starts_at, Timex.Ecto.DateTime)
end
end
In the priv/repo/seeds.exs
, I inserts a record like this:
import MyApp.Repo
time0 = Timex.now("Asia/Tokyo") |> Timex.beginning_of_day()
insert!(%MyApp.Schedule.Item{
name: "Example",
starts_at: Timex.shift(time0, days: 1, hours: 10)
})
I need some adivice to upgrade my application to Phoenix 1.4 (rc.3) and Ecto 3.0.
Firstly, I removed timex_ecto
from mix.exs
because timex_ecto
does not work with Ecto 3.0.
defp deps do
[
{:phoenix, "~> 1.4.0-rc"},
{:phoenix_pubsub, "~> 1.1"},
{:phoenix_ecto, "~> 4.0"},
{:ecto_sql, "~> 3.0-rc"},
{:postgrex, ">= 0.0.0-rc"},
{:phoenix_html, "~> 2.11"},
{:phoenix_live_reload, "~> 1.2-rc", only: :dev},
{:gettext, "~> 0.11"},
{:jason, "~> 1.0"},
{:plug_cowboy, "~> 2.0"},
{:timex, "~> 3.1"}
]
end
Secondry, I rewrote the MyApp.Schedule.Item
module as follows:
defmodule MyApp.Schedule.Item do
use Ecto.Schema
schema "items" do
field(:name, :string)
field(:starts_at, :utc_datetime)
end
end
Then, the priv/repo/seeds.exs
came to cause this error:
** (ArgumentError) :utc_datetime expects the time zone to be "Etc/UTC", got `#DateTime<2018-11-03 11:00:00+09:00 JST Asia/Tokyo>`
Of course, to fix this error I can rewrite the script like this:
time0 =
Timex.now("Asia/Tokyo")
|> Timex.beginning_of_day()
|> Timex.Timezone.convert("Etc/UTC")
insert!(%MyApp.Schedule.Item{
name: "Example",
starts_at: Timex.shift(time0, days: 1, hours: 10)
})
But, I think such a change is a little burdensome, because my app has quite a lot similar expressions.
So, taking advice in https://github.com/elixir-ecto/ecto/issues/2770#issuecomment-435140215, I created a custom Ecto type for my application:
defmodule MyApp.Ecto.DatetimeWithTimezone do
@behaviour Ecto.Type
def type, do: :datetime
def cast(%DateTime{} = dt), do: {:ok, dt}
def cast(_), do: :error
def load(%NaiveDateTime{} = ndt), do: DateTime.from_naive(ndt, "Etc/UTC")
def load(_), do: :error
def dump(%DateTime{} = datetime) do
case Timex.Timezone.convert(datetime, "Etc/UTC") do
%DateTime{} = dt -> {:ok, dt}
{:error, _} -> :error
end
end
def dump(_), do: :error
end
Then, I made following modifications on my Model.User
module:
defmodule MyApp.Schedule.Item do
use Ecto.Schema
# use Timex.Ecto.Timestamps # <- Removed
alias MyApp.Ecto.DatetimeWithTimezone # <- Added
schema "users" do
field(:name, :string, null: false)
# field(:starts_at, Timex.Ecto.Datetime) # <- Removed
field(:starts_at, DatetimeWithTimezone) # <- Added
...
With these changes, my app works just like before.
Am I upgrading my app in a correct way? Or should I basically change its design?