tzf

package module
v1.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 14, 2026 License: MIT Imports: 14 Imported by: 17

README

TZF: a fast timezone finder for Go. Go Reference codecov

TZF is a fast timezone finder package designed for Go. It allows you to quickly find the timezone for a given latitude and longitude, making it ideal for geo queries and services such as weather forecast APIs. With optimized performance and two different data options, TZF is a powerful tool for any Go developer's toolkit.


[!NOTE]

Here are some language or server which built with tzf or it's other language bindings:

Language or Sever Link Note
Go ringsaturn/tzf
Ruby HarlemSquirrel/tzf-rb build with tzf-rs
Rust ringsaturn/tzf-rs
Swift ringsaturn/tzf-swift
Python ringsaturn/tzfpy build with tzf-rs
HTTP API ringsaturn/tzf-server build with tzf
HTTP API racemap/rust-tz-service build with tzf-rs
Redis Server ringsaturn/tzf-server build with tzf
Redis Server ringsaturn/redizone build with tzf-rs
JS via Wasm(browser only) ringsaturn/tzf-wasm build with tzf-rs
Online ringsaturn/tzf-web build with tzf-wasm

Quick Start

To start using TZF in your Go project, you first need to install the package:

go get github.com/ringsaturn/tzf

Then, you can use the following code to locate:

// Use about 150MB memory for init, and 60MB after GC.
package main

import (
	"fmt"

	"github.com/ringsaturn/tzf"
)

func main() {
	finder, err := tzf.NewDefaultFinder()
	if err != nil {
		panic(err)
	}
	fmt.Println(finder.GetTimezoneName(116.6386, 40.0786))  // In longitude-latitude order
}

If you require a query result that is 100% accurate, use the following to locate:

// Use about 900MB memory for init, and 660MB after GC.
package main

import (
	"fmt"

	"github.com/ringsaturn/tzf"
	tzfrel "github.com/ringsaturn/tzf-rel"
	pb "github.com/ringsaturn/tzf/gen/go/tzf/v1"
	"google.golang.org/protobuf/proto"
)

func main() {
	input := &pb.Timezones{}

	// Full data, about 83.5MB
	dataFile := tzfrel.FullData

	if err := proto.Unmarshal(dataFile, input); err != nil {
		panic(err)
	}
	finder, _ := tzf.NewFinderFromPB(input)
	fmt.Println(finder.GetTimezoneName(116.6386, 40.0786))  // In longitude-latitude order
}
Best Practice

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))
}

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-rel, which has Go's embedded support. These files are Protocol Buffers messages for more efficient binary distribution, similar to Python wheels. 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[Full: Probuf based data]
    Lite[Lite: smaller of Full data]
    Compressed[Compressed: Lite compressed via Polyline]
    Preindex[Tile based data]

    Finder[Finder: Polygon Based Finder]
    FuzzyFinder[FuzzyFinder: Tile based Finder]
    DefaultFinder[DefaultFinder: combine FuzzyFinder and Compressed Finder]

    Raw --> |cmd/geojson2tzpb|Full
    Full --> |cmd/reducetzpb|Lite
    Lite --> |cmd/compresstzpb|Compressed
    Lite --> |cmd/preindextzpb|Preindex

    Full --> |tzf.NewFinderFromPB|Finder
    Lite --> |tzf.NewFinderFromPB|Finder
    Compressed --> |tzf.NewFinderFromCompressed|Finder --> |tzf.NewDefaultFinder|DefaultFinder
    Preindex --> |tzf.NewFuzzyFinderFromPB|FuzzyFinder --> |tzf.NewDefaultFinder|DefaultFinder

The complete dataset (~80MB) can be used anywhere, but requires higher memory usage.

The lightweight dataset (~10MB) may not function optimally in some border areas.

You can observe points with different outcomes on this page.

If a slightly longer initialization time is tolerable, the compressed dataset (~5MB) derived from the lightweight dataset will be more suitable for binary distribution.

