From ba7518109958cd8060968582033ff29eb8b7a800 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Wed, 20 May 2020 09:30:23 -0700 Subject: [PATCH] REF: Make Period arith mirror PeriodArray arith --- pandas/_libs/tslibs/period.pyx | 72 +++++++++++++++++++----------- pandas/_libs/tslibs/timedeltas.pyx | 7 ++- 2 files changed, 49 insertions(+), 30 deletions(-) diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index 9bcf162b6d666..bf4e5dce123d7 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -1580,36 +1580,37 @@ cdef class _Period: return PyObject_RichCompareBool(self.ordinal, other.ordinal, op) elif other is NaT: return _nat_scalar_rules[op] - return NotImplemented + return NotImplemented # TODO: ndarray[object]? def __hash__(self): return hash((self.ordinal, self.freqstr)) - def _add_delta(self, other): + def _add_delta(self, other) -> "Period": cdef: int64_t nanos, offset_nanos - if (PyDelta_Check(other) or util.is_timedelta64_object(other) or - is_tick_object(other)): - offset = to_offset(self.freq.rule_code) - if is_tick_object(offset): - nanos = delta_to_nanoseconds(other) - offset_nanos = delta_to_nanoseconds(offset) - if nanos % offset_nanos == 0: - ordinal = self.ordinal + (nanos // offset_nanos) - return Period(ordinal=ordinal, freq=self.freq) - raise IncompatibleFrequency("Input cannot be converted to " - f"Period(freq={self.freqstr})") - elif is_offset_object(other): - if other.base == self.freq.base: - ordinal = self.ordinal + other.n + if is_tick_object(self.freq): + nanos = delta_to_nanoseconds(other) + offset_nanos = self.freq.base.nanos + if nanos % offset_nanos == 0: + ordinal = self.ordinal + (nanos // offset_nanos) return Period(ordinal=ordinal, freq=self.freq) - msg = DIFFERENT_FREQ.format(cls=type(self).__name__, - own_freq=self.freqstr, - other_freq=other.freqstr) - raise IncompatibleFrequency(msg) - else: # pragma no cover - return NotImplemented + raise IncompatibleFrequency("Input cannot be converted to " + f"Period(freq={self.freqstr})") + + def _add_offset(self, other) -> "Period": + # Non-Tick DateOffset other + cdef: + int64_t ordinal + + if other.base == self.freq.base: + ordinal = self.ordinal + other.n + return Period(ordinal=ordinal, freq=self.freq) + + msg = DIFFERENT_FREQ.format(cls=type(self).__name__, + own_freq=self.freqstr, + other_freq=other.freqstr) + raise IncompatibleFrequency(msg) def __add__(self, other): if not is_period_object(self): @@ -1618,9 +1619,10 @@ cdef class _Period: return NaT return other.__add__(self) - if (PyDelta_Check(other) or util.is_timedelta64_object(other) or - is_offset_object(other)): + if is_any_tdlike_scalar(other): return self._add_delta(other) + elif is_offset_object(other): + return self._add_offset(other) elif other is NaT: return NaT elif util.is_integer_object(other): @@ -1644,8 +1646,11 @@ cdef class _Period: return NaT return NotImplemented - elif (PyDelta_Check(other) or util.is_timedelta64_object(other) or - is_offset_object(other)): + elif is_any_tdlike_scalar(other): + neg_other = -other + return self + neg_other + elif is_offset_object(other): + # Non-Tick DateOffset neg_other = -other return self + neg_other elif util.is_integer_object(other): @@ -2516,3 +2521,18 @@ def validate_end_alias(how): if how not in {'S', 'E'}: raise ValueError('How must be one of S or E') return how + + +cpdef is_any_tdlike_scalar(object obj): + """ + Cython equivalent for `isinstance(obj, (timedelta, np.timedelta64, Tick))` + + Parameters + ---------- + obj : object + + Returns + ------- + bool + """ + return util.is_timedelta64_object(obj) or PyDelta_Check(obj) or is_tick_object(obj) diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index c336e5f990f9a..f0e1dce223f7b 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -176,7 +176,7 @@ cdef convert_to_timedelta64(object ts, object unit): """ if checknull_with_nat(ts): return np.timedelta64(NPY_NAT) - elif isinstance(ts, Timedelta): + elif isinstance(ts, _Timedelta): # already in the proper format ts = np.timedelta64(ts.value) elif is_datetime64_object(ts): @@ -1220,10 +1220,9 @@ class Timedelta(_Timedelta): # GH 30543 if pd.Timedelta already passed, return it # check that only value is passed - if (isinstance(value, Timedelta) and unit is None and - len(kwargs) == 0): + if isinstance(value, _Timedelta) and unit is None and len(kwargs) == 0: return value - elif isinstance(value, Timedelta): + elif isinstance(value, _Timedelta): value = value.value elif isinstance(value, str): if len(value) > 0 and value[0] == 'P':