Skip to content
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

Merged
merged 11 commits into from
Jan 13, 2025

Conversation

tfiedlerdejanze
Copy link
Contributor

No description provided.

@tfiedlerdejanze tfiedlerdejanze force-pushed the date-range-with-durations branch from 72659ab to 7650afb Compare January 10, 2025 20:52
@tfiedlerdejanze tfiedlerdejanze changed the title support Duration in Date.range/3 support Duration in Date.range/3 Jan 10, 2025
@tfiedlerdejanze tfiedlerdejanze force-pushed the date-range-with-durations branch from 7650afb to 563fd96 Compare January 10, 2025 21:01
@@ -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
Copy link
Member

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? :)

Copy link
Contributor Author

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 👍

@@ -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
Copy link
Contributor

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 Show resolved Hide resolved
@@ -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`:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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 Show resolved Hide resolved
@@ -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]) ::
Copy link
Member

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):

Suggested change
@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]) ::

Copy link
Contributor Author

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]) ::

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2025-01-13 at 10 27 29

Screenshot 2025-01-13 at 10 30 34

Copy link
Member

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 🙃

Comment on lines 147 to 149
def range(%{calendar: _, year: _, month: _, day: _}, _duration) do
raise ArgumentError, "expected a date or duration as second argument"
end
Copy link
Member

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

Comment on lines 169 to 170
Calendar.date(),
Calendar.date() | Duration.t() | [duration_unit_pair],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same naming issue here

Comment on lines 211 to 215
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
Copy link
Contributor

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?

Copy link
Member

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

Copy link
Contributor Author

@tfiedlerdejanze tfiedlerdejanze Jan 13, 2025

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 range/2 and range/3. Actually limiting it to range/3 feels right.

Copy link
Contributor Author

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

Copy link
Contributor

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.

Copy link
Contributor

@sabiwara sabiwara Jan 13, 2025

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.

Copy link
Contributor Author

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!

@josevalim josevalim merged commit 8deaaf4 into elixir-lang:main Jan 13, 2025
9 checks passed
@josevalim
Copy link
Member

💚 💙 💜 💛 ❤️

josevalim added a commit that referenced this pull request Jan 20, 2025
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.
sabiwara added a commit to sabiwara/elixir that referenced this pull request Jan 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

5 participants