Resolves timezones from a coordinate using an R-tree spatial index
held in :persistent_term.
This is the recommended backend and is also the default — applications
that do not pin :default_backend will pick it up automatically as
long as it is started in the supervision tree.
How it works
At startup the backend reads the compressed timezone shape data that
mix tz_world.update installs and builds two structures:
a Sort-Tile-Recursive packed R-tree over every shape's bounding box, with one entry per sub-polygon of a
Geo.MultiPolygon;each shape compiled by
TzWorld.PackedGeometry: its rings packed into binaries, with their edges indexed by horizontal band.
Both are stored with the data version as a single term under one
:persistent_term key. A lookup finds candidate shapes in the
R-tree, then tests each against only the ring edges in the query
point's band — typically a few dozen, even in rings with nearly
200,000 vertices — so it takes microseconds.
Lookups (TzWorld.timezone_at/1 and TzWorld.all_timezones_at/1)
read the stored term directly. They do not go through the GenServer
mailbox and do not copy any term, which makes them lock-free and
safe to call from any number of processes concurrently.
Reload
reload_timezone_data/0 rebuilds the index and replaces the stored
term in one :persistent_term.put/2. Lookups carry on against the
previous index until then, so a reload never blocks them. Loading
and reloading compile the shapes in parallel, one task per shape, and
take a couple of seconds on a multi-core machine and several on a
single core. The put also triggers a global garbage collection for
every process that references a persistent term — fine when reload
is invoked after a data update, costly on a hot path. Reload should
not be called on every request.
Memory profile
About 150 MB for the data without ocean coverage, the default. Ring
vertices are held as packed binaries at 16 bytes a vertex, and those
binaries live off-heap: :persistent_term.info/0 reports only the
small term that refers to them, so measure :erlang.memory/1
instead.
Public API
In normal use you will not call functions on this module directly —
use TzWorld.timezone_at/1, TzWorld.all_timezones_at/1,
TzWorld.version/0, and TzWorld.reload_timezone_data/0. Add this
module to your supervision tree to make it the running backend:
children = [
TzWorld.Backend.SpatialIndex
]
Summary
Functions
Returns a specification to start this module under a supervisor.
Starts the backend and loads the time zone data.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
Starts the backend and loads the time zone data.
The load is performed synchronously inside init/1, so once
start_link/1 returns the index is in :persistent_term and
lookups are immediately ready to serve. The backend is registered
under its module name.
Arguments
optionsis a keyword list of options.
Options
- There are none.
optionsis accepted so the module can be used as a child specification, and is otherwise ignored.
Returns
{:ok, pid}once the data is loaded. If the data has not been installed the backend still starts and logs a warning, and lookups return{:error, :enoent}until the data is installed withmix tz_world.updateand loaded withTzWorld.reload_timezone_data/0.{:error, {:already_started, pid}}if the backend is already running.{:error, reason}if the installed data could not be decoded.
Examples
Add the backend to an application's supervision tree:
children = [
TzWorld.Backend.SpatialIndex
]
Supervisor.start_link(children, strategy: :one_for_one)