diff --git a/doc/source/whatsnew/v2.2.1.rst b/doc/source/whatsnew/v2.2.1.rst index e1da0af59b3b8..792f98f1366dd 100644 --- a/doc/source/whatsnew/v2.2.1.rst +++ b/doc/source/whatsnew/v2.2.1.rst @@ -40,6 +40,7 @@ Fixed regressions - Fixed regression in :meth:`Index.join` raising ``TypeError`` when joining an empty index to a non-empty index containing mixed dtype values (:issue:`57048`) - Fixed regression in :meth:`Series.pct_change` raising a ``ValueError`` for an empty :class:`Series` (:issue:`57056`) - Fixed regression in :meth:`Series.to_numpy` when dtype is given as float and the data contains NaNs (:issue:`57121`) +- Fixed regression where dividing a :class:`tseries.offsets.Tick` by a value greater than its :attr:`tseries.offsets.Tick.n` would incorrectly return 0 instead of changing to a higher resolution (:issue:`57264`) .. --------------------------------------------------------------------------- .. _whatsnew_221.bug_fixes: diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index f6c69cf6d3875..9e068e952a94c 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -60,6 +60,7 @@ from pandas._libs.tslibs.np_datetime cimport ( cmp_dtstructs, cmp_scalar, convert_reso, + get_conversion_factor, get_datetime64_unit, get_unit_from_dtype, import_pandas_datetime, @@ -965,6 +966,7 @@ cdef _timedelta_from_value_and_reso(cls, int64_t value, NPY_DATETIMEUNIT reso): # many cases would fall outside of the pytimedelta implementation bounds. # We pass 0 instead, and override seconds, microseconds, days. # In principle we could pass 0 for ns and us too. + if reso == NPY_FR_ns: td_base = _Timedelta.__new__(cls, microseconds=int(value) // 1000) elif reso == NPY_DATETIMEUNIT.NPY_FR_us: @@ -2047,6 +2049,9 @@ class Timedelta(_Timedelta): __rmul__ = __mul__ def __truediv__(self, other): + cdef: + NPY_DATETIMEUNIT curr_unit, next_unit + int64_t curr_value if _should_cast_to_timedelta(other): # We interpret NaT as timedelta64("NaT") other = Timedelta(other) @@ -2066,8 +2071,21 @@ class Timedelta(_Timedelta): other = int(other) if isinstance(other, cnp.floating): other = float(other) + + # If we have a non-nano timedelta and + # self._value < other, we would get 0 as a result, + # which is not correct. + # We need to try going to a higher resolution to fix this + curr_value = self._value + curr_unit = self._creso + while curr_value < other and curr_unit < NPY_FR_ns: + next_unit = (curr_unit + 1) + cf = get_conversion_factor(curr_unit, next_unit) + curr_value *= cf + curr_unit = next_unit + return Timedelta._from_value_and_reso( - (self._value/ other), self._creso + (curr_value / other), curr_unit ) elif is_array(other): diff --git a/pandas/tests/tseries/offsets/test_ticks.py b/pandas/tests/tseries/offsets/test_ticks.py index 399b7038d3426..085e22c315d23 100644 --- a/pandas/tests/tseries/offsets/test_ticks.py +++ b/pandas/tests/tseries/offsets/test_ticks.py @@ -266,6 +266,11 @@ def test_tick_division(cls): result = off / 1000 assert isinstance(result, offsets.Tick) assert not isinstance(result, cls) + # GH57264 + # Test that roundtripping works + # to ensure that the timedelta division isn't giving something wrong + # (e.g. 0) + assert result * 1000 == off assert result._as_pd_timedelta == off._as_pd_timedelta / 1000 if cls._nanos_inc < Timedelta(seconds=1)._value: