# `Feel.Type.Moment`
[🔗](https://github.com/FeeJai/BDM/blob/feel-v0.1.0/apps/feel/lib/feel/type/moment.ex#L1)

The numbers that stand for a time and for a date and time in a set (D-109).

A date and a duration are ordered as a number is, and the number of a value is its *image* (`Feel.Type.Image`). A
time and a date and time are not: the order of two of them depends on their zones
(`Feel.Runtime.Temporal.compare/3`). When either has **no zone** their local clocks are compared, and when both have
**a zone** (an offset or a zone id) their instants are, at the second (D-54: `10:30:00.0001` and `10:30:00.0002` are
equal). A value with no zone has one number to compare, its clock; one with a zone has two, its instant and its clock,
and which one a comparison needs depends on the other side.

So the image of a value is the whole second it falls in, and a set has **three lines** of them, far apart on the
number line (each within 3.2E+16 of its centre, the centres 1E+17 apart):

| Line | Of | Its numbers | Written |
| ---- | -- | ----------- | ------- |
| the clock | the values with no zone | the seconds of the local clock since `0000-01-01T00:00:00`; for a time the seconds of the day, 0 to 86,399 | `2021-01-31T10:30:00` |
| the instants | the values with a zone | the seconds of the instant since `0000-01-01T00:00:00Z`; for a time the seconds of the day less the offset, as the run orders times (not wrapped: `00:30:00+01:00` is -1,800) | `2021-01-31T09:30:00Z` |
| the zoned clock | the values with a zone | the seconds of the local clock, as the first | `zoned clock 2021-01-31T10:30:00` |

A fourth line holds the **fraction of the second** (D-118): the images are whole seconds, and a value has a fraction of
one past it, `.5` for `10:00:00.5`. It is the same for the values of every zone, so the set has the one line of them:
the seconds `[0..1)` that a value can have, which are all of them when nothing is known (a range of allowed values has
any), `{0}` for a value of whole seconds, and so on. A comparison does not look at it (the run compares at the
second); the sum with a fraction, the difference, the `second` and the `value` do (`Feel.Analysis.MomentArithmetic`,
`Feel.Analysis.MomentZones`, `Feel.Analysis.MomentProperties`). It is on the number line at 3E+17, and a set that has
none has every fraction.

A set holds the values of the first line, and those of a zone that have their instant in the second **and** their
clock in the third: a set of three lines is a box, and a value of a zone is in it when both its numbers are. The
clock of an instant is the instant and an offset, and an offset is within **18 hours** (a fixed offset is within 14,
and the zones of the time zone database had a local mean time of up to 15 hours 56 minutes, `Asia/Manila` before
1844), so each of the two lines is within that of the other, and `normalize/2` cuts each to that. A set with a line
of the instants and none of the zoned clock has no value with a zone, and neither is kept.

    iex> {:ok, date_time} = Feel.Value.DateTime.parse("2021-01-01T00:00:00")
    iex> {:ok, [image]} = Feel.Type.Moment.images(date_time)
    iex> Feel.Type.Moment.format_value(:date_and_time, image)
    "2021-01-01T00:00:00"
    iex> {:ok, date_time} = Feel.Value.DateTime.parse("2021-01-01T00:00:00+01:00")
    iex> {:ok, [instant, clock]} = Feel.Type.Moment.images(date_time)
    iex> {Feel.Type.Moment.format_value(:date_and_time, instant), Feel.Type.Moment.format_value(:date_and_time, clock)}
    {"2020-12-31T23:00:00Z", "2021-01-01T00:00:00"}

# `kind`

```elixir
@type kind() :: :time | :date_and_time
```

A kind whose sets are sets of moments.

# `lines`

```elixir
@type lines() ::
  {Feel.Type.NumberSet.t(), Feel.Type.NumberSet.t(), Feel.Type.NumberSet.t()}
```

The three lines of a set, each as the seconds it holds (`split/1`).

# `clock_range`

```elixir
@spec clock_range(kind()) :: {integer(), integer()}
```

The first and the last second of the clock of a kind: of the calendar for a date and time, of a day for a time.

    iex> Feel.Type.Moment.clock_range(:time)
    {0, 86399}

# `complement`

```elixir
@spec complement(Feel.Type.NumberSet.t(), kind()) :: Feel.Type.NumberSet.t()
```

The values of a kind that a set leaves out (D-152), as a set: every one of them, and no other where a set of lines can say
it.

A value with no zone is left out when the set has not its clock. A value with a zone is in the set when its instant is on
the second line **and** its clock on the third, so it is left out when its instant is not, or its clock: two boxes, and a
set is one. The set that holds both has the instants that the set leaves out and those within the largest offset of the
clocks that it leaves out, and the clocks likewise. It holds a value that the set holds as well only if an instant and a
clock that it leaves out are both there and near each other, so the answer is **exact** when the set holds a line whole,
when it holds no value with a zone, and when its instants are what its clocks give within the largest offset (which is how a
bound on a time of any zone is made: `time >= 10:00:00`). Otherwise it holds more.

The fractions of a second are one line for the values of every zone, and a set of some of them leaves values out on every
other line, which is no box. For that, and for a set that holds no value, the answer is every value of the kind.

    iex> alias Feel.Type.{Moment, NumberSet, Union}
    iex> morning = Moment.join({NumberSet.interval(Decimal.new(0), Decimal.new(36_000)), [], []})
    iex> Union.format([{:time, Moment.complement(morning, :time)}])
    "time in (10:00:00..23:59:59], instants [-18:00:00Z..41:59:59Z]"
    iex> Moment.complement(Moment.universe(:time), :time)
    []
    iex> {:ok, time} = Feel.Value.Time.parse("10:00:00.5")
    iex> {:ok, point} = Moment.point(time)
    iex> Moment.complement(point, :time) == Moment.universe(:time)
    true

# `contains?`

```elixir
@spec contains?(
  Feel.Type.NumberSet.t(),
  Feel.Value.Time.t() | Feel.Value.DateTime.t()
) :: boolean()
```

Whether a value is one of a set: its seconds are on the lines, and its fraction is one of the fractions.

# `dilate`

```elixir
@spec dilate(Feel.Type.NumberSet.t(), non_neg_integer()) :: Feel.Type.NumberSet.t()
```

A set with every number within some seconds of one of its own, ends included: what the clock of a value can be when
its instant is in the set and its offset is within that many seconds, and the other way round.

    iex> set = Feel.Type.NumberSet.point(Decimal.new(100))
    iex> Feel.Type.NumberSet.format(Feel.Type.Moment.dilate(set, 10))
    "[90..110]"

# `floating`

```elixir
@spec floating(kind()) :: Feel.Type.NumberSet.t()
```

The images of the values of a kind with no zone: the first line.

# `format_member`

```elixir
@spec format_member(kind(), Feel.Type.NumberSet.t()) :: String.t()
```

Writes the set of a kind of moments after the name of the kind: `= 2021-01-31T10:30:00+01:00` for the one value of
a zone, which is written with its offset, and `in` and the lines of it otherwise: those with no zone as they are, the
instants of the values with a zone with a `Z`, and their clocks as `zoned clock` (left out when the instants say as
much, and when the clocks are those of the values with no zone, `any zone` takes their place).

    iex> {:ok, date_time} = Feel.Value.DateTime.parse("2021-01-31T10:30:00+01:00")
    iex> Feel.Type.Union.format(Feel.Type.Union.of_value(date_time))
    "date and time = 2021-01-31T10:30:00+01:00"

# `format_value`

```elixir
@spec format_value(kind(), Decimal.t()) :: String.t()
```

Writes the value that an image stands for: a clock as `2021-01-31T10:30:00`, an instant in UTC as
`2021-01-31T09:30:00Z`; a time as `10:30:00` and `09:30:00Z`. The clock of a zoned value is written as a clock.
A time on the line of the instants is not wrapped, so one before midnight UTC is written with a minus
(`-00:30:00Z`) and one after it past 24 hours (`24:30:00Z`). A number that is none of these is written as it is.

    iex> Feel.Type.Moment.format_value(:time, Decimal.new(37_800))
    "10:30:00"
    iex> Feel.Type.Moment.format_value(:time, Decimal.new(100_000_000_000_000_000 - 1800))
    "-00:30:00Z"

# `fraction`

```elixir
@spec fraction(Feel.Value.Time.t() | Feel.Value.DateTime.t()) :: Decimal.t()
```

The fraction of a second of a value, exactly: `0.5` for `10:00:00.5`.

    iex> {:ok, time} = Feel.Value.Time.parse("10:00:00.5")
    iex> Feel.Type.Moment.fraction(time)
    Decimal.new("0.5")

# `fraction_universe`

```elixir
@spec fraction_universe() :: Feel.Type.NumberSet.t()
```

The fractions of a second that any value can have: `[0..1)`.

# `fractions`

```elixir
@spec fractions(Feel.Type.NumberSet.t()) :: Feel.Type.NumberSet.t()
```

The fractions of a second that the values of a set can have, as numbers in `[0..1)`. A set that has no line of them
has every fraction.

    iex> alias Feel.Type.{Moment, NumberSet}
    iex> {:ok, time} = Feel.Value.Time.parse("10:00:00.5")
    iex> {:ok, set} = Moment.point(time)
    iex> NumberSet.format(Moment.fractions(set))
    "0.5"
    iex> NumberSet.format(Moment.fractions(NumberSet.point(Decimal.new(36_000))))
    "[0..1)"

# `images`

```elixir
@spec images(Feel.Value.Time.t() | Feel.Value.DateTime.t()) ::
  {:ok, [Decimal.t(), ...]} | :error
```

The images of a time or a date and time: `{:ok, [clock]}` for one with no zone, and `{:ok, [instant, zoned_clock]}`
for one with a zone. It is `:error` for a value that is not one of FEEL (a zone that is not in the database, a year
out of range).

    iex> {:ok, time} = Feel.Value.Time.parse("10:30:00.5")
    iex> Feel.Type.Moment.images(time)
    {:ok, [Decimal.new(37_800)]}
    iex> {:ok, time} = Feel.Value.Time.parse("00:30:00+01:00")
    iex> {:ok, [instant, clock]} = Feel.Type.Moment.images(time)
    iex> {Decimal.sub(instant, Decimal.new(100_000_000_000_000_000)), Decimal.sub(clock, Decimal.new(200_000_000_000_000_000))}
    {Decimal.new(-1800), Decimal.new(1800)}

# `join`

```elixir
@spec join(lines()) :: Feel.Type.NumberSet.t()
```

The set of three lines, as `split/1` gives them: the inverse.

# `kinds`

```elixir
@spec kinds() :: [kind(), ...]
```

The kinds whose sets are sets of moments: `:time` and `:date_and_time`.

# `max_offset`

```elixir
@spec max_offset() :: pos_integer()
```

The largest offset of a zone from UTC, in seconds (18 hours).

# `normalize`

```elixir
@spec normalize(Feel.Type.NumberSet.t(), kind()) :: Feel.Type.NumberSet.t()
```

The set cut to the values of the kind there are, with its lines of a zone made to agree: the instants within the
largest offset of the zoned clocks and the zoned clocks within it of the instants, and neither if the other is none.

    iex> alias Feel.Type.{Moment, NumberSet}
    iex> {clock, instants, _} = Moment.universe_lines(:time)
    iex> set = Moment.join({[], instants, NumberSet.interval(Decimal.new(0), Decimal.new(3600))})
    iex> {_, instants, zoned_clock} = set |> Moment.normalize(:time) |> Moment.split()
    iex> {NumberSet.format(instants), NumberSet.format(zoned_clock)}
    {"[-64800..68400]", "[0..3600]"}
    iex> set |> Moment.join_without_clocks() |> Moment.normalize(:time) |> Moment.split()
    {[], [], []}
    iex> NumberSet.format(clock)
    "[0..86399]"

# `overlap`

```elixir
@spec overlap(integer()) :: pos_integer()
```

The most that the instant of a date and time, as the run reads it, can be below the instant it was moved to, for
instants from the one given: 2 hours from 2023-03-09, and 25 hours before it.

    iex> Feel.Type.Moment.overlap(63_845_596_800)
    7200
    iex> Feel.Type.Moment.overlap(0)
    90000

# `point`

```elixir
@spec point(Feel.Value.Time.t() | Feel.Value.DateTime.t()) ::
  {:ok, Feel.Type.NumberSet.t()} | :error
```

The set of the one value of a time or a date and time: its lines and its fraction. It is `:error` for a value that is
not one of FEEL.

    iex> {:ok, time} = Feel.Value.Time.parse("10:00:00.5")
    iex> {:ok, set} = Feel.Type.Moment.point(time)
    iex> Feel.Type.Union.format([{:time, set}])
    "time = 10:00:00.5"

# `shift`

```elixir
@spec shift(Feel.Type.NumberSet.t(), integer()) :: Feel.Type.NumberSet.t()
```

A set moved by a number of seconds: every end of every interval.

# `shift_exact`

```elixir
@spec shift_exact(Feel.Type.NumberSet.t(), integer()) :: Feel.Type.NumberSet.t()
```

A set moved by whole seconds with no rounding: a fraction of a second is as long as a second can be, and the
arithmetic of `Decimal` rounds to 28 digits.

    iex> alias Feel.Type.{Moment, NumberSet}
    iex> set = NumberSet.point(Decimal.new("0.123456789012345678901234567890"))
    iex> NumberSet.format(Moment.shift_exact(set, 5))
    "5.12345678901234567890123456789"

# `split`

```elixir
@spec split(Feel.Type.NumberSet.t()) :: lines()
```

The three lines of a set, each as the seconds it holds, with the numbers of the second and third taken back to the
seconds they are: `{clock, instants, zoned_clock}`.

    iex> alias Feel.Type.{Moment, NumberSet}
    iex> set = NumberSet.new([{{Decimal.new(5), :closed}, {Decimal.new(5), :closed}}, {{Decimal.new(100_000_000_000_003_600), :closed}, {Decimal.new(100_000_000_000_003_600), :closed}}])
    iex> {clock, instants, zoned_clock} = Moment.split(set)
    iex> {NumberSet.format(clock), NumberSet.format(instants), zoned_clock}
    {"5", "3600", []}

# `union`

```elixir
@spec union(Feel.Type.NumberSet.t(), Feel.Type.NumberSet.t()) ::
  Feel.Type.NumberSet.t()
```

The union of two sets of one kind, line by line: a line of more than 16 intervals is made its own hull, and not the
hull of the lines together, which a `Feel.Type.NumberSet` of that many would be.

# `universe`

```elixir
@spec universe(kind()) :: Feel.Type.NumberSet.t()
```

The images of every value of a kind, on the three lines.

    iex> Feel.Type.NumberSet.format(Feel.Type.Moment.floating(:time))
    "[0..86399]"

# `universe_lines`

```elixir
@spec universe_lines(kind()) :: lines()
```

The three lines of the universe of a kind, as the seconds they hold.

# `with_fractions`

```elixir
@spec with_fractions(Feel.Type.NumberSet.t(), Feel.Type.NumberSet.t()) ::
  Feel.Type.NumberSet.t()
```

The set with the fractions of a second given (a set of numbers in `[0..1)`), in place of the ones it had. An empty set
of fractions leaves the set with every fraction.

---

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