From cd0dd3ab5264836b2ef5c27c9596b9dd7ff4eeb5 Mon Sep 17 00:00:00 2001 From: iabhi4 Date: Thu, 26 Jun 2025 23:13:42 -0700 Subject: [PATCH] BUG: Raise OutOfBoundsDatetime when replacing with out-of-bounds datetime64[ns] (GH#61671) --- doc/source/whatsnew/v3.0.0.rst | 1 + pandas/core/dtypes/cast.py | 19 +++++++++++++++++++ pandas/tests/frame/methods/test_replace.py | 12 ++++++++++++ 3 files changed, 32 insertions(+) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 099e5bc48353a..c238f29e87c62 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -685,6 +685,7 @@ Datetimelike - Bug in :func:`tseries.frequencies.to_offset` would fail to parse frequency strings starting with "LWOM" (:issue:`59218`) - Bug in :meth:`DataFrame.fillna` raising an ``AssertionError`` instead of ``OutOfBoundsDatetime`` when filling a ``datetime64[ns]`` column with an out-of-bounds timestamp. Now correctly raises ``OutOfBoundsDatetime``. (:issue:`61208`) - Bug in :meth:`DataFrame.min` and :meth:`DataFrame.max` casting ``datetime64`` and ``timedelta64`` columns to ``float64`` and losing precision (:issue:`60850`) +- Bug in :meth:`DataFrame.replace` where attempting to replace a ``datetime64[ns]`` column with an out-of-bounds timestamp would raise an ``AssertionError`` or silently coerce. Now correctly raises ``OutOfBoundsDatetime``. (:issue:`61671`) - Bug in :meth:`Dataframe.agg` with df with missing values resulting in IndexError (:issue:`58810`) - 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`) diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py index dae04ba6244d4..7e6900b03bf4b 100644 --- a/pandas/core/dtypes/cast.py +++ b/pandas/core/dtypes/cast.py @@ -53,6 +53,7 @@ ensure_str, is_bool, is_complex, + is_datetime64_dtype, is_float, is_integer, is_object_dtype, @@ -1358,6 +1359,24 @@ def find_result_type(left_dtype: DtypeObj, right: Any) -> DtypeObj: dtype, _ = infer_dtype_from(right) new_dtype = find_common_type([left_dtype, dtype]) + # special case: datetime64[ns] inferred but the value may be out of bounds + if ( + is_datetime64_dtype(new_dtype) + and lib.is_scalar(right) + and isinstance(right, (np.datetime64, dt.datetime, Timestamp)) + ): + try: + ts = Timestamp(right) + casted = np.datetime64(ts, "ns") + if Timestamp(casted) != ts: + raise OutOfBoundsDatetime( + f"{right!r} overflows datetime64[ns] during dtype inference" + ) + except (OverflowError, ValueError) as e: + raise OutOfBoundsDatetime( + f"Cannot safely store {right!r} in inferred dtype 'datetime64[ns]'" + ) from e + return new_dtype diff --git a/pandas/tests/frame/methods/test_replace.py b/pandas/tests/frame/methods/test_replace.py index 9e302dc5f94ee..442055d7bc061 100644 --- a/pandas/tests/frame/methods/test_replace.py +++ b/pandas/tests/frame/methods/test_replace.py @@ -1430,6 +1430,18 @@ def test_replace_with_nil_na(self): result = ser.replace("nil", "anything else") tm.assert_frame_equal(expected, result) + def test_replace_outofbounds_datetime_raises(self): + # GH 61671 + df = DataFrame([np.nan], dtype="datetime64[ns]") + too_big = datetime(3000, 1, 1) + from pandas.errors import OutOfBoundsDatetime + + with pytest.raises( + OutOfBoundsDatetime, + match="Cannot safely store .* in inferred dtype 'datetime64\\[ns\\]'", + ): + df.replace(np.nan, too_big) + class TestDataFrameReplaceRegex: @pytest.mark.parametrize(