BUG: Raise OutOfBoundsDatetime in DataFrame.replace when value exceeds datetime64[ns] bounds (GH#61671)
State: open

Fixes a bug where `DataFrame.replace` would raise a generic `AssertionError` when trying to replace `np.nan` in a `datetime64[ns]` column with an out-of-bounds `datetime.datetime` object (e.g., `datetime(3000, 1, 1)`). 

This PR fixes that by explicitly raising `OutOfBoundsDatetime` when the replacement datetime can't safely fit into the `datetime64[ns]` dtype.
- [x] closes #61671  
- [x] Added a test that reproduces the issue  
- [x] Pre-commit hooks passed  
- [x] Added a changelog entry under `Datetimelike` for 3.0.0  

Let me know if you'd like to test other edge cases or if there's a more idiomatic way to handle this!


Labels:
name: Bug
name: Datetime
description: Datetime data dtype
name: Stale

Comments:
looking into the CI failures
Regarding CI failures —

So after the changes in `find_result_type`, we're now catching cases like `Timestamp('1677-09-21 00:12:43.145224193')` early and raising `OutOfBoundsDatetime` during coercion itself, which makes sense and is in line with what #56410 was aiming for (no silent truncation between datetime units).

Because of that, `test_clip_with_timestamps_and_oob_datetimes_non_nano` is now failing since it hits the error earlier with, Just wanted to confirm, should I go ahead and update the test to reflect this message? or is the earlier failure point problematic?

Happy to revert or gate the check if needed.

Thanks for the review and suggestions @simonjayhawkins @jbrockmendel!
I did some testing to better understand how different assignment and replacement operations behave with out-of-bounds datetimes, both tz-naive and tz-aware. Here's what I found:

---

### Observed Behavior (Confirmed via Logs)

| Operation                     | Value Type                     | Outcome                          | Notes |
|------------------------------|--------------------------------|----------------------------------|-------|
| `replace(np.nan, ts)`        | `Timestamp("3000-01-01")`      | Raises `OutOfBoundsDatetime`  | Expected behavior, works as intended |
| `replace(np.nan, ts)`        | `Timestamp("3000-01-01", tz)`  | Succeeds silently              | Column upcasts to `object` dtype silently |
| `df.iloc[0, 0] = ts`         | `Timestamp("3000-01-01")`      | Raises `OutOfBoundsDatetime`  | Same as above, correct behavior |
| `df.iloc[0, 0] = ts`         | `Timestamp("3000-01-01", tz)`  | Raises `TypeError`            | Due to tz-naive column (`datetime64[ns]`) being incompatible with tz-aware value |

---

### Additional Context

- For **tz-naive** out-of-bounds values:
  - Both `replace()` and `iloc` correctly raise `OutOfBoundsDatetime`.
- For **tz-aware** values:
  - `replace()` allows insertion silently by upcasting the column to `object` dtype (confirmed via debug logs).
  - `iloc` correctly raises a `TypeError` because of tz-awareness mismatch (`datetime64[ns]` vs tz-aware).

---

### Next Steps from My Side

Before I add tests for these cases, just wanted to check:
- Should we treat tz-aware out-of-bounds timestamps as valid (fallback to object)?
- Or do we want to enforce stricter checks across the board?

Happy to add the tests once I get a bit of guidance on how we want to handle these edge cases consistently.
> Column upcasts to object dtype silently

I would expect this to raise rather than silently upcast.
This pull request is stale because it has been open for thirty days with no activity. Please [update](https://pandas.pydata.org/pandas-docs/stable/development/contributing.html#updating-your-pull-request) and respond to this comment if you're still interested in working on this.