'How to make Ecto interpret/write local time instead of UTC for `timestamps` macro

This is about integrating a database from a legacy app. The legacy app is reading and writing timestamps (i.e. created_at, updated_at in local time of the host).

And while I'm fully aware that this should be highly avoided this behaviour cannot be changed.

Therefore the Elixir application that uses the same database must be configured to use local time as well.

Ecto provides the timestamps macro but it seems that I cannot make it work with local timestamps. While it's possible to provide a type (i.e. :naive_datetime or :utc_datetime) both types seem to use the utc_now() function.

Also reading the fields might cause difficulties, since the date fields are defined as timestamp without time zone in the database. Hence the database fields also should be interpreted as local time and not as UTC.



Solution 1:[1]

Per @AlekseiMatiushkin, you should be able to use the :autogenerate for this, something like:

defmodule MyThing do

  use Ecto.Schema

  schema "things" do
    field(:something, :string, null: false)
    # ...
    timestamps(autogenerate: {MyThing, :local_time, []})
  end

  def local_time do
    DateTime.now!("Europe/Vienna") |> DateTime.to_naive()
  end

  # ...
end

If relying on the macro is too constrained for your use-case, you can manually write values to the fields (some languages call these mutations, virtual fields, or calculated fields). Something like:


defmodule MyThing do

  use Ecto.Schema

  import Ecto.Changeset

  schema "things" do
    field(:something, :string, null: false)
    field(:my_timestamp_col, :map) # <-- or integer, or ???
    # ...
  end

  def changeset(thing, attrs) do
    thing
    |> cast(
         attrs,
         [
           :something,
           # ...
         ]
       )
    # validate, constraints, etc.
    |> calc_local_time()
  end



  defp calc_local_time(changeset) do
    case changeset do
      %Ecto.Changeset{
        valid?: true,
        changes: thing
      } ->
        put_change(thing, :my_timestamp_col, local_time())
      _ ->
        changeset
    end
  end

  def local_time do
    # add custom date/time calcs here
    DateTime.now!("Europe/Vienna") |> DateTime.to_naive()
  end
end

Hopefully you can find a solution leveraging the above patterns.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Everett