-
Notifications
You must be signed in to change notification settings - Fork 7
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
Fix bug in FiniteDateRange.is_disjoint (and union as a result) #99
Conversation
9ac0358
to
a8cec90
Compare
495d8a1
to
cdf8acf
Compare
cdf8acf
to
6d6ab5f
Compare
I also can't figure out the typing complaints here and they are driving me insane. Insights welcome. |
What about being more explicit? def is_disjoint(self, other: Range[datetime.date]) -> bool:
# Adjacent dates should not be considered disjoint, we extend the other
# range to allow them to be considered adjacent.
other_start = other.start
if other._is_right_inclusive:
if other_start is not None: # replaces assertion
other_start -= datetime.timedelta(days=1)
other_end = other.end
if other._is_right_inclusive:
if other_end is not None: # replaces assertion
other_end += datetime.timedelta(days=1)
return super().is_disjoint(
Range(start=other_start, end=other_end, boundaries=other.boundaries)
) |
When you do this, mypy still doesn't know if so when you do
what you actually want is
(I have not tested this theory, but I imagine that's what it is) |
Yes was just coming here to say this (I have tested it and that sorts it). Other option is doing |
other_start = other.start | ||
if other._is_right_inclusive: | ||
assert other.start | ||
other_start -= datetime.timedelta(days=1) |
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.
🐮 this bug is wider than dates - it also affects any Range
where the values are discrete. For example: integers.
I think it could also potentially affect other boundary conditions than inclusive-inclusive, although I guess we'd have to go through and figure it out.
I wonder if a wider solution would be some kind of increment
or resolution
field? Either directly on the Range
itself, or coming from a mapping RANGE_TYPE_RESOLUTIONS = {datetime.Date: datetime.timedelta(days=1), int: 1, ...}
.
(I don't want my comment to block this PR. I'm sure date ranges are our biggest problem in Kraken and your fix as it stands would be a very useful win.)
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 also think this might be a wider problem, and some kind of resolution might be a good idea (although since ranges can be for any orderable type, it might not always work).
I agree that we shouldn't block here though.
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 did consider the integer problem here, but I think it's a bit different because if you consider float values then there are plenty of values that exist between 2 and 3.
tests/test_ranges.py
Outdated
end=datetime.date(2000, 1, 4), | ||
) | ||
|
||
def test_handles_ranges_out_of_order(self): |
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 don't follow - isn't this identical to the 2nd case for the test above?
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.
Yeah I think I meant to delete this in a test refactor, my bad.
union = range | other | ||
assert union is None | ||
|
||
@pytest.mark.parametrize( |
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.
🐼 this test is complex enough that it should probably have ids on its cases.
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.
Definitely a 👍 from me - this has been bugging me for a while.
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.
This looks sensible to me, but I think it might be bugged in its current form so am requesting changes.
xocto/ranges.py
Outdated
# range to allow them to be considered adjacent. | ||
other_start = other.start | ||
if other._is_right_inclusive: | ||
assert other.start |
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.
❓ Why does this assertion hold? I would expect (None, '2022-01-01']
to be a valid range.
Maybe this should be ._is_left_inclusive
?
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.
🤔 You are right, this is a bug, I will review my tests to see why they weren't failing.
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.
Because I was testing this behaviour via union
it turns out this method wasn't run in the problematic case, because the union
method orders the ranges first and then was calling the is_disjoint method on the other range instead.
I have addressed now with additional tests specifically for the is_disjoint method.
other_start = other.start | ||
if other._is_right_inclusive: | ||
assert other.start | ||
other_start -= datetime.timedelta(days=1) |
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 also think this might be a wider problem, and some kind of resolution might be a good idea (although since ranges can be for any orderable type, it might not always work).
I agree that we shouldn't block here though.
Ahh @delfick thanks that makes total sense 👌 |
6d6ab5f
to
3cdc852
Compare
3cdc852
to
f3b0bba
Compare
Primarily this change is aimed at changing the behaviour of the FiniteDateRange.union. Previously FiniteDateRanges would not make a union with adjacent ranges, despite their inclusive boundaries meaning the presence of an adjacent range means they cover a fully contiguous period of time. This change makes adjacent FiniteDateRanges unionize by no longer considering ranges on adjacent days disjoint.
f3b0bba
to
c36a375
Compare
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.
LGTM
Primarily this change is aimed at changing the behaviour of the
FiniteDateRange.union. Previously FiniteDateRanges would not make
a union with adjacent ranges, despite their inclusive boundaries
meaning the presence of an adjacent range means they cover a
fully contiguous period of time.
This change makes adjacent FiniteDateRanges unionize by no longer
considering ranges on adjacent days disjoint.