diff --git a/lib/elixir/lib/calendar/date.ex b/lib/elixir/lib/calendar/date.ex index f51ae3e56d..876cbc642a 100644 --- a/lib/elixir/lib/calendar/date.ex +++ b/lib/elixir/lib/calendar/date.ex @@ -84,6 +84,11 @@ defmodule Date do iex> Date.range(~D[1999-01-01], ~D[2000-01-01]) Date.range(~D[1999-01-01], ~D[2000-01-01]) + Alternatively, a range may be built from a first `Date` and a `Duration`: + + iex> Date.range(~D[1999-01-01], Duration.new!(year: 1)) + Date.range(~D[1999-01-01], ~D[2000-01-01]) + A range of dates implements the `Enumerable` protocol, which means functions in the `Enum` module can be used to work with ranges: @@ -100,7 +105,7 @@ defmodule Date do """ @doc since: "1.5.0" - @spec range(Calendar.date(), Calendar.date()) :: Date.Range.t() + @spec range(Calendar.date(), Calendar.date() | Duration.t()) :: Date.Range.t() def range(%{calendar: calendar} = first, %{calendar: calendar} = last) do {first_days, _} = to_iso_days(first) {last_days, _} = to_iso_days(last) @@ -119,6 +124,11 @@ defmodule Date do range(first, first_days, last, last_days, calendar, step) end + def range(%{calendar: calendar} = first, %Duration{} = duration) do + last = shift(first, duration) + range(first, last) + end + def range(%{calendar: _, year: _, month: _, day: _}, %{calendar: _, year: _, month: _, day: _}) do raise ArgumentError, "both dates must have matching calendars" end @@ -140,7 +150,7 @@ defmodule Date do """ @doc since: "1.12.0" - @spec range(Calendar.date(), Calendar.date(), step :: pos_integer | neg_integer) :: + @spec range(Calendar.date(), Calendar.date() | Duration.t(), step :: pos_integer | neg_integer) :: Date.Range.t() def range(%{calendar: calendar} = first, %{calendar: calendar} = last, step) when is_integer(step) and step != 0 do @@ -149,6 +159,11 @@ defmodule Date do range(first, first_days, last, last_days, calendar, step) end + def range(%{calendar: calendar} = first, %Duration{} = duration, step) do + last = shift(first, duration) + range(first, last, step) + end + def range( %{calendar: _, year: _, month: _, day: _} = first, %{calendar: _, year: _, month: _, day: _} = last, diff --git a/lib/elixir/test/elixir/calendar/date_range_test.exs b/lib/elixir/test/elixir/calendar/date_range_test.exs index 77f4c5b047..d0f6363c1e 100644 --- a/lib/elixir/test/elixir/calendar/date_range_test.exs +++ b/lib/elixir/test/elixir/calendar/date_range_test.exs @@ -6,8 +6,12 @@ defmodule Date.RangeTest do @asc_range Date.range(~D[2000-01-01], ~D[2001-01-01]) @asc_range_2 Date.range(~D[2000-01-01], ~D[2001-01-01], 2) + @asc_range_duration Date.range(~D[2000-01-01], Duration.new!(year: 1)) + @asc_range_duration_2 Date.range(~D[2000-01-01], Duration.new!(year: 1), 2) @desc_range Date.range(~D[2001-01-01], ~D[2000-01-01], -1) @desc_range_2 Date.range(~D[2001-01-01], ~D[2000-01-01], -2) + @desc_range_duration Date.range(~D[2001-01-01], Duration.new!(year: -1)) + @desc_range_duration_2 Date.range(~D[2001-01-01], Duration.new!(year: -1), 2) @empty_range Date.range(~D[2001-01-01], ~D[2000-01-01], 1) describe "Enum.member?/2" do @@ -20,6 +24,9 @@ defmodule Date.RangeTest do assert Enum.member?(@asc_range_2, ~D[2000-01-03]) refute Enum.member?(@asc_range_2, ~D[2000-01-02]) + + assert Enum.member?(@asc_range_duration, ~D[2000-01-03]) + refute Enum.member?(@asc_range_duration_2, ~D[2000-01-02]) end test "for descending range" do @@ -31,6 +38,9 @@ defmodule Date.RangeTest do assert Enum.member?(@desc_range_2, ~D[2000-12-30]) refute Enum.member?(@desc_range_2, ~D[2000-12-29]) + + assert Enum.member?(@desc_range_duration, ~D[2000-12-30]) + refute Enum.member?(@desc_range_duration_2, ~D[2000-12-29]) end test "empty range" do @@ -109,6 +119,18 @@ defmodule Date.RangeTest do assert Enum.to_list(range) == [~D[2000-01-01], ~D[2000-01-03]] end + test "works with durations" do + range = Date.range(~D[2000-01-01], Duration.new!(day: 1)) + assert range.first == ~D[2000-01-01] + assert range.last == ~D[2000-01-02] + assert Enum.to_list(range) == [~D[2000-01-01], ~D[2000-01-02]] + + range = Date.range(~D[2000-01-01], Duration.new!(day: 2), 2) + assert range.first == ~D[2000-01-01] + assert range.last == ~D[2000-01-03] + assert Enum.to_list(range) == [~D[2000-01-01], ~D[2000-01-03]] + end + test "both dates must have matching calendars" do first = ~D[2000-01-01] last = Calendar.Holocene.date(12001, 1, 1)