BUG: Fix Timestamp constructor changes value on ambiguous DST
State: closed

- [X] closes #24329
- [X] 1 tests added / passed
- [X] passes `black pandas`
- [X] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [X] whatsnew entry


Labels:
name: Bug
name: Timezones
description: Timezone data dtype

Comments:
This took some digging, so please bear with me.

**Short version**
When we make a Timestamp using epoch time that corresponds to the second time an ambigous DST time occurs, the Timestamp gets created and gets assigned a correct `Timestamp.value`. However, when we call the Timestamp constructor again on this Timestamp, this is what happens:

1. `convert_datetime_to_tsobject` in `conversion.pyx` gets called.
2. It eventually calls `npy_datetimestruct_to_datetime` which doesn't care about DST. As it simply adds up date components after converting them to seconds.
3. Then we attempt to shift the `Timestamp.value` using `get_utcoffset`. It sees the state its arguments are in, and, if we use `dateutil` assumes that we are in DST. `pytz` assumes we aren't in DST, which means that only `dateutil` breaks, because we end up shifting `Timezone.value` by DST timedelta (`npy_datetimestruct_to_datetime` doesn't shift, and then `get_utcoffset` shifts).

**Notes**
The core of the problem is that when we go from summer to winter time in DST timezones, the same time occurs twice as the clock is moved back one hour at a certain time. In `Europe/London` (which is used in the test), the clocks are moved at 27th of November 2:00 A.M. This means that we get 1:00 A.M. twice: before and after the shift.
`dateutil` handles the move differently from `pytz` here. For the ambiguous period it assumes that we are still in DST, and this is why the bug manifested only with `dateutil` timezones.

**PS**
I've come upon another bug while working on this, but I think it's out-of-scope for this PR and would like to handle it in a new issue and PR:
```
IN:
import pandas as pd
t = pd.Timestamp('2013-10-27 01:00:00+0000', tz='dateutil/Europe/London')
t

OUT:
Timestamp('2013-10-27 01:00:00+0100', tz='dateutil/GB-Eire')
```
Wasn't able to reproduce the test fail on Windows. Will need to set up a dev environment in Linux and try to reproduce it. Ideas on why this might be happening would be appreciated.
**Update:** for some reason a dateutil timezone object belongs to different classes on Windows and Linux/Mac. Both classes are called tzfile which makes aliases in the import section necessary.
**Update 2:** imported `dateutil.tz.tzfile` instead, same way it's used in `timezones.pyx`. Works like a charm.
Does this also fix the case for nonexistent times near DST?

https://github.com/pandas-dev/pandas/issues/24329#issuecomment-506546068
@mroeschke Unfortunately, no. Shifts it back by an hour. I would have to look into why this happens.
@mroeschke Okay, looked into it. You'll be happy to know that, according to `dateutil`, summer time starts 128 nanoseconds before 2 A.M. Looks to be a bug in `dateutil` that isn't connected to the issue that this PR is trying to handle. The bug manifests starting with 128 seconds before 2 A.M. both on master and my branch.
**Details**
This is fine:
```
>>> pd.__version__
'0.26.0.dev0+1773.g664d928fd'
>>> epoch =  1552211999999999871
>>> t = pd.Timestamp(epoch, tz='dateutil/US/Pacific')
>>> t
Timestamp('2019-03-10 01:59:59.999999871-0800', tz='dateutil/US/Pacific')
>>> t.value
1552211999999999871
>>> pd.Timestamp(t)
Timestamp('2019-03-10 01:59:59.999999871-0800', tz='dateutil/US/Pacific')
>>> pd.Timestamp(t).value
1552211999999999871
```
This is also fine:
```
>>> epoch =  1552212000000000000
>>> t = pd.Timestamp(epoch, tz='dateutil/US/Pacific')
>>> t
Timestamp('2019-03-10 03:00:00-0700', tz='dateutil/US/Pacific')
>>>
>>> t.value
1552212000000000000
>>> pd.Timestamp(t)
Timestamp('2019-03-10 03:00:00-0700', tz='dateutil/US/Pacific')
>>> pd.Timestamp(t).value
1552212000000000000
```
Meanwhile, this breaks representation and gets us nonexistent times:
```
>>> epoch =  1552211999999999872
>>> t = pd.Timestamp(epoch, tz='dateutil/US/Pacific')
>>> t
Timestamp('2019-03-10 01:59:59.999999872-0700', tz='dateutil/US/Pacific')
>>> t.value
1552211999999999872
>>> pd.Timestamp(t)
Timestamp('2019-03-10 01:59:59.999999872-0800', tz='dateutil/US/Pacific')
>>> pd.Timestamp(t).value
1552208399999999872
```
And right on the cusp, the value breaks too:
```
>>> epoch =  1552211999999999999
>>> t = pd.Timestamp(epoch, tz='dateutil/US/Pacific')
>>> t
Timestamp('2019-03-10 01:59:59.999999999-0700', tz='dateutil/US/Pacific')
>>> t.value
1552211999999999999
>>> pd.Timestamp(t)
Timestamp('2019-03-10 01:59:59.999999999-0800', tz='dateutil/US/Pacific')
>>> pd.Timestamp(t).value
1552208399999999999
```
So the bug is there, but it turns out to be unrelated and matters only for 128 nanoseconds before the switch to summer time.
I can make a separate issue, but frankly, I think it should be a `dateutil` issue. The chances that a user will encounter this are miniscule, so it's not like we have to compensate for `dateutil`.
@mroeschke Made the required changes. Also performed the final cleanup: rebased to updated master and squashed tiny commits.
Please take a look if it's ready for merging.
Thanks @AlexKirko!

If you'd like to continue investigating https://github.com/pandas-dev/pandas/issues/31043, it would be much appreciated.