Skip to content

Commit

Permalink
Allow Date to accept year outside of -9999..9999 range (#13551)
Browse files Browse the repository at this point in the history
  • Loading branch information
cocoa-xu committed May 11, 2024
1 parent 4226a8a commit 3d7f4dd
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 22 deletions.
12 changes: 11 additions & 1 deletion lib/elixir/lib/calendar/date.ex
Expand Up @@ -1157,10 +1157,20 @@ defmodule Date do
end

defimpl Inspect do
def inspect(%{calendar: calendar, year: year, month: month, day: day}, _) do
def inspect(%{calendar: calendar, year: year, month: month, day: day}, _)
when year in -9999..9999 do
"~D[" <> calendar.date_to_string(year, month, day) <> suffix(calendar) <> "]"
end

def inspect(%{calendar: calendar, year: year, month: month, day: day}, _)
when calendar == Calendar.ISO do
"Date.new!(#{Integer.to_string(year)}, #{Integer.to_string(month)}, #{Integer.to_string(day)})"
end

def inspect(%{calendar: calendar, year: year, month: month, day: day}, _) do
"Date.new!(#{Integer.to_string(year)}, #{Integer.to_string(month)}, #{Integer.to_string(day)}, #{inspect(calendar)})"
end

defp suffix(Calendar.ISO), do: ""
defp suffix(calendar), do: " " <> inspect(calendar)
end
Expand Down
8 changes: 4 additions & 4 deletions lib/elixir/lib/calendar/iso.ex
Expand Up @@ -230,9 +230,9 @@ defmodule Calendar.ISO do
]
end

defguardp is_year(year) when year in -9999..9999
defguardp is_year_BCE(year) when year in -9999..0
defguardp is_year_CE(year) when year in 1..9999
defguardp is_year(year) when is_integer(year)
defguardp is_year_BCE(year) when year <= 0
defguardp is_year_CE(year) when year >= 1
defguardp is_month(month) when month in 1..12
defguardp is_day(day) when day in 1..31
defguardp is_hour(hour) when hour in 0..23
Expand Down Expand Up @@ -809,7 +809,7 @@ defmodule Calendar.ISO do

# Converts count of days since 0000-01-01 to {year, month, day} tuple.
@doc false
def date_from_iso_days(days) when days in -3_652_059..3_652_424 do
def date_from_iso_days(days) do
{year, day_of_year} = days_to_year(days)
extra_day = if leap_year?(year), do: 1, else: 0
{month, day_in_month} = year_day_to_year_date(extra_day, day_of_year)
Expand Down
61 changes: 52 additions & 9 deletions lib/elixir/test/elixir/calendar/date_test.exs
Expand Up @@ -42,6 +42,14 @@ defmodule DateTest do

assert to_string(%{date | calendar: FakeCalendar}) == "1/1/2000"
assert Date.to_string(%{date | calendar: FakeCalendar}) == "1/1/2000"

date2 = Date.new!(5_874_897, 12, 31)
assert to_string(date2) == "5874897-12-31"
assert Date.to_string(date2) == "5874897-12-31"
assert Date.to_string(Map.from_struct(date2)) == "5874897-12-31"

assert to_string(%{date2 | calendar: FakeCalendar}) == "31/12/5874897"
assert Date.to_string(%{date2 | calendar: FakeCalendar}) == "31/12/5874897"
end

test "inspect/1" do
Expand All @@ -50,29 +58,56 @@ defmodule DateTest do

date = %{~D[2000-01-01] | calendar: FakeCalendar}
assert inspect(date) == "~D[1/1/2000 FakeCalendar]"

assert inspect(Date.new!(5_874_897, 12, 31)) == "Date.new!(5874897, 12, 31)"
assert inspect(Date.new!(-5_874_897, 1, 1)) == "Date.new!(-5874897, 1, 1)"

date2 = %{Date.new!(5_874_897, 12, 31) | calendar: FakeCalendar}

assert inspect(%{date2 | calendar: FakeCalendar}) ==
"Date.new!(5874897, 12, 31, FakeCalendar)"
end

test "compare/2" do
date1 = ~D[-0001-12-30]
date2 = ~D[-0001-12-31]
date3 = ~D[0001-01-01]
date4 = Date.new!(5_874_897, 12, 31)
date5 = Date.new!(-4713, 1, 1)

assert Date.compare(date1, date1) == :eq
assert Date.compare(date1, date2) == :lt
assert Date.compare(date2, date1) == :gt
assert Date.compare(date3, date3) == :eq
assert Date.compare(date2, date3) == :lt
assert Date.compare(date3, date2) == :gt
assert Date.compare(date4, date1) == :gt
assert Date.compare(date1, date4) == :lt
assert Date.compare(date4, date4) == :eq
assert Date.compare(date4, date5) == :gt
assert Date.compare(date5, date4) == :lt
assert Date.compare(date5, date5) == :eq
end

