diff --git a/doc/source/reference/testing.rst b/doc/source/reference/testing.rst index 65b20b70ca317..5746e468a9a83 100644 --- a/doc/source/reference/testing.rst +++ b/doc/source/reference/testing.rst @@ -66,6 +66,7 @@ Exceptions and warnings errors.PyperclipException errors.PyperclipWindowsException errors.SpecificationError + errors.TimezoneDtypeMismatchError errors.UndefinedVariableError errors.UnsortedIndexError errors.UnsupportedFunctionCall diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 64d0347aa815e..d6a28d4c9d164 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -45,7 +45,10 @@ tzconversion, ) from pandas._libs.tslibs.dtypes import abbrev_to_npy_unit -from pandas.errors import PerformanceWarning +from pandas.errors import ( + PerformanceWarning, + TimezoneDtypeMismatchError, +) from pandas.util._exceptions import find_stack_level from pandas.util._validators import validate_inclusive @@ -2830,7 +2833,7 @@ def _validate_tz_from_dtype( # We also need to check for the case where the user passed a # tz-naive dtype (i.e. datetime64[ns]) if tz is not None and not timezones.tz_compare(tz, dtz): - raise ValueError( + raise TimezoneDtypeMismatchError( "cannot supply both a tz and a " "timezone-naive dtype (i.e. datetime64[ns])" ) diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py index 3b615c70ebea2..e56a0061b2e17 100644 --- a/pandas/core/dtypes/cast.py +++ b/pandas/core/dtypes/cast.py @@ -45,6 +45,7 @@ from pandas.errors import ( IntCastingNaNError, LossySetitemError, + TimezoneDtypeMismatchError, ) from pandas.core.dtypes.common import ( @@ -1095,14 +1096,13 @@ def maybe_cast_to_datetime( else: try: dta = DatetimeArray._from_sequence(value, dtype=dtype) - except ValueError as err: - # We can give a Series-specific exception message. - if "cannot supply both a tz and a timezone-naive dtype" in str(err): - raise ValueError( - "Cannot convert timezone-aware data to " - "timezone-naive dtype. Use " - "pd.Series(values).dt.tz_localize(None) instead." - ) from err + except TimezoneDtypeMismatchError as err: + raise ValueError( + "Cannot convert timezone-aware data to " + "timezone-naive dtype. Use " + "pd.Series(values).dt.tz_localize(None) instead." + ) from err + except ValueError: raise return dta diff --git a/pandas/errors/__init__.py b/pandas/errors/__init__.py index 8c26744f171c3..6e556a84d6e65 100644 --- a/pandas/errors/__init__.py +++ b/pandas/errors/__init__.py @@ -1038,6 +1038,30 @@ class InvalidComparison(Exception): """ +class TimezoneDtypeMismatchError(ValueError): + """ + Raised when a tz is supplied with a timezone-naive numpy datetime64 dtype. + + Use case / message: + "cannot supply both a tz and a timezone-naive dtype (i.e. datetime64[ns])" + + See Also + -------- + core.dtypes.dtypes.DatetimeTZDtype : Datetime dtype with an associated timezone. + core.arrays.datetimes._validate_tz_from_dtype : Validation helper that may + raise this error. + + Examples + -------- + >>> from pandas.core.arrays import datetimes # doctest: +SKIP + >>> datetimes._validate_tz_from_dtype("datetime64[ns]", tz="UTC") # doctest: +SKIP + Traceback (most recent call last): + ... + TimezoneDtypeMismatchError: cannot supply both a tz and a timezone-naive dtype + (i.e. datetime64[ns]) + """ + + __all__ = [ "AbstractMethodError", "AttributeConflictWarning", @@ -1081,6 +1105,7 @@ class InvalidComparison(Exception): "PyperclipException", "PyperclipWindowsException", "SpecificationError", + "TimezoneDtypeMismatchError", "UndefinedVariableError", "UnsortedIndexError", "UnsupportedFunctionCall", diff --git a/pandas/tests/indexes/datetimes/test_constructors.py b/pandas/tests/indexes/datetimes/test_constructors.py index c418b2a18008b..593b4d8c70088 100644 --- a/pandas/tests/indexes/datetimes/test_constructors.py +++ b/pandas/tests/indexes/datetimes/test_constructors.py @@ -19,6 +19,7 @@ astype_overflowsafe, timezones, ) +from pandas.errors import TimezoneDtypeMismatchError import pandas as pd from pandas import ( @@ -709,7 +710,7 @@ def test_constructor_dtype_tz_mismatch_raises(self): "cannot supply both a tz and a timezone-naive dtype " r"\(i\.e\. datetime64\[ns\]\)" ) - with pytest.raises(ValueError, match=msg): + with pytest.raises(TimezoneDtypeMismatchError, match=msg): DatetimeIndex(idx, dtype="datetime64[ns]") # this is effectively trying to convert tz's