Current section
Files
Jump to
Current section
Files
lib/belay/clock.ex
defmodule Belay.Clock do
@moduledoc """
Injectable time source. Everything time-dependent in the engine (backoff,
lease expiry, rate windows, await deadlines, cron slots) reads through this,
so tests advance a `Belay.Clock.Sim` instead of sleeping.
"""
@callback now(term()) :: DateTime.t()
@doc "Read the current time from a clock module or `{module, argument}` pair."
@spec now(module() | {module(), term()}) :: DateTime.t()
def now({mod, arg}), do: mod.now(arg)
def now(mod) when is_atom(mod), do: mod.now(nil)
end
defmodule Belay.Clock.System do
@moduledoc "Real time."
@behaviour Belay.Clock
@impl true
def now(_), do: DateTime.utc_now()
end
defmodule Belay.Clock.Sim do
@moduledoc "A settable, advanceable clock for deterministic tests."
@behaviour Belay.Clock
@doc "Start a simulated clock at `start`."
@spec start_link(DateTime.t()) :: Agent.on_start()
def start_link(start \\ ~U[2026-01-01 00:00:00.000000Z]) do
Agent.start_link(fn -> start end)
end
@impl true
def now(pid), do: Agent.get(pid, & &1)
@doc "Advance the simulated clock by a number of seconds."
@spec advance(Agent.agent(), number()) :: :ok
def advance(pid, seconds) do
Agent.update(pid, &DateTime.add(&1, round(seconds * 1_000_000), :microsecond))
end
@doc "Set the simulated clock to an exact value."
@spec set(Agent.agent(), DateTime.t()) :: :ok
def set(pid, %DateTime{} = at), do: Agent.update(pid, fn _ -> at end)
end