Windows over a feed's window field (spec/08 §11.2, D-140, D-154).
A window is nil — whatever a feed holds — or [from, to], either bound nil for
open. Its bounds are values of the feed's window field: numbers for a :number field,
or instants — DateTime, NaiveDateTime or Date values — for a :time one, a window
and a column of one kind (applies?/2). A window may also be a box (spec/08 §11.1,
D-168), %{ranges: %{field => [from, to]}, latest: boolean}: ranges over any :time or
:number field, and each identity's last row. box/2 gives any window as its box,
range/2 reads one field's range, check/2 holds a box to a schema and
latest_rows/3 keeps each identity's last row. These are the comparisons, the trims and
the numeric position that Visualize.Feed.finish/3, Visualize.Feed.Monitor and
Visualize.Feed.Combine share.
Examples
iex> Visualize.Feed.Window.within?(5, [0, 10])
true
iex> Visualize.Feed.Window.within?(~U[2026-10-11 12:00:00Z], [~U[2026-10-11 13:00:00Z], nil])
false
iex> Visualize.Feed.Window.trim([%{t: 1}, %{t: 5}, %{t: 9}], :t, [2, nil])
[%{t: 5}, %{t: 9}]
Summary
Types
A box (spec/08 §11.1, D-168): ranges over fields, and whether to keep each identity's last row.
Functions
Whether a window applies to a window field of the type, :time or :number (spec/08
§11.2): its bounds instants for :time, numbers for :number. A nil window, and one
with no bound, applies to either; a window that mixes kinds, or whose bounds are no
times, applies to neither.
The rows whose field lies in [from, upper): from inclusive and open when nil,
upper exclusive and open when nil. The seam of a combination (spec/08 §11.5).
The box a window asks of a schema (spec/08 §11.1, D-168): %{ranges: ranges, latest: boolean}, or nil for no window. A map's absent ranges is %{} and its absent
latest false; the shorthand [from, to] is the window field's range where it applies
to the window field (applies?/2), and nil — the feed's own span — where it does not
or the schema has no window field. The box is not checked (check/2).
Whether a schema can answer a window (spec/08 §11.1): :ok, or {:error, faults}, every
fault {path, reason} from the box to the value, ordered by path. nil and the
shorthand [from, to] are :ok, as they always were: a shorthand that does not apply is
the feed's own span. A box is held to the schema — each ranged field described, :time
or :number, its range two ends of its type, not inverted — and latest: true to a
window field and a declared identity.
:lt, :eq or :gt between two times of one kind: numbers by value, and DateTime,
NaiveDateTime and Date each by their own compare/2.
The earlier of two times, a nil ignored.
Whether a value is an instant: a DateTime, a NaiveDateTime or a Date — what a
:time window field holds (spec/08 §10.1, D-154). A number is not one.
The intersection of two windows, either nil for everything. Bounds take the later
from and the earlier to.
The later of two times, a nil ignored.
Each identity's last row (spec/08 §11.1, D-168): of the rows, for each identity
identify gives a row, the row whose time — a function of a row giving a number, as
position/1 gives one — is greatest, the later row in order where two tie, kept in their
order. A row whose identity or time is nil names nothing and is dropped. A feed's
latest and a design's :window step's (spec/14 §5.4.1) are this function.
The rows whose field lies strictly after time, every row for nil. The seam
between what a feed holds and a push (spec/08 §11.4).
A time as a number, for a reduction's x: a number itself, a DateTime its Unix
microseconds, a NaiveDateTime its Gregorian seconds and a Date its Gregorian days.
Anything else is nil, a gap.
A field's range of a window (spec/08 §11.1): a box's ranges entry for the field, read by
either spelling of its name; the shorthand [from, to] itself, which is the window
field's; nil when there is none. Any other window is returned as it is, so a provider
that reads its window field's range this way still refuses what it cannot read.
Whether a value is one a window can be over: a number, or an instant (instant?/1).
The rows whose field lies within the window (within?/2), in their order; every row
for a nil window. The field is read by either spelling (Visualize.Data.Table.get/2).
Whether a time lies within a window: both bounds inclusive, a nil bound open, a nil
window everything. A nil time lies within no window but nil.
Types
@type box() :: %{ ranges: %{required(Visualize.Data.Schema.name()) => [time() | nil]}, latest: boolean() }
A box (spec/08 §11.1, D-168): ranges over fields, and whether to keep each identity's last row.
@type t() :: [time() | nil] | nil
@type time() :: number() | DateTime.t() | NaiveDateTime.t() | Date.t()
Functions
Whether a window applies to a window field of the type, :time or :number (spec/08
§11.2): its bounds instants for :time, numbers for :number. A nil window, and one
with no bound, applies to either; a window that mixes kinds, or whose bounds are no
times, applies to neither.
iex> Visualize.Feed.Window.applies?([~U[2026-10-12 00:00:00Z], nil], :time)
true
iex> Visualize.Feed.Window.applies?([~U[2026-10-12 00:00:00Z], nil], :number)
false
iex> Visualize.Feed.Window.applies?([0, 10], :number)
true
The rows whose field lies in [from, upper): from inclusive and open when nil,
upper exclusive and open when nil. The seam of a combination (spec/08 §11.5).
iex> Visualize.Feed.Window.before([%{t: 1}, %{t: 5}, %{t: 9}], :t, nil, 5)
[%{t: 1}]
@spec box(Visualize.Feed.window(), Visualize.Data.Schema.t()) :: box() | nil
The box a window asks of a schema (spec/08 §11.1, D-168): %{ranges: ranges, latest: boolean}, or nil for no window. A map's absent ranges is %{} and its absent
latest false; the shorthand [from, to] is the window field's range where it applies
to the window field (applies?/2), and nil — the feed's own span — where it does not
or the schema has no window field. The box is not checked (check/2).
iex> alias Visualize.Data.Schema
iex> schema = Schema.new!(%{t: %{type: :number, window: true}, v: %{type: :number}})
iex> Visualize.Feed.Window.box([0, 10], schema)
%{ranges: %{t: [0, 10]}, latest: false}
iex> Visualize.Feed.Window.box([~U[2026-10-14 00:00:00Z], nil], schema)
nil
iex> Visualize.Feed.Window.box(%{ranges: %{v: [0, 1]}}, schema)
%{ranges: %{v: [0, 1]}, latest: false}
@spec check(term(), Visualize.Data.Schema.t()) :: :ok | {:error, [{list(), term()}, ...]}
Whether a schema can answer a window (spec/08 §11.1): :ok, or {:error, faults}, every
fault {path, reason} from the box to the value, ordered by path. nil and the
shorthand [from, to] are :ok, as they always were: a shorthand that does not apply is
the feed's own span. A box is held to the schema — each ranged field described, :time
or :number, its range two ends of its type, not inverted — and latest: true to a
window field and a declared identity.
iex> alias Visualize.Data.Schema
iex> schema = Schema.new!(%{t: %{type: :number, window: true}, s: %{type: :category}})
iex> Visualize.Feed.Window.check(%{ranges: %{t: [9, 1], s: ["a", "b"]}, latest: true}, schema)
{:error,
[
{[:latest], {:window, :no_identity}},
{[:ranges, :s], {:invalid_for, :category}},
{[:ranges, :t], {:window, :inverted}}
]}
:lt, :eq or :gt between two times of one kind: numbers by value, and DateTime,
NaiveDateTime and Date each by their own compare/2.
iex> Visualize.Feed.Window.compare(1, 1.0)
:eq
The earlier of two times, a nil ignored.
Whether a value is an instant: a DateTime, a NaiveDateTime or a Date — what a
:time window field holds (spec/08 §10.1, D-154). A number is not one.
iex> Visualize.Feed.Window.instant?(~D[2026-10-12])
true
iex> Visualize.Feed.Window.instant?(12)
false
The intersection of two windows, either nil for everything. Bounds take the later
from and the earlier to.
iex> Visualize.Feed.Window.intersect([0, 10], [5, nil])
[5, 10]
The later of two times, a nil ignored.
iex> Visualize.Feed.Window.latest(nil, 3)
3
Each identity's last row (spec/08 §11.1, D-168): of the rows, for each identity
identify gives a row, the row whose time — a function of a row giving a number, as
position/1 gives one — is greatest, the later row in order where two tie, kept in their
order. A row whose identity or time is nil names nothing and is dropped. A feed's
latest and a design's :window step's (spec/14 §5.4.1) are this function.
iex> rows = [%{id: "a", t: 1}, %{id: "b", t: 2}, %{id: "a", t: 3}, %{id: nil, t: 4}]
iex> Visualize.Feed.Window.latest_rows(rows, & &1.t, & &1.id)
[%{id: "b", t: 2}, %{id: "a", t: 3}]
The rows whose field lies strictly after time, every row for nil. The seam
between what a feed holds and a push (spec/08 §11.4).
iex> Visualize.Feed.Window.newer([%{t: 1}, %{t: 5}, %{t: 9}], :t, 5)
[%{t: 9}]
A time as a number, for a reduction's x: a number itself, a DateTime its Unix
microseconds, a NaiveDateTime its Gregorian seconds and a Date its Gregorian days.
Anything else is nil, a gap.
iex> Visualize.Feed.Window.position(~U[1970-01-01 00:00:01Z])
1000000
A field's range of a window (spec/08 §11.1): a box's ranges entry for the field, read by
either spelling of its name; the shorthand [from, to] itself, which is the window
field's; nil when there is none. Any other window is returned as it is, so a provider
that reads its window field's range this way still refuses what it cannot read.
iex> Visualize.Feed.Window.range(%{ranges: %{"t" => [1, 5], x: [0, 9]}}, :t)
[1, 5]
iex> Visualize.Feed.Window.range([1, 5], :t)
[1, 5]
iex> Visualize.Feed.Window.range(%{latest: true}, :t)
nil
Whether a value is one a window can be over: a number, or an instant (instant?/1).
The rows whose field lies within the window (within?/2), in their order; every row
for a nil window. The field is read by either spelling (Visualize.Data.Table.get/2).
Whether a time lies within a window: both bounds inclusive, a nil bound open, a nil
window everything. A nil time lies within no window but nil.