The pre-indexed dataset (~1.78MB) consists of multiple tiles. It is used within the DefaultFinder, which is built on FuzzyFinder, to reduce execution times of the raycasting algorithm.

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 services, such as weather forecasting APIs. Most queries can be returned within a very short time, averaging around 2000 nanoseconds.

Here is what has been done to improve performance:

  1. Using pre-indexing to handle most queries takes approximately 1000 nanoseconds.
  2. Using an RTree to filter candidate polygons, instead of iterating through all polygons, reduces the execution times of the Ray Casting algorithm.
  3. Using a finely-tuned Ray Casting algorithm package https://github.com/tidwall/geojson to verify whether a polygon contains a point.

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

The benchmark was conducted using version https://github.com/ringsaturn/tzf/releases/tag/v0.16.0

goos: darwin
goarch: arm64
pkg: github.com/ringsaturn/tzf
cpu: Apple M3 Max
BenchmarkDefaultFinder_GetTimezoneName_Random_WorldCities-16    	  823786	      1261 ns/op	      1000 ns/p50	      5000 ns/p90	      8000 ns/p99	       8 B/op	       0 allocs/op
BenchmarkFuzzyFinder_GetTimezoneName_Random_WorldCities-16      	 2239102	       572.1 ns/op	      1000 ns/p50	      1000 ns/p90	      1000 ns/p99	       8 B/op	       0 allocs/op
BenchmarkGetTimezoneName-16                                     	  423015	      2852 ns/op	      3000 ns/p50	      3000 ns/p90	      4000 ns/p99	       8 B/op	       0 allocs/op
BenchmarkGetTimezoneNameAtEdge-16                               	  399050	      3036 ns/op	      3000 ns/p50	      3000 ns/p90	      4000 ns/p99	       8 B/op	       0 allocs/op
BenchmarkGetTimezoneName_Random_WorldCities-16                  	  288864	      3867 ns/op	      4000 ns/p50	      6000 ns/p90	      8000 ns/p99	       8 B/op	       0 allocs/op
PASS
coverage: 65.5% of statements
ok  	github.com/ringsaturn/tzf	7.995s

Thanks

LICENSE

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

[^anti_csdn]: This license is to prevent the use of this project by CSDN, has no effect on other use cases.

Documentation

Overview

Package tzf is a package convert (lng,lat) to timezone.

Inspired by timezonefinder https://github.com/jannikmi/timezonefinder, fast python package for finding the timezone of any point on earth (coordinates) offline.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNoTimezoneFound = errors.New("tzf: no timezone found")

Functions

func SetDropPBTZ added in v0.9.2

func SetDropPBTZ(opt *Option)

SetDropPBTZ will make Finder not save github.com/ringsaturn/tzf/pb.Timezone in memory

Types

type DefaultFinder added in v0.9.0

type DefaultFinder struct {
	// contains filtered or unexported fields
}

DefaultFinder is a finder impl combine both FuzzyFinder and Finder.

It's designed for performance first and allow some not so correct return at some area.

func (*DefaultFinder) DataVersion added in v0.13.0

func (f *DefaultFinder) DataVersion() string

func (*DefaultFinder) GetTimezoneName added in v0.9.0

func (f *DefaultFinder) GetTimezoneName(lng float64, lat float64) string
Example
package main

import (
	"fmt"

	"github.com/ringsaturn/tzf"
)

func main() {
	finder, err := tzf.NewDefaultFinder()
	if err != nil {
		panic(err)
	}
	fmt.Println(finder.GetTimezoneName(116.6386, 40.0786)) // In longitude-latitude order
}
Output:

Asia/Shanghai

func (*DefaultFinder) GetTimezoneNames added in v0.10.0

func (f *DefaultFinder) GetTimezoneNames(lng float64, lat float64) ([]string, error)
Example
package main

import (
	"fmt"

	"github.com/ringsaturn/tzf"
)

func main() {
	finder, err := tzf.NewDefaultFinder()
	if err != nil {
		panic(err)
	}
	fmt.Println(finder.GetTimezoneNames(87.6168, 43.8254)) // In longitude-latitude order
}
Output:

