kuroda

kuroda

How to upgrade an app using timex_ecto to Phoenix 1.4 and Ecto 3.0

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 Compile error with ecto 3.0 and timex_ecto 3.3 · Issue #2770 · elixir-ecto/ecto · GitHub, 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?

Most Liked

michalmuskala

michalmuskala

Just to clarify - Ecto uses the native elixir datetime types since Ecto 2.1 released almost 2 years ago. If the timex_ecto package is not needed now, it wasn’t needed back then as well. It’s just that now the old custom ecto datetime types were removed after being deprecated for almost 2 years.

The things in this area that Ecto 3 changes are that the low-level “raw” query interface also returns the elixir types instead of the erlang-style tuples and that we have the explicit separation of _usec and non-usec types.

bitwalker

bitwalker

Leader

@kurtome The approach above is fine, but probably shouldn’t be called DateTimeWithTimezone, since it doesn’t actually store the timezone, it just converts to and from UTC, but naming aside, yeah that’s close enough. You can always refer to the current Timex.Ecto implementations for reference too.

I’d be glad to merge a PR with a link! I just don’t have time to throw something together and test that it is correct at the moment :frowning: - normally I have a bit more free time, but I’m more swamped than I have been in a long time, so my backlog has gotten a bit out of hand haha

I don’t really get what makes Ecto 3.0 that different from previous versions as it relates to these datetime types

Ecto 3.0 makes use of the new calendar types in Elixir, rather than its own Ecto.* calendar types. This is important because it means the last remaining piece needed to have Ecto handle everything natively (well, with a timezone library) is timezone support (which is under construction as far as I’m aware). But if you don’t need to support arbitrary timezones in the database, Ecto 3 already supports NaiveDateTimes and DateTimes in UTC (as well as the other types of course). The bottom line is that Timex becomes a support library for the calendar types, but is no longer an active part of the Elixir/Ecto date/time interaction, since it is all natively supported :). This is ultimately one of the main goals of adding the Elixir calendar types, is to unify these things so that libraries are all mutually compatible with one another, without needing explicit adapters between them (which is effectively what Timex.Ecto was).

dhanson

dhanson

One note on this subject - I migrated from using Timex to using naive datetimes when I upgraded after reading this thread. My services started erroring with data from my frontend that had been working prior to the upgrade. I use postgres for the database. timestamps were fine. the problem was in fields like ‘appointment datetime’ - my frontend JS datetime pickers were not sending seconds ie “2018-12-12T16:00” was sent - and the cast in my models was returning invalid date format errors. iso 8601 does not mandate seconds from what I read - but NaiveDateTime.from_iso8601 throws the exception, and the Timex usage/implementation did not error. So I rolled my ownNaiveDateTimeNoSeconds Ecto type to deal with this.

Where Next?

Popular in Questions Top

lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
vac
Hi, I’m quite new in Elixir and I’m trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and I...
New
Lily
In templates/appointment/index.html.eex: &lt;%= for appointment &lt;- @appointments do %&gt; &lt;tr&gt; &lt;td&gt;&lt;%= appoi...
New
JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call t...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New

Other popular topics Top

Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 39523 209
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New

We're in Beta

About us Mission Statement