Skip to content

Commit

Permalink
refactor[python]: Replace pytz by stdlib zoneinfo (#4809)
Browse files Browse the repository at this point in the history
  • Loading branch information
zundertj committed Sep 11, 2022
1 parent 0ac48cb commit 7d0ba98
Show file tree
Hide file tree
Showing 8 changed files with 51 additions and 25 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,10 @@ $ pip3 install -U 'polars[connectorx]'
# Install Polars and xlsx2csv (read data from Excel).
$ pip3 install -U 'polars[xlsx2csv]'
# Install Polars and pytz (for timezone support).
$ pip3 install -U 'polars[pytz]'
# Install Polars with timezone support, only needed if
# 1. you are on Python < 3.9, Python 3.9+ has this in stdlib
# 2. you are on Windows
$ pip3 install -U 'polars[timezone]'
```

Releases happen quite often (weekly / every few days) at the moment, so updating polars regularly to get the latest bugfixes / features might not be a bad idea.
Expand Down
3 changes: 2 additions & 1 deletion py-polars/polars/datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ def __init__(self, time_unit: TimeUnit = "us", time_zone: str | None = None):
time_unit : {'us', 'ns', 'ms'}
Time unit.
time_zone
Timezone string as defined in pytz.
Timezone string as defined in zoneinfo (run
``import zoneinfo; zoneinfo.available_timezones()`` for a full list).
"""
self.tu = time_unit
Expand Down
1 change: 0 additions & 1 deletion py-polars/polars/show_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ def _get_dependency_info() -> dict[str, str]:
"fsspec",
"connectorx",
"xlsx2csv",
"pytz",
]
return {name: _get_dep_version(name) for name in opt_deps}

Expand Down
32 changes: 24 additions & 8 deletions py-polars/polars/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@
else:
from typing_extensions import ParamSpec, TypeGuard


if sys.version_info >= (3, 9):
import zoneinfo

_ZONEINFO_AVAILABLE = True
else:
try:
from backports import zoneinfo

_ZONEINFO_AVAILABLE = True
except ImportError:
_ZONEINFO_AVAILABLE = False

if TYPE_CHECKING:
from polars.internals.type_aliases import SizeUnit, TimeUnit

Expand Down Expand Up @@ -204,19 +217,22 @@ def _to_python_datetime(
else:
raise ValueError(f"tu must be one of {{'ns', 'us', 'ms'}}, got {tu}")
if tz is not None and len(tz) > 0:
try:
import pytz
except ImportError:
raise ImportError(
"pytz is not installed. Please run `pip install pytz`."
) from None

return pytz.timezone(tz).localize(dt)
return _localize(dt, tz)
return dt
else:
raise NotImplementedError # pragma: no cover


def _localize(dt: datetime, tz: str) -> datetime:
if not _ZONEINFO_AVAILABLE:
raise ImportError(
"backports.zoneinfo is not installed. Please run "
"`pip install backports.zoneinfo`."
)

return dt.astimezone(zoneinfo.ZoneInfo(tz))


def _in_notebook() -> bool:
try:
from IPython import get_ipython
Expand Down
4 changes: 2 additions & 2 deletions py-polars/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ numpy = ["numpy >= 1.16.0"]
fsspec = ["fsspec"]
connectorx = ["connectorx"]
xlsx2csv = ["xlsx2csv >= 0.8.0"]
pytz = ["pytz"]
timezone = ["backports.zoneinfo; python_version < '3.9'", "tzdata; platform_system == 'Windows'"]

[tool.isort]
profile = "black"
Expand All @@ -36,7 +36,7 @@ enable_error_code = [
]

[[tool.mypy.overrides]]
module = ["pyarrow.*", "polars.polars", "matplotlib.*", "fsspec.*", "connectorx", "IPython.*"]
module = ["backports", "pyarrow.*", "polars.polars", "matplotlib.*", "fsspec.*", "connectorx", "IPython.*", "zoneinfo"]
ignore_missing_imports = true

[[tool.mypy.overrides]]
Expand Down
4 changes: 2 additions & 2 deletions py-polars/requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
numpy
pandas
pyarrow
pytz
backports.zoneinfo; python_version < '3.9'
tzdata; platform_system == 'Windows'
xlsx2csv

# Tooling
Expand All @@ -18,4 +19,3 @@ pytest-cov==3.0.0

# Stub files
pandas-stubs==1.2.0.61
types-pytz==2022.2.1.0
13 changes: 8 additions & 5 deletions py-polars/src/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,11 +489,14 @@ impl<'s> FromPyObject<'s> for Wrap<AnyValue<'s>> {
kwargs.set_item("tzinfo", py.None())?;
let dt = ob.call_method("replace", (), Some(kwargs))?;

let pytz = PyModule::import(py, "pytz")?;
let tz = pytz.call_method("timezone", ("UTC",), None)?;
let kwargs = PyDict::new(py);
kwargs.set_item("is_dst", py.None())?;
let loc_tz = tz.call_method("localize", (dt,), Some(kwargs))?;
let pypolars = PyModule::import(py, "polars").unwrap();
let localize = pypolars
.getattr("utils")
.unwrap()
.getattr("_localize")
.unwrap();
let loc_tz = localize.call1((dt, "UTC"));

loc_tz.call_method0("timestamp")?;
// s to us
let v = (ts.extract::<f64>()? * 1000_000.0) as i64;
Expand Down
13 changes: 9 additions & 4 deletions py-polars/tests/unit/test_datelike.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
from __future__ import annotations

import io
import sys
from datetime import date, datetime, time, timedelta
from typing import TYPE_CHECKING, no_type_check

import numpy as np
import pandas as pd
import pyarrow as pa
import pytest
import pytz

if sys.version_info >= (3, 9):
import zoneinfo
else:
from backports import zoneinfo

import polars as pl
from polars.datatypes import DTYPE_TEMPORAL_UNITS
Expand Down Expand Up @@ -660,8 +665,8 @@ def test_read_utc_times_parquet() -> None:
df.to_parquet(f)
f.seek(0)
df_in = pl.read_parquet(f)
tz = pytz.timezone("UTC")
assert df_in["Timestamp"][0] == tz.localize(datetime(2022, 1, 1, 0, 0))
tz = zoneinfo.ZoneInfo("UTC")
assert df_in["Timestamp"][0] == datetime(2022, 1, 1, 0, 0).astimezone(tz)


def test_epoch() -> None:
Expand Down Expand Up @@ -1369,7 +1374,7 @@ def test_weekday() -> None:


def test_from_dict_tu_consistency() -> None:
tz = pytz.timezone("PRC")
tz = zoneinfo.ZoneInfo("PRC")
dt = datetime(2020, 8, 1, 12, 0, 0, tzinfo=tz)
from_dict = pl.from_dict({"dt": [dt]})
from_dicts = pl.from_dicts([{"dt": dt}])
Expand Down

0 comments on commit 7d0ba98

Please sign in to comment.