[Asia/Shanghai Asia/Urumqi] <nil>

func (*DefaultFinder) TimezoneNames added in v0.9.0

func (f *DefaultFinder) TimezoneNames() []string
Example
package main

import (
	"fmt"

	"github.com/ringsaturn/tzf"
)

func main() {
	finder, err := tzf.NewDefaultFinder()
	if err != nil {
		panic(err)
	}
	fmt.Println(finder.TimezoneNames())
}

type F added in v0.12.0

type F interface {
	GetTimezoneName(lng float64, lat float64) string
	GetTimezoneNames(lng float64, lat float64) ([]string, error)
	TimezoneNames() []string
	DataVersion() string
}

func NewDefaultFinder added in v0.9.0

func NewDefaultFinder() (F, error)

func NewFinderFromCompressed added in v0.8.0

func NewFinderFromCompressed(input *pb.CompressedTimezones, opts ...OptionFunc) (F, error)

func NewFinderFromPB

func NewFinderFromPB(input *pb.Timezones, opts ...OptionFunc) (F, error)

func NewFinderFromRawJSON added in v0.2.0

func NewFinderFromRawJSON(input *convert.BoundaryFile, opts ...OptionFunc) (F, error)

func NewFuzzyFinderFromPB added in v0.9.0

func NewFuzzyFinderFromPB(input *pb.PreindexTimezones) (F, error)

type Finder

type Finder struct {
	// contains filtered or unexported fields
}

Finder is based on point-in-polygon search algo.

Memory will use about 100MB if lite data and 1G if full data. Performance is very stable and very accuate.

func (*Finder) DataVersion added in v0.13.0

func (f *Finder) DataVersion() string

func (*Finder) GetTimezoneName

func (f *Finder) GetTimezoneName(lng float64, lat float64) string

GetTimezoneName will use alphabet order and return first matched result.

Example
package main

import (
	"fmt"

	"github.com/ringsaturn/tzf"

	tzfrellite "github.com/ringsaturn/tzf-rel-lite"

	pb "github.com/ringsaturn/tzf/gen/go/tzf/v1"
	"google.golang.org/protobuf/proto"
)

func main() {
	input := &pb.Timezones{}
	if err := proto.Unmarshal(tzfrellite.LiteData, input); err != nil {
		panic(err)
	}
	finder, _ := tzf.NewFinderFromPB(input)
	fmt.Println(finder.GetTimezoneName(116.6386, 40.0786))
}
Output:

Asia/Shanghai

func (*Finder) GetTimezoneNames added in v0.10.0

func (f *Finder) GetTimezoneNames(lng float64, lat float64) ([]string, error)
Example
package main

import (
	"fmt"

	"github.com/ringsaturn/tzf"

	tzfrellite "github.com/ringsaturn/tzf-rel-lite"

	pb "github.com/ringsaturn/tzf/gen/go/tzf/v1"
	"google.golang.org/protobuf/proto"
)

func main() {
	input := &pb.Timezones{}
	if err := proto.Unmarshal(tzfrellite.LiteData, input); err != nil {
		panic(err)
	}
	finder, _ := tzf.NewFinderFromPB(input)
	fmt.Println(finder.GetTimezoneNames(87.6168, 43.8254))
}
Output:

[Asia/Shanghai Asia/Urumqi] <nil>

func (*Finder) TimezoneNames added in v0.6.2

func (f *Finder) TimezoneNames() []string
Example
package main

import (
	"fmt"

	"github.com/ringsaturn/tzf"

	tzfrellite "github.com/ringsaturn/tzf-rel-lite"

	pb "github.com/ringsaturn/tzf/gen/go/tzf/v1"
	"google.golang.org/protobuf/proto"
)

func main() {
	input := &pb.Timezones{}
	if err := proto.Unmarshal(tzfrellite.LiteData, input); err != nil {
		panic(err)
	}
	finder, _ := tzf.NewFinderFromPB(input)
	fmt.Println(finder.TimezoneNames())
}

