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 TestClock).
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)Both types are immutable and compare by value: two equal instants are
Equal.equals, and so are two equal durations.
Duration
Section titled “Duration”A Duration stores a span as either an integer milliseconds number, an
integer nanoseconds bigint, or one of the infinity sentinels. It can be
finite or infinite (positive or negative). Constructors pick the representation
for you, and arithmetic preserves it.
Creating durations
Section titled “Creating durations”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 constantsconst forever = Duration.infinityconst none = Duration.zeroDuration.Input
Section titled “Duration.Input”Many Effect APIs (delays, timeouts, schedules) accept a Duration.Input
directly, so you rarely call a constructor at the call site. An input can be:
- a
number— interpreted as milliseconds (not seconds!), - a
bigint— interpreted as nanoseconds, - a
[seconds, nanos]tuple — Node-style high-resolution time, - a string like
"2 seconds"/"500 millis"/"Infinity"/"-Infinity", - a
DurationObjectsuch as{ hours: 1, minutes: 30 }, - or an existing
Duration.
import { Effect } from "effect"
// "2 seconds" is a valid Duration.Inputconst delayed = Effect.succeed(1).pipe(Effect.delay("2 seconds"))Combining and comparing
Section titled “Combining and comparing”The math and comparison helpers are dual (data-first and data-last), so they
work both directly and inside pipe:
import { Duration } from "effect"
// Add two spans togetherconsole.log(Duration.format(Duration.sum(Duration.minutes(1), Duration.seconds(30))))// "1m 30s"
// Scale a spanconsole.log(Duration.format(Duration.times(Duration.seconds(10), 3)))// "30s"
// Convert to a primitive at the boundaryconsole.log(Duration.toMillis(Duration.seconds(2))) // 2000
// Range check via the provided Orderconsole.log( Duration.between(Duration.seconds(5), { minimum: Duration.seconds(1), maximum: Duration.seconds(10) })) // trueDuration.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.
DateTime
Section titled “DateTime”A DateTime is always an absolute instant (epoch milliseconds) and is one of two
variants:
DateTime.Utc— an instant with no associated zone.DateTime.nowreturns aUtc.DateTime.Zoned— the same instant plus aTimeZone, used for wall-clock parts, formatting, and calendar-aware arithmetic.
A TimeZone is either TimeZone.Named (an IANA zone like "Europe/Rome")
or TimeZone.Offset (a fixed offset in milliseconds from UTC). Equality and
ordering use the instant, so two equal instants in different zones are still
equivalent.
When a wall-clock time is ambiguous — during a DST gap or overlap — a
Disambiguation ("compatible" | "earlier" | "later" | "reject")
decides which instant is chosen.
Arithmetic
Section titled “Arithmetic”DateTime.add / DateTime.subtract shift an instant by calendar parts (days,
months, years are time-zone aware) 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)For pure elapsed-time math (always milliseconds, never calendar-aware) use
addDuration / subtractDuration with a Duration.Input.
Time zones
Section titled “Time zones”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)By default attaching a zone keeps the instant fixed (only the display
changes). Pass { adjustForTimeZone: true } to instead interpret the input as
wall-clock time in the target zone.
Ambient current time zone
Section titled “Ambient current time zone”For workflows that operate “in the app’s zone”, provide a CurrentTimeZone
service and use the *Current* helpers instead of threading a zone everywhere:
import { DateTime, Effect } from "effect"
const program = Effect.gen(function* () { // reads the current Clock instant, in the provided zone const now = yield* DateTime.nowInCurrentZone console.log(DateTime.formatIsoZoned(now))}).pipe(DateTime.withCurrentZoneNamed("Europe/London"))
Effect.runPromise(program)Formatting
Section titled “Formatting”DateTime.formatIso— UTC ISO 8601 string.DateTime.format— locale-aware viaIntl.DateTimeFormatoptions.DateTime.formatIsoZoned—YYYY-MM-DDTHH:mm:ss.sss+HH:MM[Time/Zone].DateTime.toDateUtc— convert to a nativeDateat the boundary of your code.
Duration reference
Section titled “Duration reference”Constructors
Section titled “Constructors”fromInput
Section titled “fromInput”Decodes a Duration.Input into a Duration safely, returning Option.none()
on invalid input.
import { Duration, Option } from "effect"
Duration.fromInput("90 seconds").pipe(Option.map(Duration.toSeconds))// => Some(90)Duration.fromInput("nonsense" as any)// => NonefromInputUnsafe
Section titled “fromInputUnsafe”Like fromInput but throws on invalid input.
import { Duration } from "effect"
Duration.fromInputUnsafe([2, 500_000_000]) // 2s + 500ms// => Duration "2s 500ms"Duration.fromInputUnsafe({ days: 1 })// => Duration "1d"A Duration representing no time.
import { Duration } from "effect"
Duration.toMillis(Duration.zero) // => 0infinity / negativeInfinity
Section titled “infinity / negativeInfinity”Sentinels for unbounded spans.
import { Duration } from "effect"
Duration.toMillis(Duration.infinity) // => InfinityDuration.toMillis(Duration.negativeInfinity) // => -Infinitynanos / micros
Section titled “nanos / micros”Build a finite duration from a bigint of nanoseconds or microseconds.
import { Duration } from "effect"
Duration.toMillis(Duration.nanos(BigInt(500_000_000))) // => 500Duration.toMillis(Duration.micros(BigInt(500_000))) // => 500millis / seconds / minutes / hours / days / weeks
Section titled “millis / seconds / minutes / hours / days / weeks”Build a finite duration from a number in the named unit.
import { Duration } from "effect"
Duration.toMillis(Duration.millis(1000)) // => 1000Duration.toMillis(Duration.seconds(30)) // => 30000Duration.toMillis(Duration.minutes(5)) // => 300000Duration.toMillis(Duration.hours(2)) // => 7200000Duration.toMillis(Duration.days(1)) // => 86400000Duration.toMillis(Duration.weeks(1)) // => 604800000Guards and predicates
Section titled “Guards and predicates”isDuration
Section titled “isDuration”Type guard for Duration values.
import { Duration } from "effect"
Duration.isDuration(Duration.seconds(1)) // => trueDuration.isDuration(1000) // => falseisFinite
Section titled “isFinite”true for any non-infinite duration.
import { Duration } from "effect"
Duration.isFinite(Duration.seconds(5)) // => trueDuration.isFinite(Duration.infinity) // => falseisZero
Section titled “isZero”true if the duration is exactly zero.
import { Duration } from "effect"
Duration.isZero(Duration.zero) // => trueDuration.isZero(Duration.seconds(1)) // => falseisNegative / isPositive
Section titled “isNegative / isPositive”Strict sign checks (zero is neither).
import { Duration } from "effect"
Duration.isNegative(Duration.seconds(-5)) // => trueDuration.isNegative(Duration.zero) // => falseDuration.isPositive(Duration.seconds(5)) // => trueDuration.isPositive(Duration.infinity) // => trueConversions and getters
Section titled “Conversions and getters”toMillis / toSeconds / toMinutes / toHours / toDays / toWeeks
Section titled “toMillis / toSeconds / toMinutes / toHours / toDays / toWeeks”Convert to a number in the requested unit. These accept a Duration.Input, so
you can pass a string directly.
import { Duration } from "effect"
Duration.toMillis(Duration.seconds(5)) // => 5000Duration.toSeconds(Duration.minutes(2)) // => 120Duration.toMinutes(Duration.hours(1)) // => 60Duration.toHours(Duration.days(1)) // => 24Duration.toDays(Duration.weeks(1)) // => 7Duration.toWeeks(Duration.days(14)) // => 2toNanos
Section titled “toNanos”Get nanoseconds as Option<bigint> — None for infinite durations.
import { Duration } from "effect"
Duration.toNanos(Duration.seconds(1)) // => Some(1000000000n)Duration.toNanos(Duration.infinity) // => NonetoNanosUnsafe
Section titled “toNanosUnsafe”Get nanoseconds as a bigint, throwing for infinite durations.
import { Duration } from "effect"
Duration.toNanosUnsafe(Duration.seconds(2)) // => 2000000000n// Duration.toNanosUnsafe(Duration.infinity) // throwstoHrTime
Section titled “toHrTime”Convert to Node-style [seconds, nanoseconds] high-resolution time.
import { Duration } from "effect"
Duration.toHrTime(Duration.millis(1500)) // => [1, 500000000]Decompose a finite duration into normalized signed components.
import { Duration } from "effect"
Duration.parts(Duration.sum(Duration.hours(25), Duration.minutes(90)))// => { days: 1, hours: 2, minutes: 30, seconds: 0, millis: 0, nanos: 0 }format
Section titled “format”Render a human-readable string (used by the examples above).
import { Duration } from "effect"
Duration.format(Duration.millis(1000)) // => "1s"Duration.format(Duration.millis(1001)) // => "1s 1ms"Duration.format(Duration.infinity) // => "Infinity"All of these are dual (data-first / data-last).
sum / subtract
Section titled “sum / subtract”Add or subtract two durations. The result of subtract can be negative.
Infinity follows IEEE-like rules (e.g. infinity - infinity = zero).
import { Duration } from "effect"
Duration.toSeconds(Duration.sum(Duration.seconds(5), Duration.seconds(3))) // => 8Duration.toSeconds(Duration.subtract(Duration.seconds(10), Duration.seconds(3))) // => 7Multiply a duration by a number.
import { Duration } from "effect"
Duration.toSeconds(Duration.times(Duration.seconds(5), 2)) // => 10divide
Section titled “divide”Safely divide by a finite, non-zero number; returns Option.
import { Duration, Option } from "effect"
Duration.divide(Duration.seconds(10), 2).pipe(Option.map(Duration.toSeconds))// => Some(5)Duration.divide(Duration.seconds(10), 0) // => NonedivideUnsafe
Section titled “divideUnsafe”Divide using fallback rules instead of Option (e.g. divide-by-zero of a finite
duration yields signed infinity).
import { Duration } from "effect"
Duration.toSeconds(Duration.divideUnsafe(Duration.seconds(10), 2)) // => 5Duration.toMillis(Duration.divideUnsafe(Duration.seconds(10), 0)) // => Infinitynegate / abs
Section titled “negate / abs”Flip the sign, or take the absolute value.
import { Duration } from "effect"
Duration.toMillis(Duration.negate(Duration.seconds(5))) // => -5000Duration.toMillis(Duration.abs(Duration.seconds(-5))) // => 5000min / max
Section titled “min / max”Select the shorter or longer of two durations.
import { Duration } from "effect"
Duration.toSeconds(Duration.min(Duration.seconds(5), Duration.seconds(3))) // => 3Duration.toSeconds(Duration.max(Duration.seconds(5), Duration.seconds(3))) // => 5Constrain a duration to an inclusive [minimum, maximum] range.
import { Duration } from "effect"
Duration.toSeconds( Duration.clamp(Duration.seconds(10), { minimum: Duration.seconds(2), maximum: Duration.seconds(5) })) // => 5between
Section titled “between”true if a duration is within an inclusive range.
import { Duration } from "effect"
Duration.between(Duration.seconds(3), { minimum: Duration.seconds(2), maximum: Duration.seconds(5)}) // => trueComparison and equivalence
Section titled “Comparison and equivalence”An Order<Duration> (NegativeInfinity < finite < Infinity). Use it with
sorting and Array/Order utilities.
import { Array, Duration } from "effect"
Array.sort([Duration.seconds(3), Duration.seconds(1), Duration.seconds(2)], Duration.Order) .map(Duration.toSeconds)// => [1, 2, 3]Equivalence / equals
Section titled “Equivalence / equals”Value equality (independent of internal representation).
import { Duration } from "effect"
Duration.Equivalence(Duration.seconds(5), Duration.millis(5000)) // => trueDuration.equals(Duration.seconds(5), Duration.millis(5000)) // => trueisLessThan / isLessThanOrEqualTo / isGreaterThan / isGreaterThanOrEqualTo
Section titled “isLessThan / isLessThanOrEqualTo / isGreaterThan / isGreaterThanOrEqualTo”Pairwise comparisons (dual).
import { Duration } from "effect"
Duration.isLessThan(Duration.seconds(3), Duration.seconds(5)) // => trueDuration.isLessThanOrEqualTo(Duration.seconds(5), Duration.seconds(5)) // => trueDuration.isGreaterThan(Duration.seconds(5), Duration.seconds(3)) // => trueDuration.isGreaterThanOrEqualTo(Duration.seconds(5), Duration.seconds(5)) // => truePattern matching
Section titled “Pattern matching”Match on the underlying representation: millis, nanos, or infinity.
onNegativeInfinity is optional and falls back to onInfinity.
import { Duration } from "effect"
Duration.match(Duration.seconds(5), { onMillis: (millis) => `${millis} milliseconds`, onNanos: (nanos) => `${nanos} nanoseconds`, onInfinity: () => "infinite"}) // => "5000 milliseconds"matchPair
Section titled “matchPair”Match on two durations at once. Effect picks the larger-precision
representation: if either side is nanosecond-backed, both are handed to
onNanos; otherwise onMillis; any infinity routes to onInfinity.
import { Duration } from "effect"
Duration.matchPair(Duration.seconds(3), Duration.seconds(2), { onMillis: (a, b) => a + b, onNanos: (a, b) => Number(a + b), onInfinity: () => Infinity}) // => 5000Combiners and reducers
Section titled “Combiners and reducers”These come from the Combiner / Reducer traits and are consumed by APIs that
fold over many durations (for example via Array/Iterable reduction).
ReducerSum
Section titled “ReducerSum”Sums many durations, starting from Duration.zero. combineAll folds an
iterable; combineAll([]) returns Duration.zero.
import { Duration } from "effect"
Duration.ReducerSum.combineAll([ Duration.seconds(1), Duration.seconds(2), Duration.seconds(3)]).pipe(Duration.toSeconds) // => 6CombinerMax / CombinerMin
Section titled “CombinerMax / CombinerMin”Keep the longest or shortest duration when combining.
import { Duration } from "effect"
Duration.CombinerMax.combine(Duration.seconds(2), Duration.seconds(5)).pipe(Duration.toSeconds) // => 5Duration.CombinerMin.combine(Duration.seconds(2), Duration.seconds(5)).pipe(Duration.toSeconds) // => 2Duration— the public value (Equal,Pipeable,Inspectable).DurationValue— the tagged internal representation:{ _tag: "Millis"; millis } | { _tag: "Nanos"; nanos } | { _tag: "Infinity" } | { _tag: "NegativeInfinity" }.Unit— string unit names accepted in inputs ("second"/"seconds", …).Input— everything an API can decode into aDuration(see Duration.Input).DurationObject— additive object input with optionalweeks,days,hours,minutes,seconds,milliseconds,microseconds,nanoseconds.
DateTime reference
Section titled “DateTime reference”Construction
Section titled “Construction”Safely build a DateTime from an input, returning Option. Inputs: a
DateTime, a Date, epoch number, a parts object, or a parseable string. A
zoned input is preserved as Zoned; everything else becomes Utc.
import { DateTime } from "effect"
DateTime.make("2024-01-01")._tag // => "Some"DateTime.make("not a date")._tag // => "None"makeUnsafe
Section titled “makeUnsafe”Like make but throws on invalid input.
import { DateTime } from "effect"
DateTime.formatIso(DateTime.makeUnsafe({ year: 2024 }))// => "2024-01-01T00:00:00.000Z"makeZoned
Section titled “makeZoned”Safely build a DateTime.Zoned. By default the input is a UTC instant and the
zone is attached without moving it; with adjustForTimeZone: true the input is
treated as wall-clock time in the zone.
import { DateTime } from "effect"
DateTime.makeZoned("2024-06-15T14:30:00Z", { timeZone: "Europe/London" })._tag// => "Some"makeZonedUnsafe
Section titled “makeZonedUnsafe”Like makeZoned but throws on invalid input.
import { DateTime } from "effect"
DateTime.formatIsoZoned( DateTime.makeZonedUnsafe("2024-06-15T14:30:00Z", { timeZone: "Europe/London" })) // => "2024-06-15T15:30:00.000+01:00[Europe/London]"makeZonedFromString
Section titled “makeZonedFromString”Parse a zoned ISO string (offset-only or [Time/Zone] form) into Option<Zoned>.
import { DateTime } from "effect"
DateTime.makeZonedFromString("2024-01-01T12:00:00+02:00[Europe/Berlin]")._tag// => "Some"DateTime.makeZonedFromString("invalid")._tag // => "None"fromDateUnsafe
Section titled “fromDateUnsafe”Build a Utc from a JavaScript Date (throws on an invalid Date).
import { DateTime } from "effect"
DateTime.formatIso(DateTime.fromDateUnsafe(new Date("2024-01-01T12:00:00Z")))// => "2024-01-01T12:00:00.000Z"now / nowAsDate / nowUnsafe
Section titled “now / nowAsDate / nowUnsafe”Read the current instant. now is an Effect<Utc> from the Clock;
nowAsDate returns an Effect<Date>; nowUnsafe reads Date.now() directly
(prefer the Clock-based versions in Effect code).
import { DateTime, Effect } from "effect"
const eff = DateTime.now // => Effect<DateTime.Utc>const asDate = DateTime.nowAsDate // => Effect<Date>const sync = DateTime.nowUnsafe() // => DateTime.UtcnowInCurrentZone
Section titled “nowInCurrentZone”Effect<Zoned, never, CurrentTimeZone> — current instant in the ambient zone.
import { DateTime } from "effect"
DateTime.nowInCurrentZone.pipe(DateTime.withCurrentZoneNamed("Asia/Tokyo"))// => Effect<DateTime.Zoned>Guards
Section titled “Guards”isDateTime / isTimeZone / isTimeZoneOffset / isTimeZoneNamed
Section titled “isDateTime / isTimeZone / isTimeZoneOffset / isTimeZoneNamed”Narrow unknown values to DateTime / TimeZone and its variants.
import { DateTime } from "effect"
DateTime.isDateTime(DateTime.makeUnsafe(0)) // => trueconst zone = DateTime.zoneMakeOffset(3_600_000)DateTime.isTimeZone(zone) // => trueDateTime.isTimeZoneOffset(zone) // => trueDateTime.isTimeZoneNamed(zone) // => falseisUtc / isZoned
Section titled “isUtc / isZoned”Narrow a known DateTime to its variant.
import { DateTime } from "effect"
DateTime.isUtc(DateTime.makeUnsafe(0)) // => trueDateTime.isZoned(DateTime.makeZonedUnsafe(0, { timeZone: "UTC" })) // => trueTime zones
Section titled “Time zones”setZone / setZoneOffset
Section titled “setZone / setZoneOffset”Attach a TimeZone (or a fixed offset in ms) to a DateTime, returning Zoned.
import { DateTime } from "effect"
const z = DateTime.zoneMakeNamedUnsafe("Europe/London")DateTime.setZone(DateTime.makeUnsafe(0), z) // => DateTime.ZonedDateTime.setZoneOffset(DateTime.makeUnsafe(0), 3 * 60 * 60 * 1000) // => DateTime.Zoned (+03:00)setZoneNamed / setZoneNamedUnsafe
Section titled “setZoneNamed / setZoneNamedUnsafe”Attach a named zone by IANA id. The safe version returns Option; the unsafe
version throws on an invalid id.
import { DateTime } from "effect"
DateTime.setZoneNamed(DateTime.makeUnsafe(0), "Europe/London")._tag // => "Some"DateTime.setZoneNamedUnsafe(DateTime.makeUnsafe(0), "Europe/London") // => DateTime.ZonedDrop the zone, keeping the same instant, returning Utc.
import { DateTime } from "effect"
const zoned = DateTime.makeZonedUnsafe(0, { timeZone: "Europe/London" })DateTime.formatIso(DateTime.toUtc(zoned)) // => "1970-01-01T00:00:00.000Z"zoneMakeNamed / zoneMakeNamedUnsafe / zoneMakeNamedEffect
Section titled “zoneMakeNamed / zoneMakeNamedUnsafe / zoneMakeNamedEffect”Construct a TimeZone.Named from an IANA id — as Option, throwing, or as an
Effect failing with IllegalArgumentError.
import { DateTime } from "effect"
DateTime.zoneMakeNamed("Europe/London")._tag // => "Some"DateTime.zoneToString(DateTime.zoneMakeNamedUnsafe("Asia/Tokyo")) // => "Asia/Tokyo"DateTime.zoneMakeNamedEffect("Europe/London") // => Effect<TimeZone.Named, IllegalArgumentError>zoneMakeOffset / zoneMakeLocal
Section titled “zoneMakeOffset / zoneMakeLocal”Construct a fixed-offset zone (offset in ms), or the system’s local named zone.
import { DateTime } from "effect"
DateTime.zoneToString(DateTime.zoneMakeOffset(3 * 60 * 60 * 1000)) // => "+03:00"DateTime.zoneMakeLocal() // => TimeZone.Named (system zone)zoneFromString / zoneToString
Section titled “zoneFromString / zoneToString”Parse a zone from an IANA id or ±HH:MM offset (Option), and render one back.
import { DateTime } from "effect"
DateTime.zoneFromString("Europe/London")._tag // => "Some"DateTime.zoneFromString("+03:00")._tag // => "Some"DateTime.zoneToString(DateTime.zoneMakeOffset(0)) // => "+00:00"zonedOffset / zonedOffsetIso
Section titled “zonedOffset / zonedOffsetIso”Read a Zoned value’s offset, in milliseconds or as an ISO ±HH:MM string.
import { DateTime } from "effect"
const z = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { timeZone: DateTime.zoneMakeOffset(3 * 60 * 60 * 1000)})DateTime.zonedOffset(z) // => 10800000DateTime.zonedOffsetIso(z) // => "+03:00"Ambient current time zone
Section titled “Ambient current time zone”The CurrentTimeZone service supplies a zone to the *Current* helpers, so you
do not have to pass it everywhere.
CurrentTimeZone
Section titled “CurrentTimeZone”The Context.Service holding the ambient TimeZone.
import { DateTime, Effect } from "effect"
Effect.gen(function* () { const zone = yield* DateTime.CurrentTimeZone console.log(DateTime.zoneToString(zone))}).pipe(DateTime.withCurrentZoneNamed("Europe/London"))setZoneCurrent
Section titled “setZoneCurrent”Attach the ambient zone to a DateTime.
import { DateTime, Effect } from "effect"
Effect.gen(function* () { const now = yield* DateTime.now const zoned = yield* DateTime.setZoneCurrent(now) // => DateTime.Zoned}).pipe(DateTime.withCurrentZoneNamed("Europe/London"))withCurrentZone / withCurrentZoneLocal / withCurrentZoneOffset / withCurrentZoneNamed
Section titled “withCurrentZone / withCurrentZoneLocal / withCurrentZoneOffset / withCurrentZoneNamed”Provide CurrentTimeZone to an effect from a TimeZone, the system zone, a
fixed offset (ms), or an IANA id (the named version may fail with
IllegalArgumentError).
import { DateTime, Effect } from "effect"
const z = DateTime.zoneMakeNamedUnsafe("Europe/London")DateTime.nowInCurrentZone.pipe(DateTime.withCurrentZone(z))DateTime.nowInCurrentZone.pipe(DateTime.withCurrentZoneLocal)DateTime.nowInCurrentZone.pipe(DateTime.withCurrentZoneOffset(3 * 60 * 60 * 1000))DateTime.nowInCurrentZone.pipe(DateTime.withCurrentZoneNamed("Asia/Tokyo"))layerCurrentZone / layerCurrentZoneOffset / layerCurrentZoneNamed / layerCurrentZoneLocal
Section titled “layerCurrentZone / layerCurrentZoneOffset / layerCurrentZoneNamed / layerCurrentZoneLocal”Layer versions for app-wide wiring (the named layer can fail with
IllegalArgumentError).
import { DateTime } from "effect"
DateTime.layerCurrentZone(DateTime.zoneMakeNamedUnsafe("Europe/London"))DateTime.layerCurrentZoneOffset(3 * 60 * 60 * 1000)DateTime.layerCurrentZoneNamed("Europe/London")DateTime.layerCurrentZoneLocalComparison
Section titled “Comparison”Order / Equivalence
Section titled “Order / Equivalence”Order by epoch milliseconds; equivalence is instant-based regardless of zone.
import { Array, DateTime } from "effect"
Array.sort( [DateTime.makeUnsafe("2024-03-01"), DateTime.makeUnsafe("2024-01-01")], DateTime.Order) // => [2024-01-01, 2024-03-01]
DateTime.Equivalence( DateTime.makeUnsafe("2024-01-01T12:00:00Z"), DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { timeZone: "Europe/London" })) // => trueclamp / min / max / between
Section titled “clamp / min / max / between”Constrain or select instants; between is an inclusive range check.
import { DateTime } from "effect"
const min = DateTime.makeUnsafe("2024-01-01")const max = DateTime.makeUnsafe("2024-12-31")
DateTime.min(min, max) // => 2024-01-01DateTime.max(min, max) // => 2024-12-31DateTime.clamp(DateTime.makeUnsafe("2025-06-15"), { minimum: min, maximum: max }) // => 2024-12-31DateTime.between(DateTime.makeUnsafe("2024-06-15"), { minimum: min, maximum: max }) // => trueisGreaterThan / isGreaterThanOrEqualTo / isLessThan / isLessThanOrEqualTo
Section titled “isGreaterThan / isGreaterThanOrEqualTo / isLessThan / isLessThanOrEqualTo”Pairwise comparisons (dual).
import { DateTime } from "effect"
const a = DateTime.makeUnsafe("2024-02-01")const b = DateTime.makeUnsafe("2024-01-01")
DateTime.isGreaterThan(a, b) // => trueDateTime.isGreaterThanOrEqualTo(b, b) // => trueDateTime.isLessThan(b, a) // => trueDateTime.isLessThanOrEqualTo(b, b) // => trueisFuture / isPast (and Unsafe)
Section titled “isFuture / isPast (and Unsafe)”Compare against “now”. The effectful versions read the Clock; the *Unsafe
versions read Date.now() synchronously.
import { DateTime, Effect } from "effect"
const later = DateTime.add(DateTime.nowUnsafe(), { hours: 1 })DateTime.isFutureUnsafe(later) // => trueDateTime.isPastUnsafe(later) // => false
Effect.gen(function* () { return yield* DateTime.isFuture(later) // => Effect<boolean>})distance
Section titled “distance”Signed Duration between two instants (positive when other is later).
import { DateTime } from "effect"
DateTime.distance( DateTime.makeUnsafe(0), DateTime.makeUnsafe(60_000)) // => Duration "1m"Arithmetic
Section titled “Arithmetic”add / subtract
Section titled “add / subtract”Calendar-aware part arithmetic. Day/week/month/year amounts respect the zone.
import { DateTime } from "effect"
DateTime.formatIso(DateTime.add(DateTime.makeUnsafe(0), { days: 1 }))// => "1970-01-02T00:00:00.000Z"DateTime.formatIso(DateTime.subtract(DateTime.makeUnsafe(0), { minutes: 5 }))// => "1969-12-31T23:55:00.000Z"addDuration / subtractDuration
Section titled “addDuration / subtractDuration”Elapsed-time math (always milliseconds, not calendar-aware). Accepts a
Duration.Input.
import { DateTime, Duration } from "effect"
DateTime.makeUnsafe(0).pipe(DateTime.addDuration("5 minutes")) // => +5m instantDateTime.makeUnsafe(0).pipe(DateTime.subtractDuration(Duration.hours(1))) // => -1h instantmutate / mutateUtc
Section titled “mutate / mutateUtc”Adjust calendar fields with a mutable Date copy — in the value’s own zone, or
in UTC.
import { DateTime } from "effect"
DateTime.formatIso( DateTime.mutate(DateTime.makeUnsafe("2024-01-01T12:00:00Z"), (d) => { d.setHours(15) d.setMinutes(30) })) // => "2024-01-01T15:30:00.000Z"mapEpochMillis
Section titled “mapEpochMillis”Transform the underlying epoch milliseconds directly.
import { DateTime } from "effect"
DateTime.makeUnsafe(0).pipe(DateTime.mapEpochMillis((ms) => ms + 10))// => instant at 10msstartOf / endOf / nearest
Section titled “startOf / endOf / nearest”Round to the start, end, or nearest boundary of a unit. For "week", pass
{ weekStartsOn } (0 = Sunday).
import { DateTime } from "effect"
DateTime.makeUnsafe("2024-01-01T12:00:00Z").pipe(DateTime.startOf("day"), DateTime.formatIso)// => "2024-01-01T00:00:00.000Z"DateTime.makeUnsafe("2024-01-01T12:00:00Z").pipe(DateTime.endOf("day"), DateTime.formatIso)// => "2024-01-01T23:59:59.999Z"DateTime.makeUnsafe("2024-01-01T12:01:00Z").pipe(DateTime.nearest("day"), DateTime.formatIso)// => "2024-01-02T00:00:00.000Z"removeTime
Section titled “removeTime”Strip the time, keeping just the (zone-adjusted) date as Utc.
import { DateTime } from "effect"
DateTime.makeUnsafe("2024-01-01T23:30:00Z").pipe(DateTime.removeTime, DateTime.formatIso)// => "2024-01-01T00:00:00.000Z"withDate / withDateUtc
Section titled “withDate / withDateUtc”Run a function against a Date view — zone-adjusted, or UTC — and return its
result.
import { DateTime } from "effect"
DateTime.makeUnsafe(0).pipe(DateTime.withDateUtc((d) => d.getTime())) // => 0Parts and conversion
Section titled “Parts and conversion”toParts / toPartsUtc
Section titled “toParts / toPartsUtc”Get all calendar parts (with weekDay) — zone-adjusted, or always UTC.
import { DateTime } from "effect"
DateTime.toParts(DateTime.makeUnsafe("2024-01-01T12:30:45.123Z"))// => { year: 2024, month: 1, day: 1, hour: 12, minute: 30, second: 45, millisecond: 123, weekDay: 1 }getPart / getPartUtc
Section titled “getPart / getPartUtc”Read one part by key — zone-adjusted, or UTC.
import { DateTime } from "effect"
DateTime.getPartUtc(DateTime.makeUnsafe({ year: 2024 }), "year") // => 2024setParts / setPartsUtc
Section titled “setParts / setPartsUtc”Return a new value with the given parts replaced — zone-adjusted, or UTC.
import { DateTime } from "effect"
DateTime.formatIso( DateTime.setPartsUtc(DateTime.makeUnsafe("2024-01-01T12:00:00Z"), { year: 2025, hour: 18 })) // => "2025-01-01T18:00:00.000Z"toDate / toDateUtc
Section titled “toDate / toDateUtc”Convert to a native Date — zone-adjusted wall-clock, or the raw UTC instant.
import { DateTime } from "effect"
DateTime.toDateUtc(DateTime.makeUnsafe("2024-01-01T12:00:00Z")).toISOString()// => "2024-01-01T12:00:00.000Z"toEpochMillis
Section titled “toEpochMillis”Get milliseconds since the Unix epoch (always UTC).
import { DateTime } from "effect"
DateTime.toEpochMillis(DateTime.makeUnsafe("2024-01-01T00:00:00Z"))// => 1704067200000Pattern matching
Section titled “Pattern matching”Handle Utc and Zoned cases separately.
import { DateTime } from "effect"
DateTime.match(DateTime.makeUnsafe("2024-01-01T12:00:00Z"), { onUtc: (utc) => `UTC: ${DateTime.formatIso(utc)}`, onZoned: (zoned) => `Zoned: ${DateTime.formatIsoZoned(zoned)}`}) // => "UTC: 2024-01-01T12:00:00.000Z"Formatting
Section titled “Formatting”format / formatLocal / formatUtc
Section titled “format / formatLocal / formatUtc”Intl.DateTimeFormat-based formatting. format uses the value’s zone (UTC for
Utc); formatLocal uses the system zone/locale; formatUtc forces UTC. All
accept Intl.DateTimeFormatOptions plus an optional locale.
import { DateTime } from "effect"
const dt = DateTime.makeZonedUnsafe("2024-06-15T14:30:00Z", { timeZone: "Europe/London" })DateTime.format(dt, { dateStyle: "full", timeStyle: "short", locale: "en-US" })// => "Saturday, June 15, 2024 at 3:30 PM"formatIntl
Section titled “formatIntl”Format using an existing Intl.DateTimeFormat instance (it controls
locale/zone/options).
import { DateTime } from "effect"
const fmt = new Intl.DateTimeFormat("de-DE", { dateStyle: "long", timeZone: "Europe/Berlin" })DateTime.formatIntl(DateTime.makeUnsafe("2024-06-15T14:30:00Z"), fmt)// => "15. Juni 2024"formatIso
Section titled “formatIso”UTC ISO 8601 string.
import { DateTime } from "effect"
DateTime.formatIso(DateTime.makeUnsafe("2024-01-01T12:30:45.123Z"))// => "2024-01-01T12:30:45.123Z"formatIsoDate / formatIsoDateUtc
Section titled “formatIsoDate / formatIsoDateUtc”Date-only ISO string — zone-adjusted, or always UTC.
import { DateTime } from "effect"
const z = DateTime.makeZonedUnsafe("2024-01-01T23:30:00Z", { timeZone: "Pacific/Auckland" })DateTime.formatIsoDate(z) // => "2024-01-02" (next day in Auckland)DateTime.formatIsoDateUtc(z) // => "2024-01-01"formatIsoOffset
Section titled “formatIsoOffset”ISO string including the offset (same as formatIso for Utc).
import { DateTime } from "effect"
const z = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", { timeZone: DateTime.zoneMakeOffset(3 * 60 * 60 * 1000)})DateTime.formatIsoOffset(z) // => "2024-01-01T15:00:00.000+03:00"formatIsoZoned
Section titled “formatIsoZoned”Full zoned string: YYYY-MM-DDTHH:mm:ss.sss+HH:MM[Time/Zone].
import { DateTime } from "effect"
DateTime.formatIsoZoned( DateTime.makeZonedUnsafe("2024-06-15T14:30:45.123Z", { timeZone: "Europe/London" })) // => "2024-06-15T15:30:45.123+01:00[Europe/London]"DateTime—Utc | Zoned.Utc— instant with no zone (_tag: "Utc").Zoned— instant plus aTimeZone(_tag: "Zoned").TimeZone—TimeZone.Offset(fixedoffsetin ms) orTimeZone.Named(IANAid).Disambiguation—"compatible" | "earlier" | "later" | "reject"for DST gaps/overlaps.DateTime.Parts/PartsWithWeekday— calendar components (one-basedmonth);PartsWithWeekdayaddsweekDay(0 = Sunday).DateTime.PartsForMath— plural amounts (days,weeks,months,years, …) accepted byadd/subtract.