Skip to content

Commit

Permalink
Migrate from pytz to zoneinfo
Browse files Browse the repository at this point in the history
Migrates codebase and tests (including resources) from `pytz` to
`zoneinfo`.

Drops `pytz` from dependencies and adds `tzdata`.
  • Loading branch information
maread99 committed Sep 9, 2023
1 parent fd24703 commit 12ccca2
Show file tree
Hide file tree
Showing 25 changed files with 396 additions and 389 deletions.
11 changes: 4 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ classifiers = [
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
Expand All @@ -52,11 +51,11 @@ classifiers = [
]

dependencies = [
"exchange_calendars >=4.0.1",
"exchange_calendars",
"numpy",
"pandas >= 1.1",
"pytz",
"yahooquery >=2.3",
"pandas",
"tzdata",
"yahooquery",
"valimp",
]

Expand All @@ -71,7 +70,6 @@ tests = [
"pytest",
"pytest-mock",
"tables",
"types-pytz",
]
dev = [
"black",
Expand All @@ -81,7 +79,6 @@ dev = [
"pytest",
"pytest-mock",
"tables",
"types-pytz",
"mypy",
"mypy-extensions",
"pandas-stubs",
Expand Down
12 changes: 6 additions & 6 deletions src/market_prices/daterange.py
Original file line number Diff line number Diff line change
Expand Up @@ -1112,14 +1112,14 @@ def daterange_tight(self) -> tuple[mptypes.DateRangeReq, pd.Timestamp]:
For example, if `interval` were 1H, `ds_interval` were 2H and
`daterange` were to return:
((Timestamp('2022-03-08 14:30', tz='UTC'),
Timestamp('2022-03-10 22:30', tz='UTC')),
Timestamp('2022-03-10 21:00', tz='UTC'))
((Timestamp('2022-03-08 14:30', tz=zoneinfo.ZoneInfo("UTC")),
Timestamp('2022-03-10 22:30', tz=zoneinfo.ZoneInfo("UTC"))),
Timestamp('2022-03-10 21:00', tz=zoneinfo.ZoneInfo("UTC"))
...then `daterange_tight` would return:
((Timestamp('2022-03-08 14:30', tz='UTC'),
Timestamp('2022-03-10 21:30', tz='UTC')),
Timestamp('2022-03-10 21:00', tz='UTC'))
((Timestamp('2022-03-08 14:30', tz=zoneinfo.ZoneInfo("UTC")),
Timestamp('2022-03-10 21:30', tz=zoneinfo.ZoneInfo("UTC"))),
Timestamp('2022-03-10 21:00', tz=zoneinfo.ZoneInfo("UTC"))
"""
(start, end), end_accuracy = self.daterange
if end - end_accuracy >= self.interval:
Expand Down
25 changes: 15 additions & 10 deletions src/market_prices/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,24 @@

import re
import sys
from typing import Literal
from typing import Literal, TYPE_CHECKING
import zoneinfo

import pandas as pd
import numpy as np
import pytz

from market_prices import intervals, mptypes
from market_prices import mptypes
from market_prices.utils import general_utils as genutils
from market_prices.utils import pandas_utils as pdutils

if TYPE_CHECKING:
from market_prices import intervals

if "pytest" in sys.modules:
import pytest # noqa: F401 # pylint: disable=unused-import # used within doctest

UTC = zoneinfo.ZoneInfo("UTC")

ONE_DAY: pd.Timedelta = pd.Timedelta(1, "D")
ONE_MIN: pd.Timedelta = pd.Timedelta(1, "T")
ONE_SEC: pd.Timedelta = pd.Timedelta(1, "S")
Expand Down Expand Up @@ -97,9 +102,9 @@ def to_utc(ts: pd.Timestamp) -> pd.Timestamp:
Timestamp to return a copy of with timezone set to UTC.
"""
try:
return ts.tz_convert(pytz.UTC)
return ts.tz_convert(UTC)
except TypeError:
return ts.tz_localize(pytz.UTC)
return ts.tz_localize(UTC)


def to_tz_naive(ts: pd.Timestamp) -> pd.Timestamp:
Expand All @@ -113,8 +118,8 @@ def to_tz_naive(ts: pd.Timestamp) -> pd.Timestamp:
Timestamp to return a timezone-naive copy of.
"""
if ts.tz is None:
return ts # type: ignore[unreachable] # 'tis very reachable
return ts.tz_convert(pytz.UTC).tz_convert(None)
return ts
return ts.tz_convert(UTC).tz_convert(None)


def now(
Expand Down Expand Up @@ -145,7 +150,7 @@ def now(
UTC time.
"""
# pylint: disable=missing-param-doc
now_ = pd.Timestamp.now(tz=pytz.UTC)
now_ = pd.Timestamp.now(tz=UTC)
if interval is not None and not interval.is_intraday:
now_ = now_.tz_convert(None)
res = "D"
Expand Down Expand Up @@ -312,7 +317,7 @@ def volume_to_na(df: pd.DataFrame) -> pd.DataFrame:
bv: pd.Series
if has_symbols(df):
for s in df.columns.remove_unused_levels().levels[0]:
bv = df[(s, "close")].isna() # type: ignore[assignment] # is a Series
bv = df[(s, "close")].isna()
df.loc[bv, (s, "volume")] = np.nan
else:
bv = df["close"].isna()
Expand All @@ -322,7 +327,7 @@ def volume_to_na(df: pd.DataFrame) -> pd.DataFrame:

def resample(
resample_me: pd.DataFrame | pd.core.groupby.groupby.GroupBy,
rule: pd.offsets.BaseOffset, # type: ignore[name-defined] # is defined
rule: pd.offsets.BaseOffset,
data: pd.DataFrame | None = None,
origin: str = "start",
) -> pd.DataFrame:
Expand Down
3 changes: 0 additions & 3 deletions src/market_prices/mptypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from typing import TypedDict, Union

import pandas as pd
import pytz
from exchange_calendars import ExchangeCalendar


Expand Down Expand Up @@ -86,8 +85,6 @@ def as_offset(
tuple[1]: Range end date.
"""

PytzUTC = type(pytz.UTC)

DateTimestamp = Union[pd.Timestamp, str, datetime.datetime, int, float]
"""Type to annotate an input that takes a value representing a date.
Expand Down
52 changes: 19 additions & 33 deletions src/market_prices/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@

import typing
from typing import Any
from zoneinfo import ZoneInfo

import exchange_calendars as xcals
import pandas as pd
import pytz

from market_prices import errors, helpers, mptypes, intervals
from market_prices.helpers import UTC


def verify_period_parameters(pp: mptypes.PP):
Expand Down Expand Up @@ -61,7 +62,7 @@ def verify_period_parameters(pp: mptypes.PP):
raise ValueError(msg)


def parse_timestamp(ts: pd.Timestamp, tzin: pytz.BaseTzInfo) -> pd.Timestamp:
def parse_timestamp(ts: pd.Timestamp, tzin: ZoneInfo) -> pd.Timestamp:
"""Parse timestamp to date or UTC time.
Parameters
Expand All @@ -76,7 +77,7 @@ def parse_timestamp(ts: pd.Timestamp, tzin: pytz.BaseTzInfo) -> pd.Timestamp:
return ts
if ts.tz is None:
ts = ts.tz_localize(tzin)
return ts.tz_convert(pytz.UTC)
return ts.tz_convert(UTC)


def _parse_start(
Expand Down Expand Up @@ -491,19 +492,6 @@ def verify_time_not_oob(
# ):


def verify_interval_datetime_index(
name: str, obj: pd.DatetimeIndex | pd.IntervalIndex, _
) -> pd.DatetimeIndex | pd.IntervalIndex:
"""Verify pd.IntervalIndex has both sides as pd.DatetimeIndex."""
if isinstance(obj, pd.IntervalIndex) and not isinstance(obj.left, pd.DatetimeIndex):
raise ValueError(
f"'{name}' can only take a pd.IntervalIndex that has each side"
" as type pd.DatetimeIndex, although received with left side"
f" as type '{type(obj.left)}'."
)
return obj


def lead_symbol(name, obj: str | None, params: dict[str, Any]) -> str:
"""Parse `lead_symbol` parameter of `PricesBase.get`."""
if obj is None:
Expand Down Expand Up @@ -550,47 +538,45 @@ def verify_timetimestamp(name: str, obj: pd.Timestamp, _) -> pd.Timestamp:
return obj


def to_timezone(name: str, obj: pytz.BaseTzInfo | str, _) -> pytz.BaseTzInfo:
def to_timezone(name: str, obj: ZoneInfo | str, _) -> ZoneInfo:
"""Parse input to a timezone.
A parameter parsed with this function can take either of:
- an instance returned by pytz.timezone (i.e. instance of subclass
of pytz.BaseTzInfo)
- `str` that can be passed to pytz.timezone (for example 'utc' or
'US/Eastern`)
- an instance of `zoneinfo.ZoneInfo`.
- `str` that can be passed to `zoneinfo.ZoneInfo` (for example
'UTC' or 'US/Eastern`).
"""
if isinstance(obj, pytz.BaseTzInfo):
if isinstance(obj, ZoneInfo):
return obj
return pytz.timezone(obj)
return ZoneInfo(obj)


def to_prices_timezone(
name: str, obj: str | pytz.BaseTzInfo, params: dict[str, Any]
) -> pytz.BaseTzInfo:
name: str, obj: str | ZoneInfo, params: dict[str, Any]
) -> ZoneInfo:
"""Parse a tz input to a timezone.
Only for parsing `tz`, `tzin` or `tzout` parameters of public
methods of PricesBase (or subclass of).
Parameters
----------
obj : pytz.BaseTzInfo | str
obj : ZoneInfo | str
pytz.BaseTzInfo, any instance returned by pytz.timezone.
ZoneInfo, any instance returned by `zoneinfo.ZoneInfo`
str, as either:
- valid input to `pytz.timezone`, for example 'utc' or
'US/Eastern`, to parse to pytz.timezone(<value>)
- valid input to `zoneinfo.ZoneInfo`, for example 'UTC' or
'US/Eastern`
- any symbol of `PricesBase.symbols` to parse to the timezone
associated with that symbol.
Returns
-------
timezone : pytz.BaseTzInfo
Instance of a subclass of `pytz.BaseTzInfo`.
timezone : zoneinfo.ZoneInfo
"""
if isinstance(obj, pytz.BaseTzInfo):
if isinstance(obj, ZoneInfo):
return obj
if obj in params["self"].symbols:
return params["self"].timezones[obj]
return pytz.timezone(obj)
return ZoneInfo(obj)
Loading

0 comments on commit 12ccca2

Please sign in to comment.