diff --git a/pandas/_libs/tslibs/c_timestamp.pyx b/pandas/_libs/tslibs/c_timestamp.pyx index cde2c2573072f..86979fa7946d9 100644 --- a/pandas/_libs/tslibs/c_timestamp.pyx +++ b/pandas/_libs/tslibs/c_timestamp.pyx @@ -40,6 +40,7 @@ from pandas._libs.tslibs.timezones cimport ( get_timezone, is_utc, tz_compare) from pandas._libs.tslibs.timezones import UTC from pandas._libs.tslibs.tzconversion cimport tz_convert_single +from pandas._libs.tslibs.offsets cimport is_tick_object class NullFrequencyError(ValueError): @@ -247,13 +248,9 @@ cdef class _Timestamp(datetime): elif is_integer_object(other): raise integer_op_not_supported(self) - elif PyDelta_Check(other) or hasattr(other, 'delta'): - # delta --> offsets.Tick + elif PyDelta_Check(other): # logic copied from delta_to_nanoseconds to prevent circular import - if hasattr(other, 'nanos'): - # Tick - nanos = other.nanos - elif hasattr(other, 'delta'): + if hasattr(other, 'delta'): # pd.Timedelta nanos = other.value elif PyDelta_Check(other): @@ -264,6 +261,16 @@ cdef class _Timestamp(datetime): result = type(self)(self.value + nanos, tz=self.tzinfo, freq=self.freq) return result + elif is_tick_object(other): + try: + nanos = other.nanos + except OverflowError: + raise OverflowError( + f"the add operation between {other} and {self} will overflow" + ) + result = type(self)(self.value + nanos, tz=self.tzinfo, freq=self.freq) + return result + elif is_array(other): if other.dtype.kind in ['i', 'u']: raise integer_op_not_supported(self) @@ -280,8 +287,7 @@ cdef class _Timestamp(datetime): def __sub__(self, other): if (is_timedelta64_object(other) or is_integer_object(other) or - PyDelta_Check(other) or hasattr(other, 'delta')): - # `delta` attribute is for offsets.Tick or offsets.Week obj + PyDelta_Check(other) or is_tick_object(other)): neg_other = -other return self + neg_other diff --git a/pandas/_libs/tslibs/nattype.pyx b/pandas/_libs/tslibs/nattype.pyx index ec397a470f2ec..a45e10e8337a4 100644 --- a/pandas/_libs/tslibs/nattype.pyx +++ b/pandas/_libs/tslibs/nattype.pyx @@ -147,8 +147,7 @@ cdef class _NaT(datetime): return c_NaT elif util.is_datetime64_object(other) or util.is_timedelta64_object(other): return c_NaT - elif hasattr(other, "delta"): - # Timedelta, offsets.Tick, offsets.Week + elif util.is_offset_object(other): return c_NaT elif util.is_integer_object(other) or util.is_period_object(other): @@ -184,8 +183,7 @@ cdef class _NaT(datetime): return c_NaT elif util.is_datetime64_object(other) or util.is_timedelta64_object(other): return c_NaT - elif hasattr(other, "delta"): - # offsets.Tick, offsets.Week + elif util.is_offset_object(other): return c_NaT elif util.is_integer_object(other) or util.is_period_object(other): diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index 878dbcfd99842..f5d32237280eb 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -57,8 +57,7 @@ from pandas._libs.tslibs.resolution import Resolution from pandas._libs.tslibs.nattype import nat_strings from pandas._libs.tslibs.nattype cimport ( _nat_scalar_rules, NPY_NAT, is_null_datetimelike, c_NaT as NaT) -from pandas._libs.tslibs.offsets cimport to_offset -from pandas._libs.tslibs.offsets import _Tick +from pandas._libs.tslibs.offsets cimport to_offset, is_tick_object from pandas._libs.tslibs.tzconversion cimport tz_convert_utc_to_tzlocal @@ -1588,9 +1587,9 @@ cdef class _Period: int64_t nanos, offset_nanos if (PyDelta_Check(other) or util.is_timedelta64_object(other) or - isinstance(other, _Tick)): + is_tick_object(other)): offset = to_offset(self.freq.rule_code) - if isinstance(offset, _Tick): + if is_tick_object(offset): nanos = delta_to_nanoseconds(other) offset_nanos = delta_to_nanoseconds(offset) if nanos % offset_nanos == 0: diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index 032d3186217e3..58600678c0938 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -31,8 +31,7 @@ from pandas._libs.tslibs.np_datetime cimport ( from pandas._libs.tslibs.nattype import nat_strings from pandas._libs.tslibs.nattype cimport ( checknull_with_nat, NPY_NAT, c_NaT as NaT) -from pandas._libs.tslibs.offsets cimport to_offset -from pandas._libs.tslibs.offsets import _Tick as Tick +from pandas._libs.tslibs.offsets cimport to_offset, is_tick_object # ---------------------------------------------------------------------- # Constants @@ -140,10 +139,10 @@ def ints_to_pytimedelta(const int64_t[:] arr, box=False): # ---------------------------------------------------------------------- cpdef int64_t delta_to_nanoseconds(delta) except? -1: - if hasattr(delta, 'nanos'): + if is_tick_object(delta): return delta.nanos - if hasattr(delta, 'delta'): - delta = delta.delta + if isinstance(delta, _Timedelta): + delta = delta.value if is_timedelta64_object(delta): return delta.astype("timedelta64[ns]").item() if is_integer_object(delta): @@ -204,8 +203,8 @@ cdef convert_to_timedelta64(object ts, object unit): else: ts = parse_timedelta_string(ts) ts = np.timedelta64(ts) - elif hasattr(ts, 'delta'): - ts = np.timedelta64(delta_to_nanoseconds(ts), 'ns') + elif is_tick_object(ts): + ts = np.timedelta64(ts.nanos, 'ns') if PyDelta_Check(ts): ts = np.timedelta64(delta_to_nanoseconds(ts), 'ns') @@ -562,12 +561,10 @@ cdef bint _validate_ops_compat(other): # return True if we are compat with operating if checknull_with_nat(other): return True - elif PyDelta_Check(other) or is_timedelta64_object(other): + elif is_any_td_scalar(other): return True elif isinstance(other, str): return True - elif hasattr(other, 'delta'): - return True return False @@ -779,8 +776,7 @@ cdef class _Timedelta(timedelta): if isinstance(other, _Timedelta): ots = other - elif (is_timedelta64_object(other) or PyDelta_Check(other) - or isinstance(other, Tick)): + elif is_any_td_scalar(other): ots = Timedelta(other) # TODO: watch out for overflows @@ -1249,8 +1245,8 @@ class Timedelta(_Timedelta): if unit is not None: value = value.astype(f'timedelta64[{unit}]') value = value.astype('timedelta64[ns]') - elif hasattr(value, 'delta'): - value = np.timedelta64(delta_to_nanoseconds(value.delta), 'ns') + elif is_tick_object(value): + value = np.timedelta64(value.nanos, 'ns') elif is_integer_object(value) or is_float_object(value): # unit=None is de-facto 'ns' unit = parse_timedelta_unit(unit) @@ -1460,7 +1456,7 @@ class Timedelta(_Timedelta): cdef bint is_any_td_scalar(object obj): return ( - PyDelta_Check(obj) or is_timedelta64_object(obj) or isinstance(obj, Tick) + PyDelta_Check(obj) or is_timedelta64_object(obj) or is_tick_object(obj) )