From aa0e29212da7e2656f4d09fd9ff079c13edc0375 Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 20 Sep 2022 09:05:12 -0700 Subject: [PATCH 1/2] BUG: DatetimeIndex ignoring explicit tz=None --- doc/source/whatsnew/v1.6.0.rst | 2 +- pandas/core/arrays/datetimes.py | 24 +++++++++++++++---- pandas/core/indexes/datetimes.py | 6 ++--- .../indexes/datetimes/test_constructors.py | 14 +++++++++++ 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/doc/source/whatsnew/v1.6.0.rst b/doc/source/whatsnew/v1.6.0.rst index 61546994748c7..93894b9580f51 100644 --- a/doc/source/whatsnew/v1.6.0.rst +++ b/doc/source/whatsnew/v1.6.0.rst @@ -129,7 +129,7 @@ Categorical Datetimelike ^^^^^^^^^^^^ - Bug in :func:`pandas.infer_freq`, raising ``TypeError`` when inferred on :class:`RangeIndex` (:issue:`47084`) -- +- Bug in :class:`DatetimeIndex` constructor failing to raise when ``tz=None`` is explicitly specified in conjunction with timezone-aware ``dtype`` or data (:issue:`??`) Timedelta ^^^^^^^^^ diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index a3641a06c1964..3bb3d455dc331 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -294,7 +294,7 @@ def _from_sequence_not_strict( data, dtype=None, copy: bool = False, - tz=None, + tz=lib.no_default, freq: str | BaseOffset | lib.NoDefault | None = lib.no_default, dayfirst: bool = False, yearfirst: bool = False, @@ -1984,7 +1984,7 @@ def _sequence_to_dt64ns( data, dtype=None, copy: bool = False, - tz=None, + tz=lib.no_default, dayfirst: bool = False, yearfirst: bool = False, ambiguous="raise", @@ -2021,6 +2021,9 @@ def _sequence_to_dt64ns( ------ TypeError : PeriodDType data is passed """ + explicit_tz_none = tz is None + if tz is lib.no_default: + tz = None inferred_freq = None @@ -2028,7 +2031,7 @@ def _sequence_to_dt64ns( tz = timezones.maybe_get_tz(tz) # if dtype has an embedded tz, capture it - tz = validate_tz_from_dtype(dtype, tz) + tz = validate_tz_from_dtype(dtype, tz, explicit_tz_none) data, copy = dtl.ensure_arraylike_for_datetimelike( data, copy, cls_name="DatetimeArray" @@ -2124,7 +2127,12 @@ def _sequence_to_dt64ns( assert result.dtype == "M8[ns]", result.dtype # We have to call this again after possibly inferring a tz above - validate_tz_from_dtype(dtype, tz) + validate_tz_from_dtype(dtype, tz, explicit_tz_none) + if tz is not None and explicit_tz_none: + raise ValueError( + "Passed data is timezone-aware, incompatible with 'tz=None'. " + "Use obj.tz_localize(None) instead." + ) return result, tz, inferred_freq @@ -2366,7 +2374,9 @@ def _validate_dt64_dtype(dtype): return dtype -def validate_tz_from_dtype(dtype, tz: tzinfo | None) -> tzinfo | None: +def validate_tz_from_dtype( + dtype, tz: tzinfo | None, explicit_tz_none: bool = False +) -> tzinfo | None: """ If the given dtype is a DatetimeTZDtype, extract the implied tzinfo object from it and check that it does not conflict with the given @@ -2376,6 +2386,8 @@ def validate_tz_from_dtype(dtype, tz: tzinfo | None) -> tzinfo | None: ---------- dtype : dtype, str tz : None, tzinfo + explicit_tz_none : bool, default False + Whether tz=None was passed explicitly, as opposed to lib.no_default. Returns ------- @@ -2399,6 +2411,8 @@ def validate_tz_from_dtype(dtype, tz: tzinfo | None) -> tzinfo | None: if dtz is not None: if tz is not None and not timezones.tz_compare(tz, dtz): raise ValueError("cannot supply both a tz and a dtype with a tz") + elif explicit_tz_none: + raise ValueError("Cannot pass both a timezone-aware dtype and tz=None") tz = dtz if tz is not None and is_datetime64_dtype(dtype): diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index b91fbb8244cb5..f1d3059d66f5a 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -315,7 +315,7 @@ def __new__( cls, data=None, freq: str | BaseOffset | lib.NoDefault = lib.no_default, - tz=None, + tz=lib.no_default, normalize: bool = False, closed=None, ambiguous="raise", @@ -336,7 +336,7 @@ def __new__( if ( isinstance(data, DatetimeArray) and freq is lib.no_default - and tz is None + and tz is lib.no_default and dtype is None ): # fastpath, similar logic in TimedeltaIndex.__new__; @@ -347,7 +347,7 @@ def __new__( elif ( isinstance(data, DatetimeArray) and freq is lib.no_default - and tz is None + and tz is lib.no_default and is_dtype_equal(data.dtype, dtype) ): # Reached via Index.__new__ when we call .astype diff --git a/pandas/tests/indexes/datetimes/test_constructors.py b/pandas/tests/indexes/datetimes/test_constructors.py index 1d161630b1356..781c6b6fdfc3b 100644 --- a/pandas/tests/indexes/datetimes/test_constructors.py +++ b/pandas/tests/indexes/datetimes/test_constructors.py @@ -37,6 +37,20 @@ class TestDatetimeIndex: + def test_explicit_tz_none(self): + dti = date_range("2016-01-01", periods=10, tz="UTC") + + msg = "Passed data is timezone-aware, incompatible with 'tz=None'" + with pytest.raises(ValueError, match=msg): + DatetimeIndex(dti, tz=None) + + with pytest.raises(ValueError, match=msg): + DatetimeIndex(np.array(dti), tz=None) + + msg = "Cannot pass both a timezone-aware dtype and tz=None" + with pytest.raises(ValueError, match=msg): + DatetimeIndex([], dtype="M8[ns, UTC]", tz=None) + @pytest.mark.parametrize( "dt_cls", [DatetimeIndex, DatetimeArray._from_sequence_not_strict] ) From 8694a180757ecf8bab4f5cb4db496688fd191921 Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 20 Sep 2022 09:06:16 -0700 Subject: [PATCH 2/2] GH ref --- doc/source/whatsnew/v1.6.0.rst | 2 +- pandas/tests/indexes/datetimes/test_constructors.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.6.0.rst b/doc/source/whatsnew/v1.6.0.rst index 93894b9580f51..2c8d8f3d0fe35 100644 --- a/doc/source/whatsnew/v1.6.0.rst +++ b/doc/source/whatsnew/v1.6.0.rst @@ -129,7 +129,7 @@ Categorical Datetimelike ^^^^^^^^^^^^ - Bug in :func:`pandas.infer_freq`, raising ``TypeError`` when inferred on :class:`RangeIndex` (:issue:`47084`) -- Bug in :class:`DatetimeIndex` constructor failing to raise when ``tz=None`` is explicitly specified in conjunction with timezone-aware ``dtype`` or data (:issue:`??`) +- Bug in :class:`DatetimeIndex` constructor failing to raise when ``tz=None`` is explicitly specified in conjunction with timezone-aware ``dtype`` or data (:issue:`48659`) Timedelta ^^^^^^^^^ diff --git a/pandas/tests/indexes/datetimes/test_constructors.py b/pandas/tests/indexes/datetimes/test_constructors.py index 781c6b6fdfc3b..d129f5b365ca4 100644 --- a/pandas/tests/indexes/datetimes/test_constructors.py +++ b/pandas/tests/indexes/datetimes/test_constructors.py @@ -38,6 +38,7 @@ class TestDatetimeIndex: def test_explicit_tz_none(self): + # GH#48659 dti = date_range("2016-01-01", periods=10, tz="UTC") msg = "Passed data is timezone-aware, incompatible with 'tz=None'"