Repository: pandas-dev/pandas

Issue:
BUG: Cannot create third-party ExtensionArrays for datetime types
- [x] I have checked that this issue has not already been reported.

- [ ] I have confirmed this bug exists on the latest version of pandas.

- [x] (optional) I have confirmed this bug exists on the master branch of pandas.

---

Code Sample: https://github.com/pandas-dev/pandas/pull/34987

#### Problem description

With https://github.com/pandas-dev/pandas/pull/33400 type checks now fasttrack on the kind of the `dtype`. Thus for every `ExtensionDtype` that is of kind `M`, `is_datetime64tz_dtype` returns `True`.  This is then used in `get_block_type` which leads to the usage of `DatetimeTZBlock` for all of these arrays although this block only supports `DatetimeArray`.

https://github.com/pandas-dev/pandas/blob/4f7d8bba44aa45a81333cdefd7ec627370e7117b/pandas/core/internals/blocks.py#L2685-L2690


Discussion:
I ran into this in https://github.com/pandas-dev/pandas/pull/47515

Since https://github.com/pandas-dev/pandas/pull/34987, just added some unit tests, I think this behavior should still be fixed so reopening.
@mroeschke will this be fixed by your is_datetime64tz_dtype PR?
I did refactor my original `is_datetime64tz_dtype` PR https://github.com/pandas-dev/pandas/pull/48258 to only be a unit test since I think another one of your PRs fixed the original issue, but can open a separate PR to reintroduce the fix

Patch:
diff --git a/doc/source/whatsnew/v1.6.0.rst b/doc/source/whatsnew/v1.6.0.rst
index 405b8cc0a5ded..61546994748c7 100644
--- a/doc/source/whatsnew/v1.6.0.rst
+++ b/doc/source/whatsnew/v1.6.0.rst
@@ -150,6 +150,7 @@ Conversion
 ^^^^^^^^^^
 - Bug in constructing :class:`Series` with ``int64`` dtype from a string list raising instead of casting (:issue:`44923`)
 - Bug in :meth:`DataFrame.eval` incorrectly raising an ``AttributeError`` when there are negative values in function call (:issue:`46471`)
+- Bug where any :class:`ExtensionDtype` subclass with ``kind="M"`` would be interpreted as a timezone type (:issue:`34986`)
 -
 
 Strings
diff --git a/pandas/core/dtypes/common.py b/pandas/core/dtypes/common.py
index aeb9bc7a31674..f8752e1c16c38 100644
--- a/pandas/core/dtypes/common.py
+++ b/pandas/core/dtypes/common.py
@@ -382,9 +382,10 @@ def is_datetime64tz_dtype(arr_or_dtype) -> bool:
     >>> is_datetime64tz_dtype(s)
     True
     """
-    if isinstance(arr_or_dtype, ExtensionDtype):
+    if isinstance(arr_or_dtype, DatetimeTZDtype):
         # GH#33400 fastpath for dtype object
-        return arr_or_dtype.kind == "M"
+        # GH 34986
+        return True
 
     if arr_or_dtype is None:
         return False


Test patch:
diff --git a/pandas/tests/dtypes/test_common.py b/pandas/tests/dtypes/test_common.py
index 984655c68d56b..61c2b58388cc2 100644
--- a/pandas/tests/dtypes/test_common.py
+++ b/pandas/tests/dtypes/test_common.py
@@ -13,6 +13,7 @@
     CategoricalDtype,
     CategoricalDtypeType,
     DatetimeTZDtype,
+    ExtensionDtype,
     IntervalDtype,
     PeriodDtype,
 )
@@ -239,6 +240,18 @@ def test_is_datetime64tz_dtype():
     assert com.is_datetime64tz_dtype(pd.DatetimeIndex(["2000"], tz="US/Eastern"))
 
 
+def test_custom_ea_kind_M_not_datetime64tz():
+    # GH 34986
+    class NotTZDtype(ExtensionDtype):
+        @property
+        def kind(self) -> str:
+            return "M"
+
+    not_tz_dtype = NotTZDtype()
+    assert not com.is_datetime64tz_dtype(not_tz_dtype)
+    assert not com.needs_i8_conversion(not_tz_dtype)
+
+
 def test_is_timedelta64_dtype():
     assert not com.is_timedelta64_dtype(object)
     assert not com.is_timedelta64_dtype(None)