From 9a71cc7b291dbf6737ee3c062233aa52a01e3cd7 Mon Sep 17 00:00:00 2001 From: Benjamin Milde Date: Wed, 25 Aug 2021 11:38:15 +0200 Subject: [PATCH 1/2] Store utc_datetime as offsetless iso8601 and ignore offset when fetching from the db --- lib/ecto/adapters/sqlite3.ex | 24 ++++++++++++++++------- lib/ecto/adapters/sqlite3/codec.ex | 23 +++++++++++----------- test/ecto/adapters/sqlite3/codec_test.exs | 24 +++++++++++++++++++++++ 3 files changed, 53 insertions(+), 18 deletions(-) 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..5894b98 100644 --- a/test/ecto/adapters/sqlite3/codec_test.exs +++ b/test/ecto/adapters/sqlite3/codec_test.exs @@ -80,4 +80,28 @@ defmodule Ecto.Adapters.SQLite3.CodecTest do {:ok, ^decimal} = Codec.decimal_decode(1.2) end end + + describe ".utc_datetime_decode/1" do + test "nil" do + {:ok, nil} = Codec.utc_datetime_decode(nil) + end + + test "string" do + {:ok, ~U[2021-08-25 10:58:59Z]} = Codec.utc_datetime_decode("2021-08-25 10:58:59") + + {:ok, ~U[2021-08-25 10:58:59.111111Z]} = + Codec.utc_datetime_decode("2021-08-25 10:58:59.111111") + + {:ok, ~U[2021-08-25 10:58:59.111111Z]} = + Codec.utc_datetime_decode("2021-08-25 10:58:59.111111Z") + end + + test "ignores timezone offset if present" do + {:ok, ~U[2021-08-25 10:58:59.111111Z]} = + Codec.utc_datetime_decode("2021-08-25 10:58:59.111111Z") + + {:ok, ~U[2021-08-25 10:58:59.111111Z]} = + Codec.utc_datetime_decode("2021-08-25 10:58:59.111111+02:30") + end + end end From 5dd8eac9b8a7eae55c774a90019015d3b316b955 Mon Sep 17 00:00:00 2001 From: Benjamin Milde Date: Wed, 25 Aug 2021 14:49:23 +0200 Subject: [PATCH 2/2] Update tests to not use sigil_U --- test/ecto/adapters/sqlite3/codec_test.exs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/test/ecto/adapters/sqlite3/codec_test.exs b/test/ecto/adapters/sqlite3/codec_test.exs index 5894b98..48f46d6 100644 --- a/test/ecto/adapters/sqlite3/codec_test.exs +++ b/test/ecto/adapters/sqlite3/codec_test.exs @@ -83,25 +83,21 @@ defmodule Ecto.Adapters.SQLite3.CodecTest do describe ".utc_datetime_decode/1" do test "nil" do - {:ok, nil} = Codec.utc_datetime_decode(nil) + assert {:ok, nil} = Codec.utc_datetime_decode(nil) end test "string" do - {:ok, ~U[2021-08-25 10:58:59Z]} = Codec.utc_datetime_decode("2021-08-25 10:58:59") + {: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, ~U[2021-08-25 10:58:59.111111Z]} = - Codec.utc_datetime_decode("2021-08-25 10:58:59.111111") - - {:ok, ~U[2021-08-25 10:58:59.111111Z]} = - Codec.utc_datetime_decode("2021-08-25 10:58:59.111111Z") + {: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, ~U[2021-08-25 10:58:59.111111Z]} = - Codec.utc_datetime_decode("2021-08-25 10:58:59.111111Z") - - {:ok, ~U[2021-08-25 10:58:59.111111Z]} = - Codec.utc_datetime_decode("2021-08-25 10:58:59.111111+02:30") + {: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