Skip to content

Commit 1f74ec7

Browse files
MunksgaardQqwy
andcommitted
Date generators
This is a reimplementation of the date generators by @Qqwy in whatyouhide#161. As such, it is a (partial) implementation for whatyouhide#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 <[email protected]>
1 parent 1f220e8 commit 1f74ec7

File tree

2 files changed

+126
-0
lines changed

2 files changed

+126
-0
lines changed

lib/stream_data.ex

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

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

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)