APITime.Clock
Time.Clock
Scoped access to the current moment and a default zone. Install runSystem or runSystemIn explicitly in production. Tests can freeze time with runFixed, advance each reading with runStepped, or let real time pass from a chosen moment with runOffset. withZone changes only the default zone of an enclosing clock. Existing values never consult this effect for arithmetic. Handlers nest and are inherited by tasks like other effects. None carries Fail; an advancing clock outside the supported instants is a runtime fault.
import Time.Clock as Clock
import Time.Date as Date
import Time.DateTime as DT
import Time.Zone as Zone
import Fail
import Result exposing (Result(..))
result = Fail.attempt {
fixed = DT.parse "2026-01-31T23:00:00-03:00"
Clock.runFixed fixed { (Date.toIso Clock.today(), Date.toIso (Clock.todayIn Zone.utc)) }
}
result == Ok ("2026-01-31", "2026-02-01")
Clock
effect ClockSource
Reads the clock. A main that performs it runs under runSystem unless
it installs another runner; elsewhere it needs a handler.
now
now : () ->{Clock} DateTimeSource
The current moment in the default zone.
nowIn
nowIn : Zone ->{Clock} DateTimeSource
The current moment in the given zone.
runFixed
runFixed : DateTime -> (() ->{Clock, e} a) ->{e} aSource
Answers every reading with the same moment and the value’s zone, without performing IO.
runOffset
runOffset : DateTime -> (() ->{Clock, e} a) ->{IO, e} aSource
Starts at value and advances by real elapsed time measured monotonically from runner entry, performing IO. The first reading may already be later than value. System wall-clock adjustments do not change the progression. Readings use value’s zone; leaving the supported instant range is a runtime fault. Tasks inherit the same elapsed-time origin.
import Time.Clock as Clock
import Time.DateTime as DT
import Fail
import Result exposing (Result(..))
result = Fail.attempt {
start = DT.parse "2000-01-01T00:00:00Z"
Clock.runOffset start { Clock.now() >= start }
}
result == Ok True
runStepped
runStepped : DateTime -> Duration -> (() ->{Clock, e} a) ->{e} aSource
Answers the first instant reading with value, then advances each subsequent reading by step, without IO. step is an exact signed duration: zero freezes time and a negative step moves backwards. now, nowIn, today, and todayIn each consume one reading; zone queries consume none. Readings use value’s zone. A requested instant outside the supported range is a runtime fault; the last valid reading still succeeds.
Nested runners have independent sequences. Tasks that inherit the runner share its sequence: each reading consumes one step exactly once, so simultaneous reads never repeat a value, but which task receives which reading depends on their scheduling.
import Time.Clock as Clock
import Time.DateTime as DT
import Time.Duration as Duration
import Fail
import Result exposing (Result(..))
result = Fail.attempt {
start = DT.parse "2026-01-31T23:59:59Z"
Clock.runStepped start (Duration.seconds 1) {
first = Clock.now()
second = Clock.now()
(DT.toIso first, DT.toIso second)
}
}
result == Ok ("2026-01-31T23:59:59Z", "2026-02-01T00:00:00Z")
runSystem
runSystem : (() ->{Clock, e} a) ->{IO, e} aSource
Samples the system clock once per reading, with the machine’s zone (see Time.Zone.system) as the default, detected when the handler is installed.
runSystemIn
runSystemIn : Zone -> (() ->{Clock, e} a) ->{IO, e} aSource
Samples the system clock once per reading and uses the given default zone. A host clock outside years 1 through 9999 is a runtime fault, not a value.
today
today : () ->{Clock} DateSource
The date from a single reading of the clock, in the default zone.
todayIn
todayIn : Zone ->{Clock} DateSource
The date from a single reading of the clock, in the given zone.
withZone
withZone : Zone -> (() ->{Clock, e} a) ->{Clock, e} aSource
Changes the default zone while readings continue to come from the enclosing clock.
