from datetime import (
|
datetime,
|
timedelta,
|
)
|
|
import dateutil.tz
|
from dateutil.tz import gettz
|
import numpy as np
|
import pytest
|
import pytz
|
|
from pandas import (
|
DatetimeIndex,
|
Timestamp,
|
bdate_range,
|
date_range,
|
offsets,
|
to_datetime,
|
)
|
import pandas._testing as tm
|
|
try:
|
from zoneinfo import ZoneInfo
|
except ImportError:
|
# Cannot assign to a type [misc]
|
ZoneInfo = None # type: ignore[misc, assignment]
|
|
|
easts = [pytz.timezone("US/Eastern"), gettz("US/Eastern")]
|
if ZoneInfo is not None:
|
try:
|
tz = ZoneInfo("US/Eastern")
|
except KeyError:
|
# no tzdata
|
pass
|
else:
|
easts.append(tz)
|
|
|
class TestTZLocalize:
|
def test_tz_localize_invalidates_freq(self):
|
# we only preserve freq in unambiguous cases
|
|
# if localized to US/Eastern, this crosses a DST transition
|
dti = date_range("2014-03-08 23:00", "2014-03-09 09:00", freq="h")
|
assert dti.freq == "h"
|
|
result = dti.tz_localize(None) # no-op
|
assert result.freq == "h"
|
|
result = dti.tz_localize("UTC") # unambiguous freq preservation
|
assert result.freq == "h"
|
|
result = dti.tz_localize("US/Eastern", nonexistent="shift_forward")
|
assert result.freq is None
|
assert result.inferred_freq is None # i.e. we are not _too_ strict here
|
|
# Case where we _can_ keep freq because we're length==1
|
dti2 = dti[:1]
|
result = dti2.tz_localize("US/Eastern")
|
assert result.freq == "h"
|
|
def test_tz_localize_utc_copies(self, utc_fixture):
|
# GH#46460
|
times = ["2015-03-08 01:00", "2015-03-08 02:00", "2015-03-08 03:00"]
|
index = DatetimeIndex(times)
|
|
res = index.tz_localize(utc_fixture)
|
assert not tm.shares_memory(res, index)
|
|
res2 = index._data.tz_localize(utc_fixture)
|
assert not tm.shares_memory(index._data, res2)
|
|
def test_dti_tz_localize_nonexistent_raise_coerce(self):
|
# GH#13057
|
times = ["2015-03-08 01:00", "2015-03-08 02:00", "2015-03-08 03:00"]
|
index = DatetimeIndex(times)
|
tz = "US/Eastern"
|
with pytest.raises(pytz.NonExistentTimeError, match="|".join(times)):
|
index.tz_localize(tz=tz)
|
|
with pytest.raises(pytz.NonExistentTimeError, match="|".join(times)):
|
index.tz_localize(tz=tz, nonexistent="raise")
|
|
result = index.tz_localize(tz=tz, nonexistent="NaT")
|
test_times = ["2015-03-08 01:00-05:00", "NaT", "2015-03-08 03:00-04:00"]
|
dti = to_datetime(test_times, utc=True)
|
expected = dti.tz_convert("US/Eastern")
|
tm.assert_index_equal(result, expected)
|
|
@pytest.mark.parametrize("tz", easts)
|
def test_dti_tz_localize_ambiguous_infer(self, tz):
|
# November 6, 2011, fall back, repeat 2 AM hour
|
# With no repeated hours, we cannot infer the transition
|
dr = date_range(datetime(2011, 11, 6, 0), periods=5, freq=offsets.Hour())
|
with pytest.raises(pytz.AmbiguousTimeError, match="Cannot infer dst time"):
|
dr.tz_localize(tz)
|
|
@pytest.mark.parametrize("tz", easts)
|
def test_dti_tz_localize_ambiguous_infer2(self, tz, unit):
|
# With repeated hours, we can infer the transition
|
dr = date_range(
|
datetime(2011, 11, 6, 0), periods=5, freq=offsets.Hour(), tz=tz, unit=unit
|
)
|
times = [
|
"11/06/2011 00:00",
|
"11/06/2011 01:00",
|
"11/06/2011 01:00",
|
"11/06/2011 02:00",
|
"11/06/2011 03:00",
|
]
|
di = DatetimeIndex(times).as_unit(unit)
|
result = di.tz_localize(tz, ambiguous="infer")
|
expected = dr._with_freq(None)
|
tm.assert_index_equal(result, expected)
|
result2 = DatetimeIndex(times, tz=tz, ambiguous="infer").as_unit(unit)
|
tm.assert_index_equal(result2, expected)
|
|
@pytest.mark.parametrize("tz", easts)
|
def test_dti_tz_localize_ambiguous_infer3(self, tz):
|
# When there is no dst transition, nothing special happens
|
dr = date_range(datetime(2011, 6, 1, 0), periods=10, freq=offsets.Hour())
|
localized = dr.tz_localize(tz)
|
localized_infer = dr.tz_localize(tz, ambiguous="infer")
|
tm.assert_index_equal(localized, localized_infer)
|
|
@pytest.mark.parametrize("tz", easts)
|
def test_dti_tz_localize_ambiguous_times(self, tz):
|
# March 13, 2011, spring forward, skip from 2 AM to 3 AM
|
dr = date_range(datetime(2011, 3, 13, 1, 30), periods=3, freq=offsets.Hour())
|
with pytest.raises(pytz.NonExistentTimeError, match="2011-03-13 02:30:00"):
|
dr.tz_localize(tz)
|
|
# after dst transition, it works
|
dr = date_range(
|
datetime(2011, 3, 13, 3, 30), periods=3, freq=offsets.Hour(), tz=tz
|
)
|
|
# November 6, 2011, fall back, repeat 2 AM hour
|
dr = date_range(datetime(2011, 11, 6, 1, 30), periods=3, freq=offsets.Hour())
|
with pytest.raises(pytz.AmbiguousTimeError, match="Cannot infer dst time"):
|
dr.tz_localize(tz)
|
|
# UTC is OK
|
dr = date_range(
|
datetime(2011, 3, 13), periods=48, freq=offsets.Minute(30), tz=pytz.utc
|
)
|
|
@pytest.mark.parametrize("tzstr", ["US/Eastern", "dateutil/US/Eastern"])
|
def test_dti_tz_localize_pass_dates_to_utc(self, tzstr):
|
strdates = ["1/1/2012", "3/1/2012", "4/1/2012"]
|
|
idx = DatetimeIndex(strdates)
|
conv = idx.tz_localize(tzstr)
|
|
fromdates = DatetimeIndex(strdates, tz=tzstr)
|
|
assert conv.tz == fromdates.tz
|
tm.assert_numpy_array_equal(conv.values, fromdates.values)
|
|
@pytest.mark.parametrize("prefix", ["", "dateutil/"])
|
def test_dti_tz_localize(self, prefix):
|
tzstr = prefix + "US/Eastern"
|
dti = date_range(start="1/1/2005", end="1/1/2005 0:00:30.256", freq="ms")
|
dti2 = dti.tz_localize(tzstr)
|
|
dti_utc = date_range(
|
start="1/1/2005 05:00", end="1/1/2005 5:00:30.256", freq="ms", tz="utc"
|
)
|
|
tm.assert_numpy_array_equal(dti2.values, dti_utc.values)
|
|
dti3 = dti2.tz_convert(prefix + "US/Pacific")
|
tm.assert_numpy_array_equal(dti3.values, dti_utc.values)
|
|
dti = date_range(start="11/6/2011 1:59", end="11/6/2011 2:00", freq="ms")
|
with pytest.raises(pytz.AmbiguousTimeError, match="Cannot infer dst time"):
|
dti.tz_localize(tzstr)
|
|
dti = date_range(start="3/13/2011 1:59", end="3/13/2011 2:00", freq="ms")
|
with pytest.raises(pytz.NonExistentTimeError, match="2011-03-13 02:00:00"):
|
dti.tz_localize(tzstr)
|
|
@pytest.mark.parametrize(
|
"tz",
|
[
|
"US/Eastern",
|
"dateutil/US/Eastern",
|
pytz.timezone("US/Eastern"),
|
gettz("US/Eastern"),
|
],
|
)
|
def test_dti_tz_localize_utc_conversion(self, tz):
|
# Localizing to time zone should:
|
# 1) check for DST ambiguities
|
# 2) convert to UTC
|
|
rng = date_range("3/10/2012", "3/11/2012", freq="30min")
|
|
converted = rng.tz_localize(tz)
|
expected_naive = rng + offsets.Hour(5)
|
tm.assert_numpy_array_equal(converted.asi8, expected_naive.asi8)
|
|
# DST ambiguity, this should fail
|
rng = date_range("3/11/2012", "3/12/2012", freq="30min")
|
# Is this really how it should fail??
|
with pytest.raises(pytz.NonExistentTimeError, match="2012-03-11 02:00:00"):
|
rng.tz_localize(tz)
|
|
def test_dti_tz_localize_roundtrip(self, tz_aware_fixture):
|
# note: this tz tests that a tz-naive index can be localized
|
# and de-localized successfully, when there are no DST transitions
|
# in the range.
|
idx = date_range(start="2014-06-01", end="2014-08-30", freq="15min")
|
tz = tz_aware_fixture
|
localized = idx.tz_localize(tz)
|
# can't localize a tz-aware object
|
with pytest.raises(
|
TypeError, match="Already tz-aware, use tz_convert to convert"
|
):
|
localized.tz_localize(tz)
|
reset = localized.tz_localize(None)
|
assert reset.tzinfo is None
|
expected = idx._with_freq(None)
|
tm.assert_index_equal(reset, expected)
|
|
def test_dti_tz_localize_naive(self):
|
rng = date_range("1/1/2011", periods=100, freq="h")
|
|
conv = rng.tz_localize("US/Pacific")
|
exp = date_range("1/1/2011", periods=100, freq="h", tz="US/Pacific")
|
|
tm.assert_index_equal(conv, exp._with_freq(None))
|
|
def test_dti_tz_localize_tzlocal(self):
|
# GH#13583
|
offset = dateutil.tz.tzlocal().utcoffset(datetime(2011, 1, 1))
|
offset = int(offset.total_seconds() * 1000000000)
|
|
dti = date_range(start="2001-01-01", end="2001-03-01")
|
dti2 = dti.tz_localize(dateutil.tz.tzlocal())
|
tm.assert_numpy_array_equal(dti2.asi8 + offset, dti.asi8)
|
|
dti = date_range(start="2001-01-01", end="2001-03-01", tz=dateutil.tz.tzlocal())
|
dti2 = dti.tz_localize(None)
|
tm.assert_numpy_array_equal(dti2.asi8 - offset, dti.asi8)
|
|
@pytest.mark.parametrize("tz", easts)
|
def test_dti_tz_localize_ambiguous_nat(self, tz):
|
times = [
|
"11/06/2011 00:00",
|
"11/06/2011 01:00",
|
"11/06/2011 01:00",
|
"11/06/2011 02:00",
|
"11/06/2011 03:00",
|
]
|
di = DatetimeIndex(times)
|
localized = di.tz_localize(tz, ambiguous="NaT")
|
|
times = [
|
"11/06/2011 00:00",
|
np.nan,
|
np.nan,
|
"11/06/2011 02:00",
|
"11/06/2011 03:00",
|
]
|
di_test = DatetimeIndex(times, tz="US/Eastern")
|
|
# left dtype is datetime64[ns, US/Eastern]
|
# right is datetime64[ns, tzfile('/usr/share/zoneinfo/US/Eastern')]
|
tm.assert_numpy_array_equal(di_test.values, localized.values)
|
|
@pytest.mark.parametrize("tz", easts)
|
def test_dti_tz_localize_ambiguous_flags(self, tz, unit):
|
# November 6, 2011, fall back, repeat 2 AM hour
|
|
# Pass in flags to determine right dst transition
|
dr = date_range(
|
datetime(2011, 11, 6, 0), periods=5, freq=offsets.Hour(), tz=tz, unit=unit
|
)
|
times = [
|
"11/06/2011 00:00",
|
"11/06/2011 01:00",
|
"11/06/2011 01:00",
|
"11/06/2011 02:00",
|
"11/06/2011 03:00",
|
]
|
|
# Test tz_localize
|
di = DatetimeIndex(times).as_unit(unit)
|
is_dst = [1, 1, 0, 0, 0]
|
localized = di.tz_localize(tz, ambiguous=is_dst)
|
expected = dr._with_freq(None)
|
tm.assert_index_equal(expected, localized)
|
|
result = DatetimeIndex(times, tz=tz, ambiguous=is_dst).as_unit(unit)
|
tm.assert_index_equal(result, expected)
|
|
localized = di.tz_localize(tz, ambiguous=np.array(is_dst))
|
tm.assert_index_equal(dr, localized)
|
|
localized = di.tz_localize(tz, ambiguous=np.array(is_dst).astype("bool"))
|
tm.assert_index_equal(dr, localized)
|
|
# Test constructor
|
localized = DatetimeIndex(times, tz=tz, ambiguous=is_dst).as_unit(unit)
|
tm.assert_index_equal(dr, localized)
|
|
# Test duplicate times where inferring the dst fails
|
times += times
|
di = DatetimeIndex(times).as_unit(unit)
|
|
# When the sizes are incompatible, make sure error is raised
|
msg = "Length of ambiguous bool-array must be the same size as vals"
|
with pytest.raises(Exception, match=msg):
|
di.tz_localize(tz, ambiguous=is_dst)
|
|
# When sizes are compatible and there are repeats ('infer' won't work)
|
is_dst = np.hstack((is_dst, is_dst))
|
localized = di.tz_localize(tz, ambiguous=is_dst)
|
dr = dr.append(dr)
|
tm.assert_index_equal(dr, localized)
|
|
@pytest.mark.parametrize("tz", easts)
|
def test_dti_tz_localize_ambiguous_flags2(self, tz, unit):
|
# When there is no dst transition, nothing special happens
|
dr = date_range(datetime(2011, 6, 1, 0), periods=10, freq=offsets.Hour())
|
is_dst = np.array([1] * 10)
|
localized = dr.tz_localize(tz)
|
localized_is_dst = dr.tz_localize(tz, ambiguous=is_dst)
|
tm.assert_index_equal(localized, localized_is_dst)
|
|
def test_dti_tz_localize_bdate_range(self):
|
dr = bdate_range("1/1/2009", "1/1/2010")
|
dr_utc = bdate_range("1/1/2009", "1/1/2010", tz=pytz.utc)
|
localized = dr.tz_localize(pytz.utc)
|
tm.assert_index_equal(dr_utc, localized)
|
|
@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/"])
|
def test_dti_tz_localize_nonexistent_shift(
|
self, start_ts, tz, end_ts, shift, tz_type, unit
|
):
|
# GH#8917
|
tz = tz_type + tz
|
if isinstance(shift, str):
|
shift = "shift_" + shift
|
dti = DatetimeIndex([Timestamp(start_ts)]).as_unit(unit)
|
result = dti.tz_localize(tz, nonexistent=shift)
|
expected = DatetimeIndex([Timestamp(end_ts)]).tz_localize(tz).as_unit(unit)
|
tm.assert_index_equal(result, expected)
|
|
@pytest.mark.parametrize("offset", [-1, 1])
|
def test_dti_tz_localize_nonexistent_shift_invalid(self, offset, warsaw):
|
# GH#8917
|
tz = warsaw
|
dti = DatetimeIndex([Timestamp("2015-03-29 02:20:00")])
|
msg = "The provided timedelta will relocalize on a nonexistent time"
|
with pytest.raises(ValueError, match=msg):
|
dti.tz_localize(tz, nonexistent=timedelta(seconds=offset))
|