@@ -2,12 +2,13 @@ defmodule Date.Range do
2
2
@ moduledoc """
3
3
Returns an inclusive range between dates.
4
4
5
- Ranges must be created with the `Date.range/2` function.
5
+ Ranges must be created with the `Date.range/2` or `Date.range/3` function.
6
6
7
7
The following fields are public:
8
8
9
9
* `:first` - the initial date on the range
10
10
* `:last` - the last date on the range
11
+ * `:step` - (since v1.12.0) the step
11
12
12
13
The remaining fields are private and should not be accessed.
13
14
"""
@@ -16,98 +17,98 @@ defmodule Date.Range do
16
17
first: Date . t ( ) ,
17
18
last: Date . t ( ) ,
18
19
first_in_iso_days: iso_days ( ) ,
19
- last_in_iso_days: iso_days ( )
20
+ last_in_iso_days: iso_days ( ) ,
21
+ step: pos_integer | neg_integer
20
22
}
21
23
22
24
@ typep iso_days ( ) :: Calendar . iso_days ( )
23
25
24
- defstruct [ :first , :last , :first_in_iso_days , :last_in_iso_days ]
26
+ defstruct [ :first , :last , :first_in_iso_days , :last_in_iso_days , :step ]
25
27
26
28
defimpl Enumerable do
27
29
def member? ( % { first: % { calendar: calendar } } = range , % Date { calendar: calendar } = date ) do
28
30
% {
29
- first: first ,
30
- last: last ,
31
- first_in_iso_days: first_in_iso_days ,
32
- last_in_iso_days: last_in_iso_days
31
+ first_in_iso_days: first_days ,
32
+ last_in_iso_days: last_days ,
33
+ step: step
33
34
} = range
34
35
35
- % { year: first_year , month: first_month , day: first_day } = first
36
- % { year: last_year , month: last_month , day: last_day } = last
37
- % { year: year , month: month , day: day } = date
38
- first = { first_year , first_month , first_day }
39
- last = { last_year , last_month , last_day }
40
- date = { year , month , day }
41
-
42
- if first_in_iso_days <= last_in_iso_days do
43
- { :ok , date >= first and date <= last }
44
- else
45
- { :ok , date >= last and date <= first }
36
+ { days , _ } = Date . to_iso_days ( date )
37
+
38
+ cond do
39
+ empty? ( range ) ->
40
+ { :ok , false }
41
+
42
+ first_days <= last_days ->
43
+ { :ok , first_days <= days and days <= last_days and rem ( days - first_days , step ) == 0 }
44
+
45
+ true ->
46
+ { :ok , last_days <= days and days <= first_days and rem ( days - first_days , step ) == 0 }
46
47
end
47
48
end
48
49
49
50
def member? ( _ , _ ) do
50
51
{ :ok , false }
51
52
end
52
53
53
- def count ( % { first_in_iso_days: first , last_in_iso_days: last } ) do
54
- { :ok , abs ( first - last ) + 1 }
54
+ def count ( range ) do
55
+ { :ok , size ( range ) }
55
56
end
56
57
57
58
def slice ( range ) do
58
59
% {
59
60
first_in_iso_days: first ,
60
- last_in_iso_days: last ,
61
- first: % { calendar: calendar }
61
+ first: % { calendar: calendar } ,
62
+ step: step
62
63
} = range
63
64
64
- if first <= last do
65
- { :ok , last - first + 1 , & slice_asc ( first + & 1 , & 2 , calendar ) }
66
- else
67
- { :ok , first - last + 1 , & slice_desc ( first - & 1 , & 2 , calendar ) }
68
- end
65
+ { :ok , size ( range ) , & slice ( first + & 1 * step , step , & 2 , calendar ) }
69
66
end
70
67
71
- defp slice_asc ( current , 1 , calendar ) , do: [ date_from_iso_days ( current , calendar ) ]
72
-
73
- defp slice_asc ( current , remaining , calendar ) do
74
- [ date_from_iso_days ( current , calendar ) | slice_asc ( current + 1 , remaining - 1 , calendar ) ]
68
+ defp slice ( current , _step , 1 , calendar ) do
69
+ [ date_from_iso_days ( current , calendar ) ]
75
70
end
76
71
77
- defp slice_desc ( current , 1 , calendar ) , do: [ date_from_iso_days ( current , calendar ) ]
78
-
79
- defp slice_desc ( current , remaining , calendar ) do
80
- [ date_from_iso_days ( current , calendar ) | slice_desc ( current - 1 , remaining - 1 , calendar ) ]
72
+ defp slice ( current , step , remaining , calendar ) do
73
+ [
74
+ date_from_iso_days ( current , calendar )
75
+ | slice ( current + step , step , remaining - 1 , calendar )
76
+ ]
81
77
end
82
78
83
79
def reduce ( range , acc , fun ) do
84
80
% {
85
- first_in_iso_days: first_in_iso_days ,
86
- last_in_iso_days: last_in_iso_days ,
87
- first: % { calendar: calendar }
81
+ first_in_iso_days: first_days ,
82
+ last_in_iso_days: last_days ,
83
+ first: % { calendar: calendar } ,
84
+ step: step
88
85
} = range
89
86
90
- up? = first_in_iso_days <= last_in_iso_days
91
- reduce ( first_in_iso_days , last_in_iso_days , acc , fun , calendar , up? )
87
+ reduce ( first_days , last_days , acc , fun , step , calendar )
92
88
end
93
89
94
- defp reduce ( _x , _y , { :halt , acc } , _fun , _calendar , _up? ) do
90
+ defp reduce ( _first_days , _last_days , { :halt , acc } , _fun , _step , _calendar ) do
95
91
{ :halted , acc }
96
92
end
97
93
98
- defp reduce ( x , y , { :suspend , acc } , fun , calendar , up? ) do
99
- { :suspended , acc , & reduce ( x , y , & 1 , fun , calendar , up? ) }
94
+ defp reduce ( first_days , last_days , { :suspend , acc } , fun , step , calendar ) do
95
+ { :suspended , acc , & reduce ( first_days , last_days , & 1 , fun , step , calendar ) }
100
96
end
101
97
102
- defp reduce ( x , y , { :cont , acc } , fun , calendar , up? = true ) when x <= y do
103
- reduce ( x + 1 , y , fun . ( date_from_iso_days ( x , calendar ) , acc ) , fun , calendar , up? )
98
+ defp reduce ( first_days , last_days , { :cont , acc } , fun , step , calendar )
99
+ when step > 0 and first_days <= last_days
100
+ when step < 0 and first_days >= last_days do
101
+ reduce (
102
+ first_days + step ,
103
+ last_days ,
104
+ fun . ( date_from_iso_days ( first_days , calendar ) , acc ) ,
105
+ fun ,
106
+ step ,
107
+ calendar
108
+ )
104
109
end
105
110
106
- defp reduce ( x , y , { :cont , acc } , fun , calendar , up? = false ) when x >= y do
107
- reduce ( x - 1 , y , fun . ( date_from_iso_days ( x , calendar ) , acc ) , fun , calendar , up? )
108
- end
109
-
110
- defp reduce ( _ , _ , { :cont , acc } , _fun , _calendar , _up ) do
111
+ defp reduce ( _ , _ , { :cont , acc } , _fun , _step , _calendar ) do
111
112
{ :done , acc }
112
113
end
113
114
@@ -122,11 +123,44 @@ defmodule Date.Range do
122
123
123
124
% Date { year: year , month: month , day: day , calendar: calendar }
124
125
end
126
+
127
+ defp size ( % Date.Range { first_in_iso_days: first_days , last_in_iso_days: last_days , step: step } )
128
+ when step > 0 and first_days > last_days ,
129
+ do: 0
130
+
131
+ defp size ( % Date.Range { first_in_iso_days: first_days , last_in_iso_days: last_days , step: step } )
132
+ when step < 0 and first_days < last_days ,
133
+ do: 0
134
+
135
+ defp size ( % Date.Range { first_in_iso_days: first_days , last_in_iso_days: last_days , step: step } ) ,
136
+ do: abs ( div ( last_days - first_days , step ) ) + 1
137
+
138
+ defp empty? ( % Date.Range {
139
+ first_in_iso_days: first_days ,
140
+ last_in_iso_days: last_days ,
141
+ step: step
142
+ } )
143
+ when step > 0 and first_days > last_days ,
144
+ do: true
145
+
146
+ defp empty? ( % Date.Range {
147
+ first_in_iso_days: first_days ,
148
+ last_in_iso_days: last_days ,
149
+ step: step
150
+ } )
151
+ when step < 0 and first_days < last_days ,
152
+ do: true
153
+
154
+ defp empty? ( % Date.Range { } ) , do: false
125
155
end
126
156
127
157
defimpl Inspect do
128
- def inspect ( % Date.Range { first: first , last: last } , _ ) do
158
+ def inspect ( % Date.Range { first: first , last: last , step: 1 } , _ ) do
129
159
"#DateRange<" <> inspect ( first ) <> ", " <> inspect ( last ) <> ">"
130
160
end
161
+
162
+ def inspect ( % Date.Range { first: first , last: last , step: step } , _ ) do
163
+ "#DateRange<" <> inspect ( first ) <> ", " <> inspect ( last ) <> ", #{ step } >"
164
+ end
131
165
end
132
166
end
0 commit comments