@@ -69,6 +69,11 @@ defmodule Date do
6969 calendar: Calendar . calendar ( )
7070 }
7171
72+ @ typedoc "A duration unit expressed as a tuple."
73+ @ typedoc since: "1.19.0"
74+ @ type duration_unit_pair ::
75+ { :year , integer } | { :month , integer } | { :week , integer } | { :day , integer }
76+
7277 @ doc """
7378 Returns a range of dates.
7479
@@ -84,6 +89,20 @@ defmodule Date do
8489 iex> Date.range(~D[1999-01-01], ~D[2000-01-01])
8590 Date.range(~D[1999-01-01], ~D[2000-01-01])
8691
92+ A range may also be built from a `Date` and a `Duration`
93+ (also expressed as a keyword list of `t:duration_unit_pair/0`):
94+
95+ iex> Date.range(~D[1999-01-01], Duration.new!(year: 1))
96+ Date.range(~D[1999-01-01], ~D[2000-01-01])
97+ iex> Date.range(~D[1999-01-01], year: 1)
98+ Date.range(~D[1999-01-01], ~D[2000-01-01])
99+
100+ > #### Durations {: .warning}
101+ >
102+ > Support for expressing `last` as a [`Duration`](`t:Duration.t/0`) or
103+ > keyword list of `t:duration_unit_pair/0`s was introduced in
104+ > v1.19.0.
105+
87106 A range of dates implements the `Enumerable` protocol, which means
88107 functions in the `Enum` module can be used to work with
89108 ranges:
@@ -100,7 +119,11 @@ defmodule Date do
100119
101120 """
102121 @ doc since: "1.5.0"
103- @ spec range ( Calendar . date ( ) , Calendar . date ( ) ) :: Date.Range . t ( )
122+ @ spec range (
123+ first :: Calendar . date ( ) ,
124+ last_or_duration :: Calendar . date ( ) | Duration . t ( ) | [ duration_unit_pair ]
125+ ) ::
126+ Date.Range . t ( )
104127 def range ( % { calendar: calendar } = first , % { calendar: calendar } = last ) do
105128 { first_days , _ } = to_iso_days ( first )
106129 { last_days , _ } = to_iso_days ( last )
@@ -123,6 +146,16 @@ defmodule Date do
123146 raise ArgumentError , "both dates must have matching calendars"
124147 end
125148
149+ def range ( % { calendar: _ } = first , % Duration { } = duration ) do
150+ last = shift ( first , duration )
151+ range ( first , last )
152+ end
153+
154+ def range ( % { calendar: _ } = first , duration ) when is_list ( duration ) do
155+ last = shift ( first , duration )
156+ range ( first , last )
157+ end
158+
126159 @ doc """
127160 Returns a range of dates with a step.
128161
@@ -140,8 +173,11 @@ defmodule Date do
140173
141174 """
142175 @ doc since: "1.12.0"
143- @ spec range ( Calendar . date ( ) , Calendar . date ( ) , step :: pos_integer | neg_integer ) ::
144- Date.Range . t ( )
176+ @ spec range (
177+ first :: Calendar . date ( ) ,
178+ last_or_duration :: Calendar . date ( ) | Duration . t ( ) | [ duration_unit_pair ] ,
179+ step :: pos_integer | neg_integer
180+ ) :: Date.Range . t ( )
145181 def range ( % { calendar: calendar } = first , % { calendar: calendar } = last , step )
146182 when is_integer ( step ) and step != 0 do
147183 { first_days , _ } = to_iso_days ( first )
@@ -159,6 +195,24 @@ defmodule Date do
159195 "non-zero integer, got: #{ inspect ( first ) } , #{ inspect ( last ) } , #{ step } "
160196 end
161197
198+ def range ( % { calendar: _ } = first , % Duration { } = duration , step )
199+ when is_integer ( step ) and step != 0 do
200+ last = shift ( first , duration )
201+ range ( first , last , step )
202+ end
203+
204+ def range ( % { calendar: _ } = first , duration , step )
205+ when is_list ( duration ) and is_integer ( step ) and step != 0 do
206+ last = shift ( first , duration )
207+ range ( first , last , step )
208+ end
209+
210+ def range ( % { calendar: _ } = first , last , step ) do
211+ raise ArgumentError ,
212+ "expected a date or duration as second argument and the step must be a " <>
213+ "non-zero integer, got: #{ inspect ( first ) } , #{ inspect ( last ) } , #{ step } "
214+ end
215+
162216 defp range ( first , first_days , last , last_days , calendar , step ) do
163217 % Date.Range {
164218 first: % Date { calendar: calendar , year: first . year , month: first . month , day: first . day } ,
@@ -795,8 +849,7 @@ defmodule Date do
795849
796850 """
797851 @ doc since: "1.17.0"
798- @ spec shift ( Calendar . date ( ) , Duration . t ( ) | [ unit_pair ] ) :: t
799- when unit_pair: { :year , integer } | { :month , integer } | { :week , integer } | { :day , integer }
852+ @ spec shift ( Calendar . date ( ) , Duration . t ( ) | [ duration_unit_pair ] ) :: t
800853 def shift ( % { calendar: calendar } = date , duration ) do
801854 % { year: year , month: month , day: day } = date
802855 { year , month , day } = calendar . shift_date ( year , month , day , __duration__! ( duration ) )
0 commit comments