123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493 |
- """
- Tests for Timestamp timezone-related methods
- """
- from datetime import (
- date,
- datetime,
- timedelta,
- timezone,
- )
- import re
- import dateutil
- from dateutil.tz import (
- gettz,
- tzoffset,
- )
- import pytest
- import pytz
- from pytz.exceptions import (
- AmbiguousTimeError,
- NonExistentTimeError,
- )
- from pandas._libs.tslibs import timezones
- from pandas._libs.tslibs.dtypes import NpyDatetimeUnit
- from pandas.errors import OutOfBoundsDatetime
- import pandas.util._test_decorators as td
- from pandas import (
- NaT,
- Timestamp,
- )
- try:
- from zoneinfo import ZoneInfo
- except ImportError:
- ZoneInfo = None
- class TestTimestampTZOperations:
- # --------------------------------------------------------------
- # Timestamp.tz_localize
- def test_tz_localize_pushes_out_of_bounds(self):
- # GH#12677
- # tz_localize that pushes away from the boundary is OK
- msg = (
- f"Converting {Timestamp.min.strftime('%Y-%m-%d %H:%M:%S')} "
- f"underflows past {Timestamp.min}"
- )
- 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, match=msg):
- Timestamp.min.tz_localize("Asia/Tokyo")
- # tz_localize that pushes away from the boundary is OK
- msg = (
- f"Converting {Timestamp.max.strftime('%Y-%m-%d %H:%M:%S')} "
- f"overflows past {Timestamp.max}"
- )
- 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, match=msg):
- Timestamp.max.tz_localize("US/Pacific")
- @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"])
- def test_tz_localize_ambiguous_bool(self, unit):
- # make sure that we are correctly accepting bool values as ambiguous
- # GH#14402
- ts = Timestamp("2015-11-01 01:00:03").as_unit(unit)
- expected0 = Timestamp("2015-11-01 01:00:03-0500", tz="US/Central")
- expected1 = Timestamp("2015-11-01 01:00:03-0600", tz="US/Central")
- msg = "Cannot infer dst time from 2015-11-01 01:00:03"
- with pytest.raises(pytz.AmbiguousTimeError, match=msg):
- ts.tz_localize("US/Central")
- with pytest.raises(pytz.AmbiguousTimeError, match=msg):
- ts.tz_localize("dateutil/US/Central")
- if ZoneInfo is not None:
- try:
- tz = ZoneInfo("US/Central")
- except KeyError:
- # no tzdata
- pass
- else:
- with pytest.raises(pytz.AmbiguousTimeError, match=msg):
- ts.tz_localize(tz)
- result = ts.tz_localize("US/Central", ambiguous=True)
- assert result == expected0
- assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value
- result = ts.tz_localize("US/Central", ambiguous=False)
- assert result == expected1
- assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value
- 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 == 3600
- msg = re.escape(
- "'ambiguous' parameter must be one of: "
- "True, False, 'NaT', 'raise' (default)"
- )
- with pytest.raises(ValueError, match=msg):
- ts.tz_localize("US/Eastern", ambiguous="infer")
- # GH#8025
- msg = "Cannot localize tz-aware Timestamp, use tz_convert for conversions"
- with pytest.raises(TypeError, match=msg):
- Timestamp("2011-01-01", tz="US/Eastern").tz_localize("Asia/Tokyo")
- msg = "Cannot convert tz-naive Timestamp, use tz_localize to localize"
- with pytest.raises(TypeError, match=msg):
- 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, match=stamp):
- ts.tz_localize(tz)
- # GH 22644
- with pytest.raises(NonExistentTimeError, match=stamp):
- ts.tz_localize(tz, nonexistent="raise")
- assert ts.tz_localize(tz, nonexistent="NaT") is NaT
- def test_tz_localize_ambiguous_raise(self):
- # GH#13057
- ts = Timestamp("2015-11-1 01:00")
- msg = "Cannot infer dst time from 2015-11-01 01:00:00,"
- with pytest.raises(AmbiguousTimeError, match=msg):
- ts.tz_localize("US/Pacific", ambiguous="raise")
- def test_tz_localize_nonexistent_invalid_arg(self, warsaw):
- # GH 22644
- tz = warsaw
- ts = Timestamp("2015-03-29 02:00:00")
- msg = (
- "The nonexistent argument must be one of 'raise', 'NaT', "
- "'shift_forward', 'shift_backward' or a timedelta object"
- )
- with pytest.raises(ValueError, match=msg):
- ts.tz_localize(tz, nonexistent="foo")
- @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)
- msg = "Cannot localize tz-aware Timestamp"
- with pytest.raises(TypeError, match=msg):
- 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=False)
- result_dateutil = naive.tz_localize(dateutil_zone, ambiguous=False)
- assert result_pytz._value == result_dateutil._value
- assert result_pytz._value == 1382835600
- # fixed ambiguous behavior
- # see gh-14621, GH#45087
- assert result_pytz.to_pydatetime().tzname() == "GMT"
- assert result_dateutil.to_pydatetime().tzname() == "GMT"
- assert str(result_pytz) == str(result_dateutil)
- # 1 hour difference
- result_pytz = naive.tz_localize(pytz_zone, ambiguous=True)
- result_dateutil = naive.tz_localize(dateutil_zone, ambiguous=True)
- assert result_pytz._value == result_dateutil._value
- assert result_pytz._value == 1382832000
- # 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
- @pytest.mark.parametrize(
- "start_ts, tz, end_ts, shift",
- [
- ["2015-03-29 02:20:00", "Europe/Warsaw", "2015-03-29 03:00:00", "forward"],
- [
- "2015-03-29 02:20:00",
- "Europe/Warsaw",
- "2015-03-29 01:59:59.999999999",
- "backward",
- ],
- [
- "2015-03-29 02:20:00",
- "Europe/Warsaw",
- "2015-03-29 03:20:00",
- timedelta(hours=1),
- ],
- [
- "2015-03-29 02:20:00",
- "Europe/Warsaw",
- "2015-03-29 01:20:00",
- timedelta(hours=-1),
- ],
- ["2018-03-11 02:33:00", "US/Pacific", "2018-03-11 03:00:00", "forward"],
- [
- "2018-03-11 02:33:00",
- "US/Pacific",
- "2018-03-11 01:59:59.999999999",
- "backward",
- ],
- [
- "2018-03-11 02:33:00",
- "US/Pacific",
- "2018-03-11 03:33:00",
- timedelta(hours=1),
- ],
- [
- "2018-03-11 02:33:00",
- "US/Pacific",
- "2018-03-11 01:33:00",
- timedelta(hours=-1),
- ],
- ],
- )
- @pytest.mark.parametrize("tz_type", ["", "dateutil/"])
- @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"])
- def test_timestamp_tz_localize_nonexistent_shift(
- self, start_ts, tz, end_ts, shift, tz_type, unit
- ):
- # GH 8917, 24466
- tz = tz_type + tz
- if isinstance(shift, str):
- shift = "shift_" + shift
- ts = Timestamp(start_ts).as_unit(unit)
- result = ts.tz_localize(tz, nonexistent=shift)
- expected = Timestamp(end_ts).tz_localize(tz)
- if unit == "us":
- assert result == expected.replace(nanosecond=0)
- elif unit == "ms":
- micros = expected.microsecond - expected.microsecond % 1000
- assert result == expected.replace(microsecond=micros, nanosecond=0)
- elif unit == "s":
- assert result == expected.replace(microsecond=0, nanosecond=0)
- else:
- assert result == expected
- assert result._creso == getattr(NpyDatetimeUnit, f"NPY_FR_{unit}").value
- @pytest.mark.parametrize("offset", [-1, 1])
- def test_timestamp_tz_localize_nonexistent_shift_invalid(self, offset, warsaw):
- # GH 8917, 24466
- tz = warsaw
- ts = Timestamp("2015-03-29 02:20:00")
- msg = "The provided timedelta will relocalize on a nonexistent time"
- with pytest.raises(ValueError, match=msg):
- ts.tz_localize(tz, nonexistent=timedelta(seconds=offset))
- @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"])
- def test_timestamp_tz_localize_nonexistent_NaT(self, warsaw, unit):
- # GH 8917
- tz = warsaw
- ts = Timestamp("2015-03-29 02:20:00").as_unit(unit)
- result = ts.tz_localize(tz, nonexistent="NaT")
- assert result is NaT
- @pytest.mark.parametrize("unit", ["ns", "us", "ms", "s"])
- def test_timestamp_tz_localize_nonexistent_raise(self, warsaw, unit):
- # GH 8917
- tz = warsaw
- ts = Timestamp("2015-03-29 02:20:00").as_unit(unit)
- msg = "2015-03-29 02:20:00"
- with pytest.raises(pytz.NonExistentTimeError, match=msg):
- ts.tz_localize(tz, nonexistent="raise")
- msg = (
- "The nonexistent argument must be one of 'raise', 'NaT', "
- "'shift_forward', 'shift_backward' or a timedelta object"
- )
- with pytest.raises(ValueError, match=msg):
- ts.tz_localize(tz, nonexistent="foo")
- # ------------------------------------------------------------------
- # 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 system utc to real utc
- ts = Timestamp("2001-01-05 11:56", tz=timezones.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=timezones.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 timezone.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_from_utc_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
- msg = "Cannot infer dst time from 2015-10-25 02:00:00"
- with pytest.raises(pytz.AmbiguousTimeError, match=msg):
- 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
- msg = "2017-03-26 02:00"
- with pytest.raises(pytz.NonExistentTimeError, match=msg):
- 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
- msg = "2017-03-26 02:00"
- with pytest.raises(pytz.NonExistentTimeError, match=msg):
- Timestamp("2017-03-26 02:00", tz="Europe/Paris")
- result = Timestamp("2017-03-26 02:00:00+0100", tz="Europe/Paris")
- naive = Timestamp(result.as_unit("ns")._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
- def test_timestamp_timetz_equivalent_with_datetime_tz(self, tz_naive_fixture):
- # GH21358
- tz = timezones.maybe_get_tz(tz_naive_fixture)
- stamp = Timestamp("2018-06-04 10:20:30", tz=tz)
- _datetime = datetime(2018, 6, 4, hour=10, minute=20, second=30, tzinfo=tz)
- result = stamp.timetz()
- expected = _datetime.timetz()
- assert result == expected
|