293 lines
12 KiB
Python
293 lines
12 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
"""
|
||
|
Tests for Timestamp timezone-related methods
|
||
|
"""
|
||
|
from datetime import date, timedelta
|
||
|
|
||
|
from distutils.version import LooseVersion
|
||
|
import pytest
|
||
|
import pytz
|
||
|
from pytz.exceptions import AmbiguousTimeError, NonExistentTimeError
|
||
|
import dateutil
|
||
|
from dateutil.tz import gettz, tzoffset
|
||
|
|
||
|
import pandas.util.testing as tm
|
||
|
import pandas.util._test_decorators as td
|
||
|
|
||
|
from pandas import Timestamp, NaT
|
||
|
from pandas.errors import OutOfBoundsDatetime
|
||
|
|
||
|
|
||
|
class TestTimestampTZOperations(object):
|
||
|
# --------------------------------------------------------------
|
||
|
# Timestamp.tz_localize
|
||
|
|
||
|
def test_tz_localize_pushes_out_of_bounds(self):
|
||
|
# GH#12677
|
||
|
# tz_localize that pushes away from the boundary is OK
|
||
|
pac = Timestamp.min.tz_localize('US/Pacific')
|
||
|
assert pac.value > Timestamp.min.value
|
||
|
pac.tz_convert('Asia/Tokyo') # tz_convert doesn't change value
|
||
|
with pytest.raises(OutOfBoundsDatetime):
|
||
|
Timestamp.min.tz_localize('Asia/Tokyo')
|
||
|
|
||
|
# tz_localize that pushes away from the boundary is OK
|
||
|
tokyo = Timestamp.max.tz_localize('Asia/Tokyo')
|
||
|
assert tokyo.value < Timestamp.max.value
|
||
|
tokyo.tz_convert('US/Pacific') # tz_convert doesn't change value
|
||
|
with pytest.raises(OutOfBoundsDatetime):
|
||
|
Timestamp.max.tz_localize('US/Pacific')
|
||
|
|
||
|
def test_tz_localize_ambiguous_bool(self):
|
||
|
# make sure that we are correctly accepting bool values as ambiguous
|
||
|
# GH#14402
|
||
|
ts = Timestamp('2015-11-01 01:00:03')
|
||
|
expected0 = Timestamp('2015-11-01 01:00:03-0500', tz='US/Central')
|
||
|
expected1 = Timestamp('2015-11-01 01:00:03-0600', tz='US/Central')
|
||
|
|
||
|
with pytest.raises(pytz.AmbiguousTimeError):
|
||
|
ts.tz_localize('US/Central')
|
||
|
|
||
|
result = ts.tz_localize('US/Central', ambiguous=True)
|
||
|
assert result == expected0
|
||
|
|
||
|
result = ts.tz_localize('US/Central', ambiguous=False)
|
||
|
assert result == expected1
|
||
|
|
||
|
def test_tz_localize_ambiguous(self):
|
||
|
ts = Timestamp('2014-11-02 01:00')
|
||
|
ts_dst = ts.tz_localize('US/Eastern', ambiguous=True)
|
||
|
ts_no_dst = ts.tz_localize('US/Eastern', ambiguous=False)
|
||
|
|
||
|
assert (ts_no_dst.value - ts_dst.value) / 1e9 == 3600
|
||
|
with pytest.raises(ValueError):
|
||
|
ts.tz_localize('US/Eastern', ambiguous='infer')
|
||
|
|
||
|
# GH#8025
|
||
|
with tm.assert_raises_regex(TypeError,
|
||
|
'Cannot localize tz-aware Timestamp, '
|
||
|
'use tz_convert for conversions'):
|
||
|
Timestamp('2011-01-01', tz='US/Eastern').tz_localize('Asia/Tokyo')
|
||
|
|
||
|
with tm.assert_raises_regex(TypeError,
|
||
|
'Cannot convert tz-naive Timestamp, '
|
||
|
'use tz_localize to localize'):
|
||
|
Timestamp('2011-01-01').tz_convert('Asia/Tokyo')
|
||
|
|
||
|
@pytest.mark.parametrize('stamp, tz', [
|
||
|
('2015-03-08 02:00', 'US/Eastern'),
|
||
|
('2015-03-08 02:30', 'US/Pacific'),
|
||
|
('2015-03-29 02:00', 'Europe/Paris'),
|
||
|
('2015-03-29 02:30', 'Europe/Belgrade')])
|
||
|
def test_tz_localize_nonexistent(self, stamp, tz):
|
||
|
# GH#13057
|
||
|
ts = Timestamp(stamp)
|
||
|
with pytest.raises(NonExistentTimeError):
|
||
|
ts.tz_localize(tz)
|
||
|
with pytest.raises(NonExistentTimeError):
|
||
|
ts.tz_localize(tz, errors='raise')
|
||
|
assert ts.tz_localize(tz, errors='coerce') is NaT
|
||
|
|
||
|
def test_tz_localize_errors_ambiguous(self):
|
||
|
# GH#13057
|
||
|
ts = Timestamp('2015-11-1 01:00')
|
||
|
with pytest.raises(AmbiguousTimeError):
|
||
|
ts.tz_localize('US/Pacific', errors='coerce')
|
||
|
|
||
|
@pytest.mark.parametrize('stamp', ['2014-02-01 09:00', '2014-07-08 09:00',
|
||
|
'2014-11-01 17:00', '2014-11-05 00:00'])
|
||
|
def test_tz_localize_roundtrip(self, stamp, tz_aware_fixture):
|
||
|
tz = tz_aware_fixture
|
||
|
ts = Timestamp(stamp)
|
||
|
localized = ts.tz_localize(tz)
|
||
|
assert localized == Timestamp(stamp, tz=tz)
|
||
|
|
||
|
with pytest.raises(TypeError):
|
||
|
localized.tz_localize(tz)
|
||
|
|
||
|
reset = localized.tz_localize(None)
|
||
|
assert reset == ts
|
||
|
assert reset.tzinfo is None
|
||
|
|
||
|
def test_tz_localize_ambiguous_compat(self):
|
||
|
# validate that pytz and dateutil are compat for dst
|
||
|
# when the transition happens
|
||
|
naive = Timestamp('2013-10-27 01:00:00')
|
||
|
|
||
|
pytz_zone = 'Europe/London'
|
||
|
dateutil_zone = 'dateutil/Europe/London'
|
||
|
result_pytz = naive.tz_localize(pytz_zone, ambiguous=0)
|
||
|
result_dateutil = naive.tz_localize(dateutil_zone, ambiguous=0)
|
||
|
assert result_pytz.value == result_dateutil.value
|
||
|
assert result_pytz.value == 1382835600000000000
|
||
|
|
||
|
if LooseVersion(dateutil.__version__) < LooseVersion('2.6.0'):
|
||
|
# dateutil 2.6 buggy w.r.t. ambiguous=0
|
||
|
# see gh-14621
|
||
|
# see https://github.com/dateutil/dateutil/issues/321
|
||
|
assert (result_pytz.to_pydatetime().tzname() ==
|
||
|
result_dateutil.to_pydatetime().tzname())
|
||
|
assert str(result_pytz) == str(result_dateutil)
|
||
|
elif LooseVersion(dateutil.__version__) > LooseVersion('2.6.0'):
|
||
|
# fixed ambiguous behavior
|
||
|
assert result_pytz.to_pydatetime().tzname() == 'GMT'
|
||
|
assert result_dateutil.to_pydatetime().tzname() == 'BST'
|
||
|
assert str(result_pytz) != str(result_dateutil)
|
||
|
|
||
|
# 1 hour difference
|
||
|
result_pytz = naive.tz_localize(pytz_zone, ambiguous=1)
|
||
|
result_dateutil = naive.tz_localize(dateutil_zone, ambiguous=1)
|
||
|
assert result_pytz.value == result_dateutil.value
|
||
|
assert result_pytz.value == 1382832000000000000
|
||
|
|
||
|
# dateutil < 2.6 is buggy w.r.t. ambiguous timezones
|
||
|
if LooseVersion(dateutil.__version__) > LooseVersion('2.5.3'):
|
||
|
# see gh-14621
|
||
|
assert str(result_pytz) == str(result_dateutil)
|
||
|
assert (result_pytz.to_pydatetime().tzname() ==
|
||
|
result_dateutil.to_pydatetime().tzname())
|
||
|
|
||
|
@pytest.mark.parametrize('tz', [pytz.timezone('US/Eastern'),
|
||
|
gettz('US/Eastern'),
|
||
|
'US/Eastern', 'dateutil/US/Eastern'])
|
||
|
def test_timestamp_tz_localize(self, tz):
|
||
|
stamp = Timestamp('3/11/2012 04:00')
|
||
|
|
||
|
result = stamp.tz_localize(tz)
|
||
|
expected = Timestamp('3/11/2012 04:00', tz=tz)
|
||
|
assert result.hour == expected.hour
|
||
|
assert result == expected
|
||
|
|
||
|
# ------------------------------------------------------------------
|
||
|
# Timestamp.tz_convert
|
||
|
|
||
|
@pytest.mark.parametrize('stamp', ['2014-02-01 09:00', '2014-07-08 09:00',
|
||
|
'2014-11-01 17:00', '2014-11-05 00:00'])
|
||
|
def test_tz_convert_roundtrip(self, stamp, tz_aware_fixture):
|
||
|
tz = tz_aware_fixture
|
||
|
|
||
|
ts = Timestamp(stamp, tz='UTC')
|
||
|
converted = ts.tz_convert(tz)
|
||
|
|
||
|
reset = converted.tz_convert(None)
|
||
|
assert reset == Timestamp(stamp)
|
||
|
assert reset.tzinfo is None
|
||
|
assert reset == converted.tz_convert('UTC').tz_localize(None)
|
||
|
|
||
|
@pytest.mark.parametrize('tzstr', ['US/Eastern', 'dateutil/US/Eastern'])
|
||
|
def test_astimezone(self, tzstr):
|
||
|
# astimezone is an alias for tz_convert, so keep it with
|
||
|
# the tz_convert tests
|
||
|
utcdate = Timestamp('3/11/2012 22:00', tz='UTC')
|
||
|
expected = utcdate.tz_convert(tzstr)
|
||
|
result = utcdate.astimezone(tzstr)
|
||
|
assert expected == result
|
||
|
assert isinstance(result, Timestamp)
|
||
|
|
||
|
@td.skip_if_windows
|
||
|
def test_tz_convert_utc_with_system_utc(self):
|
||
|
from pandas._libs.tslibs.timezones import maybe_get_tz
|
||
|
|
||
|
# from system utc to real utc
|
||
|
ts = Timestamp('2001-01-05 11:56', tz=maybe_get_tz('dateutil/UTC'))
|
||
|
# check that the time hasn't changed.
|
||
|
assert ts == ts.tz_convert(dateutil.tz.tzutc())
|
||
|
|
||
|
# from system utc to real utc
|
||
|
ts = Timestamp('2001-01-05 11:56', tz=maybe_get_tz('dateutil/UTC'))
|
||
|
# check that the time hasn't changed.
|
||
|
assert ts == ts.tz_convert(dateutil.tz.tzutc())
|
||
|
|
||
|
# ------------------------------------------------------------------
|
||
|
# Timestamp.__init__ with tz str or tzinfo
|
||
|
|
||
|
def test_timestamp_constructor_tz_utc(self):
|
||
|
utc_stamp = Timestamp('3/11/2012 05:00', tz='utc')
|
||
|
assert utc_stamp.tzinfo is pytz.utc
|
||
|
assert utc_stamp.hour == 5
|
||
|
|
||
|
utc_stamp = Timestamp('3/11/2012 05:00').tz_localize('utc')
|
||
|
assert utc_stamp.hour == 5
|
||
|
|
||
|
def test_timestamp_to_datetime_tzoffset(self):
|
||
|
tzinfo = tzoffset(None, 7200)
|
||
|
expected = Timestamp('3/11/2012 04:00', tz=tzinfo)
|
||
|
result = Timestamp(expected.to_pydatetime())
|
||
|
assert expected == result
|
||
|
|
||
|
def test_timestamp_constructor_near_dst_boundary(self):
|
||
|
# GH#11481 & GH#15777
|
||
|
# Naive string timestamps were being localized incorrectly
|
||
|
# with tz_convert_single instead of tz_localize_to_utc
|
||
|
|
||
|
for tz in ['Europe/Brussels', 'Europe/Prague']:
|
||
|
result = Timestamp('2015-10-25 01:00', tz=tz)
|
||
|
expected = Timestamp('2015-10-25 01:00').tz_localize(tz)
|
||
|
assert result == expected
|
||
|
|
||
|
with pytest.raises(pytz.AmbiguousTimeError):
|
||
|
Timestamp('2015-10-25 02:00', tz=tz)
|
||
|
|
||
|
result = Timestamp('2017-03-26 01:00', tz='Europe/Paris')
|
||
|
expected = Timestamp('2017-03-26 01:00').tz_localize('Europe/Paris')
|
||
|
assert result == expected
|
||
|
|
||
|
with pytest.raises(pytz.NonExistentTimeError):
|
||
|
Timestamp('2017-03-26 02:00', tz='Europe/Paris')
|
||
|
|
||
|
# GH#11708
|
||
|
naive = Timestamp('2015-11-18 10:00:00')
|
||
|
result = naive.tz_localize('UTC').tz_convert('Asia/Kolkata')
|
||
|
expected = Timestamp('2015-11-18 15:30:00+0530', tz='Asia/Kolkata')
|
||
|
assert result == expected
|
||
|
|
||
|
# GH#15823
|
||
|
result = Timestamp('2017-03-26 00:00', tz='Europe/Paris')
|
||
|
expected = Timestamp('2017-03-26 00:00:00+0100', tz='Europe/Paris')
|
||
|
assert result == expected
|
||
|
|
||
|
result = Timestamp('2017-03-26 01:00', tz='Europe/Paris')
|
||
|
expected = Timestamp('2017-03-26 01:00:00+0100', tz='Europe/Paris')
|
||
|
assert result == expected
|
||
|
|
||
|
with pytest.raises(pytz.NonExistentTimeError):
|
||
|
Timestamp('2017-03-26 02:00', tz='Europe/Paris')
|
||
|
|
||
|
result = Timestamp('2017-03-26 02:00:00+0100', tz='Europe/Paris')
|
||
|
naive = Timestamp(result.value)
|
||
|
expected = naive.tz_localize('UTC').tz_convert('Europe/Paris')
|
||
|
assert result == expected
|
||
|
|
||
|
result = Timestamp('2017-03-26 03:00', tz='Europe/Paris')
|
||
|
expected = Timestamp('2017-03-26 03:00:00+0200', tz='Europe/Paris')
|
||
|
assert result == expected
|
||
|
|
||
|
@pytest.mark.parametrize('tz', [pytz.timezone('US/Eastern'),
|
||
|
gettz('US/Eastern'),
|
||
|
'US/Eastern', 'dateutil/US/Eastern'])
|
||
|
def test_timestamp_constructed_by_date_and_tz(self, tz):
|
||
|
# GH#2993, Timestamp cannot be constructed by datetime.date
|
||
|
# and tz correctly
|
||
|
|
||
|
result = Timestamp(date(2012, 3, 11), tz=tz)
|
||
|
|
||
|
expected = Timestamp('3/11/2012', tz=tz)
|
||
|
assert result.hour == expected.hour
|
||
|
assert result == expected
|
||
|
|
||
|
@pytest.mark.parametrize('tz', [pytz.timezone('US/Eastern'),
|
||
|
gettz('US/Eastern'),
|
||
|
'US/Eastern', 'dateutil/US/Eastern'])
|
||
|
def test_timestamp_add_timedelta_push_over_dst_boundary(self, tz):
|
||
|
# GH#1389
|
||
|
|
||
|
# 4 hours before DST transition
|
||
|
stamp = Timestamp('3/10/2012 22:00', tz=tz)
|
||
|
|
||
|
result = stamp + timedelta(hours=6)
|
||
|
|
||
|
# spring forward, + "7" hours
|
||
|
expected = Timestamp('3/11/2012 05:00', tz=tz)
|
||
|
|
||
|
assert result == expected
|