# `Statifier.Duration`
[🔗](https://github.com/riddler/statifier-ex/blob/v2.6.0/lib/statifier/duration.ex#L1)

The one place this engine turns an SCXML duration designation - `<send
delay>`'s attribute string, or a `delayexpr` result - into milliseconds.
Wraps `Predicator.Duration` rather than reimplementing CSS2-style duration
parsing.

The delegation is deliberate rather than incidental: one duration vocabulary
across the platform, with `Predicator.Duration.parse/1` (whole-string, no
partial consumption, `:error` on junk) and `to_milliseconds/1` as its only
implementation, so a duration means the same thing in a `<send delay>` as in
any expression predicator evaluates. Everything below is a consequence of
that, not a second look at it.

## The unit set is a superset, delegated as-is

The SCXML schema's `delay`/`delayexpr` pattern
(`\d*(\.\d+)?(ms|s|m|h|d)`) recognizes five units: `ms`, `s`, `m`
(minutes), `h`, `d`. `Predicator.Duration.parse/1` recognizes eight:
`y`, `mo`, `w`, `d`, `h`, `m`, `s`, `ms` - the same five plus `y`
(years), `mo` (months), and `w` (weeks). Every unit the two sets share
means the same thing in both (`m` is minutes, not months, in both), so
predicator's set is a strict superset of the schema's, and this module
delegates it whole rather than re-restricting parsing to the schema's five.
A document that writes `delay="2w"` gets a working two-week delay; nothing
here rejects it for being outside the schema pattern, since accepting more
than the schema requires is never a conformance violation.

## The leading-dot concession

The SCXML schema pattern above allows a bare leading dot - `".5s"` is a
schema-valid `delay` value - but `Predicator.Duration.parse/1` does not:
`parse(".5s")` is `:error` while `parse("1.5s")` is `{:ok, ...}` (verified
against `deps/predicator/lib/predicator/duration.ex`'s own doctests).
`normalize_leading_dot/1` is the one-line concession this schema pattern
requires and predicator 8.0 deliberately does not make. The split is by
agreement rather than by omission: `".5s"` is schema-valid SCXML but not a
duration predicator means to accept, so pre-normalizing it stays this
engine's job and does not become a widening of predicator's own grammar.
So it rewrites a leading `.` to `0.` before parsing, and `".5s"` and
`"0.5s"` parse identically. This is the only rewrite this module performs;
every other character reaches `Predicator.Duration.parse/1` untouched.

# `value`

```elixir
@type value() :: binary() | Predicator.Types.duration()
```

An SCXML duration designation: the raw `delay` string, or a native predicator duration value off `delayexpr`.

# `normalize_leading_dot`

```elixir
@spec normalize_leading_dot(value :: binary()) :: binary()
```

Rewrites a leading `.` to `0.` (`".5s"` -> `"0.5s"`) - the concession the
SCXML schema's `\d*(\.\d+)?(ms|s|m|h|d)` pattern requires and
`Predicator.Duration.parse/1` does not make on its own. A string with no
leading dot passes through unchanged.

# `to_ms`

```elixir
@spec to_ms(value :: value()) :: {:ok, non_neg_integer()} | {:error, term()}
```

Resolves an SCXML duration designation to whole milliseconds.

Accepts either a `delay`-style string (parsed via
`Predicator.Duration.parse/1`, after `normalize_leading_dot/1`) or a
native `Predicator.Duration` value straight off `delayexpr` (a computed
duration such as `2s + backoff` needs no parsing - `delayexpr` already
evaluated it to a duration map). A string that fails to parse - a bad
unit, a sub-millisecond fractional remainder, or anything else
`Predicator.Duration.parse/1` refuses - is `{:error, {:invalid_delay,
value}}`; the caller is responsible for treating that as an argument
failure (ADR-0036 governs `<send>`'s own case).

---

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