implement .dt accessor for Index classes
State: closed

`index.dt` --> `self` for `DatetimeIndex`, `TimedeltaIndex`, `PeriodIndex`.  Raises `AttributeError` for others (identical error to `Series.dt`).

@jorisvandenbossche in the discussion in #17134 you had a concern about tab completion/namespacing.  Is that a sticking point for you?

- [x] closes #17134
- [x] tests added / passed
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [x] whatsnew entry


Labels:
name: API Design

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

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

```diff
@@            Coverage Diff             @@
##           master   #18429      +/-   ##
==========================================
- Coverage   91.35%   91.33%   -0.02%     
==========================================
  Files         163      163              
  Lines       49714    49719       +5     
==========================================
- Hits        45415    45410       -5     
- Misses       4299     4309      +10
```

| Flag | Coverage Δ | |
|---|---|---|
| #multiple | `89.13% <100%> (-0.01%)` | :arrow_down: |
| #single | `39.64% <60%> (-0.06%)` | :arrow_down: |

| [Impacted Files](https://codecov.io/gh/pandas-dev/pandas/pull/18429?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pandas/core/indexes/datetimelike.py](https://codecov.io/gh/pandas-dev/pandas/pull/18429?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW5kZXhlcy9kYXRldGltZWxpa2UucHk=) | `97.13% <100%> (+0.01%)` | :arrow_up: |
| [pandas/core/indexes/base.py](https://codecov.io/gh/pandas-dev/pandas/pull/18429?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW5kZXhlcy9iYXNlLnB5) | `96.43% <100%> (ø)` | :arrow_up: |
| [pandas/io/gbq.py](https://codecov.io/gh/pandas-dev/pandas/pull/18429?src=pr&el=tree#diff-cGFuZGFzL2lvL2dicS5weQ==) | `25% <0%> (-58.34%)` | :arrow_down: |
| [pandas/io/json/table\_schema.py](https://codecov.io/gh/pandas-dev/pandas/pull/18429?src=pr&el=tree#diff-cGFuZGFzL2lvL2pzb24vdGFibGVfc2NoZW1hLnB5) | `94.44% <0%> (-1.39%)` | :arrow_down: |
| [pandas/core/frame.py](https://codecov.io/gh/pandas-dev/pandas/pull/18429?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvZnJhbWUucHk=) | `97.8% <0%> (-0.1%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/18429?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/18429?src=pr&el=footer). Last update [d421a09...bda766e](https://codecov.io/gh/pandas-dev/pandas/pull/18429?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/18429?src=pr&el=h1) Report
> Merging [#18429](https://codecov.io/gh/pandas-dev/pandas/pull/18429?src=pr&el=desc) into [master](https://codecov.io/gh/pandas-dev/pandas/commit/d421a09e382109c1bbe064107c4024b065839de2?src=pr&el=desc) will **decrease** coverage by `0.01%`.
> The diff coverage is `100%`.

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

```diff
@@            Coverage Diff             @@
##           master   #18429      +/-   ##
==========================================
- Coverage   91.35%   91.33%   -0.02%     
==========================================
  Files         163      163              
  Lines       49714    49719       +5     
==========================================
- Hits        45415    45410       -5     
- Misses       4299     4309      +10
```

| Flag | Coverage Δ | |
|---|---|---|
| #multiple | `89.13% <100%> (-0.01%)` | :arrow_down: |
| #single | `39.64% <60%> (-0.06%)` | :arrow_down: |

| [Impacted Files](https://codecov.io/gh/pandas-dev/pandas/pull/18429?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pandas/core/indexes/base.py](https://codecov.io/gh/pandas-dev/pandas/pull/18429?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW5kZXhlcy9iYXNlLnB5) | `96.43% <100%> (ø)` | :arrow_up: |
| [pandas/core/indexes/datetimelike.py](https://codecov.io/gh/pandas-dev/pandas/pull/18429?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW5kZXhlcy9kYXRldGltZWxpa2UucHk=) | `97.13% <100%> (+0.01%)` | :arrow_up: |
| [pandas/io/gbq.py](https://codecov.io/gh/pandas-dev/pandas/pull/18429?src=pr&el=tree#diff-cGFuZGFzL2lvL2dicS5weQ==) | `25% <0%> (-58.34%)` | :arrow_down: |
| [pandas/io/json/table\_schema.py](https://codecov.io/gh/pandas-dev/pandas/pull/18429?src=pr&el=tree#diff-cGFuZGFzL2lvL2pzb24vdGFibGVfc2NoZW1hLnB5) | `94.44% <0%> (-1.39%)` | :arrow_down: |
| [pandas/core/frame.py](https://codecov.io/gh/pandas-dev/pandas/pull/18429?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvZnJhbWUucHk=) | `97.8% <0%> (-0.1%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/18429?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/18429?src=pr&el=footer). Last update [d421a09...bda766e](https://codecov.io/gh/pandas-dev/pandas/pull/18429?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

> @jorisvandenbossche in the discussion in #17134 you had a concern about tab completion/namespacing. Is that a sticking point for you?

Yes, I think so (and not only for tab completion). Doing `df.index.dt.mean()` should be invalid IMO.
> Yes, I think so (and not only for tab completion). Doing df.index.dt.mean() should be invalid IMO.

OK.  Would returning `self.to_series().dt` solve your concerns?  @jreback back in the issue you were pretty adamant that returning anything other than `self` would be inconsistent.  Can you two reach a consensus on this?
>  Would returning self.to_series().dt solve your concerns?

No, because that would return a Series, while the `Index.dt.` attributes should return an Index IMO (see https://github.com/pandas-dev/pandas/issues/17134#issuecomment-321190508, which @jreback agrees with). 
So it's more an implementation question: it should be like returning self, but a restricted version.

Eg we could add a switch to `_delegate_method` to return either raw index result or wrapped in Series ?
> No, because that would return a Series, while the Index.dt. attributes should return an Index IMO 

I had hoped to avoid introducing a new class, but that should be doable.  There is a lot of machinery in indexes.datetimelike, some of which can probably be made useful.
how is this any more complicated than for ``.str``?
``str = accessor.AccessorProperty(strings.StringMethods)``

> how is this any more complicated than for .str?
> str = accessor.AccessorProperty(strings.StringMethods)

StringMethods has special handling for cases where it is derived from a Series vs Index.  I can do something analogous in `indexes.accessors`, but last time I tried that you said it was unnecessarily complicated and should just return `self` (in fairness, that implementation _was_ unnecessarily complicated and returned a Series instead of an Index...)

So let's nail down the specs before I implement anything else.  It modifying indexes.accessors to behave analogously to StringMethods going to make everyone happy?
> So let's nail down the specs before I implement anything else. It modifying indexes.accessors to behave analogously to StringMethods going to make everyone happy?

I think these should all be consistent, so yes, not sure there should actually be that much complexity.
closing as stale, if you want to work on this, pls ping.

I think the idea of this PR is fine, but needs resolution