# `Macaw.Data.Window`
[🔗](https://github.com/dcoai/macaw_data/blob/v0.6.0/lib/macaw/data/window.ex#L1)

Windows, boxes and regions over a source's rows (spec/01 §2.3, §2.4).

- A **window** is `nil` (whatever a source holds) or `[from, to]`, either bound `nil`
  for open, over the window field: numbers for a `:number` field, instants (`DateTime`,
  `NaiveDateTime` or `Date`) for a `:time` one (`applies?/2`).
- A **box** is `%{west, south, east, north}` in degrees over a `:point` window field
  (`Macaw.Data.Source.Request.box?/1`); `west` east of `east` crosses the antimeridian.
- A **region** is ranges over named `:time` or `:number` fields, `%{field => [from, to]}`.

These are the comparisons, the trims and the numeric position that finishing
(`Macaw.Data.Source.Finish`) and push share. Fields are read by either spelling of their
name (`Macaw.Data.Table.get/2`). Ported from visualize's `Visualize.Feed.Window`, its box
rewritten for a geographic box and its ranges kept as the region.

## Examples

    iex> Macaw.Data.Window.within?(5, [0, 10])
    true
    iex> Macaw.Data.Window.trim([%{t: 1}, %{t: 5}, %{t: 9}], :t, [2, nil])
    [%{t: 5}, %{t: 9}]
    iex> Macaw.Data.Window.in_box?({179.5, 0}, %{west: 170, south: -10, east: -170, north: 10})
    true

# `t`

```elixir
@type t() :: [time() | nil] | nil
```

# `time`

```elixir
@type time() :: number() | DateTime.t() | NaiveDateTime.t() | Date.t()
```

# `applies?`

```elixir
@spec applies?(t(), :time | :number) :: boolean()
```

Whether a window applies to a window field of the type: its bounds instants for
`:time`, numbers for `:number`; a `nil` window, and a window with no bound, to either.

    iex> Macaw.Data.Window.applies?([~U[2026-10-12 00:00:00Z], nil], :number)
    false

# `before`

```elixir
@spec before([map()], Macaw.Data.Table.name(), time() | nil, time() | nil) :: [map()]
```

The rows whose `field` lies in `[from, upper)`: `from` inclusive and open when `nil`,
`upper` exclusive and open when `nil`; a row with no time lies in none. The seam of a
combination, where one source's rows stop and the next's begin (macaw#185, ported from
visualize's `Visualize.Feed.Window.before/4`).

    iex> Macaw.Data.Window.before([%{t: 1}, %{t: 5}, %{t: 9}], :t, nil, 5)
    [%{t: 1}]
    iex> Macaw.Data.Window.before([%{t: 1}, %{t: 5}, %{t: 9}], :t, 5, nil)
    [%{t: 5}, %{t: 9}]

# `check`

```elixir
@spec check(Macaw.Data.Source.Request.t() | map(), Macaw.Data.Schema.t()) ::
  :ok | {:error, [{list(), term()}, ...]}
```

Whether a schema can answer a request's window, box, region and latest: `:ok`, or
`{:error, faults}`, every fault `{path, reason}` by path. A window must apply to a
`:time` or `:number` window field; a box needs a `:point` window field; each region
field is described, `:time` or `:number`, its range two ends of its type, not inverted;
`latest` needs a `:time` or `:number` window field and a declared identity. A request asking none of them is
`:ok`.

It takes a `%Request{}`, or the axes alone as a map of its fields (`window`, `box`,
`region`, `latest`, each defaulting as a request's does), so a source stepping its
window checks one without a whole request (macaw#185); any other key is
`{[key], :unknown_key}`.

    iex> alias Macaw.Data.Schema
    iex> schema = Schema.new!(%{t: %{type: :number, window: true}, v: %{type: :number}})
    iex> Macaw.Data.Window.check(%{region: %{t: [0, 5]}}, schema)
    :ok
    iex> Macaw.Data.Window.check(%{window: [9, 1], at: 3}, schema)
    {:error, [{[:at], :unknown_key}]}

    iex> alias Macaw.Data.{Schema, Source.Request}
    iex> schema = Schema.new!(%{t: %{type: :number, window: true}, s: %{type: :category}})
    iex> Macaw.Data.Window.check(Request.new!(now: ~U[2026-10-27 00:00:00Z], region: %{t: [9, 1], s: ["a", "b"]}, latest: true), schema)
    {:error,
     [
       {[:latest], {:window, :no_identity}},
       {[:region, :s], {:invalid_for, :category}},
       {[:region, :t], {:window, :inverted}}
     ]}

# `compare`

```elixir
@spec compare(time(), time()) :: :lt | :eq | :gt
```

`:lt`, `:eq` or `:gt` between two times of one kind.

# `earliest`

```elixir
@spec earliest(time() | nil, time() | nil) :: time() | nil
```

The earlier of two times, a `nil` ignored.

# `in_box?`

```elixir
@spec in_box?(term(), map()) :: boolean()
```

Whether a position `{longitude, latitude}` lies in a box, bounds inclusive; a box whose
`west` is east of its `east` crosses the antimeridian. Anything but a position lies in
none.

# `instant?`

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

Whether a value is an instant: a `DateTime`, a `NaiveDateTime` or a `Date`.

# `intersect`

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

The intersection of two windows, either `nil` for everything.

# `latest`

```elixir
@spec latest(time() | nil, time() | nil) :: time() | nil
```

The later of two times, a `nil` ignored.

# `latest_rows`

```elixir
@spec latest_rows([map()], (map() -&gt; number() | nil), (map() -&gt; term())) :: [map()]
```

Each identity's last row: of the rows, for each identity `identify` gives, the row whose
`time` (a number) is greatest, the later in order on a tie, kept in their order. A row
whose identity or time is `nil` names nothing and is dropped.

    iex> rows = [%{id: "a", t: 1}, %{id: "b", t: 2}, %{id: "a", t: 3}, %{id: nil, t: 4}]
    iex> Macaw.Data.Window.latest_rows(rows, & &1.t, & &1.id)
    [%{id: "b", t: 2}, %{id: "a", t: 3}]

# `newer`

```elixir
@spec newer([map()], Macaw.Data.Table.name(), time() | nil) :: [map()]
```

The rows whose `field` lies strictly after `time`; every row for `nil`. A push's seam.

# `position`

```elixir
@spec position(term()) :: number() | nil
```

A time as a number, for a reduction's `x`: a number itself, a `DateTime` its Unix
microseconds, a `NaiveDateTime` its Gregorian seconds, a `Date` its Gregorian days;
anything else `nil`, a gap.

# `range`

```elixir
@spec range(term(), Macaw.Data.Table.name()) :: term()
```

A field's range of a window or a region (macaw#185, visualize's `range/2`): a region's
entry for the field, by either spelling of its name; a range `[from, to]` itself, which
is the window field's; `nil` when there is none. Anything else is returned as it is,
so a source that reads its window field's range this way still refuses what it can't.

    iex> Macaw.Data.Window.range(%{"t" => [1, 5], x: [0, 9]}, :t)
    [1, 5]
    iex> Macaw.Data.Window.range([1, 5], :t)
    [1, 5]
    iex> Macaw.Data.Window.range(%{x: [0, 9]}, :t)
    nil

# `region`

```elixir
@spec region(t() | %{required(Macaw.Data.Table.name()) =&gt; t()}, Macaw.Data.Schema.t()) ::
  %{required(Macaw.Data.Table.name()) =&gt; t()} | nil
```

The region a window asks of a schema (macaw#185): a range `[from, to]` is the window
field's region, `%{field => [from, to]}`, where it applies to the window field's type,
and `nil` (the source's own span) where it does not or the schema has no `:time` or
`:number` window field; a region passes as it is; `nil` is `nil`.

visualize's box (D-168), `%{ranges: ranges, latest: latest}`, is a request's `region`
and `latest` here: `region(ranges, schema)` and `latest:` beside it.

    iex> alias Macaw.Data.Schema
    iex> schema = Schema.new!(%{t: %{type: :number, window: true}, v: %{type: :number}})
    iex> Macaw.Data.Window.region([0, 10], schema)
    %{t: [0, 10]}
    iex> Macaw.Data.Window.region([~U[2026-10-14 00:00:00Z], nil], schema)
    nil
    iex> Macaw.Data.Window.region(%{v: [0, 1]}, schema)
    %{v: [0, 1]}

# `time?`

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

Whether a value is one a window can be over: a number or an instant.

# `trim`

```elixir
@spec trim([map()], Macaw.Data.Table.name(), t()) :: [map()]
```

The rows whose `field` lies within the window, in their order.

# `trim_box`

```elixir
@spec trim_box([map()], Macaw.Data.Table.name(), map() | nil) :: [map()]
```

The rows whose `field` lies in the box, in their order; every row for `nil`.

# `trim_region`

```elixir
@spec trim_region([map()], %{required(Macaw.Data.Table.name()) =&gt; t()} | nil) :: [
  map()
]
```

The rows whose every ranged field lies in its range; every row for `nil`.

# `within?`

```elixir
@spec within?(time() | nil, t()) :: boolean()
```

Whether a time lies within a window, both bounds inclusive; `nil` lies in none but `nil`.

---

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