From 7da8d8b803f6f4037eb96b3026f4bf45d698f3a7 Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 9 Sep 2020 17:58:33 -0700 Subject: [PATCH 1/2] ENH: consistently cast strings for DTA/TDA/PA.__setitem__ --- pandas/core/arrays/datetimelike.py | 3 +-- pandas/tests/arrays/test_datetimelike.py | 31 ++++++++++++++++++++---- pandas/tests/arrays/test_datetimes.py | 23 ++++++++++++++++++ 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 6477b94a823ce..87e7baeb538bf 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -873,8 +873,7 @@ def _validate_setitem_value(self, value): if is_list_like(value): value = self._validate_listlike(value, "setitem", cast_str=True) else: - # TODO: cast_str for consistency? - value = self._validate_scalar(value, msg, cast_str=False) + value = self._validate_scalar(value, msg, cast_str=True) self._check_compatible_with(value, setitem=True) return self._unbox(value) diff --git a/pandas/tests/arrays/test_datetimelike.py b/pandas/tests/arrays/test_datetimelike.py index 292557fc04258..fa4a8c7905517 100644 --- a/pandas/tests/arrays/test_datetimelike.py +++ b/pandas/tests/arrays/test_datetimelike.py @@ -2,6 +2,7 @@ import numpy as np import pytest +import pytz from pandas._libs import OutOfBoundsDatetime from pandas.compat.numpy import _np_version_under1p18 @@ -282,15 +283,35 @@ def test_setitem(self): expected[:2] = expected[-2:] tm.assert_numpy_array_equal(arr.asi8, expected) - def test_setitem_str_array(self, arr1d): - if isinstance(arr1d, DatetimeArray) and arr1d.tz is not None: - pytest.xfail(reason="timezone comparisons inconsistent") + def test_setitem_strs(self, arr1d): + # Check that we parse strs in both scalar and listlike + if isinstance(arr1d, DatetimeArray): + tz = arr1d.tz + if ( + tz is not None + and tz is not pytz.UTC + and not isinstance(tz, pytz._FixedOffset) + ): + # If we have e.g. tzutc(), when we cast to string and parse + # back we get pytz.UTC, and then consider them different timezones + # so incorrectly raise. + pytest.xfail(reason="timezone comparisons inconsistent") + + # Setting list-like of strs expected = arr1d.copy() expected[[0, 1]] = arr1d[-2:] - arr1d[:2] = [str(x) for x in arr1d[-2:]] + result = arr1d.copy() + result[:2] = [str(x) for x in arr1d[-2:]] + tm.assert_equal(result, expected) - tm.assert_equal(arr1d, expected) + # Same thing but now for just a scalar str + expected = arr1d.copy() + expected[0] = arr1d[-1] + + result = arr1d.copy() + result[0] = str(arr1d[-1]) + tm.assert_equal(result, expected) @pytest.mark.parametrize("as_index", [True, False]) def test_setitem_categorical(self, arr1d, as_index): diff --git a/pandas/tests/arrays/test_datetimes.py b/pandas/tests/arrays/test_datetimes.py index 804654451a6d9..53f26de09f94e 100644 --- a/pandas/tests/arrays/test_datetimes.py +++ b/pandas/tests/arrays/test_datetimes.py @@ -197,6 +197,29 @@ def test_tz_setter_raises(self): with pytest.raises(AttributeError, match="tz_localize"): arr.tz = "UTC" + def test_setitem_str_impute_tz(self, tz_naive_fixture): + # Like for getitem, if we are passed a naive-like string, we impute + # our own timezone. + tz = tz_naive_fixture + + data = np.array([1, 2, 3], dtype="M8[ns]") + dtype = data.dtype if tz is None else DatetimeTZDtype(tz=tz) + arr = DatetimeArray(data, dtype=dtype) + expected = arr.copy() + + ts = pd.Timestamp("2020-09-08 16:50").tz_localize(tz) + setter = str(ts.tz_localize(None)) + + # Setting a scalar tznaive string + expected[0] = ts + arr[0] = setter + tm.assert_equal(arr, expected) + + # Setting a listlike of tznaive strings + expected[1] = ts + arr[:2] = [setter, setter] + tm.assert_equal(arr, expected) + def test_setitem_different_tz_raises(self): data = np.array([1, 2, 3], dtype="M8[ns]") arr = DatetimeArray(data, copy=False, dtype=DatetimeTZDtype(tz="US/Central")) From b1e2d71c6da83200b1fba3736fe127de3f28b04c Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 12 Sep 2020 14:32:56 -0700 Subject: [PATCH 2/2] whatsnew --- doc/source/whatsnew/v1.2.0.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index 00cbb248e4690..119b34afdb69a 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -230,6 +230,8 @@ Datetimelike - 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`) - Bug in :meth:`DatetimeIndex.slice_locs` where ``datetime.date`` objects were not accepted (:issue:`34077`) - 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`) +- Inconsistency in :class:`DatetimeArray`, :class:`TimedeltaArray`, and :class:`PeriodArray` setitem casting arrays of strings to datetimelike scalars but not scalar strings (:issue:`36261`) +- Timedelta ^^^^^^^^^