Skip to content

Commit c3a4ebf

Browse files
BUG: Timestamp.replace now reflects changes onto Timestamp.unit (#57749)
1 parent 73fd026 commit c3a4ebf

File tree

3 files changed

+32
-5
lines changed

3 files changed

+32
-5
lines changed

doc/source/whatsnew/v3.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ Bug fixes
338338
- Fixed bug in :meth:`Series.diff` allowing non-integer values for the ``periods`` argument. (:issue:`56607`)
339339
- Fixed bug in :meth:`Series.rank` that doesn't preserve missing values for nullable integers when ``na_option='keep'``. (:issue:`56976`)
340340
- Fixed bug in :meth:`Series.replace` and :meth:`DataFrame.replace` inconsistently replacing matching instances when ``regex=True`` and missing values are present. (:issue:`56599`)
341+
- Fixed bug in :meth:`Timestamp.replace` where it would not reflect changes into :meth:`Timestamp.unit`. (:issue:`57749`)
341342

342343
Categorical
343344
^^^^^^^^^^^

pandas/_libs/tslibs/timestamps.pyx

+21-5
Original file line numberDiff line numberDiff line change
@@ -2439,10 +2439,12 @@ default 'raise'
24392439
datetime ts_input
24402440
tzinfo_type tzobj
24412441
_TSObject ts
2442+
NPY_DATETIMEUNIT rep_reso
24422443

24432444
# set to naive if needed
24442445
tzobj = self.tzinfo
24452446
value = self._value
2447+
rep_reso = self._creso
24462448

24472449
# GH 37610. Preserve fold when replacing.
24482450
if fold is None:
@@ -2466,40 +2468,54 @@ default 'raise'
24662468

24672469
if year is not None:
24682470
dts.year = validate("year", year)
2471+
rep_reso = NPY_DATETIMEUNIT.NPY_FR_Y
24692472
if month is not None:
24702473
dts.month = validate("month", month)
2474+
rep_reso = NPY_DATETIMEUNIT.NPY_FR_M
24712475
if day is not None:
24722476
dts.day = validate("day", day)
2477+
rep_reso = NPY_DATETIMEUNIT.NPY_FR_D
24732478
if hour is not None:
24742479
dts.hour = validate("hour", hour)
2480+
rep_reso = NPY_DATETIMEUNIT.NPY_FR_h
24752481
if minute is not None:
24762482
dts.min = validate("minute", minute)
2483+
rep_reso = NPY_DATETIMEUNIT.NPY_FR_m
24772484
if second is not None:
24782485
dts.sec = validate("second", second)
2486+
rep_reso = NPY_DATETIMEUNIT.NPY_FR_s
24792487
if microsecond is not None:
24802488
dts.us = validate("microsecond", microsecond)
2489+
if microsecond > 999:
2490+
rep_reso = NPY_DATETIMEUNIT.NPY_FR_us
2491+
else:
2492+
rep_reso = NPY_DATETIMEUNIT.NPY_FR_ms
24812493
if nanosecond is not None:
24822494
dts.ps = validate("nanosecond", nanosecond) * 1000
2495+
rep_reso = NPY_DATETIMEUNIT.NPY_FR_ns
24832496
if tzinfo is not object:
24842497
tzobj = tzinfo
24852498

2499+
if rep_reso < self._creso:
2500+
rep_reso = self._creso
2501+
24862502
# reconstruct & check bounds
24872503
if tzobj is None:
24882504
# We can avoid going through pydatetime paths, which is robust
24892505
# to datetimes outside of pydatetime range.
24902506
ts = _TSObject()
24912507
try:
2492-
ts.value = npy_datetimestruct_to_datetime(self._creso, &dts)
2508+
ts.value = npy_datetimestruct_to_datetime(rep_reso, &dts)
24932509
except OverflowError as err:
24942510
fmt = dts_to_iso_string(&dts)
24952511
raise OutOfBoundsDatetime(
24962512
f"Out of bounds timestamp: {fmt} with frequency '{self.unit}'"
24972513
) from err
24982514
ts.dts = dts
2499-
ts.creso = self._creso
2515+
ts.creso = rep_reso
25002516
ts.fold = fold
25012517
return create_timestamp_from_ts(
2502-
ts.value, dts, tzobj, fold, reso=self._creso
2518+
ts.value, dts, tzobj, fold, reso=rep_reso
25032519
)
25042520

25052521
elif tzobj is not None and treat_tz_as_pytz(tzobj):
@@ -2518,10 +2534,10 @@ default 'raise'
25182534
ts_input = datetime(**kwargs)
25192535

25202536
ts = convert_datetime_to_tsobject(
2521-
ts_input, tzobj, nanos=dts.ps // 1000, reso=self._creso
2537+
ts_input, tzobj, nanos=dts.ps // 1000, reso=rep_reso
25222538
)
25232539
return create_timestamp_from_ts(
2524-
ts.value, dts, tzobj, fold, reso=self._creso
2540+
ts.value, dts, tzobj, fold, reso=rep_reso
25252541
)
25262542

25272543
def to_julian_date(self) -> np.float64:

pandas/tests/scalar/timestamp/methods/test_replace.py

+10
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,13 @@ def test_replace_preserves_fold(self, fold):
189189
ts_replaced = ts.replace(second=1)
190190

191191
assert ts_replaced.fold == fold
192+
193+
def test_replace_unit(self):
194+
# GH#57749
195+
ts = Timestamp("2023-07-15 23:08:12")
196+
ts1 = Timestamp("2023-07-15 23:08:12.134567")
197+
ts2 = Timestamp("2023-07-15 23:08:12.134567123")
198+
ts = ts.replace(microsecond=ts1.microsecond)
199+
assert ts == ts1
200+
ts = ts.replace(nanosecond=ts2.nanosecond)
201+
assert ts == ts2

0 commit comments

Comments
 (0)