REF: DatetimeLikeArray
State: closed

TODO:

- [x] DatetimeLikeArrayMixin.astype(int) ignores sign and size?
- [ ] _from_sequence too permissive?
- [x] OK with DatetimeArray.view?
- [x] tz vs. dtype in `DatetimeArray.__init__`: https://github.com/pandas-dev/pandas/pull/24024#discussion_r241445450
- [ ] Index._simple_new consistency: https://github.com/pandas-dev/pandas/pull/24024#discussion_r241458796

---

n.b.: right now I have a few other PRs in this branch (#23601, #23990, #24023). The eventual diff is at https://github.com/TomAugspurger/pandas/compare/disown-tz-only-target...TomAugspurger:disown-tz-only-rebased.

Also, I would say this isn't *quite* ready for review yet, but I've been promising it for a while. I'm going to try to split off a bit more into separate PRs.

---

This implements DatetimeArray and TimedeltaArray, arrays for storing datetime
(tz naive or aware) and timedelta data.

### High-level Design:

Everything here applies equally to TimedeltaIndex / TimedeltaArray, other than
references to DatetimeTZDtype. We don't have an ExtensionType for Timedelta.

DatetimeIndex's data is now a DatetimeArray, rather than an ndarray, and its dtype
is `dtype::Union['datetime64[ns]', DatetimeTZDtype]`.

**Class Hierarchy**: We've split DatetimeIndex to compose a DatetimeArray,
rather than inherit it.

Old - DatetimeIndex: ![dti_master](https://user-images.githubusercontent.com/1312546/49312507-03319980-f4aa-11e8-8ae9-2b85fb5c23f2.png)

New - DatetimeIndex: ![dti_pr](https://user-images.githubusercontent.com/1312546/49312508-03319980-f4aa-11e8-8c31-1025e64bf981.png)


New - DatetimeArray: ![dta_pr](https://user-images.githubusercontent.com/1312546/49312506-03319980-f4aa-11e8-99ae-a41e1eaad210.png)


**Internals** I've mostly given up here... Things are *basically* as they were
before, with the exception that DatetimeTZBlock now extends ExtensionBlock. **In
particular** tz-naive data is still stored in a DatetimeBlock, and is still
consolidatable. This isn't elegant, but we've lived with it for this long.
Future releases can clean it up, possibly when we rewrite / cythonize the block
manager, possibly before.

### Types of changes

1. Some changes from `x.view('i8')` to `x.astype('i8', copy=False)` to
   support getting i8 values from {ndarray, DatetimeIndex, DatetimeArray}.
   We could revert those if we implemented DatetimeArray.view, but I don't
   think we should. However, this is slower than `.view` for ndarrays, so
   I'm going to find another way.
2. Many places where we did `series.values` instead of `series._values`.
3. Moves from PeriodArray / PeriodIndex to a base class. I've annotated these
   with `NB: Moved from...`
4. Test skips / xfails. I intended to resolve all of these over the next few
   days.

---

TODO:

- [x] Split out DatelikeOps and TimelikeOps changes (#24038)
  - [x] independent tests for DatelikeOps & TimelikeOps methods on arrays.
- [x] re-clean up the constructors. I may have some cruft from rebasing
- [x] identify shared code in PeriodArray / PeriodIndex that can be moved up
- [ ] all the xfailed tests
- [ ] identify code that was special casing Datetime-dtyps (like `is_extension_type`).
- [ ] API: Series[datetimetz].unique: ndarray[object] or DatetimeArray?


Closes #23185
Closes #23932 
closes #24465 

Labels:
name: Refactor
description: Internal refactoring of code
name: Datetime
description: Datetime data dtype

Comments:
Thanks for putting this up, I'll take a look.  How did you produce those inheritance graphs?  Those are neat.
> Thanks for putting this up, I'll take a look. How did you produce those inheritance graphs? Those are neat.

lightly modified http://www.phyast.pitt.edu/~micheles/python/drawMRO.html
# [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24024?src=pr&el=h1) Report
> Merging [#24024](https://codecov.io/gh/pandas-dev/pandas/pull/24024?src=pr&el=desc) into [master](https://codecov.io/gh/pandas-dev/pandas/commit/d7e96d830c3f765565d2fd9d3f80f888fa261f02?src=pr&el=desc) will **increase** coverage by `0.17%`.
> The diff coverage is `58.59%`.

[![Impacted file tree graph](https://codecov.io/gh/pandas-dev/pandas/pull/24024/graphs/tree.svg?width=650&token=eZ4WkYLtcO&height=150&src=pr)](https://codecov.io/gh/pandas-dev/pandas/pull/24024?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master   #24024      +/-   ##
==========================================
+ Coverage   42.52%   42.69%   +0.17%     
==========================================
  Files         161      161              
  Lines       51697    51845     +148     
==========================================
+ Hits        21982    22134     +152     
+ Misses      29715    29711       -4
```

| Flag | Coverage Δ | |
|---|---|---|
| #single | `42.69% <58.59%> (+0.17%)` | :arrow_up: |

| [Impacted Files](https://codecov.io/gh/pandas-dev/pandas/pull/24024?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pandas/core/dtypes/common.py](https://codecov.io/gh/pandas-dev/pandas/pull/24024/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvZHR5cGVzL2NvbW1vbi5weQ==) | `72.49% <ø> (-0.26%)` | :arrow_down: |
| [pandas/core/arrays/interval.py](https://codecov.io/gh/pandas-dev/pandas/pull/24024/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvYXJyYXlzL2ludGVydmFsLnB5) | `35.08% <ø> (ø)` | :arrow_up: |
| [pandas/core/dtypes/cast.py](https://codecov.io/gh/pandas-dev/pandas/pull/24024/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvZHR5cGVzL2Nhc3QucHk=) | `48.42% <ø> (ø)` | :arrow_up: |
| [pandas/core/dtypes/generic.py](https://codecov.io/gh/pandas-dev/pandas/pull/24024/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvZHR5cGVzL2dlbmVyaWMucHk=) | `100% <ø> (ø)` | :arrow_up: |
| [pandas/core/reshape/reshape.py](https://codecov.io/gh/pandas-dev/pandas/pull/24024/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvcmVzaGFwZS9yZXNoYXBlLnB5) | `13.62% <0%> (ø)` | :arrow_up: |
| [pandas/core/internals/concat.py](https://codecov.io/gh/pandas-dev/pandas/pull/24024/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW50ZXJuYWxzL2NvbmNhdC5weQ==) | `73.41% <0%> (+0.68%)` | :arrow_up: |
| [pandas/core/reshape/merge.py](https://codecov.io/gh/pandas-dev/pandas/pull/24024/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvcmVzaGFwZS9tZXJnZS5weQ==) | `9.72% <0%> (+0.01%)` | :arrow_up: |
| [pandas/core/dtypes/concat.py](https://codecov.io/gh/pandas-dev/pandas/pull/24024/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvZHR5cGVzL2NvbmNhdC5weQ==) | `50.93% <0%> (-5.32%)` | :arrow_down: |
| [pandas/core/groupby/groupby.py](https://codecov.io/gh/pandas-dev/pandas/pull/24024/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvZ3JvdXBieS9ncm91cGJ5LnB5) | `25.03% <0%> (ø)` | :arrow_up: |
| [pandas/core/reshape/tile.py](https://codecov.io/gh/pandas-dev/pandas/pull/24024/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvcmVzaGFwZS90aWxlLnB5) | `11.69% <0%> (+0.06%)` | :arrow_up: |
| ... and [74 more](https://codecov.io/gh/pandas-dev/pandas/pull/24024/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24024?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24024?src=pr&el=footer). Last update [d7e96d8...7ebb9ee](https://codecov.io/gh/pandas-dev/pandas/pull/24024?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

# [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24024?src=pr&el=h1) Report
> Merging [#24024](https://codecov.io/gh/pandas-dev/pandas/pull/24024?src=pr&el=desc) into [master](https://codecov.io/gh/pandas-dev/pandas/commit/aa7cb0138da6e20b356e8d9ae1f184e78a04189f?src=pr&el=desc) will **decrease** coverage by `0.01%`.
> The diff coverage is `97.35%`.

[![Impacted file tree graph](https://codecov.io/gh/pandas-dev/pandas/pull/24024/graphs/tree.svg?width=650&token=eZ4WkYLtcO&height=150&src=pr)](https://codecov.io/gh/pandas-dev/pandas/pull/24024?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master   #24024      +/-   ##
==========================================
- Coverage   92.33%   92.31%   -0.02%     
==========================================
  Files         166      166              
  Lines       52465    52556      +91     
==========================================
+ Hits        48442    48518      +76     
- Misses       4023     4038      +15
```

| Flag | Coverage Δ | |
|---|---|---|
| #multiple | `90.74% <97.35%> (-0.02%)` | :arrow_down: |
| #single | `43.06% <68.28%> (+0.06%)` | :arrow_up: |

| [Impacted Files](https://codecov.io/gh/pandas-dev/pandas/pull/24024?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pandas/core/arrays/period.py](https://codecov.io/gh/pandas-dev/pandas/pull/24024/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvYXJyYXlzL3BlcmlvZC5weQ==) | `98.51% <ø> (ø)` | :arrow_up: |
| [pandas/core/indexes/period.py](https://codecov.io/gh/pandas-dev/pandas/pull/24024/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW5kZXhlcy9wZXJpb2QucHk=) | `92.18% <ø> (-0.11%)` | :arrow_down: |
| [pandas/core/dtypes/generic.py](https://codecov.io/gh/pandas-dev/pandas/pull/24024/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvZHR5cGVzL2dlbmVyaWMucHk=) | `100% <ø> (ø)` | :arrow_up: |
| [pandas/core/dtypes/dtypes.py](https://codecov.io/gh/pandas-dev/pandas/pull/24024/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvZHR5cGVzL2R0eXBlcy5weQ==) | `95.33% <100%> (ø)` | :arrow_up: |
| [pandas/core/base.py](https://codecov.io/gh/pandas-dev/pandas/pull/24024/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvYmFzZS5weQ==) | `97.75% <100%> (ø)` | :arrow_up: |
| [pandas/util/testing.py](https://codecov.io/gh/pandas-dev/pandas/pull/24024/diff?src=pr&el=tree#diff-cGFuZGFzL3V0aWwvdGVzdGluZy5weQ==) | `87.62% <100%> (+0.03%)` | :arrow_up: |
| [pandas/core/arrays/datetimes.py](https://codecov.io/gh/pandas-dev/pandas/pull/24024/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvYXJyYXlzL2RhdGV0aW1lcy5weQ==) | `98.01% <100%> (+0.09%)` | :arrow_up: |
| [pandas/core/dtypes/cast.py](https://codecov.io/gh/pandas-dev/pandas/pull/24024/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvZHR5cGVzL2Nhc3QucHk=) | `88.72% <100%> (ø)` | :arrow_up: |
| [pandas/core/internals/concat.py](https://codecov.io/gh/pandas-dev/pandas/pull/24024/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW50ZXJuYWxzL2NvbmNhdC5weQ==) | `96.82% <100%> (-0.02%)` | :arrow_down: |
| [pandas/core/internals/construction.py](https://codecov.io/gh/pandas-dev/pandas/pull/24024/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW50ZXJuYWxzL2NvbnN0cnVjdGlvbi5weQ==) | `96.66% <100%> (ø)` | :arrow_up: |
| ... and [19 more](https://codecov.io/gh/pandas-dev/pandas/pull/24024/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24024?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24024?src=pr&el=footer). Last update [aa7cb01...2436214](https://codecov.io/gh/pandas-dev/pandas/pull/24024?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

If people don't mind, I'm going to request that we hold off on merging things touching datetimelike stuff till we get this in. It's not been a fun morning trying to get this merged on top of master properly.
@jbrockmendel I've tagged some failures with `TODO-ops`. Haven't had a chance to look at them.
> If people don't mind, I'm going to request that we hold off on merging things touching datetimelike stuff till we get this in.

Sounds good.  FWIW the last of my major PRs went in yesterday.  All that's left is the reductions and #24123.
Which of these is correct?

```python
In [3]: list(pd.core.arrays.DatetimeArrayMixin(np.array(['2000-01-01T06:00:00'], dtype='M8[ns]'), dtype=pd.core.dtypes.dtypes.DatetimeTZDtype(tz='US/Central')))
Out[3]: [Timestamp('2000-01-01 06:00:00-0600', tz='US/Central')]
```

as of 2643721fdd66f11fd91dc245ef200ba792836c56 that was


```python
In [3]: list(pd.core.arrays.DatetimeArrayMixin(np.array(['2000-01-01T06:00:00'], dtype='M8[ns]'), dtype=pd.core.dtypes.dtypes.DatetimeTZDtype(tz='US/Central')))
Out[3]: [Timestamp('2000-01-01 00:00:00-0600', tz='US/Central')]
```

On master it's `[Timestamp('2000-01-01 06:00:00-0600', tz='US/Central')]` as well, so going to assume that master is correct...
I'm having a lot of trouble with the constructors right now. What's the recommended way to create a new DatetimeArray/TimedeltaArray/PeriodArray from an array of their underlying data? 
> What's the recommended way to create a new DatetimeArray/TimedeltaArray/PeriodArray from an array of their underlying data?

Focusing on the DatetimeArray with non-None tz (which should be isomorphic to passing dtype=Datetime64TZDtype(...)):

- `DatetimeArray(i8_data, tz=tz)` treats the `i8_data` as unix timestamps, i.e. UTC
- `DatetimeArray(M8ns_data, tz=tz)` treats the `M8_data` as _wall times_ in the given tz
- float64 behaves like M8[ns], but I'm pretty sure that is unintentional and so will be deprecated and changed to behave like int64
> Focusing on the DatetimeArray with non-None tz

The different behaviour of integers vs datetime64 is very confusing IMO, and personally I would try to clean that up here, to the extent possible (at least for the DatetimeArray constructor, for DatetimeIndex we can probably leave as that and clean up / deprecate later). 

For the DatetimeArray constructor, we could simply restrict to accepting M8 data as UTC (so not wall times), so exactly the data that is stored? (and unpacking containers that already have that, as we did for PeriodArray) That would IMO make the code a lot easier to read as it is clear what the values need to be when passed to DatetimeArray.

(note: commenting here without already taken a thorough look at the code / diff, I should probably do that first)
> The different behaviour of integers vs datetime64 is very confusing IMO, and personally I would try to clean that up here,

I *think* that's what messed me up yesterday. Thinks like DatetimeArray.copy() were failing with an invalid frequency when a DST boundary was crossed. Previously an `isinstance(datetimearray, DatetimeIndex)` converted the M8 to i8, but that condition isn't true anymore.

I'd recommend waiting on looking through the full diff until later today.
If anyone knows C / the CPython C API and is interested in a self-contained piece, the JSON IO stuff needs some work.

Adding a `DatetimeArray.values` fixes most of the tests, but we don't want that.

https://github.com/pandas-dev/pandas/blob/f74fc59793ce1f01e1afcdd29bcdf0dceccaf244/pandas/_libs/src/ujson/python/objToJSON.c#L226 is (I think) where most of the work needs to be done.

btw, the following fixes the encoding issue in the error message (at least for py3). `ValueError: � or � are not JSON serializable yet`

```diff
diff --git a/pandas/_libs/src/ujson/python/objToJSON.c b/pandas/_libs/src/ujson/python/objToJSON.c
index a5e936407..77c9ebdba 100644
--- a/pandas/_libs/src/ujson/python/objToJSON.c
+++ b/pandas/_libs/src/ujson/python/objToJSON.c
@@ -279,8 +290,8 @@ static PyObject *get_values(PyObject *obj) {
             repr = PyString_FromString("<unknown dtype>");
         }

-        PyErr_Format(PyExc_ValueError, "%s or %s are not JSON serializable yet",
-                     PyString_AS_STRING(repr), PyString_AS_STRING(typeRepr));
+        PyErr_Format(PyExc_ValueError, "%R or %R are not JSON serializable yet",
+                     repr, typeRepr);
         Py_DECREF(repr);
         Py_DECREF(typeRepr);
```
Still not quite ready. I'm going to

1. fixup the `.where`-related changes from #24114 
2. do a pass to clean up cruft
3. rewrite the summary of changes from the initial post

Will ping when that's ready.

Alrighty this should be in a reviewable state (still a WIP though).

There's some unrelated changes `Block.where`. pending the decision on https://github.com/pandas-dev/pandas/pull/24114#issuecomment-445313499 `(small hack for now, or proper support for EA.where)`, we'll have a proper fix and those changes will disappear from the diff.

Going to put up a small summary later (probably tomorrow morning).


I'll take a look.  Are there any pieces likely to be rebased away or separated into stand-alone PRs?
Thanks.

> Are there any pieces likely to be rebased away or separated into stand-alone PRs?

If you identify any candidates, let me know and I'll see what can be done. But I think we're close to the essential changes if we want to take a direct step from where we are to "standalone Datetime/TimedeltaArray".
Started a JSON branch at https://github.com/TomAugspurger/pandas/compare/disown-tz-only-rebased...TomAugspurger:disown-tz-only-rebased+json?expand=1. That fixes the ValueError we were raising, but the serialized data isn't correct.
0a8ccfde0ca58be377c9cda3413a195b69a5eb71 has a hacky fix for the groupby resample test that was failing. Some context, that may help someone more familiar with the groupby code (@WillAyd?, @jreback?) come up with a better solution:

In `_libs/reduction.pyx`, we directly manipulate the index data in a few places (e.g. https://github.com/pandas-dev/pandas/blob/72d436d4506da82b29afff7e3e07ed9843e52c56/pandas/_libs/reduction.pyx#L268https://github.com/pandas-dev/pandas/blob/72d436d4506da82b29afff7e3e07ed9843e52c56/pandas/_libs/reduction.pyx#L268 and https://github.com/pandas-dev/pandas/blob/72d436d4506da82b29afff7e3e07ed9843e52c56/pandas/_libs/reduction.pyx#L595-L598), and do stuff with the strides and sizes of the buffer that I don't really understand. Anyway, that stuff only works for indexes where `Index._data` is an ndarray.

The most obvious fix, which doesn't really require updating `_libs/reduction.pyx` is to just use a new attribute like `_index_data` (bad name) for getting / setting the actual data backing an index. For most classes this would be a reference to `Index._data`. For datetimelike, it's a reference to `index._data._data`. That change fixes the test, but feels inelegant. Any suggestions on how it could be improved?

With 0a8ccfde0ca58be377c9cda3413a195b69a5eb71 in, the xfails are down to a the ops ones (xfailed with `TODO-ops`), a few pickle ones (`TODO-pickle`), and a few JSON ones (`TODO-json`). I'm probably going to tackle the JSON ones first.

edit: No progress on JSON stuff. Moving on to pickle.
We officially have our first passing build :) Py2 is failing hard though. It seems that `indexes/datetimelike.py::wrap_array_method` won't work anymore

```python
TypeError: unbound method tz_localize() must be called with DatetimeArrayMixin instance as first argument (got DatetimeIndex instance instead)
```

Previously that worked since DatetimeIndex was a subclass of DatetimeArray.

Edit: fixing this by delegating with, e.g. `DatetimeDelegateMixin`.
All the pickle issues are (hopefully) addressed.

Back to JSON.
Anybody know how to set a breakpoint to on extension module in gdb? following https://llllllllll.github.io/c-extension-tutorial/gdb.html

```
(py37-debug) bash-4.4$ gdb python
GNU gdb (GDB) 8.0.1
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-apple-darwin17.0.0".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from python...done.
(gdb) b objToJSON.c:227
No source file named objToJSON.c.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (objToJSON.c:227) pending.
(gdb) run bug.py
Starting program: /Users/taugspurger/Envs/py37-debug/bin/python bug.py
[New Thread 0xd03 of process 64872]
warning: unhandled dyld version (15)
sys:1: RuntimeWarning: cls_dataframe
sys:1: RuntimeWarning: simple
sys:1: RuntimeWarning: in get_values
sys:1: RuntimeWarning: index/columns
sys:1: RuntimeWarning: getting-values
sys:1: RuntimeWarning: got values
sys:1: RuntimeWarning: transpose me
sys:1: RuntimeWarning: transpose-1
Traceback (most recent call last):
  File "bug.py", line 16, in <module>
    assert result == dfexp
AssertionError
[Inferior 1 (process 64872) exited with code 01]
```

doesn't seem to work.
558adf8871e98af85a3f9f6085fb1d0466292085 has the first batch of the JSON changes. I'm not proposing that as a good example for what the JSON IO interface should be for 3rd parties, just something to get the tests (mostly) passing.

There are still some failures I haven't figured out yet (marked with xfail(reason="TODO-JSON")`) 
The remaining JSON failure looks like the following

```python
In [8]: df1 = pd.DataFrame({"A": pd.date_range('2000', periods=4)})

In [9]: df2 = pd.DataFrame({"A": pd.date_range('2000', periods=4, tz="UTC")})

In [10]: df1.to_json()
Out[10]: '{"A":{"0":946684800000,"1":946771200000,"2":946857600000,"3":946944000000}}'

In [11]: df2.to_json()
Out[11]: '{"0":946684800000,"1":946771200000,"2":946857600000,"3":946944000000}'
```

`Out[10]` is correct. `Out[11] is missing the outer key for the column names.


https://github.com/TomAugspurger/pandas/compare/disown-tz-only-rebased...TomAugspurger:disown-tz-only-rebased+json?expand=1 has some debugging modifications, in case anyone more familiar with the code than me has a chance to look.
Going to unxfail the ops stuff now.

Looks like I have some 32-bit failures too: https://dev.azure.com/pandas-dev/pandas/_build/results?buildId=5131
Got most of the ops xfails in c3b7dea0a26b2b4543b2ba0d0ef303d888e929af. Turned out I was overwriting the TDI dispatching with the usual index dispatch mechanism, instead of dispatching to TDA like we do on master.
@jbrockmendel @jreback is it intentional that `Period/DatetimeIndex` ignore integer sign and size in `.astype`?

```python
In [1]: import pandas as pd

In [2]: pd.period_range('2000', periods=4).astype('int32')
Out[2]: Int64Index([10957, 10958, 10959, 10960], dtype='int64')

In [3]: pd.date_range('2000', periods=4).astype('int32')
Out[3]:
Int64Index([946684800000000000, 946771200000000000, 946857600000000000,
            946944000000000000],
           dtype='int64')
```

Perhaps I added the failing test in https://travis-ci.org/pandas-dev/pandas/jobs/466824994#L2353

Edit: this is an inconsistency between PeriodIndex.astype and PeriodArray.astype on master, that this PR fixed as part of the refactor. I'll proceed under the assumption that we want those to be consistent (ignore sign and size) and we can deprecate things later if we want.
@TomAugspurger about https://github.com/pandas-dev/pandas/pull/24024#issuecomment-446575390

yes, we don't have an int32 Index to back this with, so no choice here. In theory we *should* allow this, but I think would cause quite a lot of work / test breakage for not a lot of gain.
Do people have thoughts on the return value of `Series[datetime64[ns, tz]].unique()`

On master, return an ndarray of Timestamp objects.

Right now, this PR returns a DatetimeAray:

```python
In [3]: pd.Series(pd.date_range('2000', periods=2, tz='US/Central')).unique()
Out[3]:
<DatetimeArrayMixin>
['2000-01-01 00:00:00-06:00', '2000-01-02 00:00:00-06:00']
Length: 2, dtype: datetime64[ns, US/Central]
```