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

The FEEL type `time` (DMN 1.6, section 10.3.2.3.4): a time of day.

Neither Elixir's `Time` nor Erlang's calendar types can represent a FEEL time:
seconds need to be a decimal, and the zone has to say whether the time is
floating (no zone), has a fixed offset from UTC, or names an IANA time zone.
A floating time is not interchangeable with a zoned one, so the distinction is
part of the value.

## Fields

  * `:hour` - 0 to 23.
  * `:minute` - 0 to 59.
  * `:second` - a `t:Feel.Value.Number.t/0` from 0 up to, but not including, 60.
  * `:zone` - see `t:zone/0`.

# `t`

```elixir
@type t() :: %Feel.Value.Time{
  hour: 0..23,
  minute: 0..59,
  second: Feel.Value.Number.t(),
  zone: zone()
}
```

# `zone`

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

The time zone of a time.

  * `:floating` - no zone: a local time of day at some unspecified place.
  * `{:offset, seconds}` - a fixed offset from UTC, between -14 and +14 hours.
    An offset of 0 is a UTC form.
  * `{:iana, id}` - an IANA time zone, such as `"Europe/Paris"`.

# `new`

```elixir
@spec new(integer(), integer(), Feel.Value.Number.t() | integer(), zone()) ::
  {:ok, t()} | :error
```

Builds a time, checking the ranges of its parts.

Returns `:error` when a part is out of range or the zone is not valid. An IANA
zone has to be one of the time zone database (`Feel.Value.Zone`, D-54).

    iex> {:ok, time} = Feel.Value.Time.new(12, 30, 15, {:offset, 0})
    iex> {time.hour, time.minute, Decimal.to_string(time.second), time.zone}
    {12, 30, "15", {:offset, 0}}
    iex> Feel.Value.Time.new(24, 0, 0)
    :error
    iex> Feel.Value.Time.new(12, 0, 0, {:iana, "Europe/Atlantis"})
    :error

# `parse`

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

Parses a time string, as `time("...")` and `@"..."` read it: `hh:mm:ss`, with an
optional fraction of a second and an optional zone (DMN 1.6, section 10.3.4.1; the
forms are those of notes 06, AE-1).

The zone is `Z` (or `z`) for UTC, an offset such as `+01:00` or `-05:30`, or an IANA
zone id after an `@`, as in `10:30:00@Europe/Paris`. Without a zone the time is
floating. White space around the string is ignored. Returns `:error` for anything else,
including an offset beyond fourteen hours, a zone id that the database does not have, and a
time without seconds.

`24:00:00` is the end of the day, which XML Schema reads as midnight, and so does the TCK: it is
`00:00:00`, with the zone it has. Nothing else of hour 24 is a time. `parse_with_carry/1` says
that the day has ended, which a date and time needs.

    iex> {:ok, time} = Feel.Value.Time.parse("23:59:00")
    iex> {time.hour, time.minute, Decimal.to_string(time.second), time.zone}
    {23, 59, "0", :floating}
    iex> {:ok, time} = Feel.Value.Time.parse("11:00:30.5Z")
    iex> {Decimal.to_string(time.second), time.zone}
    {"30.5", {:offset, 0}}
    iex> {:ok, time} = Feel.Value.Time.parse("10:00:00-05:30")
    iex> time.zone
    {:offset, -19_800}
    iex> {:ok, time} = Feel.Value.Time.parse("10:30:00@Europe/Paris")
    iex> time.zone
    {:iana, "Europe/Paris"}
    iex> {:ok, time} = Feel.Value.Time.parse("24:00:00Z")
    iex> {time.hour, time.minute, time.zone}
    {0, 0, {:offset, 0}}
    iex> Feel.Value.Time.parse("24:00:01")
    :error
    iex> Feel.Value.Time.parse("10:00:00@Europe/Atlantis")
    :error
    iex> Feel.Value.Time.parse("10:00")
    :error

# `parse_with_carry`

```elixir
@spec parse_with_carry(String.t()) :: {:ok, t(), 0 | 1} | :error
```

Parses a time string as `parse/1` does, and says whether it was the end of the day: the carry is 1
for `24:00:00`, which is `00:00:00` of the next day, and 0 for any other time.

    iex> {:ok, time, carry} = Feel.Value.Time.parse_with_carry("24:00:00")
    iex> {time.hour, carry}
    {0, 1}
    iex> {:ok, _time, carry} = Feel.Value.Time.parse_with_carry("23:59:59")
    iex> carry
    0

# `parse_zone`

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

The zone that a text names: `Z` (or `z`) for UTC, an offset such as `+01:00` or `-05:30`, or the id of a zone of the
database. `:error` for anything else, and for no zone: `:floating` is not something a text can name.

    iex> Feel.Value.Time.parse_zone("Z")
    {:ok, {:offset, 0}}
    iex> Feel.Value.Time.parse_zone("-05:30")
    {:ok, {:offset, -19_800}}
    iex> Feel.Value.Time.parse_zone("Europe/Paris")
    {:ok, {:iana, "Europe/Paris"}}
    iex> Feel.Value.Time.parse_zone("Europe/Atlantis")
    :error

# `type`

```elixir
@spec type() :: Feel.Type.t()
```

Returns the type descriptor of `time`.

# `value?`

```elixir
@spec value?(term()) :: boolean()
```

Returns `true` for a `t:t/0` whose parts are all in range.

    iex> {:ok, time} = Feel.Value.Time.new(1, 2, 3)
    iex> Feel.Value.Time.value?(time)
    true
    iex> Feel.Value.Time.value?(%Feel.Value.Time{hour: 25, minute: 0, second: Decimal.new(0)})
    false

---

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