Skip to content

Commit 23e2405

Browse files
Backport PR #39194: REGR: fillna on datetime64[ns, UTC] column hits RecursionError (#39206)
1 parent fa5976e commit 23e2405

File tree

4 files changed

+42
-11
lines changed

4 files changed

+42
-11
lines changed

doc/source/whatsnew/v1.2.1.rst

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Fixed regressions
2727
- Fixed regression in :meth:`DataFrame.apply` with ``axis=1`` using str accessor in apply function (:issue:`38979`)
2828
- Fixed regression in :meth:`DataFrame.replace` raising ``ValueError`` when :class:`DataFrame` has dtype ``bytes`` (:issue:`38900`)
2929
- Fixed regression in :meth:`DataFrameGroupBy.diff` raising for ``int8`` and ``int16`` columns (:issue:`39050`)
30+
- Fixed regression in :meth:`Series.fillna` that raised ``RecursionError`` with ``datetime64[ns, UTC]`` dtype (:issue:`38851`)
3031
- Fixed regression that raised ``AttributeError`` with PyArrow versions [0.16.0, 1.0.0) (:issue:`38801`)
3132
- Fixed regression in :meth:`DataFrame.groupby` when aggregating an :class:`ExtensionDType` that could fail for non-numeric values (:issue:`38980`)
3233
- Fixed regression in :meth:`DataFrame.loc.__setitem__` raising ``KeyError`` with :class:`MultiIndex` and list-like columns indexer enlarging :class:`DataFrame` (:issue:`39147`)

pandas/core/internals/blocks.py

+30-9
Original file line numberDiff line numberDiff line change
@@ -2148,7 +2148,13 @@ def _can_hold_element(self, element: Any) -> bool:
21482148
class DatetimeLikeBlockMixin(Block):
21492149
"""Mixin class for DatetimeBlock, DatetimeTZBlock, and TimedeltaBlock."""
21502150

2151-
_can_hold_na = True
2151+
@property
2152+
def _holder(self):
2153+
return DatetimeArray
2154+
2155+
@property
2156+
def fill_value(self):
2157+
return np.datetime64("NaT", "ns")
21522158

21532159
def get_values(self, dtype=None):
21542160
"""
@@ -2216,8 +2222,10 @@ def to_native_types(self, na_rep="NaT", **kwargs):
22162222
class DatetimeBlock(DatetimeLikeBlockMixin):
22172223
__slots__ = ()
22182224
is_datetime = True
2219-
_holder = DatetimeArray
2220-
fill_value = np.datetime64("NaT", "ns")
2225+
2226+
@property
2227+
def _can_hold_na(self):
2228+
return True
22212229

22222230
def _maybe_coerce_values(self, values):
22232231
"""
@@ -2308,17 +2316,17 @@ class DatetimeTZBlock(ExtensionBlock, DatetimeBlock):
23082316
is_extension = True
23092317

23102318
internal_values = Block.internal_values
2311-
2312-
_holder = DatetimeBlock._holder
23132319
_can_hold_element = DatetimeBlock._can_hold_element
23142320
to_native_types = DatetimeBlock.to_native_types
23152321
diff = DatetimeBlock.diff
2316-
fillna = DatetimeBlock.fillna # i.e. Block.fillna
2317-
fill_value = DatetimeBlock.fill_value
2318-
_can_hold_na = DatetimeBlock._can_hold_na
2322+
fill_value = np.datetime64("NaT", "ns")
23192323

23202324
array_values = ExtensionBlock.array_values
23212325

2326+
@property
2327+
def _holder(self):
2328+
return DatetimeArray
2329+
23222330
def _maybe_coerce_values(self, values):
23232331
"""
23242332
Input validation for values passed to __init__. Ensure that
@@ -2383,6 +2391,17 @@ def external_values(self):
23832391
# return an object-dtype ndarray of Timestamps.
23842392
return np.asarray(self.values.astype("datetime64[ns]", copy=False))
23852393

2394+
def fillna(self, value, limit=None, inplace=False, downcast=None):
2395+
# We support filling a DatetimeTZ with a `value` whose timezone
2396+
# is different by coercing to object.
2397+
if self._can_hold_element(value):
2398+
return super().fillna(value, limit, inplace, downcast)
2399+
2400+
# different timezones, or a non-tz
2401+
return self.astype(object).fillna(
2402+
value, limit=limit, inplace=inplace, downcast=downcast
2403+
)
2404+
23862405
def quantile(self, qs, interpolation="linear", axis=0):
23872406
naive = self.values.view("M8[ns]")
23882407

@@ -2419,9 +2438,11 @@ def _check_ndim(self, values, ndim):
24192438
return ndim
24202439

24212440

2422-
class TimeDeltaBlock(DatetimeLikeBlockMixin):
2441+
class TimeDeltaBlock(DatetimeLikeBlockMixin, IntBlock):
24232442
__slots__ = ()
24242443
is_timedelta = True
2444+
_can_hold_na = True
2445+
is_numeric = False
24252446
fill_value = np.timedelta64("NaT", "ns")
24262447

24272448
def _maybe_coerce_values(self, values):

pandas/core/internals/managers.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1916,7 +1916,7 @@ def _consolidate(blocks):
19161916
merged_blocks = _merge_blocks(
19171917
list(group_blocks), dtype=dtype, can_consolidate=_can_consolidate
19181918
)
1919-
new_blocks.extend(merged_blocks)
1919+
new_blocks = extend_blocks(merged_blocks, new_blocks)
19201920
return new_blocks
19211921

19221922

pandas/tests/series/methods/test_fillna.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import datetime, timedelta
1+
from datetime import datetime, timedelta, timezone
22

33
import numpy as np
44
import pytest
@@ -13,6 +13,7 @@
1313
Series,
1414
Timedelta,
1515
Timestamp,
16+
date_range,
1617
isna,
1718
)
1819
import pandas._testing as tm
@@ -711,6 +712,14 @@ def test_fillna_method_and_limit_invalid(self):
711712
with pytest.raises(ValueError, match=msg):
712713
ser.fillna(1, limit=limit, method=method)
713714

715+
def test_fillna_datetime64_with_timezone_tzinfo(self):
716+
# https://github.com/pandas-dev/pandas/issues/38851
717+
s = Series(date_range("2020", periods=3, tz="UTC"))
718+
expected = s.astype(object)
719+
s[1] = NaT
720+
result = s.fillna(datetime(2020, 1, 2, tzinfo=timezone.utc))
721+
tm.assert_series_equal(result, expected)
722+
714723

715724
class TestFillnaPad:
716725
def test_fillna_bug(self):

0 commit comments

Comments
 (0)