Narrow a Tempo span by a selector — the composition primitive for "workdays of June", "the 15th of every month", "every Dec 25 in the next decade", and user-supplied holidays.
Tempo.select(~o"2026-06", Tempo.workdays(:US)) # workdays of June — locale-aware
Tempo.select(~o"2026-06", Tempo.weekend(:US)) # weekend days of June
Tempo.select(~o"2026", [1, 15])
Tempo.select(~o"2026", ~o"12-25")
Tempo.select(~o"2026", ~o"10O") # ISO 8601-2 ordinal day — the 10th day of 2026
Tempo.select(~o"2026-06", ~o"5K") # ISO 8601-2 day-of-week — every Friday in June 2026
Tempo.select(~o"2026", ~o"-1M") # ISO 8601-2 negative — the last month of 2026
Tempo.select(~o"2026-06", ~o"-1D") # ISO 8601-2 negative — the last day of June 2026
Tempo.select(~o"2026", &my_holidays/1)Every call returns {:ok, %Tempo.IntervalSet{}} (or
{:error, reason}), consistent with the other set-algebra
operations — the result composes directly into
Tempo.union/2, Tempo.intersection/2, Tempo.difference/2.
Tempo.select/2 is a pure function. It has no opts, no
ambient locale read, no implicit territory resolution. Every
input that can affect the result is a value on the selector.
Locale-dependent constraints like "workdays" or "weekend" are
constructed by Tempo.workdays/1 and Tempo.weekend/1 (which
read the locale once at construction time) and composed in:
interval
|> Tempo.select(Tempo.workdays(:US))That means the workdays(:US) call is where territory
resolution happens — not inside select/2 — and the
resulting value is safe to capture anywhere, including
module attributes.
Selector shapes
| Shape | Example | Meaning |
|---|---|---|
[integer] / Range | Tempo.select(m, [1, 15]) | Integer indices applied at base's next-finer unit |
%Tempo{} or list | Tempo.select(y, ~o"12-25") | Project the constraint's specified units onto the base |
%Tempo{day_of_week: …} | Tempo.select(m, ~o"5K") | Day-of-week pattern — every matching weekday in the base (ISO 8601-2 K suffix) |
%Tempo{day_of_week: [...]} | Tempo.select(m, Tempo.workdays(:US)) | Day-of-week list — every matching weekday in the base |
%Tempo{day: N} (ordinal) | Tempo.select(y, ~o"10O") | Ordinal day in the year — the Nth day (ISO 8601-2 O suffix) |
| Negative components | Tempo.select(y, ~o"-1M") | ISO 8601-2 §4.4.1 — count from the end of the containing unit |
%Tempo.Interval{} or list | Tempo.select(y, vacation) | Same, for explicit intervals |
| Function | Tempo.select(y, &fn/1) | The function returns any of the above; evaluated against the base |
Base can be a Tempo.t/0, Tempo.Interval.t/0, or
Tempo.IntervalSet.t/0. IntervalSet bases flat-map the
selector across each member and collect the results.
Negative components — "last N from the end"
ISO 8601-2 §4.4.1 allows any integer component to be negative,
meaning "count from the end of the containing time-scale unit".
Tempo.select/2 honours this: the resolution is context-aware
and produces end-of-span selections without string munging or
calendar arithmetic at the call site.
Tempo.select(~o"2026", ~o"-1M") #=> December 2026 (last month of year)
Tempo.select(~o"2026", ~o"-1D") #=> Dec 31 2026 (last day of year)
Tempo.select(~o"2026", ~o"-1W") #=> week 52 of 2026 (last ISO week)
Tempo.select(~o"2026-06", ~o"-1D") #=> Jun 30 2026 (last day of month)
Tempo.select(~o"2026-02", ~o"-1D") #=> Feb 28 2026 (leap-aware — Feb 29 in 2024)
Tempo.select(~o"2026-06-15", ~o"-1H") #=> 23:00 (last hour of day)
Tempo.select(~o"2026-06-15T14", ~o"T-1M") #=> 14:59 (last minute of hour)The resolution is calendar-aware — Tempo.select(~o"2024-02", ~o"-1D") returns Feb 29 because 2024 is a leap year. It is
also axis-aware: -1W on a year base uses ISO
weeks-in-year (52 or 53); -1W on a month base uses weeks of
that month (4 or 5). -1M always refers to the calendar
month; -1K to the week's last day-of-week; -1O to the
year's last ordinal day.
Time-of-day components (:hour, :minute, :second,
:day_of_week) have fixed ranges and resolve at parse time
— ~o"-1H" parses directly as hour: 23, ~o"T-1M" as
minute: 59, ~o"T-1S" as second: 59, ~o"-1K" as
day_of_week: 7. Calendar-dependent units (:month, :week,
:day, :day_of_year) keep their negative value through parse
and are resolved against the base context when Tempo.select/2
materialises them.
~o"-1M" is always "last month" (never "last minute") — use
the T time designator (~o"T-1M") to select minute-of-hour.
Negative :year values are preserved (BC designator per ISO
8601-2 expanded year form) — they're not flipped to "last
year" because a time line has no "end" to count from.
Summary
Functions
Narrow base by selector, returning the selected intervals
as a Tempo.IntervalSet.t/0.
Types
@type base() :: Tempo.t() | Tempo.Interval.t() | Tempo.IntervalSet.t()
@type selector() :: [integer()] | Range.t() | Tempo.t() | Tempo.Interval.t() | [Tempo.t() | Tempo.Interval.t()] | (base() -> selector())
Functions
@spec select(base(), selector()) :: {:ok, Tempo.IntervalSet.t()} | {:error, term()}
Narrow base by selector, returning the selected intervals
as a Tempo.IntervalSet.t/0.
See the module doc for the selector vocabulary and runtime- resolution caveats.
Supported base shapes
base can be any Tempo value that materialises to an Interval
or IntervalSet. Grouped and masked forms have their endpoints
resolved to concrete values before the selector runs, so every
ISO 8601-2 shape composes with every selector:
| Base shape | Example | Materialises to |
|---|---|---|
Scalar %Tempo{} | ~o"2026-06" | single Interval |
| Explicit Interval | ~o"2026-07/2026-10" | single Interval |
| IntervalSet | output of Tempo.union/2 etc. | IntervalSet (flat-mapped) |
Quarter (NQ) | ~o"2026Y3Q" | single Interval (group resolved) |
| Season (codes 25–32) | ~o"2026Y26M" | Interval bounded by equinox/solstice |
| Month/day range in a slot | ~o"2026Y{6..8}M" | IntervalSet of three members |
| Stepped range | ~o"2026Y{1..-1//3}M" | IntervalSet of disjoint members |
| Archaeological mask | ~o"156X" | decade-long Interval |
Example with a quarter base:
Tempo.select(~o"2026Y3Q", Tempo.workdays(:US))
#=> {:ok, IntervalSet with 66 members — workdays of Q3 2026}Examples
iex> {:ok, set} = Tempo.Select.select(~o"2026-02", [1, 15])
iex> Enum.map(Tempo.IntervalSet.to_list(set), & &1.from.time[:day])
[1, 15]
iex> {:ok, set} = Tempo.Select.select(~o"2026", ~o"12-25")
iex> [xmas] = Tempo.IntervalSet.to_list(set)
iex> xmas.from.time
[year: 2026, month: 12, day: 25]
iex> {:ok, set} = Tempo.Select.select(~o"2026", ~o"10O")
iex> [day10] = Tempo.IntervalSet.to_list(set)
iex> {day10.from.time[:month], day10.from.time[:day]}
{1, 10}
iex> {:ok, set} = Tempo.Select.select(~o"2026-06", ~o"5K")
iex> set |> Tempo.IntervalSet.to_list() |> Enum.map(& &1.from.time[:day])
[5, 12, 19, 26]
iex> {:ok, set} = Tempo.Select.select(~o"2026-02", Tempo.workdays(:US))
iex> Tempo.IntervalSet.count(set)
20