Skip to content
Getting Started

Getting Started

Project tzf provides multi-language support for looking up a timezone by longitude and latitude.

Language or ServerRepositoryAPI Docs
Goringsaturn/tzf
Rustringsaturn/tzf-rs
Pythonringsaturn/tzfpytzfpy.pyi
Swiftringsaturn/tzf-swift
RubyHarlemSquirrel/tzf-rb
JS via Wasm (browser)ringsaturn/tzf-wasm
HTTP APIracemap/rust-tz-service
Online Demoringsaturn/tzf-web

Go

tzf v2 is a new major version, so the module path carries the /v2 suffix:

go get github.com/ringsaturn/tzf/v2
package 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:

ConstructorApplies 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-rs
use 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 tzf
require '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@latest
tzf -lng 116.3883 -lat 39.9289

# Batch via stdin
echo -e "116.3883 39.9289\n116.3883, 39.9289" | tzf -stdin-order lng-lat

Rust CLI

cargo install tzf-rs
tzf --lng 116.3883 --lat 39.9289

# Batch via stdin
echo -e "116.3883 39.9289\n116.3883, 39.9289" | tzf --stdin-order lng-lat

NixOS users can install tzf-rs via Nix — see NixOS packages for details.

Last updated on