-
Notifications
You must be signed in to change notification settings - Fork 3.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
support Duration
in Date.range/3
#14172
support Duration
in Date.range/3
#14172
Conversation
72659ab
to
7650afb
Compare
Duration
in Date.range/3
7650afb
to
563fd96
Compare
lib/elixir/lib/calendar/date.ex
Outdated
@@ -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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shall we allow the keyword list version as well? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i found that range(~D[2020-01-01], [year: 1], 2)
looks a bit unergonomic, but definitely nothing speaking against allowing it 👍
lib/elixir/lib/calendar/date.ex
Outdated
@@ -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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably guard this too to check that step is a non-zero integer and update the ArgumentError clause & message?
lib/elixir/lib/calendar/date.ex
Outdated
@@ -84,6 +87,13 @@ defmodule Date do | |||
iex> Date.range(~D[1999-01-01], ~D[2000-01-01]) | |||
Date.range(~D[1999-01-01], ~D[2000-01-01]) | |||
|
|||
A range may also be built from a `Date` and a `Duration`: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A range may also be built from a `Date` and a `Duration`: | |
A range may also be built from a `Date` and a `Duration` | |
(also expressed as a keyword list of `t:duration_unit_pair/0`): |
?
lib/elixir/lib/calendar/date.ex
Outdated
@@ -100,7 +110,8 @@ 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() | [duration_unit_pair]) :: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would improve docs (now they would still show range(first, last)
:
@spec range(Calendar.date(), Calendar.date() | Duration.t() | [duration_unit_pair]) :: | |
@spec range(start :: Calendar.date(), end_or_duration :: Calendar.date() | Duration.t() | [duration_unit_pair]) :: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
while I like start
/end
, should we stick to first
/last
so it's:
@spec range(first :: Calendar.date(), last_or_duration :: Calendar.date() | Duration.t() | [duration_unit_pair]) ::
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
last_or_duration
is ok but the "duration" part doesn't work as well with first
IMO, compared to start
+ duration
/start
+ end
. Either way not a big deal 🙃
lib/elixir/lib/calendar/date.ex
Outdated
def range(%{calendar: _, year: _, month: _, day: _}, _duration) do | ||
raise ArgumentError, "expected a date or duration as second argument" | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@josevalim do we usually do this, or just let this fall through and raise a FunctionClauseError
? I think this kind of thing would be caught by types anyway
lib/elixir/lib/calendar/date.ex
Outdated
Calendar.date(), | ||
Calendar.date() | Duration.t() | [duration_unit_pair], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same naming issue here
lib/elixir/lib/calendar/date.ex
Outdated
def range(%{calendar: _, year: _, month: _, day: _} = first, duration, step) do | ||
raise ArgumentError, | ||
"expected a date or duration as second argument and the step must be a " <> | ||
"non-zero integer, got: #{inspect(first)}, #{inspect(duration)}, #{step}" | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this was removed following this comment from @whatyouhide, but a zero step won't be caught by the typesystem so I think we should probably still raise a proper ArgumentError
in this case, WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes the zero step yep, good callout
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the basic types an invalid step should be caught by the type system (pos_integer
, neg_integer
), wouldn't it?
However, as it stands it's not consistent as Date.range(d1, d2, 0)
raises an ArgumentError
and Date.range(d1, duration, 0)
raises a FunctionClauseError
.
I'll re-add the clauses with the adjusted ArgumentError message to . Actually limiting it to range/2
and range/3
range/3
feels right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, how about not matching or guarding durations at all (as we also don't do it in shift/2)? so we'd just have:
def range(%{calendar: calendar} = first, %{calendar: calendar} = last)
def range(%{calendar: _, year: _, month: _, day: _}, %{calendar: _, year: _, month: _, day: _})
def range(%{calendar: _} = first, duration)
and
def range(%{calendar: calendar} = first, %{calendar: calendar} = last, step) when is_integer(step) and step != 0
def range(%{calendar: _, year: _, month: _, day: _}, %{calendar: _, year: _, month: _, day: _}, step)
def range(%{calendar: _} = first, duration) when is_integer(step) and step != 0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the basic types an invalid step should be caught by the type system (pos_integer, neg_integer), wouldn't it?
These are the "old types" (typespecs), for now AFAIK there is no plan to implement these in the new set-theoretic types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps we should clarify this distinction in the docs, I can see how having two type systems can be confusing even if it is just a transition phase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh actually wasn't aware of that distinction! thanks!
💚 💙 💜 💛 ❤️ |
There is some ambiguity if the duration represents the end date or the step, so it is best to be explicit about it. This reverts commit 8deaaf4.
No description provided.