cleanup; use timedelta instead of relativedelta where possible
State: closed

- Cleanup, flake8, modernize imports
- Replace usage of `other + relativedelta(...)` with `other + timedelta(...)` or `other.replace(...)` where possible.  Clearer and more performant.
- Use _get_firstbday in one place that had previously duplicated its logic.

```
asv continuous -f 1.1 -E virtualenv master HEAD -b timeseries
[...]
       before           after         ratio
     [8dac6331]       [c6145385]
-      31.4±0.2μs       26.7±0.3μs     0.85  timeseries.Offsets.time_custom_bday_decr
-      14.4±0.1ms       12.0±0.2ms     0.84  timeseries.DatetimeIndex.time_infer_freq_none
-           1.76s            1.45s     0.82  timeseries.Iteration.time_iter_periodindex
-      53.8±0.2μs       42.2±0.2μs     0.78  timeseries.SemiMonthOffset.time_end_decr_n
-        53.1±4μs       41.1±0.3μs     0.78  timeseries.SemiMonthOffset.time_begin_incr_n
-      54.6±0.2μs       41.5±0.2μs     0.76  timeseries.SemiMonthOffset.time_begin_decr_n
-      3.55±0.5ms      2.70±0.01ms     0.76  timeseries.ToDatetime.time_iso8601_nosep
-      51.8±0.2μs       38.6±0.2μs     0.75  timeseries.SemiMonthOffset.time_begin_decr
-        53.0±3μs       38.1±0.3μs     0.72  timeseries.SemiMonthOffset.time_end_incr_n
-      47.5±0.3μs       34.0±0.3μs     0.72  timeseries.SemiMonthOffset.time_end_incr
-      49.2±0.2μs       34.9±0.8μs     0.71  timeseries.SemiMonthOffset.time_begin_incr
-      55.8±0.1μs         39.3±1μs     0.70  timeseries.SemiMonthOffset.time_end_decr
-      43.6±0.5μs       30.7±0.2μs     0.70  timeseries.SemiMonthOffset.time_begin_apply
-      43.6±0.4μs       30.6±0.1μs     0.70  timeseries.SemiMonthOffset.time_end_apply
```

Labels:
name: Frequency
description: DateOffsets
name: Code Style
description: Code style, linting, code_checks

Comments:
Hello @jbrockmendel! Thanks for updating the PR.



Cheers ! There are no PEP8 issues in this Pull Request. :beers: 

