Skip to content

Commit fc187b6

Browse files
jbrockmendelpmhatre1
authored andcommitted
BUG: setitem with mixed-resolution dt64s (pandas-dev#56419)
* BUG: setitem with mixed-resolution dt64s * Move whatsnew to 3.0 * de-xfail * improve exception message
1 parent 2e05c1b commit fc187b6

File tree

7 files changed

+78
-20
lines changed

7 files changed

+78
-20
lines changed

pandas/core/arrays/datetimes.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ def _unbox_scalar(self, value) -> np.datetime64:
539539
if value is NaT:
540540
return np.datetime64(value._value, self.unit)
541541
else:
542-
return value.as_unit(self.unit).asm8
542+
return value.as_unit(self.unit, round_ok=False).asm8
543543

544544
def _scalar_from_string(self, value) -> Timestamp | NaTType:
545545
return Timestamp(value, tz=self.tz)

pandas/core/arrays/timedeltas.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ def _unbox_scalar(self, value) -> np.timedelta64:
322322
if value is NaT:
323323
return np.timedelta64(value._value, self.unit)
324324
else:
325-
return value.as_unit(self.unit).asm8
325+
return value.as_unit(self.unit, round_ok=False).asm8
326326

327327
def _scalar_from_string(self, value) -> Timedelta | NaTType:
328328
return Timedelta(value)

pandas/core/indexes/datetimes.py

+2
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,8 @@ def _parsed_string_to_bounds(
515515
freq = OFFSET_TO_PERIOD_FREQSTR.get(reso.attr_abbrev, reso.attr_abbrev)
516516
per = Period(parsed, freq=freq)
517517
start, end = per.start_time, per.end_time
518+
start = start.as_unit(self.unit)
519+
end = end.as_unit(self.unit)
518520

519521
# GH 24076
520522
# If an incoming date string contained a UTC offset, need to localize

pandas/core/internals/blocks.py

+15-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@
3838
Shape,
3939
npt,
4040
)
41-
from pandas.errors import AbstractMethodError
41+
from pandas.errors import (
42+
AbstractMethodError,
43+
OutOfBoundsDatetime,
44+
)
4245
from pandas.util._decorators import cache_readonly
4346
from pandas.util._exceptions import find_stack_level
4447
from pandas.util._validators import validate_bool_kwarg
@@ -478,7 +481,17 @@ def coerce_to_target_dtype(self, other, warn_on_upcast: bool = False) -> Block:
478481
f"{self.values.dtype}. Please report a bug at "
479482
"https://github.com/pandas-dev/pandas/issues."
480483
)
481-
return self.astype(new_dtype)
484+
try:
485+
return self.astype(new_dtype)
486+
except OutOfBoundsDatetime as err:
487+
# e.g. GH#56419 if self.dtype is a low-resolution dt64 and we try to
488+
# upcast to a higher-resolution dt64, we may have entries that are
489+
# out of bounds for the higher resolution.
490+
# Re-raise with a more informative message.
491+
raise OutOfBoundsDatetime(
492+
f"Incompatible (high-resolution) value for dtype='{self.dtype}'. "
493+
"Explicitly cast before operating."
494+
) from err
482495

483496
@final
484497
def convert(self) -> list[Block]:

pandas/tests/series/indexing/test_setitem.py

+33
Original file line numberDiff line numberDiff line change
@@ -1467,6 +1467,39 @@ def test_slice_key(self, obj, key, expected, warn, val, indexer_sli, is_inplace)
14671467
raise AssertionError("xfail not relevant for this test.")
14681468

14691469

