Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 21 additions & 25 deletions lib/ecto/type.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1168,27 +1168,10 @@ defmodule Ecto.Type do

## Naive datetime

defp cast_naive_datetime("-" <> rest) do
with {:ok, naive_datetime} <- cast_naive_datetime(rest) do
{:ok, %{naive_datetime | year: naive_datetime.year * -1}}
end
end

defp cast_naive_datetime(
<<year::4-bytes, ?-, month::2-bytes, ?-, day::2-bytes, sep, hour::2-bytes, ?:,
minute::2-bytes>>
)
when sep in [?\s, ?T] do
with {:ok, date} <- cast_date(to_i(year), to_i(month), to_i(day)),
{:ok, time} <- cast_time(to_i(hour), to_i(minute), 0, nil) do
NaiveDateTime.new(date, time)
end
end

defp cast_naive_datetime(binary) when is_binary(binary) do
case NaiveDateTime.from_iso8601(binary) do
{:ok, _} = ok -> ok
{:error, _} -> :error
{:error, _} -> cast_naive_datetime_without_seconds(binary)
end
end

Expand Down Expand Up @@ -1219,25 +1202,35 @@ defmodule Ecto.Type do
:error
end

## UTC datetime
# The ISO 8601 parsers already handle negative years, so the sign
# only needs special handling for datetimes without seconds.
defp cast_naive_datetime_without_seconds("-" <> rest) do
case cast_naive_datetime_without_seconds(rest) do
{:ok, %{year: year} = naive_datetime} when year >= 0 ->
{:ok, %{naive_datetime | year: -year}}

defp cast_utc_datetime("-" <> rest) do
with {:ok, utc_datetime} <- cast_utc_datetime(rest) do
{:ok, %{utc_datetime | year: utc_datetime.year * -1}}
_ ->
:error
end
end

defp cast_utc_datetime(
defp cast_naive_datetime_without_seconds(
<<year::4-bytes, ?-, month::2-bytes, ?-, day::2-bytes, sep, hour::2-bytes, ?:,
minute::2-bytes>>
)
when sep in [?\s, ?T] do
with {:ok, date} <- cast_date(to_i(year), to_i(month), to_i(day)),
{:ok, time} <- cast_time(to_i(hour), to_i(minute), 0, nil) do
DateTime.new(date, time, "Etc/UTC")
NaiveDateTime.new(date, time)
end
end

defp cast_naive_datetime_without_seconds(_) do
:error
end

## UTC datetime

defp cast_utc_datetime(binary) when is_binary(binary) do
case DateTime.from_iso8601(binary) do
{:ok, datetime, _offset} ->
Expand All @@ -1250,7 +1243,10 @@ defmodule Ecto.Type do
end

{:error, _} ->
:error
case cast_naive_datetime_without_seconds(binary) do
{:ok, naive_datetime} -> {:ok, DateTime.from_naive!(naive_datetime, "Etc/UTC")}
:error -> :error
end
end
end

Expand Down
15 changes: 15 additions & 0 deletions test/ecto/type_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,8 @@ defmodule Ecto.TypeTest do
assert Ecto.Type.cast(:naive_datetime, "-2015-01-23 23:50") == {:ok, datetime_zero}
assert Ecto.Type.cast(:naive_datetime, "-2015-01-23 23:50:07") == {:ok, datetime}
assert Ecto.Type.cast(:naive_datetime, "-2015-01-23 23:50:07bad") == :error
assert Ecto.Type.cast(:naive_datetime, "--2015-01-23 23:50") == :error
assert Ecto.Type.cast(:naive_datetime, "--2015-01-23 23:50:07") == :error
end

test "dump" do
Expand Down Expand Up @@ -1132,6 +1134,19 @@ defmodule Ecto.TypeTest do
{:ok, ~U[-2015-01-23 23:50:07Z]}

assert Ecto.Type.cast(:utc_datetime, "-2015-01-23 23:50:07bad") == :error
assert Ecto.Type.cast(:utc_datetime, "--2015-01-23 23:50") == :error
assert Ecto.Type.cast(:utc_datetime, "--2015-01-23 23:50:07") == :error
end

test "cast negative datetime with offset" do
assert Ecto.Type.cast(:utc_datetime, "-2015-01-23T23:50:07+01:00") ==
{:ok, ~U[-2015-01-23 22:50:07Z]}

assert Ecto.Type.cast(:utc_datetime, "-2015-12-31T23:50:07-01:00") ==
{:ok, ~U[-2014-01-01 00:50:07Z]}

assert Ecto.Type.cast(:utc_datetime, "-0001-01-01T00:50:07+01:00") ==
{:ok, ~U[-0002-12-31 23:50:07Z]}
end

test "dump" do
Expand Down
Loading