# `Tempus.Slot`
[🔗](https://github.com/am-kantox/tempus/blob/v1.0.0-rc.0/lib/slot.ex#L1)

Declares a time slot interval and provides functions for interval arithmetic,
coverage checks, boundary comparisons, and transformations.

Slots in `Tempus` are modeled as mathematical intervals with configurable boundary openness:
- **Half-Open Intervals**: Standard slots default to $[from, to)$ (`from_open: false`, `to_open: true`).
  For example, a date `~D[2023-04-12]` wraps into `%Slot{from: ~U[2023-04-12 00:00:00Z], to: ~U[2023-04-13 00:00:00Z], from_open: false, to_open: true}`.
- **Infinite / Unbound Limits**: A `nil` boundary represents infinity ($-infty$ or $+infty$) and is always treated as **open** (`from_open: true` when `from: nil`, `to_open: true` when `to: nil`).
- **Void / Identity Slot**: Represented by `%Slot{from: nil, to: nil, from_open: true, to_open: true}` (`Slot.id/0`).

### Key Functions
- `Slot.wrap/1`: Converts a `Date`, `DateTime`, or `Time` into a `%Tempus.Slot{}`.
- `Slot.join/2`: Merges two overlapping or contiguous slots into a single maximal slot.
- `Slot.intersect/2`: Calculates the overlapping intersection of slots.
- `Slot.xor/2`: Returns the symmetric difference between two slots.
- `Slot.gap/1`: Finds the uncovered gap between slots.
- `Slot.cover?/2`: Checks if a `DateTime` or `Date` falls within the slot interval.
- `Slot.disjoint?/2` & `Slot.neighbour?/2`: Checks if slots are non-overlapping or adjacent/touching.

# `origin`

```elixir
@type origin() :: t() | Date.t() | DateTime.t() | nil
```

The origin used in comparisons and calculations

# `t`

```elixir
@type t() :: %Tempus.Slot{
  from: nil | DateTime.t(),
  from_open: boolean(),
  to: nil | DateTime.t(),
  to_open: boolean()
}
```

A timeslot to be used in `Tempus`

# `compare`

```elixir
@spec compare(s1 :: origin(), s2 :: origin(), strict :: boolean()) ::
  :lt | :gt | :eq | :joint
```

Compares two slot structs.

Returns `:gt` if first slot is strictly later than the second and `:lt` for vice versa.
**NB** `:eq` is returned not only if slots are equal, but also when they are overlapped.

Might be used in `Enum.sort/2`.

### Examples

    iex> slot = Tempus.Slot.wrap(~D[2020-09-30])
    iex> slot1 = %Tempus.Slot{from: nil, to: ~U[2020-09-30 00:00:00.000000Z]}
    iex> slot2 = %Tempus.Slot{from: nil, to: DateTime.utc_now()}
    iex> slot3 = %Tempus.Slot{from: ~U[2020-09-30 00:00:00.000000Z], to: nil}
    iex> slot4 = %Tempus.Slot{from: DateTime.utc_now(), to: nil}
    iex> Tempus.Slot.compare(Tempus.Slot.id(), Tempus.Slot.id(), true)
    :eq
    iex> Tempus.Slot.compare(slot1, slot2, false)
    :eq
    iex> Tempus.Slot.compare(slot1, slot2, true)
    :joint
    iex> Tempus.Slot.compare(slot3, slot4, false)
    :eq
    iex> Tempus.Slot.compare(slot3, slot4, true)
    :joint
    iex> Tempus.Slot.compare(slot, slot, true)
    :eq
    iex> Tempus.Slot.compare(slot, DateTime.utc_now(), true)
    :lt
    iex> Tempus.Slot.compare(slot, ~D|2000-01-01|, true)
    :gt
    iex> Tempus.Slot.compare(slot, slot.from, true)
    :joint

# `cover?`

```elixir
@spec cover?(slot :: t(), dt :: origin(), strict? :: boolean()) :: boolean()
```

Checks whether the `Slot` covers the date/datetime passed as a second argument.

## Examples

    iex> dt_between = ~U|2015-09-30 01:00:00Z|
    ...> dt_from = ~U|2015-09-30 00:00:00Z|
    ...> dt_to = ~U|2015-10-01 01:00:00Z|
    ...> d_from = Date.from_iso8601!("2015-09-30")
    ...> d_to = Date.from_iso8601!("2015-10-01")
    iex> slot = %Tempus.Slot{from: dt_from, to: dt_to}
    iex> Tempus.Slot.cover?(slot, dt_between)
    true
    iex> Tempus.Slot.cover?(slot, dt_to)
    false
    iex> Tempus.Slot.cover?(slot, dt_from)
    true
    iex> Tempus.Slot.cover?(slot, d_from)
    true
    iex> Tempus.Slot.cover?(slot, ~U|2000-01-01 00:00:00Z|)
    false
    iex> Tempus.Slot.cover?(slot, d_to)
    false

# `disjoint?`

```elixir
@spec disjoint?(s1 :: origin(), s2 :: origin()) :: boolean()
```

Returns `true` if two slots are disjoined, `false` otherwise.

## Examples

    iex> slot = %Tempus.Slot{from: ~U|2015-09-01 00:00:00Z|, to: ~U|2015-10-01 00:00:00Z|}
    iex> inner = %Tempus.Slot{from: ~U|2015-09-01 00:00:00Z|, to: ~U|2015-09-01 01:00:00Z|}
    iex> Tempus.Slot.disjoint?(slot, inner)
    false
    iex> outer = %Tempus.Slot{from: ~U|2015-10-01 00:00:01Z|, to: ~U|2015-10-01 01:00:00Z|}
    iex> Tempus.Slot.disjoint?(slot, outer)
    true
    iex> Tempus.Slot.disjoint?(~D|2000-01-01|, ~U|2015-10-01 00:00:01Z|)
    true

# `duration`

```elixir
@spec duration(slot :: origin(), unit :: System.time_unit()) ::
  non_neg_integer() | :infinity
```

Calculates the duration of a slot in units given as a second parameter
  (default: `:second`.)

### Example

    iex> Tempus.Slot.duration(~D|2020-09-03|)
    86400
    iex> Tempus.Slot.duration(Tempus.Slot.id())
    0
    iex> Tempus.Slot.duration(%Tempus.Slot{from: nil, to: DateTime.utc_now()})
    :infinity
    iex> Tempus.Slot.duration(%Tempus.Slot{from: DateTime.utc_now(), to: nil})
    :infinity

# `id`

```elixir
@spec id() :: %Tempus.Slot{from: nil, to: nil, from_open: true, to_open: true}
```

Identity element, void slot `~I(nil → nil)`

# `intersect`

```elixir
@spec intersect(slots :: Enum.t()) :: t() | nil
```

Intersects slots to the minimal covered timeslice.

### Example

    iex> Tempus.Slot.intersect([Tempus.Slot.id(), Tempus.Slot.id()])
    Tempus.Slot.id()

    iex> Tempus.Slot.intersect([%Tempus.Slot{from: nil, to: ~U[2020-09-30 23:00:00Z]},
    ...>   %Tempus.Slot{from: nil, to: ~U[2020-09-30 23:00:00Z]}])
    %Tempus.Slot{from: nil, to: ~U[2020-09-30 23:00:00Z], from_open: true, to_open: true}

    iex> Tempus.Slot.intersect([~D|2020-09-30|, Tempus.Slot.id()])
    %Tempus.Slot{from: ~U[2020-09-30 00:00:00.000000Z], to: ~U[2020-10-01 00:00:00.000000Z], from_open: false, to_open: true}

# `join`

```elixir
@spec join(slots :: Enum.t()) :: t()
```

Joins slots to the maximal covered timeslice.

### Example

    iex> Tempus.Slot.join([])
    Tempus.Slot.id()

    iex> Tempus.Slot.join([Tempus.Slot.wrap(~D|2020-09-30|), Tempus.Slot.wrap(~D|2020-10-02|)])
    %Tempus.Slot{from: ~U[2020-09-30 00:00:00.000000Z], to: ~U[2020-10-03 00:00:00.000000Z], from_open: false, to_open: true}

    iex> Tempus.Slot.join([~D|2020-09-30|, ~D|2020-10-02|])
    %Tempus.Slot{from: ~U[2020-09-30 00:00:00.000000Z], to: ~U[2020-10-03 00:00:00.000000Z], from_open: false, to_open: true}

# `join`

```elixir
@spec join(t(), t()) :: t()
```

Joins two slots to the maximal covered timeslice.

### Example

    iex> Tempus.Slot.join(Tempus.Slot.wrap(~D|2020-09-30|), Tempus.Slot.wrap(~D|2020-10-02|))
    %Tempus.Slot{from: ~U[2020-09-30 00:00:00.000000Z], to: ~U[2020-10-03 00:00:00.000000Z], from_open: false, to_open: true}

    iex> Tempus.Slot.join(~D|2020-09-30|, ~D|2020-10-02|)
    %Tempus.Slot{from: ~U[2020-09-30 00:00:00.000000Z], to: ~U[2020-10-03 00:00:00.000000Z], from_open: false, to_open: true}

# `neighbour?`

```elixir
@spec neighbour?(s1 :: origin(), s2 :: origin()) :: boolean()
```

Returns `true` if two slots are neighbours (touch at a boundary), `false` otherwise.

## Examples

    iex> slot = Tempus.Slot.wrap(~D|2015-09-01|)
    iex> Tempus.Slot.neighbour?(slot, Tempus.Slot.wrap(~D|2015-09-02|))
    true
    iex> Tempus.Slot.neighbour?(slot, Tempus.Slot.wrap(~D|2015-08-31|))
    true
    iex> Tempus.Slot.neighbour?(slot, Tempus.Slot.wrap(~D|2015-09-03|))
    false

# `new`

```elixir
@spec new(from: origin(), to: origin()) :: {:ok, t()} | {:error, any()}
```

Creates new slot using `arg[:from]` as a starting origin and `arg[:to]` and an ending origin.

## Examples

    iex> Tempus.Slot.new(from: ~U|2015-09-30 00:00:00Z|, to: ~U|2015-10-01 01:00:00Z|)
    {:ok, %Tempus.Slot{from: ~U|2015-09-30 00:00:00Z|, to: ~U|2015-10-01 01:00:00Z|, from_open: false, to_open: false}}
    iex> Tempus.Slot.new(%{from: ~D|2015-09-30|, to: ~U|2015-10-01T12:00:00Z|})
    {:ok, %Tempus.Slot{from: ~U|2015-09-30 00:00:00.000000Z|, to: ~U|2015-10-01 12:00:00Z|, from_open: false, to_open: false}}

# `new`

```elixir
@spec new(from :: origin(), to :: origin()) :: {:ok, t()} | {:error, any()}
```

Creates new slot using `from` as a starting origin and `to` and an ending origin.
See `new/1` for more readable implementation.

## Examples

    iex> import Tempus.Sigils
    iex> Tempus.Slot.new(~U|2015-09-30 00:00:00Z|, ~U|2015-10-01 01:00:00Z|)
    {:ok, %Tempus.Slot{from: ~U|2015-09-30 00:00:00Z|, to: ~U|2015-10-01 01:00:00Z|, from_open: false, to_open: false}}
    iex> Tempus.Slot.new(~D|2015-09-30|, ~U|2015-10-01T12:00:00Z|)
    {:ok, %Tempus.Slot{from: ~U|2015-09-30 00:00:00.000000Z|, to: ~U|2015-10-01 12:00:00Z|, from_open: false, to_open: false}}
    iex> Tempus.Slot.new(nil, nil)
    {:ok, Tempus.Slot.id()}
    iex> Tempus.Slot.new(~D|2015-09-30|, nil)
    {:ok, %Tempus.Slot{from: ~U[2015-09-30 00:00:00.000000Z], to: nil, from_open: false, to_open: true}}
    iex> Tempus.Slot.new(nil, ~D|2015-09-30|)
    {:ok, %Tempus.Slot{from: nil, to: ~U[2015-10-01 00:00:00.000000Z], from_open: true, to_open: true}}
    iex> Tempus.Slot.new(:ok, :ok)
    {:error, :invalid_input}

# `new!`

```elixir
@spec new!(from :: origin(), to :: origin()) :: t() | no_return()
```

Creates new slot using `from` as a starting origin and `to` and an ending origin.
Unlike `new/1`, this function raises on malformed input.

## Examples

    iex> Tempus.Slot.new!(~U|2015-09-30 00:00:00Z|, ~U|2015-10-01 01:00:00Z|)
    %Tempus.Slot{from: ~U|2015-09-30 00:00:00Z|, to: ~U|2015-10-01 01:00:00Z|, from_open: false, to_open: false}
    iex> Tempus.Slot.new!(~D|2015-09-30|, ~U|2015-10-01T12:00:00Z|)
    %Tempus.Slot{from: ~U|2015-09-30 00:00:00.000000Z|, to: ~U|2015-10-01 12:00:00Z|, from_open: false, to_open: false}
    iex> Tempus.Slot.new!(:ok, :ok)
    ** (ArgumentError) malformed from/to argument, expected `origin`

# `shift_tz`

```elixir
@spec shift_tz(
  slot :: t(),
  tz :: Calendar.time_zone(),
  tz_db :: Calendar.time_zone_database()
) :: t()
```

Shifts both `from` and `to` values to `UTC` zone.

### Examples

```elixir
slot = %Tempus.Slot{
   from: DateTime.from_naive!(~N|2018-01-05 21:00:00|, "America/New_York"),
   to: DateTime.from_naive!(~N|2018-01-08 08:59:59|, "Australia/Sydney")
}
#⇒ %Tempus.Slot{from: ~U[2018-01-06 02:00:00Z], to: ~U[2018-01-07 21:59:59Z], from_open: false, to_open: true}
```

# `strict_compare`

```elixir
@spec strict_compare(s1 :: origin(), s2 :: origin()) :: :eq | :lt | :gt | :joint
```

Compares two slot structs. The same as `compare/2`, but returns `:joint` if
the slots are overlapped.

### Examples

    iex> Tempus.Slot.strict_compare(~D|2020-01-01|, DateTime.utc_now())
    :lt

# `valid?`

```elixir
@spec valid?(slot :: t()) :: boolean()
```

Checks whether the `Slot` is valid (to > from, or to == from if closed point) or not.

## Examples

    iex> slot = %Tempus.Slot{from: ~U|2015-09-30 00:00:00Z|, to: ~U|2015-10-01 01:00:00Z|}
    iex> Tempus.Slot.valid?(slot)
    true
    iex> Tempus.Slot.valid?(%Tempus.Slot{from: slot.to, to: slot.from})
    false
    iex> slot = %Tempus.Slot{from: nil, to: ~U|2015-10-01 01:00:00Z|}
    ...> Tempus.Slot.valid?(slot)
    true
    iex> slot = %Tempus.Slot{from: ~U|2015-09-30 00:00:00Z|, to: nil}
    ...> Tempus.Slot.valid?(slot)
    true
    iex> Tempus.Slot.valid?(:ok)
    false

# `void`
*macro* 

Helper macro to pattern-match void slots.

# `wrap`

```elixir
@spec wrap(origin(), DateTime.t()) :: t()
```

Wraps the argument into a slot. For `DateTime` it'd be a point slot.
For a `Date`, it would be the whole day $[00:00:00.000000 	o 00:00:00.000000	ext{ next day})$.

## Examples

    iex> Tempus.Slot.wrap(~D|2020-08-06|)
    %Tempus.Slot{from: ~U[2020-08-06 00:00:00.000000Z], to: ~U[2020-08-07 00:00:00.000000Z], from_open: false, to_open: true}
    iex> Tempus.Slot.wrap(:ok)
    Tempus.Slot.id()

# `xor`

```elixir
@spec xor(outer :: t(), inner :: t()) :: [t()]
```

Splits the slot given as a first argument to two on borders given as a second slot.

## Examples

    iex> outer = Tempus.Slot.wrap(~D[2023-04-12])
    ...> {:ok, inner} = Tempus.Slot.new(~U[2023-04-12 12:00:00Z], ~U[2023-04-12 13:00:00Z])
    iex> Tempus.Slot.xor(outer, inner)
    [%Tempus.Slot{from: ~U[2023-04-12 00:00:00.000000Z], to: ~U[2023-04-12 12:00:00Z], from_open: false, to_open: true},
     %Tempus.Slot{from: ~U[2023-04-12 13:00:00Z], to: ~U[2023-04-13 00:00:00.000000Z], from_open: true, to_open: true}]
    iex> Tempus.Slot.xor(outer, inner) == Tempus.Slot.xor(inner, outer)
    false
    iex> {:ok, past} = Tempus.Slot.new(~U[2020-04-12 12:00:00Z], ~U[2020-04-12 13:00:00Z])
    ...> Tempus.Slot.xor(past, inner)
    [past, inner]
    ...> Tempus.Slot.xor(inner, past)
    [past, inner]
    iex> {:ok, border} = Tempus.Slot.new(~U[2023-04-12 11:00:00Z], ~U[2023-04-12 12:00:00Z])
    ...> Tempus.Slot.xor(border, inner)
    [Tempus.Slot.new!(~U[2023-04-12 11:00:00Z], ~U[2023-04-12 13:00:00Z])]

---

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