The one duration spelling a :duration field may hold, and the
attribute the engine reads.
One grammar in, one attribute out
A :duration field is stored as the string a person types -
30s, 15m, 1h30m, 2d, 3d8h. That is the whole of the accepted
form. Whichever string was typed is what config holds, byte for byte:
nothing canonicalises on the way in. That is ADR-0005 decision 9 as
amended 2026-09-05 (clause 9a; ADR-0002 decision 7's field type is
untouched - :duration still holds a string, and what changed is which
strings). Every type with a :duration field reads this module:
core.send's delay and core.wait's duration.
Compiling one is a single step. parse/1 hands the stored string to
Predicator.Duration.parse/1 and gets back that grammar's normalised
duration; to_delay/1 renders that normalised value as the delay
attribute the engine reads. There is no intermediate spelling and
therefore no third form to keep in step - clause 9c's whole point.
Two things follow that the earlier arrangement could not give.
Sub-second and fractional spellings become expressible: 500ms renders
as 500ms, and 1.5s normalises to one second and five hundred
milliseconds and renders as 1s500ms. Both are values
Statifier.Duration.to_ms/1 resolves, because the engine reads the
same grammar this module does.
The grammar has one home, and it is not here
Predicator.Duration.parse/1 is the grammar's owner and this module
calls it rather than re-deriving any part of it. Whatever predicator
accepts, this module accepts; whatever predicator normalises - a
fraction expanded into whole components, a repeated unit accumulated,
the documented 30-day and 365-day approximations on mo and y - is
predicator's semantics arriving here already decided, not a second
opinion formed here. There is no value predicator parses that this
module refuses.
Summary
Functions
True for a stored duration this module can compile. Total for any term - a non-binary is simply not a duration.
Parses a stored duration into the expression language's normalised duration.
Renders a normalised duration as the delay attribute the engine reads.
Functions
True for a stored duration this module can compile. Total for any term - a non-binary is simply not a duration.
iex> StatifierBlocks.Core.Duration.duration?("1h30m")
true
iex> StatifierBlocks.Core.Duration.duration?("soon")
false
iex> StatifierBlocks.Core.Duration.duration?(nil)
false
Parses a stored duration into the expression language's normalised duration.
Anything the grammar does not accept is :error, and the caller turns
that into a finding rather than a raise.
iex> StatifierBlocks.Core.Duration.parse("1h30m")
{:ok, %{years: 0, months: 0, weeks: 0, days: 0, hours: 1, minutes: 30, seconds: 0, milliseconds: 0}}
iex> StatifierBlocks.Core.Duration.parse("soon")
:error
Renders a normalised duration as the delay attribute the engine reads.
Component-wise and lossless, largest unit first, omitting the components
that are zero. A duration whose components are all zero renders as
0s rather than as the empty string, because an empty delay is not a
delay of no time.
iex> {:ok, duration} = StatifierBlocks.Core.Duration.parse("2h")
iex> StatifierBlocks.Core.Duration.to_delay(duration)
"2h"
iex> {:ok, duration} = StatifierBlocks.Core.Duration.parse("1.5s")
iex> StatifierBlocks.Core.Duration.to_delay(duration)
"1s500ms"