Skip to content

Commit 9ee14ef

Browse files
committed
Add and test contains/in method for interval
1 parent f06960a commit 9ee14ef

2 files changed

Lines changed: 85 additions & 2 deletions

File tree

src/undate/interval.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ class UndateInterval:
2323
latest: Union[Undate, None]
2424
label: Union[str, None]
2525

26-
# TODO: let's think about adding an optional precision / length /size field
27-
# using DatePrecision
26+
# TODO: think about adding an optional precision / length /size field
27+
# using DatePrecision for intervals of any standard duration (decade, century)
2828

2929
def __init__(
3030
self,
@@ -123,6 +123,41 @@ def duration(self) -> Timedelta:
123123
# if one year is known and the other is not?
124124
raise NotImplementedError
125125

126+
def __contains__(self, other: object) -> bool:
127+
"""Determine if another interval or date falls within this
128+
interval."""
129+
# support comparison with another interval
130+
if isinstance(other, UndateInterval):
131+
# if two intervals are strictly equal, don't consider
132+
# either one as containing the other
133+
if self == other:
134+
return False
135+
# otherwise compare based on earliest/latest bounds
136+
other_earliest = other.earliest
137+
other_latest = other.latest
138+
else:
139+
# otherwise, try to convert to an Undate
140+
try:
141+
other = Undate.to_undate(other)
142+
other_latest = other_earliest = other
143+
except TypeError:
144+
# if conversion fails, then we don't support comparison
145+
raise
146+
147+
# if either bound of the current interval is None,
148+
# then it is an open interval and we don't need to check the other value.
149+
# if the other value is set, then check that it falls within the
150+
# bounds of this interval
151+
return (
152+
self.earliest is None
153+
or other_earliest is not None
154+
and other_earliest >= self.earliest
155+
) and (
156+
self.latest is None
157+
or other_latest is not None
158+
and other_latest <= self.latest
159+
)
160+
126161
def intersection(self, other: "UndateInterval") -> Optional["UndateInterval"]:
127162
"""Determine the intersection or overlap between two :class:`UndateInterval`
128163
objects and return a new interval. Returns None if there is no overlap.

tests/test_interval.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,51 @@ def test_intersection(self):
181181
assert before_20th.intersection(after_c11th) == UndateInterval(
182182
Undate(1001), Undate(1901)
183183
)
184+
185+
def test_contains(self):
186+
century11th = UndateInterval(Undate(1001), Undate(1100))
187+
century20th = UndateInterval(Undate(1901), Undate(2000))
188+
decade1990s = UndateInterval(Undate(1990), Undate(1999))
189+
# an interval doesn't contain itself
190+
for interval in [century11th, century20th, decade1990s]:
191+
assert interval not in interval
192+
193+
# checking if an interval is within another interval
194+
assert decade1990s in century20th
195+
assert decade1990s not in century11th
196+
assert century11th not in decade1990s
197+
assert century20th not in decade1990s
198+
# a specific date can be contained by an interval
199+
y2k = Undate(2000)
200+
assert y2k in century20th
201+
assert y2k not in century11th
202+
# partially known date should work too
203+
april_someyear = Undate("198X", 4)
204+
assert april_someyear in century20th
205+
assert april_someyear not in century11th
206+
# conversion from datetime.date also works
207+
assert datetime.date(1922, 5, 1) in century20th
208+
# unsupported types result in a type error
209+
with pytest.raises(TypeError):
210+
"nineteen-eighty-four" in century20th
211+
212+
# contains check with half-open intervals
213+
after_c11th = UndateInterval(Undate(1001), None)
214+
before_20th = UndateInterval(None, Undate(1901))
215+
# neither of them contains the other
216+
assert after_c11th not in before_20th
217+
assert before_20th not in after_c11th
218+
# nor are they contained by a smaller range
219+
assert after_c11th not in decade1990s
220+
assert before_20th not in decade1990s
221+
222+
# all of our previous test dates are in the 1900s,
223+
# so they are after the 11th century and not before the 20th
224+
for period in [decade1990s, y2k, april_someyear]:
225+
assert period in after_c11th
226+
assert period not in before_20th
227+
228+
# fully open interval - is this even meaningful?
229+
whenever = UndateInterval(None, None)
230+
assert decade1990s in whenever
231+
assert whenever not in whenever

0 commit comments

Comments
 (0)