Skip to content

Commit 6e34e6f

Browse files
authored
Handle negative years only for datetimes without seconds (#4803)
1 parent 8a16a45 commit 6e34e6f

2 files changed

Lines changed: 36 additions & 25 deletions

File tree

‎lib/ecto/type.ex‎

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,27 +1168,10 @@ defmodule Ecto.Type do
11681168

11691169
## Naive datetime
11701170

1171-
defp cast_naive_datetime("-" <> rest) do
1172-
with {:ok, naive_datetime} <- cast_naive_datetime(rest) do
1173-
{:ok, %{naive_datetime | year: naive_datetime.year * -1}}
1174-
end
1175-
end
1176-
1177-
defp cast_naive_datetime(
1178-
<<year::4-bytes, ?-, month::2-bytes, ?-, day::2-bytes, sep, hour::2-bytes, ?:,
1179-
minute::2-bytes>>
1180-
)
1181-
when sep in [?\s, ?T] do
1182-
with {:ok, date} <- cast_date(to_i(year), to_i(month), to_i(day)),
1183-
{:ok, time} <- cast_time(to_i(hour), to_i(minute), 0, nil) do
1184-
NaiveDateTime.new(date, time)
1185-
end
1186-
end
1187-
11881171
defp cast_naive_datetime(binary) when is_binary(binary) do
11891172
case NaiveDateTime.from_iso8601(binary) do
11901173
{:ok, _} = ok -> ok
1191-
{:error, _} -> :error
1174+
{:error, _} -> cast_naive_datetime_without_seconds(binary)
11921175
end
11931176
end
11941177

@@ -1219,25 +1202,35 @@ defmodule Ecto.Type do
12191202
:error
12201203
end
12211204

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

1224-
defp cast_utc_datetime("-" <> rest) do
1225-
with {:ok, utc_datetime} <- cast_utc_datetime(rest) do
1226-
{:ok, %{utc_datetime | year: utc_datetime.year * -1}}
1212+
_ ->
1213+
:error
12271214
end
12281215
end
12291216

1230-
defp cast_utc_datetime(
1217+
defp cast_naive_datetime_without_seconds(
12311218
<<year::4-bytes, ?-, month::2-bytes, ?-, day::2-bytes, sep, hour::2-bytes, ?:,
12321219
minute::2-bytes>>
12331220
)
12341221
when sep in [?\s, ?T] do
12351222
with {:ok, date} <- cast_date(to_i(year), to_i(month), to_i(day)),
12361223
{:ok, time} <- cast_time(to_i(hour), to_i(minute), 0, nil) do
1237-
DateTime.new(date, time, "Etc/UTC")
1224+
NaiveDateTime.new(date, time)
12381225
end
12391226
end
12401227

1228+
defp cast_naive_datetime_without_seconds(_) do
1229+
:error
1230+
end
1231+
1232+
## UTC datetime
1233+
12411234
defp cast_utc_datetime(binary) when is_binary(binary) do
12421235
case DateTime.from_iso8601(binary) do
12431236
{:ok, datetime, _offset} ->
@@ -1250,7 +1243,10 @@ defmodule Ecto.Type do
12501243
end
12511244

12521245
{:error, _} ->
1253-
:error
1246+
case cast_naive_datetime_without_seconds(binary) do
1247+
{:ok, naive_datetime} -> {:ok, DateTime.from_naive!(naive_datetime, "Etc/UTC")}
1248+
:error -> :error
1249+
end
12541250
end
12551251
end
12561252

‎test/ecto/type_test.exs‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,8 @@ defmodule Ecto.TypeTest do
812812
assert Ecto.Type.cast(:naive_datetime, "-2015-01-23 23:50") == {:ok, datetime_zero}
813813
assert Ecto.Type.cast(:naive_datetime, "-2015-01-23 23:50:07") == {:ok, datetime}
814814
assert Ecto.Type.cast(:naive_datetime, "-2015-01-23 23:50:07bad") == :error
815+
assert Ecto.Type.cast(:naive_datetime, "--2015-01-23 23:50") == :error
816+
assert Ecto.Type.cast(:naive_datetime, "--2015-01-23 23:50:07") == :error
815817
end
816818

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

11341136
assert Ecto.Type.cast(:utc_datetime, "-2015-01-23 23:50:07bad") == :error
1137+
assert Ecto.Type.cast(:utc_datetime, "--2015-01-23 23:50") == :error
1138+
assert Ecto.Type.cast(:utc_datetime, "--2015-01-23 23:50:07") == :error
1139+
end
1140+
1141+
test "cast negative datetime with offset" do
1142+
assert Ecto.Type.cast(:utc_datetime, "-2015-01-23T23:50:07+01:00") ==
1143+
{:ok, ~U[-2015-01-23 22:50:07Z]}
1144+
1145+
assert Ecto.Type.cast(:utc_datetime, "-2015-12-31T23:50:07-01:00") ==
1146+
{:ok, ~U[-2014-01-01 00:50:07Z]}
1147+
1148+
assert Ecto.Type.cast(:utc_datetime, "-0001-01-01T00:50:07+01:00") ==
1149+
{:ok, ~U[-0002-12-31 23:50:07Z]}
11351150
end
11361151

11371152
test "dump" do

0 commit comments

Comments
 (0)