# TzWorld
[](https://hex.pm/packages/tz_world)
[](https://hex.pm/packages/tz_world)
[](https://hex.pm/packages/tz_world)
[](https://github.com/kipcole9/tz_world)
Resolve timezones from a location using data from the
[timezone-boundary-builder](https://github.com/evansiroky/timezone-boundary-builder)
project.
> #### Upgrading from 1.x {: .warning}
>
> The on-disk data format changed in 2.x: `priv/timezones-geodata.tzw1` replaces `priv/timezones-geodata.etf.zip`. After upgrading you must run `mix tz_world.update` once to reinstall the data in the new format. Until you do, every lookup returns `{:error, :time_zone_not_found}`. The old `.etf.zip` and `.dets` files in `priv/` are no longer read and can be deleted to reclaim disk space (≈ 900 MB).
## Installation
Add `tz_world` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:tz_world, "~> 2.0"}
]
end
```
After adding `TzWorld` as a dependency, run `mix deps.get` to install it. Then run `mix tz_world.update` to install the timezone data.
**NOTE** No data is installed with the package and until the data is installed
with `mix tz_world.update` all calls to `TzWorld.timezone_at/1` will return
`{:error, :time_zone_not_found}`.
### Configuration
There is no mandatory configuration required however four options may be configured in `config.exs`:
```elixir
config :tz_world,
# Configure a custom TzWorld backend. It will be used
# as the default backend in calls to `TzWorld.timezone_at/1`
default_backend: MyTzWorldBackend,
# The default is the `priv` directory of `:tz_world`
data_dir: "geodata/directory",
# The default is either the trust store included in the
# libraries `CAStore` or `certifi` or the platform
# trust store.
cacertfile: "path/to/ca_trust_store",
# The default is no options, however one can set any `httpc` client options.
httpc_opts: [
proxy: {{String.to_charlist(proxy_host), proxy_port}, []}
]
```
## Backend selection
`TzWorld` provides alternative strategies for managing access to the backend data. Each backend is implemented as a `GenServer` that needs to be either manually started with `BackendModule.start_link/1` or preferably added to your application's supervision tree.
The recommended backend is `TzWorld.Backend.SpatialIndex`. It is also the default — applications that do not pin `:default_backend` will pick it up automatically.
For example:
```elixir
defmodule MyApp.Application do
@moduledoc false
use Application
def start(_type, _args) do
children = [
...
TzWorld.Backend.SpatialIndex
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
```
The following backends are available:
* `TzWorld.Backend.SpatialIndex` (recommended, default) resolves a point by
querying an R-tree spatial index built once at startup and held in
`:persistent_term`. Lookups read directly from `:persistent_term` and bypass
the GenServer mailbox entirely. This is the fastest backend on every
benchmarked workload — and dramatically faster (≈18×) than any other
backend on no-match queries (e.g. ocean points), where the previous
bounding-box-scan algorithms had to walk every shape.
* `TzWorld.Backend.EtsWithIndexCache` keeps the timezone shapes in a
compressed `:ets` table, with an in-memory cache of every bounding box for
candidate filtering. Useful when you want shapes shared across processes
via `:ets` rather than via `:persistent_term`.
* `TzWorld.Backend.DetsWithIndexCache` keeps the shapes on disk in a `:dets`
file, with the same in-memory bounding-box cache. Useful when memory is
constrained — only the index is kept in memory and shapes are loaded from
disk on demand.
Other backends can be implemented as long as they follow the `TzWorld.Backend`
behaviour. Custom backends should be configured in `config.exs` or `runtime.exs`
under the `:default_backend` key so that they will be considered as the default
for calls to `TzWorld.timezone_at/1`. For example:
```elixir
config :tz_world,
default_backend: MyTzWorldBackend
```
## Installing the Timezones Geo JSON data
Installing `tz_world` from source or from hex does not include the timezones
Geo JSON data. The data is required and to install or update it run:
```elixir
mix tz_world.update
```
This task will download, transform, zip and store the timezones Geo data. Depending on internet and computer speed this may take a few minutes.
By default `mix tz_world.update` will download geojson data that does *not* include time zone information for the oceans. The following optional flags configure its behaviour:
* `--include-oceans` (`-o`) will download the geojson data, including data for the oceans. This gives almost complete global coverage of time zone data. The default is `--no-include-oceans` which does not include data that covers the oceans. The geojson data including the oceans is about 10% larger than the data that does not include the oceans.
* `--force` (`-f`) does two things:
* Forces an update even if the currently installed data is already at the latest release. Useful when switching between including and excluding ocean coverage.
* Creates the directory configured under `:data_dir` if it does not yet exist. Without `--force` a missing `:data_dir` raises `File.Error` so that a misconfigured `:data_dir` is loud rather than silently materialised. Pass `--force` on the first install when you have set a custom `:data_dir` outside the build artifacts.
* `--trace` (`-t`) emits debug-level progress logs (current memory usage, download / extract / parse phases).
### Updating the Timezone data
From time-to-time the timezones Geo JSON data is updated in the [upstream project](https://github.com/evansiroky/timezone-boundary-builder/releases). The mix task `mix tz_world.update` will update the data if it is available.
A running application can also be instructed to reload the data by executing `TzWorld.reload_timezone_data`.
## Usage
The primary API is `TzWorld.timezone_at`. It takes either a `Geo.Point` struct or a `longitude` and `latitude` in degrees. Note the parameter order: `longitude`, `latitude`. It also takes and optional second parameter, `backend`, which must be one of the configured and running backend modules. By default `timezone_at/2` will detect a running backend and will raise an exception if no running backend is found.
```elixir
iex> TzWorld.timezone_at(%Geo.Point{coordinates: {3.2, 45.32}})
{:ok, "Europe/Paris"}
iex> TzWorld.timezone_at({3.2, 45.32})
{:ok, "Europe/Paris"}
iex> TzWorld.timezone_at(%Geo.PointZ{coordinates: {-74.006, 40.7128, 0.0}})
{:ok, "America/New_York"}
# Assumes that the downloaded data does not include
# data for the oceans (the default)
iex> TzWorld.timezone_at(%Geo.Point{coordinates: {1.3, 65.62}})
{:error, :time_zone_not_found}
```
## Performance
Version 2.0 lookups are 1.4×–18× faster than 1.x (R-tree spatial index), and `mix tz_world.update` peak memory is ≈ 13× lower (end-to-end streaming pipeline). See the [Performance guide](https://hexdocs.pm/tz_world/performance.html) for measurements and methodology.