diff --git a/lib/ecto/adapters/sqlite3.ex b/lib/ecto/adapters/sqlite3.ex index 8ca889b..214eed0 100644 --- a/lib/ecto/adapters/sqlite3.ex +++ b/lib/ecto/adapters/sqlite3.ex @@ -241,12 +241,12 @@ defmodule Ecto.Adapters.SQLite3 do @impl Ecto.Adapter def loaders(:utc_datetime_usec, type) do - [&Codec.datetime_decode/1, type] + [&Codec.utc_datetime_decode/1, type] end @impl Ecto.Adapter def loaders(:utc_datetime, type) do - [&Codec.datetime_decode/1, type] + [&Codec.utc_datetime_decode/1, type] end @impl Ecto.Adapter @@ -254,11 +254,6 @@ defmodule Ecto.Adapters.SQLite3 do [&Codec.naive_datetime_decode/1, type] end - @impl Ecto.Adapter - def loaders(:datetime, type) do - [&Codec.datetime_decode/1, type] - end - @impl Ecto.Adapter def loaders(:date, type) do [&Codec.date_decode/1, type] @@ -326,11 +321,26 @@ defmodule Ecto.Adapters.SQLite3 do [type, &Codec.time_encode/1] end + @impl Ecto.Adapter + def dumpers(:utc_datetime, type) do + [type, &Codec.utc_datetime_encode/1] + end + + @impl Ecto.Adapter + def dumpers(:utc_datetime_usec, type) do + [type, &Codec.utc_datetime_encode/1] + end + @impl Ecto.Adapter def dumpers(:naive_datetime, type) do [type, &Codec.naive_datetime_encode/1] end + @impl Ecto.Adapter + def dumpers(:naive_datetime_usec, type) do + [type, &Codec.naive_datetime_encode/1] + end + @impl Ecto.Adapter def dumpers({:array, _}, type) do [type, &Codec.json_encode/1] diff --git a/lib/ecto/adapters/sqlite3/codec.ex b/lib/ecto/adapters/sqlite3/codec.ex index 3a59181..1de8703 100644 --- a/lib/ecto/adapters/sqlite3/codec.ex +++ b/lib/ecto/adapters/sqlite3/codec.ex @@ -43,17 +43,13 @@ defmodule Ecto.Adapters.SQLite3.Codec do def decimal_decode(_), do: :error - def datetime_decode(nil), do: {:ok, nil} - - def datetime_decode(val) do - # TODO: Should we be preserving the timezone? SQLite3 stores everything - # shifted to UTC. sqlite_ecto2 used a custom field type "TEXT_DATETIME" - # to preserve the original string inserted. But I don't know if that - # is desirable or not. - # - # @warmwaffles 2021-02-28 - case DateTime.from_iso8601(val) do - {:ok, dt, _offset} -> {:ok, dt} + def utc_datetime_decode(nil), do: {:ok, nil} + + def utc_datetime_decode(val) do + with {:ok, naive} <- NaiveDateTime.from_iso8601(val), + {:ok, dt} <- DateTime.from_naive(naive, "Etc/UTC") do + {:ok, dt} + else _ -> :error end end @@ -95,6 +91,11 @@ defmodule Ecto.Adapters.SQLite3.Codec do {:ok, value} end + # Ecto does check this already, so there should be no need to handle errors + def utc_datetime_encode(%{time_zone: "Etc/UTC"} = value) do + {:ok, NaiveDateTime.to_iso8601(value)} + end + def naive_datetime_encode(value) do {:ok, NaiveDateTime.to_iso8601(value)} end diff --git a/test/ecto/adapters/sqlite3/codec_test.exs b/test/ecto/adapters/sqlite3/codec_test.exs index 6bf19fa..48f46d6 100644 --- a/test/ecto/adapters/sqlite3/codec_test.exs +++ b/test/ecto/adapters/sqlite3/codec_test.exs @@ -80,4 +80,24 @@ defmodule Ecto.Adapters.SQLite3.CodecTest do {:ok, ^decimal} = Codec.decimal_decode(1.2) end end + + describe ".utc_datetime_decode/1" do + test "nil" do + assert {:ok, nil} = Codec.utc_datetime_decode(nil) + end + + test "string" do + {:ok, dt} = DateTime.from_naive(~N[2021-08-25 10:58:59Z], "Etc/UTC") + assert {:ok, ^dt} = Codec.utc_datetime_decode("2021-08-25 10:58:59") + + {:ok, dt} = DateTime.from_naive(~N[2021-08-25 10:58:59.111111], "Etc/UTC") + assert {:ok, ^dt} = Codec.utc_datetime_decode("2021-08-25 10:58:59.111111") + end + + test "ignores timezone offset if present" do + {:ok, dt} = DateTime.from_naive(~N[2021-08-25 10:58:59.111111], "Etc/UTC") + assert {:ok, ^dt} = Codec.utc_datetime_decode("2021-08-25 10:58:59.111111Z") + assert {:ok, ^dt} = Codec.utc_datetime_decode("2021-08-25 10:58:59.111111+02:30") + end + end end