type FuzzyFinder added in v0.9.0

type FuzzyFinder struct {
	// contains filtered or unexported fields
}

FuzzyFinder use a tile map to store timezone name. Data are made by github.com/ringsaturn/tzf/cmd/preindextzpb which powerd by github.com/ringsaturn/tzf/preindex.PreIndexTimezones.

func (*FuzzyFinder) DataVersion added in v0.13.0

func (f *FuzzyFinder) DataVersion() string

func (*FuzzyFinder) GetTimezoneName added in v0.9.0

func (f *FuzzyFinder) GetTimezoneName(lng float64, lat float64) string
Example
package main

import (
	"fmt"

	"github.com/ringsaturn/tzf"

	tzfrellite "github.com/ringsaturn/tzf-rel-lite"

	pb "github.com/ringsaturn/tzf/gen/go/tzf/v1"
	"google.golang.org/protobuf/proto"
)

func main() {
	input := &pb.PreindexTimezones{}
	if err := proto.Unmarshal(tzfrellite.PreindexData, input); err != nil {
		panic(err)
	}
	finder, _ := tzf.NewFuzzyFinderFromPB(input)
	fmt.Println(finder.GetTimezoneName(116.6386, 40.0786)) // In longitude-latitude order
}
Output:

Asia/Shanghai

func (*FuzzyFinder) GetTimezoneNames added in v0.10.0

func (f *FuzzyFinder) GetTimezoneNames(lng float64, lat float64) ([]string, error)
Example
package main

import (
	"fmt"

	"github.com/ringsaturn/tzf"

	tzfrellite "github.com/ringsaturn/tzf-rel-lite"

	pb "github.com/ringsaturn/tzf/gen/go/tzf/v1"
	"google.golang.org/protobuf/proto"
)

func main() {
	input := &pb.PreindexTimezones{}
	if err := proto.Unmarshal(tzfrellite.PreindexData, input); err != nil {
		panic(err)
	}
	finder, _ := tzf.NewFuzzyFinderFromPB(input)
	fmt.Println(finder.GetTimezoneNames(87.6168, 43.8254))
}
Output:

[Asia/Shanghai Asia/Urumqi] <nil>

func (*FuzzyFinder) TimezoneNames added in v0.11.2

func (f *FuzzyFinder) TimezoneNames() []string
Example
package main

import (
	"fmt"

	"github.com/ringsaturn/tzf"

	tzfrellite "github.com/ringsaturn/tzf-rel-lite"

	pb "github.com/ringsaturn/tzf/gen/go/tzf/v1"
	"google.golang.org/protobuf/proto"
)

func main() {
	input := &pb.PreindexTimezones{}
	if err := proto.Unmarshal(tzfrellite.PreindexData, input); err != nil {
		panic(err)
	}
	finder, _ := tzf.NewFuzzyFinderFromPB(input)
	fmt.Println(finder.TimezoneNames())
}

type Option added in v0.9.2

type Option struct {
	DropPBTZ bool
}

type OptionFunc added in v0.9.2

type OptionFunc = func(opt *Option)

Directories

Path Synopsis
cmd
compresstzpb command
CLI tool to reduce polygon filesize
CLI tool to reduce polygon filesize
geojson2tzpb command
CLI tool to convert GeoJSON based Timezone boundary to tzf's Probuf format.
CLI tool to convert GeoJSON based Timezone boundary to tzf's Probuf format.
preindextzpb command
CLI tool to preindex timezone shape.
CLI tool to preindex timezone shape.
reducetzpb command
CLI tool to reduce polygon filesize
CLI tool to reduce polygon filesize
tzf command
tzf-cli tool for local query.
tzf-cli tool for local query.
gen
internal
maps
Package maps defines various functions useful with maps of any type.
Package maps defines various functions useful with maps of any type.
Package preindex
Package preindex
Package reduce could reduce Polygon size both polygon lines and float precise.
Package reduce could reduce Polygon size both polygon lines and float precise.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL