diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index dc17f13da068b..897c53c5c75d1 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -48,7 +48,7 @@ import pandas.core.common as com from pandas.tseries.frequencies import get_period_alias, to_offset -from pandas.tseries.offsets import Day, Tick, generate_range +from pandas.tseries.offsets import BDay, Day, Tick _midnight = time(0, 0) @@ -2336,3 +2336,81 @@ def _maybe_localize_point(ts, is_none, is_not_none, freq, tz, ambiguous, nonexis localize_args["tz"] = tz ts = ts.tz_localize(**localize_args) return ts + + +def generate_range(start=None, end=None, periods=None, offset=BDay()): + """ + Generates a sequence of dates corresponding to the specified time + offset. Similar to dateutil.rrule except uses pandas DateOffset + objects to represent time increments. + + Parameters + ---------- + start : datetime, (default None) + end : datetime, (default None) + periods : int, (default None) + offset : DateOffset, (default BDay()) + + Notes + ----- + * This method is faster for generating weekdays than dateutil.rrule + * At least two of (start, end, periods) must be specified. + * If both start and end are specified, the returned dates will + satisfy start <= date <= end. + + Returns + ------- + dates : generator object + """ + offset = to_offset(offset) + + start = Timestamp(start) + start = start if start is not NaT else None + end = Timestamp(end) + end = end if end is not NaT else None + + if start and not offset.is_on_offset(start): + start = offset.rollforward(start) + + elif end and not offset.is_on_offset(end): + end = offset.rollback(end) + + if periods is None and end < start and offset.n >= 0: + end = None + periods = 0 + + if end is None: + end = start + (periods - 1) * offset + + if start is None: + start = end - (periods - 1) * offset + + cur = start + if offset.n >= 0: + while cur <= end: + yield cur + + if cur == end: + # GH#24252 avoid overflows by not performing the addition + # in offset.apply unless we have to + break + + # faster than cur + offset + next_date = offset.apply(cur) + if next_date <= cur: + raise ValueError(f"Offset {offset} did not increment date") + cur = next_date + else: + while cur >= end: + yield cur + + if cur == end: + # GH#24252 avoid overflows by not performing the addition + # in offset.apply unless we have to + break + + # faster than cur + offset + next_date = offset.apply(cur) + if next_date >= cur: + raise ValueError(f"Offset {offset} did not decrement date") + cur = next_date diff --git a/pandas/tests/indexes/datetimes/test_date_range.py b/pandas/tests/indexes/datetimes/test_date_range.py index 3d603d64731ee..e1936fa819baf 100644 --- a/pandas/tests/indexes/datetimes/test_date_range.py +++ b/pandas/tests/indexes/datetimes/test_date_range.py @@ -16,15 +16,9 @@ import pandas as pd from pandas import DatetimeIndex, Timestamp, bdate_range, date_range, offsets import pandas._testing as tm +from pandas.core.arrays.datetimes import generate_range -from pandas.tseries.offsets import ( - BDay, - CDay, - DateOffset, - MonthEnd, - generate_range, - prefix_mapping, -) +from pandas.tseries.offsets import BDay, CDay, DateOffset, MonthEnd, prefix_mapping START, END = datetime(2009, 1, 1), datetime(2010, 1, 1) diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py index 4c06fea51ea8d..4bfb0b0a317b7 100644 --- a/pandas/tseries/offsets.py +++ b/pandas/tseries/offsets.py @@ -6,7 +6,6 @@ import numpy as np from pandas._libs.tslibs import ( - NaT, Period, Timedelta, Timestamp, @@ -2305,89 +2304,6 @@ class Nano(Tick): CBMonthBegin = CustomBusinessMonthBegin CDay = CustomBusinessDay -# --------------------------------------------------------------------- - - -def generate_range(start=None, end=None, periods=None, offset=BDay()): - """ - Generates a sequence of dates corresponding to the specified time - offset. Similar to dateutil.rrule except uses pandas DateOffset - objects to represent time increments. - - Parameters - ---------- - start : datetime, (default None) - end : datetime, (default None) - periods : int, (default None) - offset : DateOffset, (default BDay()) - - Notes - ----- - * This method is faster for generating weekdays than dateutil.rrule - * At least two of (start, end, periods) must be specified. - * If both start and end are specified, the returned dates will - satisfy start <= date <= end. - - Returns - ------- - dates : generator object - """ - from pandas.tseries.frequencies import to_offset - - offset = to_offset(offset) - - start = Timestamp(start) - start = start if start is not NaT else None - end = Timestamp(end) - end = end if end is not NaT else None - - if start and not offset.is_on_offset(start): - start = offset.rollforward(start) - - elif end and not offset.is_on_offset(end): - end = offset.rollback(end) - - if periods is None and end < start and offset.n >= 0: - end = None - periods = 0 - - if end is None: - end = start + (periods - 1) * offset - - if start is None: - start = end - (periods - 1) * offset - - cur = start - if offset.n >= 0: - while cur <= end: - yield cur - - if cur == end: - # GH#24252 avoid overflows by not performing the addition - # in offset.apply unless we have to - break - - # faster than cur + offset - next_date = offset.apply(cur) - if next_date <= cur: - raise ValueError(f"Offset {offset} did not increment date") - cur = next_date - else: - while cur >= end: - yield cur - - if cur == end: - # GH#24252 avoid overflows by not performing the addition - # in offset.apply unless we have to - break - - # faster than cur + offset - next_date = offset.apply(cur) - if next_date >= cur: - raise ValueError(f"Offset {offset} did not decrement date") - cur = next_date - - prefix_mapping = { offset._prefix: offset for offset in [