test "before?/2 and after?/2" do
date1 = ~D[2022-11-01]
date2 = ~D[2022-11-02]
date3 = Date.new!(5_874_897, 12, 31)
date4 = Date.new!(-4713, 1, 1)

assert Date.before?(date1, date2)
assert Date.before?(date1, date3)
assert Date.before?(date4, date1)
assert not Date.before?(date2, date1)
assert not Date.before?(date3, date1)
assert not Date.before?(date1, date4)

assert Date.after?(date2, date1)
assert Date.after?(date3, date2)
assert Date.after?(date2, date4)
assert not Date.after?(date1, date2)
assert not Date.after?(date2, date3)
assert not Date.after?(date4, date2)
end

test "compare/2 across calendars" do
Expand Down Expand Up @@ -149,22 +184,16 @@ defmodule DateTest do

test "add/2" do
assert Date.add(~D[0000-01-01], 3_652_424) == ~D[9999-12-31]

assert_raise FunctionClauseError, fn ->
Date.add(~D[0000-01-01], 3_652_425)
end

assert Date.add(~D[0000-01-01], 3_652_425) == Date.new!(10000, 1, 1)
assert Date.add(~D[0000-01-01], -1) == ~D[-0001-12-31]
assert Date.add(~D[0000-01-01], -365) == ~D[-0001-01-01]
assert Date.add(~D[0000-01-01], -366) == ~D[-0002-12-31]
assert Date.add(~D[0000-01-01], -(365 * 4)) == ~D[-0004-01-02]
assert Date.add(~D[0000-01-01], -(365 * 5)) == ~D[-0005-01-02]
assert Date.add(~D[0000-01-01], -(365 * 100)) == ~D[-0100-01-25]
assert Date.add(~D[0000-01-01], -3_652_059) == ~D[-9999-01-01]

assert_raise FunctionClauseError, fn ->
Date.add(~D[0000-01-01], -3_652_060)
end
assert Date.add(~D[0000-01-01], -3_652_060) == Date.new!(-10000, 12, 31)
assert Date.add(Date.new!(5_874_897, 12, 31), 1) == Date.new!(5_874_898, 1, 1)
end

test "diff/2" do
Expand All @@ -174,6 +203,9 @@ defmodule DateTest do
assert Date.diff(~D[0000-01-01], ~D[-0001-01-01]) == 365
assert Date.diff(~D[-0003-01-01], ~D[-0004-01-01]) == 366

assert Date.diff(Date.new!(5_874_898, 1, 1), Date.new!(5_874_897, 1, 1)) == 365
assert Date.diff(Date.new!(5_874_905, 1, 1), Date.new!(5_874_904, 1, 1)) == 366

date1 = ~D[2000-01-01]
date2 = Calendar.Holocene.date(12000, 01, 14)
assert Date.diff(date1, date2) == -13
Expand All @@ -194,6 +226,17 @@ defmodule DateTest do
assert Date.shift(~D[2000-01-01], month: 12) == ~D[2001-01-01]
assert Date.shift(~D[0000-01-01], day: 2, year: 1, month: 37) == ~D[0004-02-03]

assert Date.shift(Date.new!(5_874_904, 2, 29), day: -1) == Date.new!(5_874_904, 2, 28)
assert Date.shift(Date.new!(5_874_904, 2, 29), month: -2) == Date.new!(5_874_903, 12, 29)
assert Date.shift(Date.new!(5_874_904, 2, 29), week: -9) == Date.new!(5_874_903, 12, 28)
assert Date.shift(Date.new!(5_874_904, 2, 29), month: 1) == Date.new!(5_874_904, 3, 29)
assert Date.shift(Date.new!(5_874_904, 2, 29), year: -1) == Date.new!(5_874_903, 2, 28)
assert Date.shift(Date.new!(5_874_904, 2, 29), year: -4) == Date.new!(5_874_900, 2, 28)
assert Date.shift(Date.new!(5_874_904, 2, 29), year: 4) == Date.new!(5_874_908, 2, 29)

assert Date.shift(Date.new!(5_874_904, 2, 29), day: 1, year: 4, month: 2) ==
Date.new!(5_874_908, 4, 30)

assert_raise ArgumentError,
"unsupported unit :second. Expected :year, :month, :week, :day",
fn -> Date.shift(~D[2012-02-29], second: 86400) end
Expand Down
8 changes: 0 additions & 8 deletions lib/elixir/test/elixir/calendar/iso_test.exs
Expand Up @@ -114,14 +114,6 @@ defmodule Calendar.ISOTest do

random_positive_year = Enum.random(1..9999)
assert Calendar.ISO.year_of_era(random_positive_year, 1, 1) == {random_positive_year, 1}

assert_raise FunctionClauseError, fn ->
Calendar.ISO.year_of_era(10000, 1, 1)
end

assert_raise FunctionClauseError, fn ->
Calendar.ISO.year_of_era(-10000, 12, 1)
end
end

defp iso_day_roundtrip(year, month, day) do
Expand Down

0 comments on commit 3d7f4dd

Please sign in to comment.