Skip to content

WIP: Add DayEnd, DayBegin Offsets (Help Wanted) #27087

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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion pandas/core/arrays/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,28 @@ class TimelikeOps:
def _round(self, freq, mode, ambiguous, nonexistent):
# round the local times
values = _ensure_datetimelike_to_i8(self)
result = round_nsint64(values, mode, freq)
try:
result = round_nsint64(values, mode, freq)
except ValueError as e:
# non-fixed offset, cannot do ns calculation.
# user freq.rollforward/back machinery instead
offset = frequencies.to_offset(freq)
if "non-fixed" in str(e):
if mode == RoundTo.PLUS_INFTY:
result = (self + offset).asi8
elif mode == RoundTo.MINUS_INFTY:
result = (self - offset).asi8
elif mode == RoundTo.NEAREST_HALF_EVEN:
msg = ("round only supported fixed offsets "
"(i.e. 'Day' is ok, 'MonthEnd' is not). "
"You may use snap or floor/ceil if applicable.")
raise ValueError(msg)
# upper = (self + offset).asi8
# lower = (self - offset).asi8
# mask = (upper-values) <= (values-lower)
# result = np.where(mask, lower, upper).asi8
else:
raise e
result = self._maybe_mask_results(result, fill_value=NaT)

dtype = self.dtype
Expand Down
65 changes: 65 additions & 0 deletions pandas/tseries/offsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

__all__ = ['Day', 'BusinessDay', 'BDay', 'CustomBusinessDay', 'CDay',
'CBMonthEnd', 'CBMonthBegin',
'DayBegin', 'DayEnd',
'MonthBegin', 'BMonthBegin', 'MonthEnd', 'BMonthEnd',
'SemiMonthEnd', 'SemiMonthBegin',
'BusinessHour', 'CustomBusinessHour',
Expand Down Expand Up @@ -907,6 +908,68 @@ def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri',
BusinessHourMixin.__init__(self, start=start, end=end, offset=offset)


# ---------------------------------------------------------------------
# Day-Based Offset Classes


class DayEnd(SingleConstructorOffset):
_adjust_dst = True
_attributes = frozenset(['n', 'normalize'])

__init__ = BaseOffset.__init__

@property
def name(self):
if self.isAnchored:
return self.rule_code
else:
month = ccalendar.MONTH_ALIASES[self.n]
return "{code}-{month}".format(code=self.rule_code,
month=month)

def onOffset(self, dt):
if self.normalize and not _is_normalized(dt):
return False
return self.apply(dt) == dt

@apply_wraps
def apply(self, other):
tz = other.tzinfo
naive = other.replace(tzinfo=None)
shifted = Timestamp(year=naive.year, month=naive.month, day=naive.day)
if self._day_opt == 'end':
n = self.n+1 if self.n < 0 else self.n
shifted += Timedelta(days=n, nanoseconds=-1)
elif self._day_opt == 'start':
n = self.n if self.n < 0 else self.n
shifted += Timedelta(days=n)
elif self._day_opt != 'start':
raise ValurError("Unknown _day_opt value")
return conversion.localize_pydatetime(shifted, tz)

@apply_index_wraps
def apply_index(self, i):
# TODO: going through __new__ raises on call to _validate_frequency;
# are we passing incorrect freq?
return type(i)._simple_new(np.array([self.apply(_).value for _ in i]), freq=i.freq, dtype=i.dtype)


class DayEnd(DayEnd):
"""
DateOffset of one day end.
"""
_prefix = 'DE'
_day_opt = 'end'


class DayBegin(DayEnd):
"""
DateOffset of one day at beginning.
"""
_prefix = 'DS'
_day_opt = 'start'


# ---------------------------------------------------------------------
# Month-Based Offset Classes

Expand Down Expand Up @@ -2512,6 +2575,8 @@ def generate_range(start=None, end=None, periods=None, offset=BDay()):
CustomBusinessMonthEnd, # 'CBM'
CustomBusinessMonthBegin, # 'CBMS'
CustomBusinessHour, # 'CBH'
DayEnd, # 'DE'
DayBegin, # 'DS'
MonthEnd, # 'M'
MonthBegin, # 'MS'
Nano, # 'N'
Expand Down