NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
PyPI · #2075 most downloaded on PyPI
Probably the fastest Python package to convert longitude/latitude to timezone name
Last release 18 days ago
17 Sep 2026
Release timing varies
gaps range from 2 weeks to 7 months
Nearly every release is documented
notes for 24 of 25 stable releases
1 version withdrawn
withdrawn after publishing
4 years old
26 releases · first in 2022
One column per quarter.
Full Changelog : v2.0.0...v2.1.0
Full Changelog: v2.0.0...v2.1.0
Rust core upgraded from tzf-rs 2.0.0 to 2.1.2, and the dataset from 2026c to
2026d (tzf-dist 0.0.2026-d). No Python API change.
data_version() now returns "2026d". The upstream
timezone-boundary-builder 2026d
release refreshes the OSM boundary data, assigns Golden, BC to
America/Edmonton, and recalculates the since-1970 / since-now zone sets
against the current tz database..tzb files are now encoded at 64-point chunks. Lite wheels grow from
2.77 MB to about 3.0 MB (macOS arm64); query results are identical.full.tzb (~14 MB) instead of the ~4 MB lite.tzb, via the new mutually
exclusive lite (default) / full Cargo features. They carry a +full PEP
440 local version and, being experimental, are published only to GitHub
Releases and tzfpy's own index at
https://ringsaturn.github.io/tzfpy/full/simple/ — never to PyPI, which
rejects local versions by design. The PyPI and conda-forge builds are
unchanged. The full variant runs on tzf-rs's EmbeddedFinder, which queries
the data in place: about 16 MB resident against lite's 39 MB, at a higher
per-call cost. See README "Full-precision wheels" for the measurements and
install instructions.make build-full and scripts/set_local_version.py for building the
+full wheel locally.The lite wheel is unchanged in speed. The full variant picks up the tzf-rs 2.1
EmbeddedFinder query-path work (open-time group validation, chunk block
skipping, integer segment pre-filter, endpoint-parity skip, targeted FUZZY
probes). tz-benchmark Python harness, Apple M3 Max, CPython 3.14, medians per
call:
| Candidate | random cities | edge cities | RSS delta |
|---|---|---|---|
| tzfpy 2.1.0 (lite) | 625 ns | 875 ns | 39.0 MiB |
| tzfpy 2.1.0+full | 750 ns | 1.21 µs | 16.0 MiB |
| timezonefinder 9.0.0 | 1.17 µs | 2.71 µs | 52.5 MiB |
2.1.0b1 and 2.1.0b2 shipped the same changes on tzf-rs 2.0.0 / 2.1.1 and the
2026c data; their lite wheels went to TestPyPI and GitHub Releases, not
PyPI. 2.1.0 is the first 2.1 build on PyPI.
No format or API change; results are byte-identical (parity swept over the
world cities, edge cities and a 0.25° global grid, ~2.25M points, on both
lite.tzb and full.tzb).
group_at to open
(validate_groups): the query walk no longer re-reads a group's whole
chunk run before deciding whether the group is even ray-relevant.scan_group reads each CHUNKDIR record once (lookahead) instead of three
times, and skips 16-chunk blocks by a union bbox built during the
open-time chunk validation pass.StreamCursor slices the chunk range once and inlines the one- and
two-byte varint paths (98% of stored deltas); malformed-varint detection
is unchanged.raycast_seg: segments strictly above/below the
query latitude or entirely left of the query longitude are skipped.agg_zoom..=idx_zoom over the whole
array (the 2026c preindex has keys at zooms 5–10 only, so a miss did 11
full binary searches).first/last for a group; the chunk's first point and the
next chunk's first point for a chunk). Only chunks whose bbox straddles
the query longitude are decoded — typically one or two per ring. Edge
cities on 64-point-chunk artifacts: full mean 1,000 → 448 ns, p99 3,540
→ 1,222; lite 833 → 470, p99 2,430 → 1,056.EmbeddedFinder therefore no longer holds
"~1 KB of state": the open-time index is ~100 KB on lite.0.0.2026-c-tzb2: the same 2026c boundaries encoded
at 64-point chunks (topo2embed -chunk 64, +5% lite / +11% full file
size). The .tzb format is unchanged (1.1); either reader opens either
data release.Measured on Apple M3 Max, edge-city set (benches/edges.json), mean per
query: lite in place 4.37 → 2.04 µs, full in place 6.48 → 2.74 µs. With
artifacts re-encoded at 64-point chunks (topo2embed -chunk 64, +5% lite /
+11% full file size) the same reader reaches 1.15 µs and 1.65 µs.
The Rust core behind tzfpy is now tzf-rs 2.0, which carries no protobuf dependency: timezone boundaries ship as the TZF embedded binary format ( .tzb
The Rust core behind tzfpy is now tzf-rs
2.0, which carries no protobuf dependency: timezone boundaries ship as the TZF
embedded binary format (.tzb). The dataset is the same 2026c release, and
query results are identical. Memory after import drops from 71.6–73.6 MB to
39.8–42.3 MB and the macOS arm64 wheel from 4.31 MB to 2.77 MB, both measured
below.
The Python API did not change. The same six functions with the same names,
signatures and return types:
from tzfpy import get_tz, get_tzs
get_tz(116.3883, 39.9289) # 'Asia/Shanghai' (longitude, latitude)
get_tzs(87.4160, 44.0400) # ['Asia/Shanghai', 'Asia/Urumqi']pip install -U tzfpyThe four contract changes listed below are the reason for a major version.
Code that does not depend on any of them runs unmodified after
pip install -U tzfpy.
get_tzs() results are sorted alphabetically. 1.x returned them in internalget_tz() returns the first positive match and is the_TZFPY_DISABLE_Y_STRIPES was removed. tzf-rs 2 removed FinderOptions,get_tz_polygon_geojson() and get_tz_index_geojson() raise ValueErrorpyo3_runtime.PanicException.Unchanged: coordinate order is (longitude, latitude); the dataset is 2026c
(data_version() returns the same value); a point lying exactly on a shared
border belongs to both neighbouring zones; the finder is built on first use.
MacBook Pro, Apple M3 Max, macOS 26.6.2, CPython 3.10.18. Both versions
measured back to back with the same script (scripts/measure_memory_tzfpy.py,
make bench) against the same 2026c dataset.
| Metric | 1.3.3 | 2.0.0 |
|---|---|---|
RSS after import tzfpy + one query |
71.6–73.6 MB | 39.8–42.3 MB |
| Whole-process RSS | 89.7–96.4 MB | 57.8–60.3 MB |
| Wheel size (macOS arm64) | 4.31 MB | 2.77 MB |
| Query median, random world cities | 0.636 µs | 0.683 µs |
Query latency is within run-to-run variation of 1.3.3. One .tzb file replaces
the protobuf topology file and the pre-index file of 1.x, which accounts for the
memory and wheel-size figures.
Unchanged. tzfpy uses topology-simplified polygons; every simplified
boundary stays within about 111 m of the full-precision border (certified
111.2 m), 0.41% of border length is displaced more than 100 m, and none beyond
500 m. See the README's Accuracy section and
BORDER_CHANGE.md.
abi3) and free-threaded 3.14t: manylinux andFull Changelog: v1.3.3...v2.0.0
Rust core upgraded from tzf-rs 1.3.7
to 2.0.0, which is protobuf-free: boundary data now ships as the TZF embedded
binary format (.tzb) from tzf-dist instead of protobuf artifacts. The
dataset itself is unchanged (data_version() == "2026c").
The Python API is unchanged: the same six functions with the same names, signatures and return types. Apart from the changes below, code written against 1.x runs unmodified.
get_tzs() returns names sorted alphabetically; 1.x returned them in
internal polygon order. get_tz() returns the first positive match._TZFPY_DISABLE_Y_STRIPES was removed: tzf-rs 2 removed FinderOptions and
the YStripes index is always enabled. Setting the variable has no effect and
raises no error.get_tz_polygon_geojson() / get_tz_index_geojson() raise ValueError for
an unknown timezone name instead of panicking with
pyo3_runtime.PanicException.Measured on a MacBook Pro (Apple M3 Max, macOS 26.6.2, CPython 3.10.18), same machine and script for both versions:
| Metric | 1.3.3 | 2.0.0 |
|---|---|---|
| RSS after import + one query | 71.6–73.6 MB | 39.8–42.3 MB |
| Wheel size (macOS arm64) | 4.31 MB | 2.77 MB |
Query median (make bench) |
0.636 µs | 0.683 µs |
Query latency is within run-to-run variation of 1.3.3. The measured reductions are in memory and download size.
get_tz is answered by the pre-index fast path first and falls back to exact
point-in-polygon; get_tzs is always polygon-exact. This matches tzf-rs 2 and
Go tzf/v2 semantics, and matches the behavior of 1.3.3.make bench / scripts/benchmark_index_modes.py collapsed to the single
remaining index mode.py-cpuinfo to the dev dependency group: pytest-benchmark 5.3 made it
optional, but --benchmark-json (used by make bench) still requires it.tzf-rs v2 is protobuf-free. The data source is the TZF embedded binary format
(.tzb) shipped by
tzf-dist; the protobuf artifacts of
the v1 line are no longer published, so staying on v1 means staying on its
last data release.
MSRV: Rust 1.88 (edition 2024 plus let-chains), declared as rust-version in
Cargo.toml.
Two finder types, mirroring the Go tzf/v2 design:
DefaultFinder — the recommended finder. Loads a .tzb by expanding its
geometry into geometry-rs polygons (new(), new_full(), from_tzb).
get_tz_name answers from the FUZZY preindex tiles first and falls back
to exact point-in-polygon; get_tz_names stays polygon-exact.EmbeddedFinder — the low-memory finder: queries .tzb bytes in place
(~4 MB total for the bundled lite data), FUZZY-first with the
compressed-geometry scan as fallback. from_tzb accepts &'static [u8]
or Vec<u8>.Both expose get_tz_name, get_tz_names, timezonenames and
data_version, and — under export-geojson — to_geojson,
get_tz_geojson, to_preindex_geojson and get_tz_preindex_geojson.
Alongside them the crate exports Error (new in v2), deg2num, and the
GeoJSON types (BoundaryFile, FeatureItem, GeometryDefine,
PropertiesDefine, PolygonCoordinates, MultiPolygonCoordinates) under
export-geojson. That is the whole public API.
Finder::from_pb, Finder::from_compressed_topo
(and their *_with_options variants), FuzzyFinder::from_pb, the pbgen
module, the prost / prost-build dependencies, anyhow, bytes, and the
protobuf build machinery (build.rs, buf.yaml, the pb/ directory).Finder and FuzzyFinder types. DefaultFinder covers Finder
(get_tz_names is polygon-exact); tile-only lookup is no longer a public
mechanism in any form — the preindex is the internal fast path inside every
finder.FinderOptions and the *_with_options constructors — the YStripes index
is always enabled.revert_timezones (it took a protobuf type).| v1 | v2 |
|---|---|
DefaultFinder::new() |
unchanged |
DefaultFinder::new_full() |
unchanged (full feature, git-only data) |
DefaultFinder::{get_tz_name,get_tz_names,timezonenames,data_version} |
unchanged |
Finder (polygon-only) |
DefaultFinder (get_tz_names stays polygon-exact) |
FuzzyFinder (tile-only) |
removed — the preindex is the fast path inside every finder |
Finder::from_compressed_topo(pb) / from_pb(pb) |
DefaultFinder::from_tzb(&[u8]) |
FuzzyFinder::from_pb(pb) |
removed — no separate tile-only finder |
FinderOptions / *_with_options |
removed — YStripes is always on |
tzf_rs::pbgen |
removed — no protobuf types in the public API |
tzf_rs::revert_timezones(&pb) |
removed — took a protobuf type |
finder.finder.get_tz_geojson(...) |
finder.get_tz_geojson(...) |
FuzzyFinder::to_geojson() |
DefaultFinder::to_preindex_geojson() -> Option<BoundaryFile> |
FuzzyFinder::get_tz_geojson(name) -> Option<FeatureItem> |
DefaultFinder::get_tz_preindex_geojson(name) -> Option<BoundaryFile> |
feature bundled (pb lite data) |
feature bundled (lite .tzb) |
feature full (pb full data, git-only) |
feature full (full .tzb, still git-only) |
features clap, export-geojson |
unchanged |
| — | new: EmbeddedFinder, tzf_rs::Error |
get_tz_names results are sorted lexicographically (matching the Go v2
finder).Err(tzf_rs::Error) from the byte
constructors instead of an empty default finder: files are CRC-checked and
structurally validated at open.BoundaryFile (FeatureCollection);
v1's FuzzyFinder::get_tz_geojson returned a bare FeatureItem.bundled (default): lite .tzb from tzf-dist on crates.io (~4 MB).full: full-precision .tzb (~14 MB), enables new_full(). The full
dataset is git-only: it exceeds the crates.io package limit, so the registry
tzf-dist package ships lite.tzb alone. Enabling full needs the tzf-dist
git source, either by taking tzf-rs itself from git or by adding a
[patch.crates-io] entry for tzf-dist; see the README. v1 had the same
constraint; v2 documents it explicitly.full is mutually exclusive with bundled: use
default-features = false. Enabling both is a compile_error!..tzb is the only format tzf-rs consumes. The .tzm memory image stays a
Go-runtime optimization (zero-copy ring aliasing); geometry-rs polygons own
their storage, so a Rust .tzm loader saved ~3 ms of open time while using
more memory — measured, then dropped. Opening .tzm bytes returns
Error::Profile.
v1 (protobuf) → v2 (.tzb), Apple M3 Max, dataset 2026c, measured
2026-08-28:
| Metric | v1 (pb) | v2 (.tzb) |
|---|---|---|
DefaultFinder open, lite |
71 ms | 18 ms cold / 12.7 ms warm |
DefaultFinder open, full |
239 ms | 66 ms cold |
| Peak RSS, lite / full | 77.8 / 300 MiB | 44.1 / 212.8 MiB |
| Query, random city / edge city | 316 / 457 ns | 260 / 403 ns |
| crates.io data payload | 6.9 MB topo + 2.0 MB preindex | 3.97 MB (one file) |
EmbeddedFinder (new in v2) |
— | 2.1 ms open, 5.9 MiB RSS, 1.6–3.9 µs/query |
Current make bench medians on the same machine (dataset 2026c). The
whole-dataset sweep is the stable figure: the single-coordinate benches draw
one random city and reuse it for the whole measurement, so their absolute value
varies with the draw.
| Scenario | DefaultFinder | EmbeddedFinder |
|---|---|---|
get_tz_name over 154,248 cities |
25.1 ms (~163 ns/query) | 147.0 ms (~950 ns/query) |
get_tz_name, edge city (preindex miss) |
434 ns | 4.03 µs |
get_tz_names, edge city |
531 ns | 5.71 µs |
open (new()) |
13.0 ms | 2.05 ms |
Cross-language and cross-mechanism parity is pinned by tests: DefaultFinder
(expanded) and EmbeddedFinder (in-place) return identical results, verified
against the Go tzf/v2 reference over ~195k boundary-heavy samples per
artifact plus the world-cities dataset.
Queries exactly on a timezone border no longer return an empty result. Via tzf-rs 1.3.7 ( tzf-rs#208 ): the geometry library treated a point lying on
tzf-rs 1.3.7 (tzf-rs#208): the geometry library treated a point lying on a shared polygon edge as outside both neighbouring polygons. This was easiest to hit with nautical zones, whose borders sit on whole meridians (7.5, 22.5, ...), exactly the kind of coordinate people type by hand. The finder now uses Polygon::contains_point_allow_on_edge (geometry-rs 0.5.1), so a border point belongs to both adjacent zones: get_tzs() returns both names and get_tz() returns the first grid-indexed match. On a global 1° grid, 2,753 previously empty queries now resolve; non-border lookups are unaffected in both result and speed.America/Argentina/Ushuaia, via the boundary dataset refresh in tzf-rs 1.3.6.tzf-rs 1.3.5 → 1.3.7. The dataset is still 2026c, so data_version() is unchanged, but the underlying boundaries carry the Ushuaia fix.*-pyemscripten_*_wasm32.whl, CPython 3.14) are built in CI and attached to the GitHub Release. They are deliberately not uploaded to PyPI.actions/setup-python@v7; uv.lock now triggers the CI build workflow.tzfpy.pyi modernised: List[str] → list[str], dropped the typing import and the redundant ... bodies. No runtime change, but type checkers now see builtin generics.tzf's BORDER_CHANGE.md.make bench now runs scripts/benchmark_index_modes.py, which compares the default YStripes index against _TZFPY_DISABLE_Y_STRIPES=1 and prints one Markdown table (latency, throughput, memory) instead of two raw pytest runs.make measure-memory target; scripts/measure_memory_tzfpy.py gained CLI args and JSON output.examples/) and a new pyodide dependency group for the Emscripten build.Full Changelog: v1.3.2...v1.3.3
This release upgrades tzf-rs from v1.3.2 to v1.3.5, bringing updated timezone boundary data, significantly lower memory usage, and faster timezone loo
This release upgrades tzf-rs from v1.3.2 to v1.3.5, bringing updated timezone boundary data, significantly lower memory usage, and faster timezone lookups.
2026c.The upgraded YStripes index uses a more compact representation:
i32 storage.According to the upstream benchmarks:
DefaultFinder memory usage was reduced by approximately 53% in tzf-rs v1.3.4.The tzfpy benchmark on Apple M3 Max now records approximately 663 ns per lookup with the default index, reaching about 1.5 million lookups per second.
The bundled timezone boundary data has been updated from 2026b to 2026c through tzf-dist 0.0.2026-c.
Applications can inspect the bundled data version with:
import tzfpy
print(tzfpy.data_version())There are no Python API changes in this release. Existing calls to get_tz, get_tzs, timezonenames, data_version, and GeoJSON export functions continue to work without modification.
Lookup correctness was validated upstream against linear raycasting using 700,000 coordinates, including random coordinates and points aligned to the internal storage grid, with zero mismatches.
uvx usage documentation.Upstream details:
Full Changelog: v1.3.1...v1.3.2
Add license scan report and status by @fossabot in #109
Bump pytest from 9.0.2 to 9.0.3 by @dependabot [bot] in #125
Full Changelog: v1.2.0...v1.3.0
add backup index based on github release by @ringsaturn in #116
tzfpy follows current tzf-rs behavior: DefaultFinder enables y_stripes
by default. If you need to disable y_stripes, use this environment variable:
export _TZFPY_DISABLE_Y_STRIPES=1The index requires about 40MB memory, but can speed up query missing from
pre-index, especially around borders.
Benchmark with _TZFPY_DISABLE_Y_STRIPES=1
.
---------------------------------------------- benchmark: 1 tests ----------------------------------------------
Name (time in us) Min Max Mean StdDev Median IQR Outliers OPS (Kops/s) Rounds Iterations
----------------------------------------------------------------------------------------------------------------
test_tzfpy 2.3709 2.9560 2.5338 0.0794 2.5213 0.0728 86;24 394.6709 500 10000
----------------------------------------------------------------------------------------------------------------
Legend:
Outliers: 1 Standard Deviation from Mean; 1.5 IQR (InterQuartile Range) from 1st Quartile and 3rd Quartile.
OPS: Operations Per Second, computed as 1 / Mean
Results (14.42s):
1 passed
Benchmark with default index mode
.
---------------------------------------------- benchmark: 1 tests ----------------------------------------------
Name (time in us) Min Max Mean StdDev Median IQR Outliers OPS (Kops/s) Rounds Iterations
----------------------------------------------------------------------------------------------------------------
test_tzfpy 1.7190 2.4869 1.8321 0.1139 1.7934 0.0627 53;56 545.8076 500 10000
----------------------------------------------------------------------------------------------------------------
Legend:
Outliers: 1 Standard Deviation from Mean; 1.5 IQR (InterQuartile Range) from 1st Quartile and 3rd Quartile.
OPS: Operations Per Second, computed as 1 / Mean
Results (10.35s):
1 passedFull Changelog: v1.1.3...v1.2.0
Full Changelog: https://github.com/ringsaturn/tzfpy/compare/v1.1.2...v1.1.3
Full Changelog: https://github.com/ringsaturn/tzfpy/compare/v1.1.2...v1.1.3
See https://github.com/ringsaturn/tzfpy/issues/115
See https://github.com/ringsaturn/tzfpy/issues/115
Full Changelog: https://github.com/ringsaturn/tzfpy/compare/v1.1.1...v1.1.2
Bump pyo3 from 0.27.0 to 0.27.1 in the dependencies group by @dependabot[bot] in https://github.com/ringsaturn/tzfpy/pull/104
Full Changelog: https://github.com/ringsaturn/tzfpy/compare/v1.1.0...v1.1.1
Full Changelog: https://github.com/ringsaturn/tzfpy/compare/v1.0.1...v1.1.0
Full Changelog: https://github.com/ringsaturn/tzfpy/compare/v1.0.1...v1.1.0
Bump pyo3 from 0.24.0 to 0.24.1 in the dependencies group by @dependabot[bot] in https://github.com/ringsaturn/tzfpy/pull/87
Full Changelog: https://github.com/ringsaturn/tzfpy/compare/v1.0.0...v1.0.1
NOTE: please use pytz>=2025.2 or tzdata>=2025.2 to handle timezone name(s).
NOTE: please use pytz>=2025.2 or tzdata>=2025.2 to handle timezone name(s).
Full Changelog: https://github.com/ringsaturn/tzfpy/compare/v0.16.4...v1.0.0
- fix optional dep version Full Changelog: https://github.com/ringsaturn/tzfpy/compare/v0.16.3...v0.16.4
Full Changelog: https://github.com/ringsaturn/tzfpy/compare/v0.16.3...v0.16.4
Bump astral-sh/setup-uv from 4 to 5 in the dependencies group by @dependabot in https://github.com/ringsaturn/tzfpy/pull/83
Full Changelog: https://github.com/ringsaturn/tzfpy/compare/v0.16.2...v0.16.3
Bump pyo3 from 0.23.2 to 0.23.3 by @dependabot in https://github.com/ringsaturn/tzfpy/pull/82
Full Changelog: https://github.com/ringsaturn/tzfpy/compare/v0.16.1...v0.16.2
add tzdata option by @ringsaturn in https://github.com/ringsaturn/tzfpy/pull/68
Full Changelog: https://github.com/ringsaturn/tzfpy/compare/v0.15.6...v0.16.0
Add license info to pyproject by @jayeff in https://github.com/ringsaturn/tzfpy/pull/57
NOTE: Starting from version 0.16.0, tzfpy will only support Python 3.9 and above, as Python 3.8 will reach its end of life in October 2024.
Full Changelog: https://github.com/ringsaturn/tzfpy/compare/v0.15.5...v0.15.6
Bump tzf-rs to fix empty result
Full Changelog: https://github.com/ringsaturn/tzfpy/compare/v0.15.4...v0.15.5
Bump pyo3 from 0.20.0 to 0.20.1 by @dependabot in https://github.com/ringsaturn/tzfpy/pull/47
Full Changelog: https://github.com/ringsaturn/tzfpy/compare/v0.15.3...v0.15.4
Bump rustix from 0.38.17 to 0.38.19 by @dependabot in https://github.com/ringsaturn/tzfpy/pull/40
Full Changelog: https://github.com/ringsaturn/tzfpy/compare/v0.15.2...v0.15.3
Bump maturin from 1.1.0 to 1.2.3 by @dependabot in https://github.com/ringsaturn/tzfpy/pull/37
Full Changelog: https://github.com/ringsaturn/tzfpy/compare/v0.15.1...v0.15.2
Bump pyo3 from 0.18.2 to 0.18.3 by @dependabot in https://github.com/ringsaturn/tzfpy/pull/22
Full Changelog: https://github.com/ringsaturn/tzfpy/compare/v0.15.0...v0.15.1
bump version and add data_version method by @ringsaturn in https://github.com/ringsaturn/tzfpy/pull/21
Full Changelog: https://github.com/ringsaturn/tzfpy/compare/v0.14.0...v0.15.0
Nothing published for this version
Synced from PyPI package tzfpy. Version: 0.8.2 PyPI: https://pypi.org/project/tzfpy/0.8.2/
Synced from PyPI package tzfpy.
Version: 0.8.2
PyPI: https://pypi.org/project/tzfpy/0.8.2/
Your coding agent can read these notes before it upgrades. Set up the MCP server →