Skip to content

Commit 737df91

Browse files
MunksgaardQqwywhatyouhide
authored
Date generators (#218)
This is a reimplementation of the date generators by @Qqwy in #161. As such, it is a (partial) implementation for #129. The tests and documentation are entirely copy-pasted from his PR, but the actual implementation is entirely new. As noted by @whatyouhide in a comment to the original PR, I've opted to use the new `Date.add` function for most of the cases. I think the resulting implementation is a bit easier to follow. I have not added Time, DateTime or NaiveDateTime, though I think we could probably do something for those that follow this skeleton. Co-authored-by: Qqwy / Marten <w-m@wmcode.nl> Co-authored-by: Andrea Leopardi <an.leopardi@gmail.com>
1 parent 1f220e8 commit 737df91

File tree

2 files changed

+131
-0
lines changed

2 files changed

+131
-0
lines changed

lib/stream_data.ex

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2094,6 +2094,90 @@ defmodule StreamData do
20942094
end)
20952095
end
20962096

2097+
@doc """
2098+
Generates dates according to the given `options` or `date_range`.
2099+
2100+
## Options
2101+
2102+
* `:origin` - (`t:Date.t/0`) if present, generated values will shrink towards this date.
2103+
Cannot be combined with `:min` or `:max`.
2104+
2105+
* `:min` - (`t:Date.t/0`) if present, only dates *after* this date will be
2106+
generated. Values will shrink towards this date.
2107+
2108+
* `:max` - (`t:Date.t/0`) if present, only dates *before* this date will be
2109+
generated. Values will shrink towards this date.
2110+
2111+
If both `:min` and `:max` are provided, dates between the two mentioned dates will be generated.
2112+
Values will shrink towards `:min`.
2113+
2114+
If no options are provided, `:origin` will default to the current date (that is,
2115+
`Date.utc_today/0`).
2116+
2117+
## `t:Date.Range.t/0`
2118+
2119+
Alternatively a `t:Date.Range.t/0` can be given. This will generate dates in the given range,
2120+
and with the supplied `date_range.step`.
2121+
2122+
Values will shrink towards `date_range.first`.
2123+
2124+
## Calendar Support
2125+
2126+
This generator works with `m:Calendar.ISO` and any other calendar
2127+
which implements the callbacks
2128+
`c:Calendar.naive_datetime_to_iso_days/7` and `c:Calendar.naive_datetime_from_iso_days/2`.
2129+
"""
2130+
@spec date(Date.Range.t() | keyword()) :: t(Date.t())
2131+
def date(options_or_date_range \\ [origin: Date.utc_today()])
2132+
2133+
def date(date_range = %Date.Range{}) do
2134+
Date.to_gregorian_days(date_range.first)..Date.to_gregorian_days(date_range.last)//date_range.step
2135+
|> StreamData.integer()
2136+
|> StreamData.map(&Date.from_gregorian_days(&1))
2137+
end
2138+
2139+
def date(options) when is_list(options) do
2140+
min = Keyword.get(options, :min, nil)
2141+
max = Keyword.get(options, :max, nil)
2142+
origin = Keyword.get(options, :origin)
2143+
2144+
case {min, max, origin} do
2145+
{nil, nil, nil} ->
2146+
StreamData.integer() |> StreamData.map(&Date.add(Date.utc_today(), &1))
2147+
2148+
{nil, nil, origin = %Date{}} ->
2149+
StreamData.integer() |> StreamData.map(&Date.add(origin, &1))
2150+
2151+
{nil, max = %Date{}, nil} ->
2152+
StreamData.positive_integer() |> StreamData.map(&Date.add(max, -&1))
2153+
2154+
{min = %Date{}, nil, nil} ->
2155+
StreamData.positive_integer() |> StreamData.map(&Date.add(min, &1))
2156+
2157+
{min = %Date{}, max = %Date{}, nil} ->
2158+
if Date.before?(max, min) do
2159+
raise ArgumentError, """
2160+
expected :max to be after or equal to :min, got:
2161+
2162+
* min: #{inspect(min)}
2163+
* max: #{inspect(max)}
2164+
"""
2165+
end
2166+
2167+
diff = Date.diff(max, min)
2168+
StreamData.integer(0..diff) |> StreamData.map(&Date.add(min, &1))
2169+
2170+
_ ->
2171+
raise ArgumentError, """
2172+
:origin cannot be specified together with either :min or :max, got:
2173+
2174+
* origin: #{inspect(origin)}
2175+
* min: #{inspect(min)}
2176+
* max: #{inspect(max)}
2177+
"""
2178+
end
2179+
end
2180+
20972181
@doc """
20982182
Generates iolists.
20992183

test/stream_data_test.exs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,53 @@ defmodule StreamDataTest do
332332
end
333333
end
334334

335+
describe "date/0" do
336+
property "generates any dates" do
337+
check all date <- date() do
338+
assert %Date{} = date
339+
end
340+
end
341+
end
342+
343+
describe "date/1" do
344+
property "without options, generates any dates" do
345+
check all date <- date([]) do
346+
assert %Date{} = date
347+
end
348+
end
349+
350+
property "with a :min option, generates dates after it" do
351+
check all minimum <- date(),
352+
date <- date(min: minimum) do
353+
assert Date.compare(date, minimum) in [:eq, :gt]
354+
end
355+
end
356+
357+
property "with a :max option, generates dates before it" do
358+
check all maximum <- date(),
359+
date <- date(max: maximum) do
360+
assert Date.compare(date, maximum) in [:lt, :eq]
361+
end
362+
end
363+
364+
property "with both a :min and a :max option, generates dates in-between the bounds" do
365+
check all minimum <- date(),
366+
maximum <- date(min: minimum),
367+
date <- date(min: minimum, max: maximum) do
368+
assert Enum.member?(Date.range(minimum, maximum), date)
369+
end
370+
end
371+
372+
property "with a Date.Range, generates dates in-between the bounds" do
373+
check all minimum <- date(),
374+
maximum <- date(min: minimum),
375+
range = Date.range(minimum, maximum),
376+
date <- date(range) do
377+
assert Enum.member?(range, date)
378+
end
379+
end
380+
end
381+
335382
property "byte/0" do
336383
check all value <- byte() do
337384
assert value in 0..255

0 commit comments

Comments
 (0)