##### Comment last updated on November 09, 2017 at 04:50 Hours UTC
# [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/18183?src=pr&el=h1) Report
> Merging [#18183](https://codecov.io/gh/pandas-dev/pandas/pull/18183?src=pr&el=desc) into [master](https://codecov.io/gh/pandas-dev/pandas/commit/8dac633142daa8d5bcd0cf77ad89b97628d474eb?src=pr&el=desc) will **decrease** coverage by `0.01%`.
> The diff coverage is `92.85%`.

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

```diff
@@            Coverage Diff             @@
##           master   #18183      +/-   ##
==========================================
- Coverage   91.42%    91.4%   -0.02%     
==========================================
  Files         163      163              
  Lines       50068    50065       -3     
==========================================
- Hits        45776    45764      -12     
- Misses       4292     4301       +9
```

| Flag | Coverage Δ | |
|---|---|---|
| #multiple | `89.21% <92.85%> (-0.01%)` | :arrow_down: |
| #single | `40.36% <14.28%> (-0.06%)` | :arrow_down: |

| [Impacted Files](https://codecov.io/gh/pandas-dev/pandas/pull/18183?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pandas/tseries/offsets.py](https://codecov.io/gh/pandas-dev/pandas/pull/18183?src=pr&el=tree#diff-cGFuZGFzL3RzZXJpZXMvb2Zmc2V0cy5weQ==) | `97.1% <92.85%> (-0.01%)` | :arrow_down: |
| [pandas/io/gbq.py](https://codecov.io/gh/pandas-dev/pandas/pull/18183?src=pr&el=tree#diff-cGFuZGFzL2lvL2dicS5weQ==) | `25% <0%> (-58.34%)` | :arrow_down: |
| [pandas/core/frame.py](https://codecov.io/gh/pandas-dev/pandas/pull/18183?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/18183?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/18183?src=pr&el=footer). Last update [8dac633...ce75d62](https://codecov.io/gh/pandas-dev/pandas/pull/18183?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/18183?src=pr&el=h1) Report
> Merging [#18183](https://codecov.io/gh/pandas-dev/pandas/pull/18183?src=pr&el=desc) into [master](https://codecov.io/gh/pandas-dev/pandas/commit/8dac633142daa8d5bcd0cf77ad89b97628d474eb?src=pr&el=desc) will **decrease** coverage by `0.01%`.
> The diff coverage is `92.85%`.

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

```diff
@@            Coverage Diff             @@
##           master   #18183      +/-   ##
==========================================
- Coverage   91.42%    91.4%   -0.02%     
==========================================
  Files         163      163              
  Lines       50068    50065       -3     
==========================================
- Hits        45776    45764      -12     
- Misses       4292     4301       +9
```

| Flag | Coverage Δ | |
|---|---|---|
| #multiple | `89.21% <92.85%> (-0.01%)` | :arrow_down: |
| #single | `40.36% <14.28%> (-0.06%)` | :arrow_down: |

| [Impacted Files](https://codecov.io/gh/pandas-dev/pandas/pull/18183?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pandas/tseries/offsets.py](https://codecov.io/gh/pandas-dev/pandas/pull/18183?src=pr&el=tree#diff-cGFuZGFzL3RzZXJpZXMvb2Zmc2V0cy5weQ==) | `97.1% <92.85%> (-0.01%)` | :arrow_down: |
| [pandas/io/gbq.py](https://codecov.io/gh/pandas-dev/pandas/pull/18183?src=pr&el=tree#diff-cGFuZGFzL2lvL2dicS5weQ==) | `25% <0%> (-58.34%)` | :arrow_down: |
| [pandas/core/frame.py](https://codecov.io/gh/pandas-dev/pandas/pull/18183?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/18183?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/18183?src=pr&el=footer). Last update [8dac633...ce75d62](https://codecov.io/gh/pandas-dev/pandas/pull/18183?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/18183?src=pr&el=h1) Report
> Merging [#18183](https://codecov.io/gh/pandas-dev/pandas/pull/18183?src=pr&el=desc) into [master](https://codecov.io/gh/pandas-dev/pandas/commit/8dac633142daa8d5bcd0cf77ad89b97628d474eb?src=pr&el=desc) will **decrease** coverage by `0.01%`.
> The diff coverage is `92.85%`.

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

```diff
@@            Coverage Diff             @@
##           master   #18183      +/-   ##
==========================================
- Coverage   91.42%    91.4%   -0.02%     
==========================================
  Files         163      163              
  Lines       50068    50065       -3     
==========================================
- Hits        45776    45764      -12     
- Misses       4292     4301       +9
```

| Flag | Coverage Δ | |
|---|---|---|
| #multiple | `89.21% <92.85%> (-0.01%)` | :arrow_down: |
| #single | `40.36% <14.28%> (-0.06%)` | :arrow_down: |

| [Impacted Files](https://codecov.io/gh/pandas-dev/pandas/pull/18183?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pandas/tseries/offsets.py](https://codecov.io/gh/pandas-dev/pandas/pull/18183?src=pr&el=tree#diff-cGFuZGFzL3RzZXJpZXMvb2Zmc2V0cy5weQ==) | `97.1% <92.85%> (-0.01%)` | :arrow_down: |
| [pandas/io/gbq.py](https://codecov.io/gh/pandas-dev/pandas/pull/18183?src=pr&el=tree#diff-cGFuZGFzL2lvL2dicS5weQ==) | `25% <0%> (-58.34%)` | :arrow_down: |
| [pandas/core/frame.py](https://codecov.io/gh/pandas-dev/pandas/pull/18183?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/18183?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/18183?src=pr&el=footer). Last update [8dac633...ce75d62](https://codecov.io/gh/pandas-dev/pandas/pull/18183?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

Question (it has been a long time I worked with relativedeltas): in the non-replace case (so when using plural kwargs to relativedelta), is addition with relativedelta then always fully equivalent to addition of a timedelta? 
Eg there are no differences in how they handle DST changes? (where adding 1 day might actually add 24 hours but change the hour of the result, or keep the hour of the result and thus adds 23/25 hours)
> Question (it has been a long time I worked with relativedeltas):

The fact that relativedelta's behavior is more obscure is part of why this change is helpful.  (Also the perf improvement is a free lunch)

> in the non-replace case (so when using plural kwargs to relativedelta), is addition with relativedelta then always fully equivalent to addition of a timedelta?

For everything weeks and shorter, yes.  Months and years have more complicated behavior.

> Eg there are no differences in how they handle DST changes?

In the pertinent case, `relativedelta.__add__` passes through to timedelta:

```
ret = (other.replace(**repl)
               + datetime.timedelta(days=days,
                                    hours=self.hours,
                                    minutes=self.minutes,
                                    seconds=self.seconds,
                                    microseconds=self.microseconds))
```
Working on fixes for #8386 and a few related things.  That should hurt perf very slightly.  I'd like to get these perf improvements in so as to stay "in the black".  (and to keep the cleanup separate from the non-trivial upcoming diffs)
Superceded by #18218.