fa45d8aa5f
- health_checklist.json: 192.168.1.122→node122
- ocr_client.py: docstring IP→node122
- docs/market-data-requirements.md: IP→node122
- 所有API调用通过ProxyHandler({})绕过系统代理
Privoxy对node122:18003返回500,直连正常
433 lines
14 KiB
Python
433 lines
14 KiB
Python
from datetime import (
|
|
datetime,
|
|
timedelta,
|
|
timezone,
|
|
)
|
|
import subprocess
|
|
import sys
|
|
import textwrap
|
|
import zoneinfo
|
|
|
|
import dateutil.tz
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from pandas._libs.tslibs import (
|
|
conversion,
|
|
timezones,
|
|
)
|
|
from pandas.compat import (
|
|
IS64,
|
|
is_platform_windows,
|
|
)
|
|
|
|
import pandas as pd
|
|
from pandas import (
|
|
Index,
|
|
Timedelta,
|
|
Timestamp,
|
|
date_range,
|
|
to_datetime,
|
|
)
|
|
import pandas._testing as tm
|
|
|
|
|
|
@pytest.mark.single_cpu
|
|
def test_no_timezone_data():
|
|
# https://github.com/pandas-dev/pandas/pull/63335
|
|
# Test error message when timezone data is not available.
|
|
msg = "'No time zone found with key Europe/Brussels'"
|
|
code = textwrap.dedent(
|
|
f"""\
|
|
import sys, zoneinfo, pandas as pd
|
|
sys.modules['tzdata'] = None
|
|
zoneinfo.reset_tzpath(['/path/to/nowhere'])
|
|
try:
|
|
pd.to_datetime('2012-01-01').tz_localize('Europe/Brussels')
|
|
except zoneinfo.ZoneInfoNotFoundError as err:
|
|
assert str(err) == "{msg}"
|
|
"""
|
|
)
|
|
subprocess.check_call([sys.executable, "-c", code])
|
|
|
|
|
|
def test_is_utc(utc_fixture):
|
|
tz = timezones.maybe_get_tz(utc_fixture)
|
|
assert timezones.is_utc(tz)
|
|
|
|
|
|
def test_cache_keys_are_distinct_for_pytz_vs_dateutil():
|
|
pytz = pytest.importorskip("pytz")
|
|
for tz_name in pytz.common_timezones:
|
|
tz_p = timezones.maybe_get_tz(tz_name)
|
|
tz_d = timezones.maybe_get_tz("dateutil/" + tz_name)
|
|
|
|
if tz_d is None:
|
|
pytest.skip(tz_name + ": dateutil does not know about this one")
|
|
|
|
if not (tz_name == "UTC" and is_platform_windows()):
|
|
# they both end up as tzwin("UTC") on windows
|
|
assert timezones._p_tz_cache_key(tz_p) != timezones._p_tz_cache_key(tz_d)
|
|
|
|
|
|
def test_tzlocal_repr():
|
|
# see gh-13583
|
|
ts = Timestamp("2011-01-01", tz=dateutil.tz.tzlocal())
|
|
assert ts.tz == dateutil.tz.tzlocal()
|
|
assert "tz='tzlocal()')" in repr(ts)
|
|
|
|
|
|
def test_tzlocal_maybe_get_tz():
|
|
# see gh-13583
|
|
tz = timezones.maybe_get_tz("tzlocal()")
|
|
assert tz == dateutil.tz.tzlocal()
|
|
|
|
|
|
def test_tzlocal_offset():
|
|
# see gh-13583
|
|
#
|
|
# Get offset using normal datetime for test.
|
|
ts = Timestamp("2011-01-01", tz=dateutil.tz.tzlocal()).as_unit("s")
|
|
|
|
offset = dateutil.tz.tzlocal().utcoffset(datetime(2011, 1, 1))
|
|
offset = offset.total_seconds()
|
|
|
|
assert ts._value + offset == Timestamp("2011-01-01").as_unit("s")._value
|
|
|
|
|
|
def test_tzlocal_is_not_utc():
|
|
# even if the machine running the test is localized to UTC
|
|
tz = dateutil.tz.tzlocal()
|
|
assert not timezones.is_utc(tz)
|
|
|
|
assert not timezones.tz_compare(tz, dateutil.tz.tzutc())
|
|
|
|
|
|
def test_tz_compare_utc(utc_fixture, utc_fixture2):
|
|
tz = timezones.maybe_get_tz(utc_fixture)
|
|
tz2 = timezones.maybe_get_tz(utc_fixture2)
|
|
assert timezones.tz_compare(tz, tz2)
|
|
|
|
|
|
@pytest.fixture(
|
|
params=[
|
|
("pytz/US/Eastern", lambda tz, x: tz.localize(x)),
|
|
(dateutil.tz.gettz("US/Eastern"), lambda tz, x: x.replace(tzinfo=tz)),
|
|
]
|
|
)
|
|
def infer_setup(request):
|
|
eastern, localize = request.param
|
|
if isinstance(eastern, str) and eastern.startswith("pytz/"):
|
|
pytz = pytest.importorskip("pytz")
|
|
eastern = pytz.timezone(eastern.removeprefix("pytz/"))
|
|
|
|
start_naive = datetime(2001, 1, 1)
|
|
end_naive = datetime(2009, 1, 1)
|
|
|
|
start = localize(eastern, start_naive)
|
|
end = localize(eastern, end_naive)
|
|
|
|
return eastern, localize, start, end, start_naive, end_naive
|
|
|
|
|
|
def test_infer_tz_compat(infer_setup):
|
|
eastern, _, start, end, start_naive, end_naive = infer_setup
|
|
|
|
assert (
|
|
timezones.infer_tzinfo(start, end)
|
|
is conversion.localize_pydatetime(start_naive, eastern).tzinfo
|
|
)
|
|
assert (
|
|
timezones.infer_tzinfo(start, None)
|
|
is conversion.localize_pydatetime(start_naive, eastern).tzinfo
|
|
)
|
|
assert (
|
|
timezones.infer_tzinfo(None, end)
|
|
is conversion.localize_pydatetime(end_naive, eastern).tzinfo
|
|
)
|
|
|
|
|
|
def test_infer_tz_utc_localize(infer_setup):
|
|
_, _, start, end, start_naive, end_naive = infer_setup
|
|
utc = timezone.utc
|
|
|
|
start = start_naive.astimezone(utc)
|
|
end = end_naive.astimezone(utc)
|
|
|
|
assert timezones.infer_tzinfo(start, end) is utc
|
|
|
|
|
|
@pytest.mark.parametrize("ordered", [True, False])
|
|
def test_infer_tz_mismatch(infer_setup, ordered):
|
|
eastern, _, _, _, start_naive, end_naive = infer_setup
|
|
msg = "Inputs must both have the same timezone"
|
|
|
|
utc = timezone.utc
|
|
start = start_naive.astimezone(utc)
|
|
end = conversion.localize_pydatetime(end_naive, eastern)
|
|
|
|
args = (start, end) if ordered else (end, start)
|
|
|
|
with pytest.raises(AssertionError, match=msg):
|
|
timezones.infer_tzinfo(*args)
|
|
|
|
|
|
def test_maybe_get_tz_invalid_types():
|
|
with pytest.raises(TypeError, match="<class 'float'>"):
|
|
timezones.maybe_get_tz(44.0)
|
|
|
|
with pytest.raises(TypeError, match="<class 'module'>"):
|
|
timezones.maybe_get_tz(pytest)
|
|
|
|
msg = "<class 'pandas.Timestamp'>"
|
|
with pytest.raises(TypeError, match=msg):
|
|
timezones.maybe_get_tz(Timestamp("2021-01-01", tz="UTC"))
|
|
|
|
|
|
@pytest.mark.parametrize("tz_name", ["UTC", "GMT", "Etc/GMT+1", "Etc/GMT-5"])
|
|
def test_zoneinfo_fixed_offset(tz_name):
|
|
# GH#64363
|
|
zoneinfo = pytest.importorskip("zoneinfo")
|
|
tz = zoneinfo.ZoneInfo(tz_name)
|
|
assert timezones.is_fixed_offset(tz)
|
|
|
|
|
|
def test_zoneinfo_not_fixed_offset_with_historical_transition():
|
|
# GH#64363
|
|
zoneinfo = pytest.importorskip("zoneinfo")
|
|
tz = zoneinfo.ZoneInfo("Africa/Lusaka")
|
|
assert not timezones.is_fixed_offset(tz)
|
|
|
|
|
|
def test_maybe_get_tz_offset_only():
|
|
# see gh-36004
|
|
|
|
# timezone.utc
|
|
tz = timezones.maybe_get_tz(timezone.utc)
|
|
assert tz == timezone(timedelta(hours=0, minutes=0))
|
|
|
|
# without UTC+- prefix
|
|
tz = timezones.maybe_get_tz("+01:15")
|
|
assert tz == timezone(timedelta(hours=1, minutes=15))
|
|
|
|
tz = timezones.maybe_get_tz("-01:15")
|
|
assert tz == timezone(-timedelta(hours=1, minutes=15))
|
|
|
|
# with UTC+- prefix
|
|
tz = timezones.maybe_get_tz("UTC+02:45")
|
|
assert tz == timezone(timedelta(hours=2, minutes=45))
|
|
|
|
tz = timezones.maybe_get_tz("UTC-02:45")
|
|
assert tz == timezone(-timedelta(hours=2, minutes=45))
|
|
|
|
|
|
def test_zoneinfo_utc_to_local_post_2037():
|
|
# GH#64363 - verify that ZoneInfo DST transitions after 2037
|
|
# (generated from POSIX TZ string rules) produce correct local times.
|
|
tz = zoneinfo.ZoneInfo("US/Pacific")
|
|
utc_times = date_range("2040-07-01", periods=24, freq="h", tz="UTC")
|
|
local = utc_times.tz_convert(tz)
|
|
|
|
expected_hours = np.array(
|
|
[
|
|
datetime(2040, 7, 1, hour, tzinfo=timezone.utc).astimezone(tz).hour
|
|
for hour in range(24)
|
|
],
|
|
dtype=np.int32,
|
|
)
|
|
tm.assert_numpy_array_equal(local.hour.to_numpy(), expected_hours)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"tz_name", ["America/New_York", "Australia/Melbourne", "Europe/Kaliningrad"]
|
|
)
|
|
def test_zoneinfo_utc_to_local_post_2100(tz_name):
|
|
# GH#65712 - verify zoneinfo-generated future DST transitions are used
|
|
# beyond year 2100 for both hemispheres.
|
|
# Europe/Kaliningrad is a special case because it has no rule for future DST
|
|
# transitions (and is also not a fixed offset timezone)
|
|
tz = zoneinfo.ZoneInfo(tz_name)
|
|
# fmt: off
|
|
data = [
|
|
"2038-01-01", "2038-07-01",
|
|
"2100-01-01", "2100-07-01",
|
|
"2200-01-01", "2200-07-01",
|
|
]
|
|
# fmt: on
|
|
utc_times = to_datetime(data, utc=True)
|
|
local = utc_times.tz_convert(tz)
|
|
|
|
expected = [
|
|
datetime.fromisoformat(date).replace(tzinfo=timezone.utc).astimezone(tz)
|
|
for date in data
|
|
]
|
|
|
|
tm.assert_numpy_array_equal(local.to_pydatetime(), np.array(expected))
|
|
assert [ts.utcoffset() for ts in local] == [dt.utcoffset() for dt in expected]
|
|
|
|
|
|
def test_zoneinfo_utc_to_local_far_future_seconds_resolution():
|
|
# GH#65712 - for dates beyond cached transition data, we should fall back
|
|
# to zoneinfo's API and preserve correct DST offsets.
|
|
utc_times = to_datetime(
|
|
np.array(["3000-01-01T00:00:00", "3000-07-01T00:00:00"], dtype="M8[s]"),
|
|
utc=True,
|
|
)
|
|
local = utc_times.tz_convert("Europe/Brussels")
|
|
|
|
tz = zoneinfo.ZoneInfo("Europe/Brussels")
|
|
expected = [
|
|
datetime(3000, 1, 1, tzinfo=timezone.utc).astimezone(tz),
|
|
datetime(3000, 7, 1, tzinfo=timezone.utc).astimezone(tz),
|
|
]
|
|
|
|
assert local.dtype == "datetime64[s, Europe/Brussels]"
|
|
tm.assert_numpy_array_equal(local.to_pydatetime(), np.array(expected))
|
|
|
|
|
|
def test_zoneinfo_local_to_utc_far_future_seconds_resolution():
|
|
# GH#65712 - localize should also preserve future DST rules when converting
|
|
# from local wall times to UTC beyond cached transition data.
|
|
local_times = to_datetime(
|
|
np.array(["3000-01-01T00:00:00", "3000-07-01T00:00:00"], dtype="M8[s]")
|
|
)
|
|
localized = local_times.tz_localize("Europe/Brussels")
|
|
result_utc = localized.tz_convert("UTC")
|
|
|
|
tz = zoneinfo.ZoneInfo("Europe/Brussels")
|
|
expected_utc = [
|
|
datetime(3000, 1, 1, tzinfo=tz).astimezone(timezone.utc),
|
|
datetime(3000, 7, 1, tzinfo=tz).astimezone(timezone.utc),
|
|
]
|
|
|
|
assert result_utc.dtype == "datetime64[s, UTC]"
|
|
tm.assert_numpy_array_equal(result_utc.to_pydatetime(), np.array(expected_utc))
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
not IS64,
|
|
reason="stdlib datetime.fromtimestamp fails on 32-bit platforms with overflow",
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"tz_name",
|
|
["America/New_York", "America/Santiago", "Australia/Melbourne", "Europe/Brussels"],
|
|
)
|
|
def test_zoneinfo_boundary_at_last_cached_transition(tz_name):
|
|
# GH#65733 - target the boundary where UTC is beyond the cached transition
|
|
# range while local wall time can still compare below that UTC cutoff.
|
|
tz = zoneinfo.ZoneInfo(tz_name)
|
|
|
|
from zoneinfo._zoneinfo import ZoneInfo
|
|
|
|
trans = datetime.fromtimestamp(max(ZoneInfo(tz_name)._tz_after.transitions(2099)))
|
|
start = (trans - timedelta(days=1)).replace(tzinfo=timezone.utc)
|
|
expected_utc = [(start + timedelta(minutes=30 * i)) for i in range(24 * 2 * 2)]
|
|
expected_tz = [ts.astimezone(tz) for ts in expected_utc]
|
|
expected_str = [str(ts) for ts in expected_tz]
|
|
|
|
local = to_datetime([v[:-6] for v in expected_str])
|
|
|
|
# ambiguous keyword only needed for timezones of northern hemisphere, ignored for
|
|
# the others
|
|
result = local.tz_localize(tz_name, ambiguous="infer")
|
|
|
|
# verify local to UTC
|
|
tm.assert_equal(
|
|
result.tz_convert("UTC").to_pydatetime(), np.array(expected_utc, dtype="O")
|
|
)
|
|
# verify UTC to local by converting to pydatetime/str repr of underlying UTC value
|
|
tm.assert_equal(result.astype(str), Index(expected_str))
|
|
tm.assert_equal(result.to_pydatetime(), np.array(expected_tz, dtype="O"))
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"tz_name",
|
|
["America/New_York", "America/Santiago", "Australia/Melbourne", "Europe/Brussels"],
|
|
)
|
|
def test_zoneinfo_nonexistent_at_last_cached_transition(tz_name):
|
|
# GH#65733 - target the boundary where UTC is beyond the cached transition
|
|
# range while local wall time can still compare below that UTC cutoff.
|
|
tz = zoneinfo.ZoneInfo(tz_name)
|
|
|
|
from zoneinfo._zoneinfo import ZoneInfo
|
|
|
|
start, end = ZoneInfo(tz_name)._tz_after.transitions(2099)
|
|
if start > end:
|
|
# to-dst nonexistent transition at the end of the year
|
|
ts = Timestamp(start, unit="s") + Timedelta(minutes=30)
|
|
|
|
with pytest.raises(ValueError, match="nonexistent time"):
|
|
ts.tz_localize(tz_name)
|
|
|
|
result = ts.tz_localize(tz_name, nonexistent="NaT")
|
|
assert result is pd.NaT
|
|
|
|
result_dst = ts.tz_localize(tz_name, nonexistent="shift_forward")
|
|
expected_dst = (ts.to_pydatetime() + timedelta(minutes=30)).replace(tzinfo=tz)
|
|
assert result_dst.to_pydatetime() == expected_dst
|
|
|
|
result_std = ts.tz_localize(tz_name, nonexistent="shift_backward")
|
|
expected_std = (
|
|
ts.to_pydatetime() - timedelta(minutes=30, microseconds=1)
|
|
).replace(tzinfo=tz)
|
|
assert result_std.to_pydatetime() == expected_std
|
|
|
|
else:
|
|
# to-std ambiguous transition at the end of the year
|
|
ts = Timestamp(end, unit="s") - Timedelta(minutes=30)
|
|
|
|
with pytest.raises(ValueError, match="Cannot infer dst time"):
|
|
ts.tz_localize(tz_name)
|
|
|
|
result = ts.tz_localize(tz_name, ambiguous="NaT")
|
|
assert result is pd.NaT
|
|
|
|
result_dst = ts.tz_localize(tz_name, ambiguous=True)
|
|
expected_dst = ts.to_pydatetime().replace(fold=0, tzinfo=tz)
|
|
assert result_dst.to_pydatetime() == expected_dst
|
|
|
|
result_dst = ts.tz_localize(tz_name, ambiguous=False)
|
|
expected_dst = ts.to_pydatetime().replace(fold=1, tzinfo=tz)
|
|
assert result_dst.to_pydatetime() == expected_dst
|
|
|
|
|
|
@pytest.mark.parametrize("key", ["US/Eastern", "Africa/Lusaka", "Asia/Qyzylorda"])
|
|
def test_zoneinfo_utc_to_local_pre_first_transition(key):
|
|
# GH#64363 - verify that ZoneInfo offsets before the first historical
|
|
# transition (LMT era) match what ZoneInfo itself returns. The "before"
|
|
# offset must be utcoff[0] from the TZ data (matching CPython's C
|
|
# implementation), NOT the first non-DST transition offset.
|
|
tz = zoneinfo.ZoneInfo(key)
|
|
ts = Timestamp("1850-01-01", tz="UTC").tz_convert(tz)
|
|
|
|
expected = datetime(1850, 1, 1, tzinfo=timezone.utc).astimezone(tz)
|
|
assert ts.minute == expected.minute
|
|
|
|
|
|
def test_zoneinfo_conversion_outside_range_stdlib():
|
|
# GH#65733 - verify that datetimes outside the range of Python's standard
|
|
# library (year > 9999) raises a proper error message
|
|
ts = Timestamp(np.datetime64("10000-01-01T09:00:00", "us"))
|
|
|
|
msg = "Localizing Timestamps which are outside the range of Python"
|
|
with pytest.raises(NotImplementedError, match=msg):
|
|
ts.tz_localize("Europe/Brussels")
|
|
|
|
with pytest.raises(NotImplementedError, match=msg):
|
|
ts = Timestamp(ts._value, unit="us", tz="Europe/Brussels")
|
|
|
|
|
|
def test_normalize_pytz_timezone():
|
|
pytz = pytest.importorskip("pytz")
|
|
|
|
from pandas.io._util import _normalize_pytz_timezone
|
|
|
|
for tz, expected in [
|
|
(pytz.UTC, timezone.utc),
|
|
(pytz.FixedOffset(90), timezone(timedelta(minutes=90))),
|
|
(pytz.timezone("America/New_York"), zoneinfo.ZoneInfo("America/New_York")),
|
|
(pytz.timezone("Etc/GMT+1"), zoneinfo.ZoneInfo("Etc/GMT+1")),
|
|
]:
|
|
result = _normalize_pytz_timezone(tz)
|
|
assert result == expected
|