# `TzWorld`
[🔗](https://github.com/kipcole9/tz_world/blob/v2.5.0/lib/tz_world.ex#L1)

Resolves the time zone at a location from its coordinates.

Time zone boundaries come from the
[timezone-boundary-builder](https://github.com/evansiroky/timezone-boundary-builder)
project. They are not included in the package: `mix tz_world.update`
downloads and installs them, and lookups return `{:error, :enoent}`
until it has run.

Lookups are answered by a backend running in the application's
supervision tree, normally `TzWorld.Backend.SpatialIndex`.

### Public API

* `timezone_at/2` returns the time zone at a point.

* `all_timezones_at/2` returns every time zone at a point, for the few
  places where zones overlap.

* `version/0` returns the version of the installed data.

* `reload_timezone_data/0` reloads the running backends after the data
  is updated.

# `backend`

```elixir
@type backend() :: module()
```

A backend module implementing `TzWorld.Backend`.

# `backend_result`

```elixir
@type backend_result() :: {:ok, term()} | {:error, term()}
```

# `point`

```elixir
@type point() :: Geo.Point.t() | Geo.PointZ.t() | {lng :: number(), lat :: number()}
```

A location as a `Geo.Point`, a `Geo.PointZ` or a `{longitude, latitude}`
tuple, in degrees.

# `all_timezones_at`

```elixir
@spec all_timezones_at(point(), backend()) :: {:ok, [String.t()]} | {:error, atom()}
```

Returns every time zone at a point.

Most points are in exactly one time zone. A few, in disputed
territories, are in more than one, and points outside every time zone
are in none.

### Arguments

* `point` is a `Geo.Point`, a `Geo.PointZ` or a `{longitude, latitude}`
  tuple, in degrees. Longitude always comes first.

* `backend` is the backend module to query. The default is the running
  backend, preferring the configured `:default_backend`. A `RuntimeError`
  is raised if no backend is running.

### Returns

* `{:ok, time_zones}` where `time_zones` is a list of time zone names in
  the order they were found, empty if no time zone contains the point.

* `{:error, :invalid_point}` if `point` is not one of the forms above, or
  its longitude is outside -180..180 or its latitude outside -90..90.

* `{:error, :enoent}` if the time zone data has not been installed. Run
  `mix tz_world.update` to install it.

### Examples

    iex> TzWorld.all_timezones_at({3.2, 45.32})
    {:ok, ["Europe/Paris"]}

    iex> TzWorld.all_timezones_at({87.6168, 43.8256})
    {:ok, ["Asia/Shanghai", "Asia/Urumqi"]}

    iex> TzWorld.all_timezones_at({0.0, 0.0})
    {:ok, []}

# `app_name`

```elixir
@spec app_name() :: :tz_world
```

Returns the OTP application name of `tz_world`.

### Returns

* `:tz_world`.

### Examples

    iex> TzWorld.app_name()
    :tz_world

# `reload_timezone_data`

```elixir
@spec reload_timezone_data() ::
  {:ok, [{module(), backend_result()}]} | {:error, [{module(), term()}]}
```

Reload the timezone geometry data from the on-disk files.

Iterates the list of known backends and asks each one that is
*currently running in this node* to reload itself from disk.
Backends that are not running are skipped — the function is safe
to call regardless of which backends the host application has
added to its supervision tree.

### Returns

* `{:ok, results}` when every running backend reloaded
  successfully. `results` is a list of `{backend, result}` pairs
  in reload order.

* `{:error, failures}` when one or more running backends failed
  to reload. `failures` is a list of `{backend, reason}` pairs
  for the backends that did not return `{:ok, _}`.

### Notes

* Reload is performed sequentially in a fixed order
  (`DetsWithIndexCache`, `EtsWithIndexCache`, `SpatialIndex`)
  because `EtsWithIndexCache` reads from the DETS file rebuilt
  by `DetsWithIndexCache`.

* Each backend's reload runs inside its own GenServer call, so
  concurrent reload requests against a single backend are
  serialized at its mailbox.

* Lookups during reload are safe: `SpatialIndex` swaps its
  persistent-term entry atomically, and the other backends
  process lookup messages only after the reload returns.

### Telemetry

The reload emits the following events:

* `[:tz_world, :reload, :start | :stop | :exception]` — wraps the
  full call. `:stop` measurements include `:duration`. `:stop`
  metadata includes `:result` (the return value), `:backends`
  (list of running backends that participated), and
  `:failure_count`.

* `[:tz_world, :reload, :backend, :start | :stop | :exception]` —
  wraps each per-backend reload. Metadata includes `:backend`
  (the module). `:stop` metadata also includes `:result`.

### Examples

In an application running only the default backend:

    iex> TzWorld.reload_timezone_data()
    {:ok, [{TzWorld.Backend.SpatialIndex, {:ok, :loaded}}]}

# `timezone_at`

```elixir
@spec timezone_at(point(), backend()) :: {:ok, String.t()} | {:error, atom()}
```

Returns the time zone at a point.

Where time zones overlap, which happens in a few disputed territories,
the first one found is returned. `all_timezones_at/2` returns them all.

### Arguments

* `point` is a `Geo.Point`, a `Geo.PointZ` or a `{longitude, latitude}`
  tuple, in degrees. Longitude always comes first.

* `backend` is the backend module to query. The default is the running
  backend, preferring the configured `:default_backend`. A `RuntimeError`
  is raised if no backend is running.

### Returns

* `{:ok, time_zone}` where `time_zone` is the name of the time zone.

* `{:error, :time_zone_not_found}` if no time zone contains the point.

* `{:error, :invalid_point}` if `point` is not one of the forms above, or
  its longitude is outside -180..180 or its latitude outside -90..90.

* `{:error, :enoent}` if the time zone data has not been installed. Run
  `mix tz_world.update` to install it.

### Examples

    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({0.0, 0.0})
    {:error, :time_zone_not_found}

    iex> TzWorld.timezone_at({200.0, 45.32})
    {:error, :invalid_point}

# `version`

```elixir
@spec version() :: {:ok, String.t()} | {:error, :enoent}
```

Returns the version of the installed time zone data.

The version is read from the running backend.

### Returns

* `{:ok, version}` where `version` is the name of the upstream data
  release, such as `"2026d"`.

* `{:error, :enoent}` if the time zone data has not been installed.

### Examples

    iex> {:ok, version} = TzWorld.version()
    iex> is_binary(version)
    true

---

*Consult [api-reference.md](api-reference.md) for complete listing*