1470+
@pytest.mark.parametrize(
1471+
"exp_dtype",
1472+
[
1473+
"M8[ms]",
1474+
"M8[ms, UTC]",
1475+
"m8[ms]",
1476+
],
1477+
)
1478+
class TestCoercionDatetime64HigherReso(CoercionTest):
1479+
@pytest.fixture
1480+
def obj(self, exp_dtype):
1481+
idx = date_range("2011-01-01", freq="D", periods=4, unit="s")
1482+
if exp_dtype == "m8[ms]":
1483+
idx = idx - Timestamp("1970-01-01")
1484+
assert idx.dtype == "m8[s]"
1485+
elif exp_dtype == "M8[ms, UTC]":
1486+
idx = idx.tz_localize("UTC")
1487+
return Series(idx)
1488+
1489+
@pytest.fixture
1490+
def val(self, exp_dtype):
1491+
ts = Timestamp("2011-01-02 03:04:05.678").as_unit("ms")
1492+
if exp_dtype == "m8[ms]":
1493+
return ts - Timestamp("1970-01-01")
1494+
elif exp_dtype == "M8[ms, UTC]":
1495+
return ts.tz_localize("UTC")
1496+
return ts
1497+
1498+
@pytest.fixture
1499+
def warn(self):
1500+
return FutureWarning
1501+
1502+
14701503
@pytest.mark.parametrize(
14711504
"val,exp_dtype,warn",
14721505
[

pandas/tests/series/methods/test_clip.py

+24-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import numpy as np
44
import pytest
55

6+
from pandas.errors import OutOfBoundsDatetime
7+
68
import pandas as pd
79
from pandas import (
810
Series,
@@ -131,12 +133,30 @@ def test_clip_with_datetimes(self):
131133
)
132134
tm.assert_series_equal(result, expected)
133135

134-
@pytest.mark.parametrize("dtype", [object, "M8[us]"])
135-
def test_clip_with_timestamps_and_oob_datetimes(self, dtype):
136+
def test_clip_with_timestamps_and_oob_datetimes_object(self):
136137
# GH-42794
137-
ser = Series([datetime(1, 1, 1), datetime(9999, 9, 9)], dtype=dtype)
138+
ser = Series([datetime(1, 1, 1), datetime(9999, 9, 9)], dtype=object)
138139

139140
result = ser.clip(lower=Timestamp.min, upper=Timestamp.max)
140-
expected = Series([Timestamp.min, Timestamp.max], dtype=dtype)
141+
expected = Series([Timestamp.min, Timestamp.max], dtype=object)
142+
143+
tm.assert_series_equal(result, expected)
144+
145+
def test_clip_with_timestamps_and_oob_datetimes_non_nano(self):
146+
# GH#56410
147+
dtype = "M8[us]"
148+
ser = Series([datetime(1, 1, 1), datetime(9999, 9, 9)], dtype=dtype)
149+
150+
msg = (
151+
r"Incompatible \(high-resolution\) value for dtype='datetime64\[us\]'. "
152+
"Explicitly cast before operating"
153+
)
154+
with pytest.raises(OutOfBoundsDatetime, match=msg):
155+
ser.clip(lower=Timestamp.min, upper=Timestamp.max)
156+
157+
lower = Timestamp.min.as_unit("us")
158+
upper = Timestamp.max.as_unit("us")
159+
result = ser.clip(lower=lower, upper=upper)
160+
expected = Series([lower, upper], dtype=dtype)
141161

142162
tm.assert_series_equal(result, expected)

pandas/tests/series/methods/test_fillna.py

+2-12
Original file line numberDiff line numberDiff line change
@@ -308,12 +308,7 @@ def test_datetime64_fillna(self):
308308
"scalar",
309309
[
310310
False,
311-
pytest.param(
312-
True,
313-
marks=pytest.mark.xfail(
314-
reason="GH#56410 scalar case not yet addressed"
315-
),
316-
),
311+
True,
317312
],
318313
)
319314
@pytest.mark.parametrize("tz", [None, "UTC"])
@@ -342,12 +337,7 @@ def test_datetime64_fillna_mismatched_reso_no_rounding(self, tz, scalar):
342337
"scalar",
343338
[
344339
False,
345-
pytest.param(
346-
True,
347-
marks=pytest.mark.xfail(
348-
reason="GH#56410 scalar case not yet addressed"
349-
),
350-
),
340+
True,
351341
],
352342
)
353343
def test_timedelta64_fillna_mismatched_reso_no_rounding(self, scalar):

0 commit comments

Comments
 (0)