Skip to content

Commit c149952

Browse files
committed
Change date_range inclusive
1 parent 3467469 commit c149952

File tree

3 files changed

+79
-8
lines changed

3 files changed

+79
-8
lines changed

pandas/core/arrays/datetimelike.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
Callable,
1212
Literal,
1313
Sequence,
14+
Tuple,
1415
TypeVar,
1516
Union,
1617
cast,
@@ -1823,6 +1824,38 @@ def validate_periods(periods):
18231824
return periods
18241825

18251826

1827+
def validate_inclusiveness(inclusive):
1828+
"""
1829+
Check that the `inclusive` argument is among {"both", "neither", "left", "right"}.
1830+
1831+
Parameters
1832+
----------
1833+
inclusive : {"both", "neither", "left", "right"}
1834+
1835+
Returns
1836+
-------
1837+
left_inclusive : bool
1838+
right_inclusive : bool
1839+
1840+
Raises
1841+
------
1842+
ValueError : if argument is not among valid values
1843+
"""
1844+
left_right_inclusive: Tuple[bool, bool] | None = {
1845+
"both": (True, True),
1846+
"left": (True, False),
1847+
"right": (False, True),
1848+
"neither": (False, False),
1849+
}.get(inclusive)
1850+
1851+
if left_right_inclusive is None:
1852+
raise ValueError(
1853+
"Inclusive has to be either 'both', 'neither', 'left', 'right'"
1854+
)
1855+
left_inclusive, right_inclusive = left_right_inclusive
1856+
return left_inclusive, right_inclusive
1857+
1858+
18261859
def validate_endpoints(closed):
18271860
"""
18281861
Check that the `closed` argument is among [None, "left", "right"]

pandas/core/arrays/datetimes.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ def _generate_range(
393393
normalize=False,
394394
ambiguous="raise",
395395
nonexistent="raise",
396-
closed=None,
396+
inclusive="both",
397397
):
398398

399399
periods = dtl.validate_periods(periods)
@@ -416,7 +416,7 @@ def _generate_range(
416416
if start is NaT or end is NaT:
417417
raise ValueError("Neither `start` nor `end` can be NaT")
418418

419-
left_closed, right_closed = dtl.validate_endpoints(closed)
419+
left_inclusive, right_inclusive = dtl.validate_inclusiveness(inclusive)
420420
start, end, _normalized = _maybe_normalize_endpoints(start, end, normalize)
421421
tz = _infer_tz_from_endpoints(start, end, tz)
422422

@@ -476,10 +476,20 @@ def _generate_range(
476476
arr = arr.astype("M8[ns]", copy=False)
477477
index = cls._simple_new(arr, freq=None, dtype=dtype)
478478

479-
if not left_closed and len(index) and index[0] == start:
480-
index = index[1:]
481-
if not right_closed and len(index) and index[-1] == end:
482-
index = index[:-1]
479+
# do not remove when one side is inclusive
480+
# and removing would leave index empty
481+
to_remove_any = not (
482+
(left_inclusive or right_inclusive)
483+
and len(index) == 1
484+
and start == index[0]
485+
and start == end
486+
)
487+
488+
if to_remove_any:
489+
if (not left_inclusive) and len(index) and index[0] == start:
490+
index = index[1:]
491+
if (not right_inclusive) and len(index) and index[-1] == end:
492+
index = index[:-1]
483493

484494
dtype = tz_to_dtype(tz)
485495
return cls._simple_new(index._ndarray, freq=freq, dtype=dtype)

pandas/core/indexes/datetimes.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -881,7 +881,8 @@ def date_range(
881881
tz=None,
882882
normalize: bool = False,
883883
name: Hashable = None,
884-
closed=None,
884+
closed: bool | lib.NoDefault = lib.no_default,
885+
inclusive: str | None = None,
885886
**kwargs,
886887
) -> DatetimeIndex:
887888
"""
@@ -919,6 +920,12 @@ def date_range(
919920
closed : {None, 'left', 'right'}, optional
920921
Make the interval closed with respect to the given frequency to
921922
the 'left', 'right', or both sides (None, the default).
923+
.. deprecated:: 1.4.0
924+
Argument `closed` have been deprecated
925+
to standardize boundary inputs. Use `inclusive` instead, to set
926+
each bound as closed or open.
927+
inclusive : {"both", "neither", "left", "right"}, default "both"
928+
Include boundaries; Whether to set each bound as closed or open.
922929
**kwargs
923930
For compatibility. Has no effect on the result.
924931
@@ -1029,6 +1036,27 @@ def date_range(
10291036
DatetimeIndex(['2017-01-02', '2017-01-03', '2017-01-04'],
10301037
dtype='datetime64[ns]', freq='D')
10311038
"""
1039+
if inclusive is not None and closed is not lib.no_default:
1040+
raise ValueError(
1041+
"Deprecated Argument `closed` cannot be passed if Argument `inclusive` is not None"
1042+
)
1043+
elif closed is not lib.no_default:
1044+
warnings.warn(
1045+
"Argument `closed` is deprectated in favor of `inclusive`.",
1046+
FutureWarning,
1047+
stacklevel=2,
1048+
)
1049+
if closed is None:
1050+
inclusive = "both"
1051+
elif closed in ("left", "right"):
1052+
inclusive = closed
1053+
else:
1054+
raise ValueError(
1055+
"Argument `closed` has to be either 'left', 'right' or None"
1056+
)
1057+
elif inclusive is None:
1058+
inclusive = "both"
1059+
10321060
if freq is None and com.any_none(periods, start, end):
10331061
freq = "D"
10341062

@@ -1039,7 +1067,7 @@ def date_range(
10391067
freq=freq,
10401068
tz=tz,
10411069
normalize=normalize,
1042-
closed=closed,
1070+
inclusive=inclusive,
10431071
**kwargs,
10441072
)
10451073
return DatetimeIndex._simple_new(dtarr, name=name)

0 commit comments

Comments
 (0)