Skip to content

Commit fbb1ecb

Browse files
jbrockmendelKevin D Smith
authored and
Kevin D Smith
committed
ENH: consistently cast strings for DTA/TDA/PA.__setitem__ (pandas-dev#36261)
* ENH: consistently cast strings for DTA/TDA/PA.__setitem__ * whatsnew
1 parent bcbac92 commit fbb1ecb

File tree

4 files changed

+52
-7
lines changed

4 files changed

+52
-7
lines changed

doc/source/whatsnew/v1.2.0.rst

+2
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,8 @@ Datetimelike
230230
- Bug in :meth:`DatetimeIndex.get_slice_bound` where ``datetime.date`` objects were not accepted or naive :class:`Timestamp` with a tz-aware :class:`DatetimeIndex` (:issue:`35690`)
231231
- Bug in :meth:`DatetimeIndex.slice_locs` where ``datetime.date`` objects were not accepted (:issue:`34077`)
232232
- Bug in :meth:`DatetimeIndex.searchsorted`, :meth:`TimedeltaIndex.searchsorted`, :meth:`PeriodIndex.searchsorted`, and :meth:`Series.searchsorted` with ``datetime64``, ``timedelta64`` or ``Period`` dtype placement of ``NaT`` values being inconsistent with ``NumPy`` (:issue:`36176`,:issue:`36254`)
233+
- Inconsistency in :class:`DatetimeArray`, :class:`TimedeltaArray`, and :class:`PeriodArray` setitem casting arrays of strings to datetimelike scalars but not scalar strings (:issue:`36261`)
234+
-
233235

234236
Timedelta
235237
^^^^^^^^^

pandas/core/arrays/datetimelike.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -875,8 +875,7 @@ def _validate_setitem_value(self, value):
875875
if is_list_like(value):
876876
value = self._validate_listlike(value, "setitem", cast_str=True)
877877
else:
878-
# TODO: cast_str for consistency?
879-
value = self._validate_scalar(value, msg, cast_str=False)
878+
value = self._validate_scalar(value, msg, cast_str=True)
880879

881880
return self._unbox(value, setitem=True)
882881

pandas/tests/arrays/test_datetimelike.py

+26-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import numpy as np
44
import pytest
5+
import pytz
56

67
from pandas._libs import OutOfBoundsDatetime
78
from pandas.compat.numpy import np_version_under1p18
@@ -282,15 +283,35 @@ def test_setitem(self):
282283
expected[:2] = expected[-2:]
283284
tm.assert_numpy_array_equal(arr.asi8, expected)
284285

285-
def test_setitem_str_array(self, arr1d):
286-
if isinstance(arr1d, DatetimeArray) and arr1d.tz is not None:
287-
pytest.xfail(reason="timezone comparisons inconsistent")
286+
def test_setitem_strs(self, arr1d):
287+
# Check that we parse strs in both scalar and listlike
288+
if isinstance(arr1d, DatetimeArray):
289+
tz = arr1d.tz
290+
if (
291+
tz is not None
292+
and tz is not pytz.UTC
293+
and not isinstance(tz, pytz._FixedOffset)
294+
):
295+
# If we have e.g. tzutc(), when we cast to string and parse
296+
# back we get pytz.UTC, and then consider them different timezones
297+
# so incorrectly raise.
298+
pytest.xfail(reason="timezone comparisons inconsistent")
299+
300+
# Setting list-like of strs
288301
expected = arr1d.copy()
289302
expected[[0, 1]] = arr1d[-2:]
290303

291-
arr1d[:2] = [str(x) for x in arr1d[-2:]]
304+
result = arr1d.copy()
305+
result[:2] = [str(x) for x in arr1d[-2:]]
306+
tm.assert_equal(result, expected)
292307

293-
tm.assert_equal(arr1d, expected)
308+
# Same thing but now for just a scalar str
309+
expected = arr1d.copy()
310+
expected[0] = arr1d[-1]
311+
312+
result = arr1d.copy()
313+
result[0] = str(arr1d[-1])
314+
tm.assert_equal(result, expected)
294315

295316
@pytest.mark.parametrize("as_index", [True, False])
296317
def test_setitem_categorical(self, arr1d, as_index):

pandas/tests/arrays/test_datetimes.py

+23
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,29 @@ def test_tz_setter_raises(self):
197197
with pytest.raises(AttributeError, match="tz_localize"):
198198
arr.tz = "UTC"
199199

200+
def test_setitem_str_impute_tz(self, tz_naive_fixture):
201+
# Like for getitem, if we are passed a naive-like string, we impute
202+
# our own timezone.
203+
tz = tz_naive_fixture
204+
205+
data = np.array([1, 2, 3], dtype="M8[ns]")
206+
dtype = data.dtype if tz is None else DatetimeTZDtype(tz=tz)
207+
arr = DatetimeArray(data, dtype=dtype)
208+
expected = arr.copy()
209+
210+
ts = pd.Timestamp("2020-09-08 16:50").tz_localize(tz)
211+
setter = str(ts.tz_localize(None))
212+
213+
# Setting a scalar tznaive string
214+
expected[0] = ts
215+
arr[0] = setter
216+
tm.assert_equal(arr, expected)
217+
218+
# Setting a listlike of tznaive strings
219+
expected[1] = ts
220+
arr[:2] = [setter, setter]
221+
tm.assert_equal(arr, expected)
222+
200223
def test_setitem_different_tz_raises(self):
201224
data = np.array([1, 2, 3], dtype="M8[ns]")
202225
arr = DatetimeArray(data, copy=False, dtype=DatetimeTZDtype(tz="US/Central"))

0 commit comments

Comments
 (0)