# `Feel.Value.Zone`
[🔗](https://github.com/FeeJai/BDM/blob/feel-v0.1.0/apps/feel/lib/feel/value/zone.ex#L1)

The time zones of times and of dates and times, and what offset they have (D-54).

A zone is `:floating` (none), `{:offset, seconds}` (a fixed offset from UTC), or `{:iana, id}`
(the id of a zone of the IANA time zone database, as `"Europe/Paris"`); see
`t:Feel.Value.Time.zone/0`. A fixed offset is its own offset. The offset of a zone id depends on
the date, since a zone changes between standard and daylight saving time, and has changed
over the years, so it is looked up in the database, which is the Elixir package `tz`: Elixir has
none of its own, and `tz` compiles one into the code, so nothing is read or fetched when a
value is used.

The functions here take a date and a time of day as a number of days (as
`Date.to_gregorian_days/1` counts them) and a number of seconds in the day, so that any year of
FEEL, from -999,999,999 to 999,999,999, can be asked about. Before the database has the zone's
first rule it gives the zone's local mean time, and after its last it applies the rules of the
zone as they stand.

## A local time that is not one time

The wall clock of a zone skips an hour when it goes to daylight saving time (the **gap**) and
repeats one when it goes back (the **overlap**). The specification does not say which instant a
local date and time in either is (notes 02, A-21). `offset_at_wall/3` reads them as `java.time` does:
a time in the gap is shifted forward by the length of the gap, which is to use the offset from
before it, and a time in the overlap is the earlier of the two instants, which is the offset
from before the clocks went back.

## A zone without a date

The zone id of a time of day (`10:30:00@Europe/Paris`) says nothing of the date, so it has no
offset of its own (notes 02, A-16). `standard_offset/1` gives it the offset the zone has in
standard time, daylight saving time left out, on 2000-01-01.

    iex> Feel.Value.Zone.offset_at_wall("Europe/Paris", Date.to_gregorian_days(~D[2021-01-02]), 36_000)
    {:ok, 3600}
    iex> Feel.Value.Zone.standard_offset("Australia/Melbourne")
    {:ok, 36_000}
    iex> Feel.Value.Zone.exists?("Foo/Bar")
    false

# `t`

```elixir
@type t() :: :floating | {:offset, integer()} | {:iana, String.t()}
```

A zone: none, a fixed offset in seconds, or the id of a zone of the IANA database.

# `exists?`

```elixir
@spec exists?(String.t()) :: boolean()
```

Returns `true` when the id is a zone of the database.

    iex> Feel.Value.Zone.exists?("Europe/Paris")
    true
    iex> Feel.Value.Zone.exists?("Europe/Atlantis")
    false

# `offset_at_utc`

```elixir
@spec offset_at_utc(String.t() | {:offset, integer()}, integer(), non_neg_integer()) ::
  {:ok, integer()} | :error
```

The offset, in seconds, that a zone has at an instant, given as the UTC date and time of day:
`{:ok, seconds}`, or `:error` when the zone is not in the database.

    iex> Feel.Value.Zone.offset_at_utc("Europe/Paris", Date.to_gregorian_days(~D[2021-07-01]), 0)
    {:ok, 7200}
    iex> Feel.Value.Zone.offset_at_utc("Foo/Bar", 0, 0)
    :error

# `offset_at_wall`

```elixir
@spec offset_at_wall(String.t() | {:offset, integer()}, integer(), non_neg_integer()) ::
  {:ok, integer()} | :error
```

The offset, in seconds, that a zone has at a local date and time: `{:ok, seconds}`, or `:error`
when the zone is not in the database.

A fixed offset is its own. A time in a gap has the offset from before it, and a time that
occurs twice has the earlier offset (see above).

    iex> Feel.Value.Zone.offset_at_wall("Europe/Paris", Date.to_gregorian_days(~D[2021-07-01]), 0)
    {:ok, 7200}
    iex> Feel.Value.Zone.offset_at_wall({:offset, -18_000}, 0, 0)
    {:ok, -18_000}
    iex> Feel.Value.Zone.offset_at_wall("Foo/Bar", 0, 0)
    :error

# `standard_offset`

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

The offset of a zone in standard time, on 2000-01-01, without the daylight saving time that
the zone may have then. It is what a time of day with a zone id is read to have (see above).

    iex> Feel.Value.Zone.standard_offset("Europe/Paris")
    {:ok, 3600}
    iex> Feel.Value.Zone.standard_offset("Etc/UTC")
    {:ok, 0}
    iex> Feel.Value.Zone.standard_offset("Foo/Bar")
    :error

---

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