Skip to content

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. (For a richer, typed timestamp see DateTime and DateTime.now, which itself reads the Clock.)

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.

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()
})

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).

TestClock is its own module with more (make, layer, withLive, testClockWith); the page above is just the common case. For the full reference, see TestClock.

The Clock module is small: three effect-level accessors and the service itself. Everything below imports from the core effect package.

An Effect<number> that succeeds with the current time in milliseconds since the Unix epoch, read from the active Clock. This is the everyday way to ask “what time is it?”.

import { Clock, Effect } from "effect"
const program = Effect.gen(function* () {
const ms = yield* Clock.currentTimeMillis
return ms
// => 1748563200000 (a Unix epoch timestamp in ms)
})

An Effect<bigint> that succeeds with the current time in nanoseconds. Use it when millisecond resolution is too coarse — for high-precision benchmarking or ordering events that happen within the same millisecond.

import { Clock, Effect } from "effect"
const program = Effect.gen(function* () {
const ns = yield* Clock.currentTimeNanos
return ns
// => 1748563200000000000n (a bigint of nanoseconds)
})

clockWith(f) accesses the active Clock service and runs your function f with it. Reach for it when you need the full interface — for example to call the unsafe synchronous accessors — inside a single effect.

import { Clock, Effect } from "effect"
// A custom timed helper built directly on the Clock service.
const timeWith = <A, E, R>(self: Effect.Effect<A, E, R>) =>
Clock.clockWith((clock) =>
Effect.gen(function* () {
const start = clock.currentTimeMillisUnsafe()
const result = yield* self
const elapsed = clock.currentTimeMillisUnsafe() - start
return [result, elapsed] as const
})
)
const program = timeWith(Effect.succeed(42))
// => [42, 0] (value paired with elapsed milliseconds)

The service reference itself. Clock is a Context.Reference, so a live implementation backed by the runtime’s wall clock is always available — you never have to provide it in production. Yield it to obtain the full service interface.

import { Clock, Effect } from "effect"
const program = Effect.gen(function* () {
const clock = yield* Clock.Clock
return clock.currentTimeMillisUnsafe()
// => 1748563200000
})

The Clock interface has these members:

  • currentTimeMillis: Effect<number> — current time in milliseconds, as an effect.
  • currentTimeNanos: Effect<bigint> — current time in nanoseconds, as an effect.
  • sleep(duration: Duration): Effect<void> — suspend for a Duration. (Note this takes a fully-constructed Duration; Effect.sleep is the friendlier entry point that accepts any Duration.Input like "2 seconds".)
  • currentTimeMillisUnsafe(): number — synchronous, non-effectful millisecond read. Only valid once you already hold the service.
  • currentTimeNanosUnsafe(): bigint — synchronous, non-effectful nanosecond read.
import { Clock, Duration, Effect } from "effect"
const program = Effect.gen(function* () {
const clock = yield* Clock.Clock
const ms = clock.currentTimeMillisUnsafe() // => 1748563200000
const ns = clock.currentTimeNanosUnsafe() // => 1748563200000000000n
yield* clock.sleep(Duration.seconds(1)) // suspends 1s (via the Clock)
return { ms, ns }
})

You rarely call clock.sleep directly. The Effect operators for delaying and bounding work — Effect.sleep, Effect.delay, and Effect.timeout — are all implemented in terms of the active Clock, which is exactly why they become instantaneous and deterministic under TestClock. Each accepts any Duration.Input.

import { Effect } from "effect"
const program = Effect.gen(function* () {
// Suspend the fiber for a duration.
yield* Effect.sleep("500 millis")
// Delay an effect before it runs.
yield* Effect.log("later").pipe(Effect.delay("1 second"))
// Bound an effect; fails with a timeout if it does not finish in time.
const result = yield* slowWork.pipe(Effect.timeout("5 seconds"))
return result
})
declare const slowWork: Effect.Effect<string>

See Schedule for repeating and retrying on a clock-driven cadence.


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 controlling the clock in tests, see TestClock and the broader Testing guide.