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.

Operations

instant

instant : () ->{Clock} InstantSource

The current moment.

zone

zone : () ->{Clock} ZoneSource

The default zone that now and today use.

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.