Skip to content

Commit 06e1978

Browse files
authored
BUG: DatetimeIndex ignoring explicit tz=None (#48659)
* BUG: DatetimeIndex ignoring explicit tz=None * GH ref
1 parent e493323 commit 06e1978

File tree

4 files changed

+38
-9
lines changed

4 files changed

+38
-9
lines changed

doc/source/whatsnew/v1.6.0.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ Categorical
162162
Datetimelike
163163
^^^^^^^^^^^^
164164
- Bug in :func:`pandas.infer_freq`, raising ``TypeError`` when inferred on :class:`RangeIndex` (:issue:`47084`)
165-
-
165+
- Bug in :class:`DatetimeIndex` constructor failing to raise when ``tz=None`` is explicitly specified in conjunction with timezone-aware ``dtype`` or data (:issue:`48659`)
166166

167167
Timedelta
168168
^^^^^^^^^

pandas/core/arrays/datetimes.py

+19-5
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ def _from_sequence_not_strict(
294294
data,
295295
dtype=None,
296296
copy: bool = False,
297-
tz=None,
297+
tz=lib.no_default,
298298
freq: str | BaseOffset | lib.NoDefault | None = lib.no_default,
299299
dayfirst: bool = False,
300300
yearfirst: bool = False,
@@ -1986,7 +1986,7 @@ def _sequence_to_dt64ns(
19861986
data,
19871987
dtype=None,
19881988
copy: bool = False,
1989-
tz=None,
1989+
tz=lib.no_default,
19901990
dayfirst: bool = False,
19911991
yearfirst: bool = False,
19921992
ambiguous="raise",
@@ -2023,14 +2023,17 @@ def _sequence_to_dt64ns(
20232023
------
20242024
TypeError : PeriodDType data is passed
20252025
"""
2026+
explicit_tz_none = tz is None
2027+
if tz is lib.no_default:
2028+
tz = None
20262029

20272030
inferred_freq = None
20282031

20292032
dtype = _validate_dt64_dtype(dtype)
20302033
tz = timezones.maybe_get_tz(tz)
20312034

20322035
# if dtype has an embedded tz, capture it
2033-
tz = validate_tz_from_dtype(dtype, tz)
2036+
tz = validate_tz_from_dtype(dtype, tz, explicit_tz_none)
20342037

20352038
data, copy = dtl.ensure_arraylike_for_datetimelike(
20362039
data, copy, cls_name="DatetimeArray"
@@ -2126,7 +2129,12 @@ def _sequence_to_dt64ns(
21262129
assert result.dtype == "M8[ns]", result.dtype
21272130

21282131
# We have to call this again after possibly inferring a tz above
2129-
validate_tz_from_dtype(dtype, tz)
2132+
validate_tz_from_dtype(dtype, tz, explicit_tz_none)
2133+
if tz is not None and explicit_tz_none:
2134+
raise ValueError(
2135+
"Passed data is timezone-aware, incompatible with 'tz=None'. "
2136+
"Use obj.tz_localize(None) instead."
2137+
)
21302138

21312139
return result, tz, inferred_freq
21322140

@@ -2368,7 +2376,9 @@ def _validate_dt64_dtype(dtype):
23682376
return dtype
23692377

23702378

2371-
def validate_tz_from_dtype(dtype, tz: tzinfo | None) -> tzinfo | None:
2379+
def validate_tz_from_dtype(
2380+
dtype, tz: tzinfo | None, explicit_tz_none: bool = False
2381+
) -> tzinfo | None:
23722382
"""
23732383
If the given dtype is a DatetimeTZDtype, extract the implied
23742384
tzinfo object from it and check that it does not conflict with the given
@@ -2378,6 +2388,8 @@ def validate_tz_from_dtype(dtype, tz: tzinfo | None) -> tzinfo | None:
23782388
----------
23792389
dtype : dtype, str
23802390
tz : None, tzinfo
2391+
explicit_tz_none : bool, default False
2392+
Whether tz=None was passed explicitly, as opposed to lib.no_default.
23812393
23822394
Returns
23832395
-------
@@ -2401,6 +2413,8 @@ def validate_tz_from_dtype(dtype, tz: tzinfo | None) -> tzinfo | None:
24012413
if dtz is not None:
24022414
if tz is not None and not timezones.tz_compare(tz, dtz):
24032415
raise ValueError("cannot supply both a tz and a dtype with a tz")
2416+
elif explicit_tz_none:
2417+
raise ValueError("Cannot pass both a timezone-aware dtype and tz=None")
24042418
tz = dtz
24052419

24062420
if tz is not None and is_datetime64_dtype(dtype):

pandas/core/indexes/datetimes.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ def __new__(
315315
cls,
316316
data=None,
317317
freq: str | BaseOffset | lib.NoDefault = lib.no_default,
318-
tz=None,
318+
tz=lib.no_default,
319319
normalize: bool = False,
320320
closed=None,
321321
ambiguous="raise",
@@ -336,7 +336,7 @@ def __new__(
336336
if (
337337
isinstance(data, DatetimeArray)
338338
and freq is lib.no_default
339-
and tz is None
339+
and tz is lib.no_default
340340
and dtype is None
341341
):
342342
# fastpath, similar logic in TimedeltaIndex.__new__;
@@ -347,7 +347,7 @@ def __new__(
347347
elif (
348348
isinstance(data, DatetimeArray)
349349
and freq is lib.no_default
350-
and tz is None
350+
and tz is lib.no_default
351351
and is_dtype_equal(data.dtype, dtype)
352352
):
353353
# Reached via Index.__new__ when we call .astype

pandas/tests/indexes/datetimes/test_constructors.py

+15
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,21 @@
3737

3838

3939
class TestDatetimeIndex:
40+
def test_explicit_tz_none(self):
41+
# GH#48659
42+
dti = date_range("2016-01-01", periods=10, tz="UTC")
43+
44+
msg = "Passed data is timezone-aware, incompatible with 'tz=None'"
45+
with pytest.raises(ValueError, match=msg):
46+
DatetimeIndex(dti, tz=None)
47+
48+
with pytest.raises(ValueError, match=msg):
49+
DatetimeIndex(np.array(dti), tz=None)
50+
51+
msg = "Cannot pass both a timezone-aware dtype and tz=None"
52+
with pytest.raises(ValueError, match=msg):
53+
DatetimeIndex([], dtype="M8[ns, UTC]", tz=None)
54+
4055
@pytest.mark.parametrize(
4156
"dt_cls", [DatetimeIndex, DatetimeArray._from_sequence_not_strict]
4257
)

0 commit comments

Comments
 (0)