Feel.Value.Zone (feel v0.1.0)

Copy Markdown View Source

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

Summary

Types

t()

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

Functions

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

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.

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.

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

Types

t()

@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.

Functions

exists?(id)

@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(id, days, second)

@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(id, days, second)

@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(id)

@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