tzf: a fast timezone finder for Go. [](https://pkg.go.dev/github.com/ringsaturn/tzf) [](https://codecov.io/gh/ringsaturn/tzf) [](https://app.fossa.com/projects/git%2Bgithub.com%2Fringsaturn%2Ftzf?ref=badge_shield)

May 3, 2026 · View on GitHub

Quick Start

Install via:

go get github.com/ringsaturn/tzf

Note

This NewDefaultFinder uses simplified shape data so it is not entirely accurate around the border.

It's expensive to init tzf's Finder/FuzzyFinder/DefaultFinder, please consider reuse it or as a global var. Below is a global var example:

package main

import (
	"fmt"

	"github.com/ringsaturn/tzf"
)

var f tzf.F

func init() {
	var err error
	f, err = tzf.NewDefaultFinder()
	if err != nil {
		panic(err)
	}
}

func main() {
	// In longitude-latitude order
	fmt.Println(f.GetTimezoneName(116.3883, 39.9289))
	fmt.Println(f.GetTimezoneName(-73.935242, 40.730610))
}

Best Practice: Setup 100% Accuracy via NewFullFinder

If you require a query result that is 100% accurate, use the following to locate(also, reuse it when possible):

package main

import (
	"fmt"

	"github.com/ringsaturn/tzf"
)

func main() {
	finder, err := tzf.NewFullFinder()
	if err != nil {
		panic(err)
	}

	fmt.Println(finder.GetTimezoneName(139.6917, 35.6895))
}

Please note that NewFullFinder() is more expensive to init and has higher memory usage than NewDefaultFinder(), but it provides 100% accuracy.

See the Performance section for more details.

CLI Tool

In addition to using tzf as a library in your Go projects, you can also use the tzf command-line interface (CLI) tool to quickly get the timezone name for a set of coordinates. To use the CLI tool, you first need to install it using the following command:

go install github.com/ringsaturn/tzf/cmd/tzf@latest

Once installed, you can use the tzf command followed by the latitude and longitude values to get the timezone name:

tzf -lng 116.3883 -lat 39.9289

Alternatively if you want to look up multiple coordinates efficiently you can specify the ordering and pipe them to the tzf command one pair of coordinates per line:

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

Data

You can download the original data from https://github.com/evansiroky/timezone-boundary-builder.

The preprocessed protobuf data can be obtained from https://github.com/ringsaturn/tzf-dist, which has Go's embedded support. These files are Protocol Buffers messages for more efficient binary distribution. You can view the pb/tzinfo.proto file or its HTML format documentation for information about the internal format.

The data pipeline for tzf can be illustrated as follows:

graph TD
    Raw[GeoJSON from evansiroky/timezone-boundary-builder]
    Full[Timezones .bin ~92MB]
    Simplified[Timezones .topology.bin ~13MB<br/>topology-aware simplified]
    SimplifiedTopo[TopoTimezones .topology.topo.bin ~10MB]
    FullTopo[TopoTimezones .topo.bin ~52MB]
    SimplifiedCompressTopo[CompressedTopoTimezones<br/>.topology.compress.topo.bin ~5.4MB]
    FullCompressTopo[CompressedTopoTimezones<br/>.compress.topo.bin ~17MB]
    Preindex[PreindexTimezones<br/>.topology.preindex.bin ~2MB]

    Finder[Finder: Polygon Based Finder]
    FuzzyFinder[FuzzyFinder: Tile based Finder]
    DefaultFinder[DefaultFinder: FuzzyFinder + Finder fallback]

    Raw --> |cmd/geojson2tzpb|Full
    Full --> |cmd/reducetzpb -topology|Simplified
    Full --> |cmd/deduplicatetzpb|FullTopo
    FullTopo --> |cmd/compresstopotzpb|FullCompressTopo
    Simplified --> |cmd/deduplicatetzpb|SimplifiedTopo
    SimplifiedTopo --> |cmd/compresstopotzpb|SimplifiedCompressTopo
    Simplified --> |cmd/preindextzpb|Preindex

    FullCompressTopo --> |tzf.NewFinderFromCompressedTopo|Finder
    SimplifiedCompressTopo --> |tzf.NewFinderFromCompressedTopo|Finder
    Preindex --> |tzf.NewFuzzyFinderFromPB|FuzzyFinder
    SimplifiedCompressTopo --> |tzf.NewDefaultFinder|DefaultFinder
    Preindex --> |tzf.NewDefaultFinder|DefaultFinder
    FullCompressTopo --> |tzf.NewFullFinder|DefaultFinder
    Preindex --> |tzf.NewFullFinder|DefaultFinder

The combined-with-oceans.compress.topo.bin (~17MB) preserves full geometric precision with shared-edge deduplication and polyline compression. Use NewFullFinder() to load it.

The combined-with-oceans.topology.compress.topo.bin (~5.4MB) applies topology-aware Douglas-Peucker simplification (86% point reduction) before deduplication and compression. It is used by the default NewDefaultFinder() and may not be perfectly accurate at some border areas.

The combined-with-oceans.topology.preindex.bin (~2MB) consists of multiple map tiles and is used within both DefaultFinder and FullFinder as the fast-path FuzzyFinder, handling most queries without polygon ray-casting.

I have written an article about the history of tzf, its Rust port, and its Rust port's Python binding; you can view it here.

Performance

The tzf package is intended for high-performance geospatial query backend services, such as weather forecasting APIs. Most queries can be returned within a very short time, averaging around 1000 nanoseconds.

Here is what has been done to improve performance:

  1. Using the simplified dataset by default.
  2. Using pre-indexing to handle most queries takes approximately 500 nanoseconds.
  3. Using the internal geom package(fork of geojson) with a YStripes index (inspired by Josh Baker's tg's ) to verify whether a polygon contains a point. Also a grid-index to quickly find candidate polygons, inspired by Aaron Roney's rtz.

That's all. There are no black magic tricks inside the tzf package.

Below is a benchmark run on my MacBook Pro with Apple M3 Max:

TargetDatasetScenarioMedian (ns)p99 (ns)Approx throughput (ops/s)Memory (MiB)
DefaultFindertopology-simplified + preindexedge case · GetTimezoneName500.01250.01694.9K74.90
FuzzyFinderpreindexedge case · GetTimezoneName250.0375.03521.1K2.40
Findertopology-simplifiededge case · GetTimezoneName250.0875.03022.1K72.70
FullFinderfull-precision + preindexedge case · GetTimezoneName542.01375.01586.3K422.90
Finderfull-precisionedge case · GetTimezoneName292.01167.02678.1K420.70
DefaultFindertopology-simplified + preindexrandom world cities · GetTimezoneName167.0791.03855.1K74.90
FuzzyFinderpreindexrandom world cities · GetTimezoneName167.0333.04608.3K2.40
Findertopology-simplifiedrandom world cities · GetTimezoneName209.01250.03076.0K72.70
FullFinderfull-precision + preindexrandom world cities · GetTimezoneName208.0917.03527.3K422.90
Finderfull-precisionrandom world cities · GetTimezoneName250.01167.02953.3K420.70
Findertopology-simplified + GridIndexrandom world cities · GetTimezoneName209.01167.03202.0K72.70
Findertopology-simplified (no GridIndex)random world cities · GetTimezoneName1833.02875.0612.4K67.00
DefaultFindertopology-simplified + preindexrandom world cities · GetTimezoneNames416.01375.01956.9K74.90
FuzzyFinderpreindexrandom world cities · GetTimezoneNames208.0334.04347.8K2.40
Findertopology-simplifiedrandom world cities · GetTimezoneNames417.01375.01931.2K72.70
FullFinderfull-precision + preindexrandom world cities · GetTimezoneNames459.01750.01623.1K422.90
Language or SeverLinkNote
Goringsaturn/tzf
RubyHarlemSquirrel/tzf-rbbuild with tzf-rs
Rustringsaturn/tzf-rs
Swiftringsaturn/tzf-swift
Pythonringsaturn/tzfpybuild with tzf-rs
HTTP APIracemap/rust-tz-servicebuild with tzf-rs
JS via Wasm(browser only)ringsaturn/tzf-wasmbuild with tzf-rs
Onlineringsaturn/tzf-webbuild with tzf-wasm

See Project tzf for more information.

Thanks

LICENSE

This project is licensed under the MIT license and Anti CSDN License1. The data is licensed under the ODbL license, same as evansiroky/timezone-boundary-builder

FOSSA Status

Footnotes

  1. This license is to prevent the use of this project by CSDN, has no effect on other use cases.