Skip to content

DateTime and Duration

Two complementary types model time in Effect. A Duration is a span — “5 seconds”, “2 hours” — used for timeouts, retries, and scheduling. A DateTime is an absolute instant on the timeline, optionally carrying a time zone. They work together: subtracting two DateTimes gives a Duration, and adding a Duration to a DateTime produces a new instant.

Crucially, you never read the wall clock directly. Instead of Date.now(), obtain the current time from the Clock service via DateTime.now. This keeps your code deterministic and testable — tests can supply a fixed clock (see Testing).

import { DateTime, Duration, Effect } from "effect"
const program = Effect.gen(function* () {
// Read the current instant from the Clock service (not Date.now)
const now = yield* DateTime.now
// Add a span of time to get a future instant
const inOneHour = DateTime.add(now, { hours: 1 })
// The gap between two instants is a Duration
const gap = DateTime.distance(now, inOneHour)
console.log(DateTime.formatIso(inOneHour))
console.log(Duration.format(gap)) // "1h"
})
Effect.runPromise(program)

Each constructor takes a number in the named unit and returns a Duration:

import { Duration } from "effect"
const d1 = Duration.seconds(30)
const d2 = Duration.minutes(5)
const d3 = Duration.hours(2)
const d4 = Duration.millis(1500)
// A Duration also has well-known constants
const forever = Duration.infinity
const none = Duration.zero

Many Effect APIs accept a DurationInput directly, so you can pass a human-readable string or tuple instead of constructing a Duration:

import { Effect } from "effect"
// "2 seconds" is a valid DurationInput
const delayed = Effect.succeed(1).pipe(Effect.delay("2 seconds"))
import { Duration } from "effect"
// Add two spans together
console.log(Duration.format(Duration.sum(Duration.minutes(1), Duration.seconds(30))))
// "1m 30s"
// Scale a span
console.log(Duration.format(Duration.times(Duration.seconds(10), 3)))
// "30s"
// Convert to a primitive at the boundary
console.log(Duration.toMillis(Duration.seconds(2))) // 2000
// Compare via the provided Order / range checks
console.log(
Duration.between(Duration.seconds(5), {
minimum: Duration.seconds(1),
maximum: Duration.seconds(10)
})
) // true

Duration.format renders a human-readable string, which is handy in logs. For ordering, Duration.Order is an Order<Duration> you can pass to sorting and comparison utilities.

A DateTime is either DateTime.Utc (an instant with no associated zone) or DateTime.Zoned (an instant plus a time zone). DateTime.now returns a Utc.

DateTime.add / DateTime.subtract shift an instant by calendar-aware parts (days, months, years) or fixed parts (hours, minutes), returning a new instant:

import { DateTime, Effect } from "effect"
const program = Effect.gen(function* () {
const now = yield* DateTime.now
const tomorrow = DateTime.add(now, { days: 1 })
const lastWeek = DateTime.subtract(now, { weeks: 1 })
// distance is signed and returns a Duration
const span = DateTime.distance(lastWeek, tomorrow)
console.log(DateTime.formatIso(tomorrow))
})
Effect.runPromise(program)

Attach a zone with DateTime.setZoneNamed, which returns an Option because the zone name might be invalid:

import { DateTime, Effect, Option } from "effect"
const program = Effect.gen(function* () {
const now = yield* DateTime.now
const zoned = DateTime.setZoneNamed(now, "America/New_York")
Option.match(zoned, {
onNone: () => console.log("unknown time zone"),
onSome: (dt) => console.log(DateTime.formatIsoZoned(dt))
})
})
Effect.runPromise(program)
  • DateTime.formatIso — ISO 8601 string.
  • DateTime.format — locale-aware formatting via Intl.DateTimeFormat options.
  • DateTime.toDateUtc — convert to a native Date at the boundary of your code.

Like the other data types here, DateTime and Duration compare by value, so two equal instants are Equal.equals.