polars-tzf
Fast timezone lookup for Polars: resolve IANA timezone names from latitude/longitude coordinates or H3 cells, powered by tzf-rs.
Runs as a native Polars expression plugin written in Rust — no Python loops, no map_elements, fully parallelized by the Polars engine.
Installation
pip install polars-tzf
Or with uv:
uv add polars-tzf
Usage
import polars as pl
import polars_tzf as tzf
df = pl.DataFrame(
{
"place": ["Berlin", "New York City", "Tokyo", "São Paulo", "Null Island"],
"lat": [52.5200, 40.7128, 35.6762, -23.5505, 0.0],
"lon": [13.4050, -74.0060, 139.6503, -46.6333, 0.0],
}
)
df.with_columns(tz=tzf.tz_name("lat", "lon"))
shape: (5, 4)
┌───────────────┬──────────┬───────────┬───────────────────┐
│ place ┆ lat ┆ lon ┆ tz │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ f64 ┆ f64 ┆ str │
╞═══════════════╪══════════╪═══════════╪═══════════════════╡
│ Berlin ┆ 52.52 ┆ 13.405 ┆ Europe/Berlin │
│ New York City ┆ 40.7128 ┆ -74.006 ┆ America/New_York │
│ Tokyo ┆ 35.6762 ┆ 139.6503 ┆ Asia/Tokyo │
│ São Paulo ┆ -23.5505 ┆ -46.6333 ┆ America/Sao_Paulo │
│ Null Island ┆ 0.0 ┆ 0.0 ┆ Etc/GMT │
└───────────────┴──────────┴───────────┴───────────────────┘
If your data is already indexed with H3 cells, resolve straight from the cell — no coordinate columns needed:
df = pl.DataFrame(
{
"cell": [
"871f1d489ffffff",
"872a1072cffffff",
"872f5a363ffffff",
],
}
)
df.with_columns(tz=tzf.tz_name_from_h3("cell"))
shape: (3, 2)
┌─────────────────┬──────────────────┐
│ cell ┆ tz │
│ --- ┆ --- │
│ str ┆ str │
╞═════════════════╪══════════════════╡
│ 871f1d489ffffff ┆ Europe/Berlin │
│ 872a1072cffffff ┆ America/New_York │
│ 872f5a363ffffff ┆ Asia/Tokyo │
└─────────────────┴──────────────────┘
It works anywhere a Polars expression works, including lazy queries:
(
pl.scan_parquet("events.parquet")
.with_columns(tz=tzf.tz_name("latitude", "longitude"))
.collect()
)
API
tz_name(lat, lng) -> pl.Expr
Resolves each (lat, lng) pair to an IANA timezone name (e.g. "Europe/Berlin").
lat,lng— column names, expressions, or anything else Polars accepts as an expression input (IntoExpr). Both must beFloat64.- Returns a
Stringexpression. The operation is elementwise, so it parallelizes across chunks and is safe to use ingroup_by/overcontexts. - Nulls: if either coordinate is null, or no timezone matches, the result is null. Points in international waters resolve to
Etc/GMT±Nzones.
tz_name_from_h3(cell) -> pl.Expr
Resolves each H3 cell to the IANA timezone containing the cell's centre.
cell— a column of H3 indices, either as an integer (UInt64,Int64, …) or as the hex string form ("871f1d489ffffff", upper or lower case). Any other dtype raises.- Returns a
Stringexpression, elementwise liketz_name. - Nulls: null input, an index that is not a valid H3 cell, and an unparseable hex string all yield null.
- Coarse cells: a low-resolution cell can span several timezones; you always get the one containing its centre. From resolution 7 (~1.2 km edges) downward this is effectively never ambiguous.
Performance
-
The timezone index (
tzf-rsDefaultFinder) is built once per process and shared across all threads, so the one-time startup cost (~100 MB of polygon index) is amortized over the whole session. -
Lookups run in native Rust with zero Python overhead per row. Tens of millions of rows resolve in seconds on a laptop.
-
tz_name_from_h3memoizes on the H3 index in a thread-local cache and builds its output by gathering from a dictionary of zone names rather than writing one string per row. Both matter: on 3M rows over 50k distinct cells at resolution 9, throughput goes from ~5M rows/s (a plain per-row lookup) to ~160M rows/s. The cache needs no locking and survives across chunks and across calls on the same worker thread. -
Throughput therefore tracks the number of distinct cells, not the row count, and it matters whether the cache is warm. Cold is a one-shot query, which pays to resolve every distinct cell; warm is what repeated calls, many row groups, or a grouped context see. Measured on 3M rows, one fresh process per row:
distinct cells cold warm 20,000 34 169 120,000 25 145 400,000 14 122 700,000 7 16 1,500,000 4 6 The cache holds 2^19 (~524k) cells per worker thread, which covers a metro area at resolution 9 comfortably. Beyond that the surplus cells fall back to a full polygon lookup and throughput converges on the uncached cost — decoding the H3 index makes that floor about 25% below
tz_nameon raw coordinates, so if your cells are near-unique you may as well look up coordinates directly.
Reproduce with uv run python scripts/bench.py.
Accuracy
tzf-rs uses simplified timezone polygons for speed. Points very close to timezone borders (roughly within ~100 m) may be assigned to the neighboring zone. This is the right trade-off for bulk analytical workloads; if you need survey-grade precision at borders, use a full-polygon lookup instead.
Timezone data comes from timezone-boundary-builder via the tzf project.
Development
Requires Rust, Python ≥ 3.10, and uv.
git clone https://github.com/drumtorben/polars-tzf
cd polars-tzf
uv sync # create venv and install dev dependencies
uv run maturin develop --release # build the Rust extension into the venv
Then test your changes:
uv run python -c "
import polars as pl, polars_tzf as tzf
print(pl.select(tz=tzf.tz_name(pl.lit(52.52), pl.lit(13.405))))
"
Run the test suite:
uv run pytest
Note: always build with
--releasewhen measuring performance — debug builds are dramatically slower.
Credits
- tzf-rs by @ringsaturn does the actual geometry work.
- pyo3-polars provides the Polars plugin machinery.
License
MIT — see LICENSE.
Metadata
Release files for polars-tzf 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| polars_tzf-0.2.0.tar.gz | 75.6 kB | Without provenance | Details for polars_tzf-0.2.0.tar.gz |
Built distributions (wheels)
Total release size: 71.0 MB
Release files / polars_tzf-0.2.0.tar.gz
| Download URL | polars_tzf-0.2.0.tar.gz |
|---|---|
| Size | 75.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4234ef37b4e33fc1d9dde33ee0d973e55c30d19e765f9bc433cd9ec4937cff34
|
|
BLAKE2b-256 checksum How to use checksums |
3577c80f79b051a9e6b3b14c9618a370f81db24522000ff6fd50198b7f356982
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / polars_tzf-0.2.0-cp310-abi3-win_arm64.whl
| Download URL | polars_tzf-0.2.0-cp310-abi3-win_arm64.whl |
|---|---|
| Size | 8.0 MB |
| Tags | CPython 3.10 Windows ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
556b10a6e56a9170bf857a8ac5cfdad1c56ff9c5c64af97a0db46498f8ae880d
|
|
BLAKE2b-256 checksum How to use checksums |
53241de7284a9dd5b8492b60347682f89eab94090c6b5c427076f10526fd9b8d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / polars_tzf-0.2.0-cp310-abi3-win_amd64.whl
| Download URL | polars_tzf-0.2.0-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 8.5 MB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
3afe6e5593cd3b8507eb46d4e25ca55bac97a4803189d4ad9264e7739c681338
|
|
BLAKE2b-256 checksum How to use checksums |
63a317097f9d3cd4639cf6bf27c67da6ecc5fcfcf3580a99c59c0671c0cfa247
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / polars_tzf-0.2.0-cp310-abi3-musllinux_1_2_x86_64.whl
| Download URL | polars_tzf-0.2.0-cp310-abi3-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 9.7 MB |
| Tags | CPython 3.10 Linux musl 1.2+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
7baf31a15f1dea312e11b33be3174aa828326c9b97ed273f5a9e09d4137e2bc9
|
|
BLAKE2b-256 checksum How to use checksums |
2254780525a457182ddfd97da040c3470b804a8edbbaafa30ad7c6b17489db70
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / polars_tzf-0.2.0-cp310-abi3-musllinux_1_2_aarch64.whl
| Download URL | polars_tzf-0.2.0-cp310-abi3-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 9.4 MB |
| Tags | CPython 3.10 Linux musl 1.2+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
7dfb9031adfd0173e269a772fe7774d6ec398849b755ccad581e28c447746ab2
|
|
BLAKE2b-256 checksum How to use checksums |
665adae7462ab028474ed33fcca6aa1f34be4ff26c706c9547bc3be615e352cc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / polars_tzf-0.2.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | polars_tzf-0.2.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 8.9 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
bd181c99552ba5f30bf19987bfd705cb0b196c1864f49bd357a8124df58b4648
|
|
BLAKE2b-256 checksum How to use checksums |
c4e78dee6f6dad5955e667cab76b026a3a13cf264137cf29da3f2ec1fc5364ff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / polars_tzf-0.2.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | polars_tzf-0.2.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 9.2 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
fed529dc3d8c0d7995dd3be8e4ddbfd49ece929098ef877e16855b94cfbf83a3
|
|
BLAKE2b-256 checksum How to use checksums |
2843e8f4878070f0110e94df130bfdfea2208e1d4390e52e653f8597191281cb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / polars_tzf-0.2.0-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | polars_tzf-0.2.0-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 8.4 MB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
86d214b736c4016140d76c20dfd11b174e77cabd94811ce045e07790122c9839
|
|
BLAKE2b-256 checksum How to use checksums |
11e99be2bb0b41ccddc6340788d9071806fd8c0c4f1e1c696ebbf58384e5afaf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / polars_tzf-0.2.0-cp310-abi3-macosx_10_12_x86_64.whl
| Download URL | polars_tzf-0.2.0-cp310-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 8.7 MB |
| Tags | CPython 3.10 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
63f9657bac2cf9dda5a0127ab5d85a147702615e2aa4fe1448e870c6ff447f72
|
|
BLAKE2b-256 checksum How to use checksums |
8c767b3d6b1c4670d0ae7ae824fc706b7ccd1057e6657eb1091160b3a2c3e27b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for polars-tzf 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.