From 5e6c223d6e8c5a08e1eaa7282e226dc32f0833ee Mon Sep 17 00:00:00 2001 From: Diogo Miranda Date: Sat, 22 Jun 2024 15:25:28 +0100 Subject: [PATCH] BUG: Fix bug where when replaced with 0 it Timestamp.replace didn't recalculate precision --- doc/source/whatsnew/v3.0.0.rst | 2 +- pandas/_libs/tslibs/timestamps.pyx | 17 ++++++++++++++++- .../scalar/timestamp/methods/test_replace.py | 6 ++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 626727a64fea7c..9aac05e0f8c7ce 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -473,7 +473,6 @@ Performance improvements Bug fixes ~~~~~~~~~ -- Fixed bug in :meth:`Timestamp.replace` where it would not reflect changes into :meth:`Timestamp.unit`. (:issue:`57749`) Categorical ^^^^^^^^^^^ @@ -491,6 +490,7 @@ Datetimelike - Bug in :meth:`DatetimeIndex.is_year_start` and :meth:`DatetimeIndex.is_quarter_start` does not raise on Custom business days frequencies bigger then "1C" (:issue:`58664`) - Bug in :meth:`DatetimeIndex.is_year_start` and :meth:`DatetimeIndex.is_quarter_start` returning ``False`` on double-digit frequencies (:issue:`58523`) - Bug in setting scalar values with mismatched resolution into arrays with non-nanosecond ``datetime64``, ``timedelta64`` or :class:`DatetimeTZDtype` incorrectly truncating those scalars (:issue:`56410`) +- Bug in :meth:`Timestamp.replace` where it would not reflect changes into :meth:`Timestamp.unit`. (:issue:`57749`) Timedelta ^^^^^^^^^ diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 9130c80c66e817..9b805e486401eb 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -2633,6 +2633,8 @@ default 'raise' pandas_datetime_to_datetimestruct(value, self._creso, &dts) dts.ps = self.nanosecond * 1000 + zero_set = False + # replace def validate(k, v): """ validate integers """ @@ -2666,13 +2668,26 @@ default 'raise' rep_reso = NPY_DATETIMEUNIT.NPY_FR_us else: rep_reso = NPY_DATETIMEUNIT.NPY_FR_ms + if microsecond == 0: + zero_set = True if nanosecond is not None: dts.ps = validate("nanosecond", nanosecond) * 1000 rep_reso = NPY_DATETIMEUNIT.NPY_FR_ns + if nanosecond == 0: + zero_set = True if tzinfo is not object: tzobj = tzinfo - if rep_reso < self._creso: + # Recalculate the replacement resolution if a unit was replaced with 0 + if zero_set: + if dts.ps != 0: + rep_reso = NPY_DATETIMEUNIT.NPY_FR_ns + elif dts.us != 0: + rep_reso = NPY_DATETIMEUNIT.NPY_FR_us + else: + rep_reso = NPY_DATETIMEUNIT.NPY_FR_s + + if rep_reso < self._creso and not zero_set: rep_reso = self._creso # reconstruct & check bounds diff --git a/pandas/tests/scalar/timestamp/methods/test_replace.py b/pandas/tests/scalar/timestamp/methods/test_replace.py index 5d511947ffdbf3..927b49ac94f9c6 100644 --- a/pandas/tests/scalar/timestamp/methods/test_replace.py +++ b/pandas/tests/scalar/timestamp/methods/test_replace.py @@ -206,3 +206,9 @@ def test_replace_unit(self): ts = ts.replace(nanosecond=ts2.nanosecond) assert ts.unit == "ns" assert ts == ts2 + + def test_replace_resets_to_more_precise(self): + # GH#57749 + ts = Timestamp(year=2020, month=1, day=1, nanosecond=5) + result = ts.replace(nanosecond=0) + assert result.unit == "s" \ No newline at end of file