Getting Started
Project tzf provides multi-language support for looking up a timezone by longitude and latitude.
| Language or Server | Repository | API Docs |
|---|---|---|
| Go | ringsaturn/tzf | |
| Rust | ringsaturn/tzf-rs | |
| Python | ringsaturn/tzfpy | tzfpy.pyi |
| Swift | ringsaturn/tzf-swift | |
| Ruby | HarlemSquirrel/tzf-rb | |
| JS via Wasm (browser) | ringsaturn/tzf-wasm | |
| HTTP API | racemap/rust-tz-service | |
| Online Demo | ringsaturn/tzf-web |
Go
tzf v2 is a new major version, so the module path carries the /v2 suffix:
go get github.com/ringsaturn/tzf/v2package main
import (
"fmt"
"github.com/ringsaturn/tzf/v2"
)
func main() {
// Construction is expensive relative to a query; build the finder once.
finder, err := tzf.NewDefaultFinder()
if err != nil {
panic(err)
}
// Coordinates are in (longitude, latitude) order.
fmt.Println(finder.GetTimezoneName(116.6386, 40.0786))
}There are exactly five constructors, all returning the tzf.F interface:
| Constructor | Applies to |
|---|---|
NewDefaultFinder() | General-purpose use: lite memory image, ~12 MB heap + 10 MB read-only data, 298 ns queries |
NewEmbeddedFinder() | Embedded and memory-constrained targets: ~4 MB total, ~1.2 µs on a preindex miss |
NewFullFinder() | Results matching the full-precision dataset (~145 MB) |
NewFinderFromTZB(data) | Caller-supplied .tzb bytes, expanded at load |
NewFinderFromTZM(data) | Caller-supplied .tzm bytes, aliased in place |
Figures measured on an Apple M3 Max against the 2026c dataset.
For 100% accurate results, use NewFullFinder (reuse it when possible — initialization is expensive):
package main
import (
"fmt"
"github.com/ringsaturn/tzf/v2"
)
func main() {
finder, err := tzf.NewFullFinder()
if err != nil {
panic(err)
}
fmt.Println(finder.GetTimezoneName(139.6917, 35.6895))
}See the Go guide for GeoJSON export, in-place queries over your own bytes, and the v1 → v2 migration table.
Rust
cargo add tzf-rsuse std::sync::LazyLock;
use tzf_rs::DefaultFinder;
static FINDER: LazyLock<DefaultFinder> = LazyLock::new(DefaultFinder::new);
fn main() {
// Coordinates are in (longitude, latitude) order.
print!("{:?}\n", FINDER.get_tz_name(116.3883, 39.9289));
print!("{:?}\n", FINDER.get_tz_names(116.3883, 39.9289));
}tzf-rs 2.1 ships two finders: DefaultFinder (the default, ~47 MiB peak RSS,
221 ns per random-city lookup) and EmbeddedFinder, which queries the embedded
file in place at ~10 MiB, 293 ns per random-city lookup and 666 ns on border
cities. Both are measured in thetz-benchmark 2026-09-14 snapshot on
an Apple M3 Max against the 2026c dataset, tzf-rs 2.1.1.
Full-precision support
Full-precision data is available via an optional Cargo feature. Because the full dataset (~14 MB) exceeds crates.io size limits, it must be referenced via a git dependency, and it is mutually exclusive with the bundled lite dataset:
[dependencies]
tzf-rs = { git = "https://github.com/ringsaturn/tzf-rs", rev = "v{X}.{Y}.{Z}", features = ["full"], default-features = false }use tzf_rs::DefaultFinder;
fn main() {
let finder = DefaultFinder::new_full();
let tz_name = finder.get_tz_name(139.767125, 35.681236);
println!("tz_name: {}", tz_name);
}Python
# Install just tzfpy
pip install tzfpy
# Install with pytz
pip install "tzfpy[pytz]"
# Install with tzdata
pip install "tzfpy[tzdata]"
# Install via conda
conda install -c conda-forge tzfpy>>> from tzfpy import get_tz, get_tzs
>>> get_tz(116.3883, 39.9289) # (longitude, latitude) order
'Asia/Shanghai'
>>> get_tzs(87.4160, 44.0400) # returns all matching timezones
['Asia/Shanghai', 'Asia/Urumqi']tzfpy 2.0 requires Python 3.10 or newer and binds tzf-rs 2.0. It also exposestimezonenames(), data_version(), get_tz_polygon_geojson(name), andget_tz_index_geojson(name). Full-precision mode ships as experimental +full
pre-release wheels on tzfpy’s own index, not on PyPI; see thePython guide.
Swift
tzf-swift 2.0 is protobuf-free: it bundles lite.tzb from tzf-dist and ships
two finders. DefaultFinder expands the polygons at load (~16 ms, ~48 MB) andEmbeddedFinder queries the .tzb bytes in place (~2 ms, ~10 MB) at a higher
per-call latency. Both accept caller-owned bytes through init(tzb:), so
tzf-dist’s full-precision full.tzb loads the same way.
Add the package to your Package.swift:
dependencies: [
.package(url: "https://github.com/ringsaturn/tzf-swift.git", from: "2.0.0")
]import Foundation
import tzf
do {
let finder = try DefaultFinder()
let timezone = try finder.getTimezone(lng: 116.3833, lat: 39.9167)
print("Beijing timezone:", timezone)
let timezones = try finder.getTimezones(lng: 87.5703, lat: 43.8146)
print("Multiple possible timezones:", timezones)
print("Data version:", finder.dataVersion())
// Low-memory alternative: query the .tzb bytes in place.
let embedded = try EmbeddedFinder()
print("Embedded finder:", try embedded.getTimezone(lng: 139.6917, lat: 35.6895))
} catch {
print("Error:", error)
}Migrating from v1: Finder() becomes DefaultFinder(), PreindexFinder is
removed (the preindex is the fast path inside every finder),FinderError.noTimezoneFound becomes TZFError.noTimezoneFound, andgetTimezones results are now sorted lexicographically.
Ruby
Ruby support is created and maintained byHarlemSquirrel, and builds on the v1 line as of 2026-09-12. See tzf-rb for detailed documentation.
bundle add tzf
# or
gem install tzfrequire 'tzf'
TZF.tz_name(40.74771675713742, -73.99350390136448)
# => "America/New_York"
TZF.tz_names(40.74771675713742, -73.99350390136448)
# => ["America/New_York"]WebAssembly
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>tzf-wasm Example</title>
<script type="module">
import init, { WasmFinder } from "https://www.unpkg.com/tzf-wasm@2.0.0/tzf_wasm.js";
async function loadWasm() {
await init();
const finder = new WasmFinder();
const timezone = finder.get_tz_name(-74.006, 40.7128);
console.log("Timezone for New York:", timezone);
}
loadWasm();
</script>
</head>
<body></body>
</html>Online preview: http://ringsaturn.github.io/tzf-web/
CLI
Both the Go and Rust implementations ship a command-line tool.
Go CLI
go install github.com/ringsaturn/tzf/cmd/tzf@latesttzf -lng 116.3883 -lat 39.9289
# Batch via stdin
echo -e "116.3883 39.9289\n116.3883, 39.9289" | tzf -stdin-order lng-latRust CLI
cargo install tzf-rstzf --lng 116.3883 --lat 39.9289
# Batch via stdin
echo -e "116.3883 39.9289\n116.3883, 39.9289" | tzf --stdin-order lng-latNixOS users can install tzf-rs via Nix —
see NixOS packages for details.