# `Tempo.IntervalSet`
[🔗](https://github.com/elixir-tempo/tempo/blob/v2.1.0/lib/tempo/interval_set.ex#L1)

Interval sets: all of several expressions, `{2026-06,2026-08}`.

An interval set is the `{:all, members}` form of a `%Tempo{}`. Its
members are in one calendar (law 5), and enumerating the set yields them
(law 6). The set operations of `Tempo` return one.

`new/2` builds one from its members and `members/1` gives them back.
`count/1`, `first/1` and `last/1` read it; `filter/2` and `map/2` work
member by member, and `coalesce/2` joins the members that touch.
`walk/1` gives the spans the set covers, `covered?/2` asks whether a
value lies within them, and `slots/3` cuts free time into lengths.
`from_stream/2` makes one from a stream of expressions.

# `coalesce`

```elixir
@spec coalesce(Tempo.t(), keyword()) :: {:ok, Tempo.t()} | {:error, Exception.t()}
```

The members of a set merged into the time they cover: touching and
overlapping members become one span, in order; a result is in canonical
form, sorted and disjoint with no two members meeting.

### Arguments

* `set` is a `t:Tempo.t/0` that has spans.

* `options` is a keyword list of options.

### Options

* `:within` is a `t:Tempo.t/0`, the window an expression with no end of its
  own (a recurrence with no count) or no year is placed in.

### Returns

* `{:ok, set}`, a `t:Tempo.t/0` of the `:all` form.

* `{:error, exception}` as for `Tempo.union/3`.

### Examples

    iex> {:ok, both} = Tempo.union(~o"2026-01", ~o"2026-02")
    iex> Tempo.IntervalSet.coalesce(both)
    {:ok, ~o"{2026-01/2026-02}"}

# `coalesce!`

```elixir
@spec coalesce!(Tempo.t(), keyword()) :: Tempo.t()
```

`coalesce/2`, returning its answer or raising the error.

### Arguments

* The arguments are those of `coalesce/2`.

### Options

* The options of `coalesce/2`.

### Returns

* What `coalesce/2` returns as `{:ok, answer}`, the answer alone.
  The error it returns as `{:error, exception}` is raised.

### Examples

    iex> Tempo.union!(~o"2026-01", ~o"2026-02") |> Tempo.IntervalSet.coalesce!()
    ~o"{2026-01/2026-02}"

# `count`

```elixir
@spec count(Tempo.t()) :: non_neg_integer()
```

How many members a set has.

### Arguments

* `expression` is a `t:Tempo.t/0`.

### Returns

* The number of its members, as `members/1` gives them, raising as it
  raises.

### Examples

    iex> Tempo.IntervalSet.count(~o"{2026-06,2026-08}")
    2

# `covered`

```elixir
@spec covered(Tempo.t(), keyword()) :: {:ok, Tempo.t()} | {:error, Exception.t()}
```

The time covered by at least so many members of a set at once, as spans.

### Arguments

* `set` is a `t:Tempo.t/0` that has spans.

* `options` is a keyword list of options.

### Options

* `:at_least` is how many members must cover a time for it to be kept. The
  default is 1.

* `:within` is a `t:Tempo.t/0`, the window an expression with no end of its
  own (a recurrence with no count) or no year is placed in.

### Returns

* `{:ok, set}`, a `t:Tempo.t/0` of the `:all` form.

### Examples

    iex> {:ok, busy} = Tempo.IntervalSet.new([~o"T09/T12", ~o"T11/T14", ~o"T13/T17"])
    iex> Tempo.IntervalSet.covered(busy, at_least: 2)
    {:ok, ~o"{T11/T12,T13/T14}"}

# `covered!`

```elixir
@spec covered!(Tempo.t(), keyword()) :: Tempo.t()
```

`covered/2`, returning its answer or raising the error.

### Arguments

* The arguments are those of `covered/2`.

### Options

* The options of `covered/2`.

### Returns

* What `covered/2` returns as `{:ok, answer}`, the answer alone.
  The error it returns as `{:error, exception}` is raised.

### Examples

    iex> busy = Tempo.IntervalSet.new!([~o"T09/T12", ~o"T11/T14", ~o"T13/T17"])
    iex> Tempo.IntervalSet.covered!(busy, at_least: 2)
    ~o"{T11/T12,T13/T14}"

# `covered?`

```elixir
@spec covered?(Tempo.t(), Tempo.t()) :: boolean()
```

Whether a set covers every instant of a value.

### Arguments

* `set` is a `t:Tempo.t/0` that has spans.

* `value` is a `t:Tempo.t/0`.

### Returns

* `true` when every instant of `value` is in a member of `set`, as
  `Tempo.within?/2` answers, and `false` when one is not.

### Examples

    iex> {:ok, set} = Tempo.IntervalSet.new([~o"2026-01", ~o"2026-03"])
    iex> Tempo.IntervalSet.covered?(set, ~o"2026-01-15")
    true
    iex> Tempo.IntervalSet.covered?(set, ~o"2026-02-15")
    false

# `filter`

```elixir
@spec filter(Tempo.t(), (Tempo.t() -&gt; as_boolean(term()))) :: Tempo.t()
```

The members a function keeps.

### Arguments

* `set` is a `t:Tempo.t/0`.

* `fun` is given each member and answers whether to keep it.

### Returns

* A `t:Tempo.t/0` of the `:all` form. A set whose members are read as
  they are asked for (a selection over a span with no end) is filtered as
  they are read, and has no last member either.

### Examples

    iex> {:ok, set} = Tempo.IntervalSet.new([~o"2026-01", ~o"2026-03"])
    iex> Tempo.IntervalSet.filter(set, &Tempo.before?(&1, ~o"2026-02"))
    ~o"{2026-01}"

    iex> {:ok, fifteenths} = Tempo.select(~o"2026-06-01/..", ~o"15D")
    iex> fifteenths |> Tempo.IntervalSet.filter(&(Tempo.month(&1) == 12)) |> Enum.take(2)
    [~o"2026-12-15", ~o"2027-12-15"]

# `first`

```elixir
@spec first(Tempo.t()) :: Tempo.t() | nil
```

The first member of a set. A recurrence with no end has a first
occurrence, which is its first member.

### Arguments

* `expression` is a `t:Tempo.t/0`.

### Returns

* Its first member, a `t:Tempo.t/0`, or `nil` for an empty set. A
  recurrence counted back with no start, or a value that is no
  expression, raises a `Tempo.ConversionError`.

### Examples

    iex> Tempo.IntervalSet.first(~o"{2026-06,2026-08}")
    ~o"2026-06"

    iex> Tempo.IntervalSet.first(~o"R/2026-06-01/P1W")
    ~o"2026-06-01/2026-06-07"

# `from_stream`

```elixir
@spec from_stream(Enumerable.t(), keyword()) :: Tempo.t()
```

A set whose members are read from an enumerable as they are asked for, so
a long or endless source is a set.

### Arguments

* `enumerable` is an `Enumerable` of `t:Tempo.t/0`, in order of start.

* `options` is a keyword list of options.

### Options

* `:metadata` is the set's own metadata. The default is `%{}`.

### Returns

* A `t:Tempo.t/0` of the `:all` form, whose members are read as they are
  asked for. A value that is no enumerable raises an `ArgumentError`.

### Examples

    iex> set = Tempo.IntervalSet.from_stream(Stream.map([~o"2026-01", ~o"2026-03"], & &1))
    iex> Enum.count(set)
    2

# `last`

```elixir
@spec last(Tempo.t()) :: Tempo.t() | nil
```

The last member of a set.

### Arguments

* `expression` is a `t:Tempo.t/0`.

### Returns

* Its last member, a `t:Tempo.t/0`, or `nil` for an empty set, raising
  as `members/1` raises.

### Examples

    iex> Tempo.IntervalSet.last(~o"{2026-06,2026-08}")
    ~o"2026-08"

# `map`

```elixir
@spec map(Tempo.t(), (Tempo.t() -&gt; term())) :: [term()] | Enumerable.t()
```

A function applied to each member, giving its answers.

### Arguments

* `set` is a `t:Tempo.t/0`.

* `fun` is given each member.

### Returns

* The list of the function's answers, in the members' order.

* A stream of them for a set whose members are read as they are asked
  for, which may have no last member.

### Examples

    iex> {:ok, set} = Tempo.IntervalSet.new([~o"2026-01", ~o"2026-03"])
    iex> Tempo.IntervalSet.map(set, &Tempo.to_iso8601!/1)
    ["2026Y1M", "2026Y3M"]

    iex> {:ok, fifteenths} = Tempo.select(~o"2026-06-01/..", ~o"15D")
    iex> fifteenths |> Tempo.IntervalSet.map(&Tempo.month/1) |> Enum.take(3)
    [6, 7, 8]

# `members`

```elixir
@spec members(Tempo.t()) :: [Tempo.t()]
```

The members of a set, or of any expression: a singular expression is its
own one member, and a plural one has the members `Enum` gives of it (a
recurrence its occurrences). One with no end, or with more members than
the application's `:max_values_at_once`, raises a `Tempo.ConversionError`.

### Arguments

* `expression` is a `t:Tempo.t/0`.

### Returns

* The list of its members, each a `t:Tempo.t/0`. An expression with no
  end, one of too many members, or a value that is no expression raises a
  `Tempo.ConversionError`.

### Examples

    iex> Tempo.IntervalSet.members(~o"{2026-06,2026-08}")
    [~o"2026-06", ~o"2026-08"]

    iex> Tempo.IntervalSet.members(~o"2026-06")
    [~o"2026-06"]

    iex> Tempo.IntervalSet.members(~o"R2/2026-06-01/P1W")
    [~o"2026-06-01/2026-06-07", ~o"2026-06-08/2026-06-14"]

# `new`

```elixir
@spec new([Tempo.t()], keyword()) :: {:ok, Tempo.t()} | {:error, Exception.t()}
```

Builds a set from its members.

### Arguments

* `members` is a list of expressions, each a `t:Tempo.t/0`.

* `options` is a keyword list of options.

### Options

* `:metadata` is a map the caller keeps on the set. The default is `%{}`.

* `:except` is a list of `t:Tempo.t/0` the set leaves out of its members,
  as `^` writes them: `{2017Y..,^2020Y}` is every year from 2017 but 2020.
  The default is `[]`.

### Returns

* `{:ok, set}`, a `t:Tempo.t/0` of the `:all` form. A set is one
  expression, in one calendar and one zone: a member of another calendar
  than the first's is converted to it, and where a member is in a named
  zone every zoned member is moved to the first such zone.

* `{:error, %Tempo.FrameError{}}` when one member is zoned and another has
  no zone: place it with `Tempo.in_zone/2`.

* `{:error, %Tempo.ConversionError{reason: :not_an_expression}}` for a
  member that is no `t:Tempo.t/0`, and `{:error, %ArgumentError{}}` for
  members that are no list.

### Examples

    iex> Tempo.IntervalSet.new([~o"2026-06", ~o"2026-08"])
    {:ok, ~o"{2026-06,2026-08}"}

    iex> {:ok, from_2017} = Tempo.Interval.new(from: Tempo.new!(year: 2017), to: :open)
    iex> Tempo.IntervalSet.new([from_2017], except: [Tempo.new!(year: 2020)])
    {:ok, ~o"{2017Y..,^2020Y}"}

    iex> {:ok, set} = Tempo.IntervalSet.new([~o"2026-06-01/2026-06-15", ~o"2026-07"])
    iex> Tempo.IntervalSet.count(set)
    2

    iex> Tempo.IntervalSet.new([~o"2026-06-01T09/2026-06-01T10[Europe/London]", ~o"2026-06-01T09/2026-06-01T10[America/New_York]"])
    {:ok, ~o"{2026-06-01T09/2026-06-01T10,2026-06-01T14/2026-06-01T15}[Europe/London]"}

# `new!`

```elixir
@spec new!([Tempo.t()], keyword()) :: Tempo.t()
```

Builds a set as `new/2` does, returning it or raising.

### Arguments

* `members` and `options` are as for `new/2`.

### Options

* The options of `new/2`.

### Returns

* The set, a `t:Tempo.t/0`, or raises the error `new/2` returns.

### Examples

    iex> Tempo.IntervalSet.new!([~o"2026-01", ~o"2026-03"])
    ~o"{2026-01,2026-03}"

# `relation_matrix`

```elixir
@spec relation_matrix(Tempo.t(), Tempo.t()) ::
  {:ok, [[Tempo.Allen.relation()]]} | {:error, Exception.t()}
```

Allen's relation of every member of one set to every member of another.

### Arguments

* `a` and `b` are each a `t:Tempo.t/0` that has spans.

### Returns

* `{:ok, rows}`, one row per member of `a`, each a list of the relations to
  the members of `b` in order.

* `{:error, exception}` as for `Tempo.union/3`.

### Examples

    iex> Tempo.IntervalSet.relation_matrix(~o"{2026-01,2026-03}", ~o"{2026-02,2026-03}")
    {:ok, [[:meets, :precedes], [:met_by, :equals]]}

# `slots`

```elixir
@spec slots(Tempo.t(), Tempo.Duration.t(), keyword()) ::
  {:ok, Tempo.t()} | {:error, Exception.t()}
```

Cuts each span an expression has into consecutive slots of a fixed length:
the bookable windows in a region of free time.

Within each span, slots of the duration are laid from its start, each
starting one `:every` after the last, and kept when they fit inside the
span. Each slot carries its span's metadata. The spans stay distinct: the
result is not coalesced.

### Arguments

* `expression` is a `t:Tempo.t/0` that has spans: an interval, a set, a
  bounded recurrence or a value.

* `duration` is the slot length, an exact `t:Tempo.Duration.t/0`.

* `options` is a keyword list of options.

### Options

* `:every` is the spacing between slot starts, an exact
  `t:Tempo.Duration.t/0`. The default is the slot length, so slots are
  adjacent; a smaller spacing overlaps them, a larger leaves gaps.

### Returns

* `{:ok, set}`, a `t:Tempo.t/0` of the `:all` form holding the slots in
  order.

* `{:error, %ArgumentError{}}` when a duration has a month or a year, which
  has no fixed length, `{:error, %Tempo.ConversionError{reason: :unbounded}}`
  for an expression with no end, whose slots cannot all be listed, and
  `{:error, exception}` when the expression has no spans.

### Examples

    iex> work = ~o"2026-06-15T09:00/2026-06-15T12:00"
    iex> {:ok, slots} = Tempo.IntervalSet.slots(work, ~o"PT1H")
    iex> Tempo.IntervalSet.count(slots)
    3

    iex> work = ~o"2026-06-15T09:00/2026-06-15T12:00"
    iex> {:ok, slots} = Tempo.IntervalSet.slots(work, ~o"PT1H", every: ~o"PT30M")
    iex> Tempo.IntervalSet.count(slots)
    5

# `slots!`

```elixir
@spec slots!(Tempo.t(), Tempo.Duration.t(), keyword()) :: Tempo.t()
```

`slots/3`, returning its answer or raising the error.

### Arguments

* The arguments are those of `slots/3`.

### Options

* The options of `slots/3`.

### Returns

* What `slots/3` returns as `{:ok, answer}`, the answer alone.
  The error it returns as `{:error, exception}` is raised.

### Examples

    iex> ~o"2026-06-15T09:00/2026-06-15T12:00" |> Tempo.IntervalSet.slots!(~o"PT1H") |> Tempo.IntervalSet.count()
    3

# `walk`

```elixir
@spec walk(Tempo.t()) :: Enumerable.t()
```

The spans of a set's members, lazily and in order: what the set covers,
member by member.

### Arguments

* `set` is a `t:Tempo.t/0`.

### Returns

* An `Enumerable` of `t:Tempo.t/0` spans. An expression with no spans,
  or that is no expression, raises its error.

### Examples

    iex> {:ok, set} = Tempo.IntervalSet.new([~o"2026-01", ~o"2026-03"])
    iex> set |> Tempo.IntervalSet.walk() |> Enum.map(&Tempo.to_iso8601!/1)
    ["2026Y1M/2026Y1M", "2026Y3M/2026Y3M"]

---

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