diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index dfd64dd50f213..1df5468869df5 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -1653,15 +1653,14 @@ class Timedelta(_Timedelta): int64_t result, unit, remainder ndarray[int64_t] arr - if self._reso != NPY_FR_ns: - raise NotImplementedError - from pandas._libs.tslibs.offsets import to_offset - unit = to_offset(freq).nanos + + to_offset(freq).nanos # raises on non-fixed freq + unit = delta_to_nanoseconds(to_offset(freq), self._reso) arr = np.array([self.value], dtype="i8") result = round_nsint64(arr, mode, unit)[0] - return Timedelta(result, unit="ns") + return Timedelta._from_value_and_reso(result, self._reso) def round(self, freq): """ diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 711d10222c133..8a2810825fc1d 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -1645,10 +1645,10 @@ class Timestamp(_Timestamp): def _round(self, freq, mode, ambiguous='raise', nonexistent='raise'): cdef: - int64_t nanos = to_offset(freq).nanos + int64_t nanos - if self._reso != NPY_FR_ns: - raise NotImplementedError(self._reso) + to_offset(freq).nanos # raises on non-fixed freq + nanos = delta_to_nanoseconds(to_offset(freq), self._reso) if self.tz is not None: value = self.tz_localize(None).value @@ -1659,7 +1659,7 @@ class Timestamp(_Timestamp): # Will only ever contain 1 element for timestamp r = round_nsint64(value, mode, nanos)[0] - result = Timestamp(r, unit='ns') + result = Timestamp._from_value_and_reso(r, self._reso, None) if self.tz is not None: result = result.tz_localize( self.tz, ambiguous=ambiguous, nonexistent=nonexistent diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index b9388c8047df9..4b1d50c23c110 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -1933,7 +1933,8 @@ def _round(self, freq, mode, ambiguous, nonexistent): values = self.view("i8") values = cast(np.ndarray, values) - nanos = to_offset(freq).nanos + nanos = to_offset(freq).nanos # raises on non-fixed frequencies + nanos = delta_to_nanoseconds(to_offset(freq), self._reso) result_i8 = round_nsint64(values, mode, nanos) result = self._maybe_mask_results(result_i8, fill_value=iNaT) result = result.view(self._ndarray.dtype) diff --git a/pandas/tests/scalar/timedelta/test_timedelta.py b/pandas/tests/scalar/timedelta/test_timedelta.py index 00072e6724a6b..99b3bbf0186bb 100644 --- a/pandas/tests/scalar/timedelta/test_timedelta.py +++ b/pandas/tests/scalar/timedelta/test_timedelta.py @@ -661,6 +661,22 @@ def test_round_sanity(self, val, method): assert np.abs((res - td).value) < nanos assert res.value % nanos == 0 + @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"]) + def test_round_non_nano(self, unit): + td = Timedelta("1 days 02:34:57")._as_unit(unit) + + res = td.round("min") + assert res == Timedelta("1 days 02:35:00") + assert res._reso == td._reso + + res = td.floor("min") + assert res == Timedelta("1 days 02:34:00") + assert res._reso == td._reso + + res = td.ceil("min") + assert res == Timedelta("1 days 02:35:00") + assert res._reso == td._reso + def test_contains(self): # Checking for any NaT-like objects # GH 13603 diff --git a/pandas/tests/scalar/timestamp/test_unary_ops.py b/pandas/tests/scalar/timestamp/test_unary_ops.py index 4ac50e3f4e034..2146e32a437a9 100644 --- a/pandas/tests/scalar/timestamp/test_unary_ops.py +++ b/pandas/tests/scalar/timestamp/test_unary_ops.py @@ -148,27 +148,26 @@ def test_round_minute_freq(self, test_input, freq, expected, rounder): result = func(freq) assert result == expected - def test_ceil(self): - dt = Timestamp("20130101 09:10:11") + @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"]) + def test_ceil(self, unit): + dt = Timestamp("20130101 09:10:11")._as_unit(unit) result = dt.ceil("D") expected = Timestamp("20130102") assert result == expected + assert result._reso == dt._reso - def test_floor(self): - dt = Timestamp("20130101 09:10:11") + @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"]) + def test_floor(self, unit): + dt = Timestamp("20130101 09:10:11")._as_unit(unit) result = dt.floor("D") expected = Timestamp("20130101") assert result == expected + assert result._reso == dt._reso @pytest.mark.parametrize("method", ["ceil", "round", "floor"]) @pytest.mark.parametrize( "unit", - [ - "ns", - pytest.param("us", marks=pytest.mark.xfail(reason="round not implemented")), - pytest.param("ms", marks=pytest.mark.xfail(reason="round not implemented")), - pytest.param("s", marks=pytest.mark.xfail(reason="round not implemented")), - ], + ["ns", "us", "ms", "s"], ) def test_round_dst_border_ambiguous(self, method, unit): # GH 18946 round near "fall back" DST @@ -203,12 +202,7 @@ def test_round_dst_border_ambiguous(self, method, unit): ) @pytest.mark.parametrize( "unit", - [ - "ns", - pytest.param("us", marks=pytest.mark.xfail(reason="round not implemented")), - pytest.param("ms", marks=pytest.mark.xfail(reason="round not implemented")), - pytest.param("s", marks=pytest.mark.xfail(reason="round not implemented")), - ], + ["ns", "us", "ms", "s"], ) def test_round_dst_border_nonexistent(self, method, ts_str, freq, unit): # GH 23324 round near "spring forward" DST