Clock
Every notion of “now” and every delay in Effect flows through the Clock
service. Schedules, Effect.sleep, Effect.timeout, and retries all ask the
Clock what time it is and to wait — none of them touch Date.now or
setTimeout directly. That single indirection is what makes time in Effect
deterministic and testable: swap the live clock for a TestClock and you control
the passage of time exactly.
import { Clock, Effect } from "effect"
// Measure how long a piece of work takes, reading time from the Clock service.const timed = Effect.gen(function* () { const start = yield* Clock.currentTimeMillis yield* doWork const end = yield* Clock.currentTimeMillis yield* Effect.log(`work took ${end - start}ms`)})
declare const doWork: Effect.Effect<void>Clock.currentTimeMillis and Clock.currentTimeNanos are effects that yield the
current time at millisecond or nanosecond precision. Reaching for Date.now()
would read the wall clock directly, bypassing the runtime and making the code
untestable — always read time through Clock.
Sleeping
Section titled “Sleeping”Effect.sleep suspends the current fiber for a duration. It is interruptible and
implemented on top of the Clock, so it cooperates with concurrency and with the
TestClock:
import { Effect } from "effect"
const program = Effect.gen(function* () { yield* Effect.log("starting") yield* Effect.sleep("2 seconds") // accepts any Duration.Input yield* Effect.log("two seconds later")})Unlike a blocking setTimeout, a sleeping fiber consumes no thread and can be
interrupted — if the surrounding scope is cancelled mid-sleep, the wait ends
immediately. See Concurrency for how fibers and interruption fit
together.
Accessing the full service
Section titled “Accessing the full service”The accessors above cover most needs. When you want the whole service — for
example to call the synchronous currentTimeMillisUnsafe() inside a tight,
already-effectful loop — read the Clock reference or use Clock.clockWith:
import { Clock, Effect } from "effect"
const program = Effect.gen(function* () { const clock = yield* Clock.Clock // Synchronous read; only valid because we already hold the service. return clock.currentTimeMillisUnsafe()})Controlling time in tests
Section titled “Controlling time in tests”The real value of routing time through a service shows up in tests. With
@effect/vitest, it.effect runs your effect against a TestClock whose time
only advances when you tell it to. TestClock.adjust moves the clock forward and
runs anything scheduled to fire on or before the new time — so a five-second
sleep “completes” the instant you advance the clock five seconds, with no real
waiting.
import { assert, describe, it } from "@effect/vitest"import { Effect, Ref } from "effect"import { TestClock } from "effect/testing"
describe("polling", () => { it.effect("ticks once per interval", () => Effect.gen(function* () { const ticks = yield* Ref.make(0)
// Fork a worker that increments a counter every second, forever. yield* Ref.update(ticks, (n) => n + 1).pipe( Effect.delay("1 second"), Effect.forever, Effect.forkChild )
// No real time passes — we drive the clock by hand. yield* TestClock.adjust("1 second") assert.strictEqual(yield* Ref.get(ticks), 1)
yield* TestClock.adjust("3 seconds") assert.strictEqual(yield* Ref.get(ticks), 4) }))})The same technique verifies retry backoff, cron jobs, and timeouts in
microseconds rather than real wall-clock time. To advance to an absolute moment
instead of by a delta, use TestClock.setTime(epochMillis).
Time, like every other capability in Effect, is just a service — which is what lets schedules and retries be tested as easily as pure functions. For more on the testing tools, see Testing.