# 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 `DateTime`s 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`](https://effect.plants.sh/scheduling/clock/) service via
`DateTime.now`. This keeps your code deterministic and testable — tests can
supply a fixed clock (see [TestClock](https://effect.plants.sh/testing/testclock/)).

```ts
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

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

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

```ts
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
```

### 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 `DurationObject` such as `{ hours: 1, minutes: 30 }`,
- or an existing `Duration`.

```ts
import { Effect } from "effect"

// "2 seconds" is a valid Duration.Input
const delayed = Effect.succeed(1).pipe(Effect.delay("2 seconds"))
```

<aside class="caution">
A plain number means **milliseconds**. `Effect.delay(5)` waits 5ms, not 5s —
write `Effect.delay("5 seconds")` or `Effect.delay(Duration.seconds(5))`.
</aside>

### Combining and comparing

The math and comparison helpers are **dual** (data-first and data-last), so they
work both directly and inside `pipe`:

```ts
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

// Range check via the provided Order
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.

## 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.now`
  returns a `Utc`.
- **`DateTime.Zoned`** — the same instant plus a `TimeZone`, 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

`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:

```ts
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

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

```ts
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

For workflows that operate "in the app's zone", provide a `CurrentTimeZone`
service and use the `*Current*` helpers instead of threading a zone everywhere:

```ts
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

- `DateTime.formatIso` — UTC ISO 8601 string.
- `DateTime.format` — locale-aware via `Intl.DateTimeFormat` options.
- `DateTime.formatIsoZoned` — `YYYY-MM-DDTHH:mm:ss.sss+HH:MM[Time/Zone]`.
- `DateTime.toDateUtc` — convert to a native `Date` at the boundary of your code.

## Duration reference

### Constructors

#### fromInput

Decodes a `Duration.Input` into a `Duration` safely, returning `Option.none()`
on invalid input.

```ts
import { Duration, Option } from "effect"

Duration.fromInput("90 seconds").pipe(Option.map(Duration.toSeconds))
// => Some(90)
Duration.fromInput("nonsense" as any)
// => None
```

#### fromInputUnsafe

Like `fromInput` but throws on invalid input.

```ts
import { Duration } from "effect"

Duration.fromInputUnsafe([2, 500_000_000]) // 2s + 500ms
// => Duration "2s 500ms"
Duration.fromInputUnsafe({ days: 1 })
// => Duration "1d"
```

#### zero

A `Duration` representing no time.

```ts
import { Duration } from "effect"

Duration.toMillis(Duration.zero) // => 0
```

#### infinity / negativeInfinity

Sentinels for unbounded spans.

```ts
import { Duration } from "effect"

Duration.toMillis(Duration.infinity) // => Infinity
Duration.toMillis(Duration.negativeInfinity) // => -Infinity
```

#### nanos / micros

Build a finite duration from a `bigint` of nanoseconds or microseconds.

```ts
import { Duration } from "effect"

Duration.toMillis(Duration.nanos(BigInt(500_000_000))) // => 500
Duration.toMillis(Duration.micros(BigInt(500_000))) // => 500
```

#### millis / seconds / minutes / hours / days / weeks

Build a finite duration from a number in the named unit.

```ts
import { Duration } from "effect"

Duration.toMillis(Duration.millis(1000)) // => 1000
Duration.toMillis(Duration.seconds(30)) // => 30000
Duration.toMillis(Duration.minutes(5)) // => 300000
Duration.toMillis(Duration.hours(2)) // => 7200000
Duration.toMillis(Duration.days(1)) // => 86400000
Duration.toMillis(Duration.weeks(1)) // => 604800000
```

### Guards and predicates

#### isDuration

Type guard for `Duration` values.

```ts
import { Duration } from "effect"

Duration.isDuration(Duration.seconds(1)) // => true
Duration.isDuration(1000) // => false
```

#### isFinite

`true` for any non-infinite duration.

```ts
import { Duration } from "effect"

Duration.isFinite(Duration.seconds(5)) // => true
Duration.isFinite(Duration.infinity) // => false
```

#### isZero

`true` if the duration is exactly zero.

```ts
import { Duration } from "effect"

Duration.isZero(Duration.zero) // => true
Duration.isZero(Duration.seconds(1)) // => false
```

#### isNegative / isPositive

Strict sign checks (zero is neither).

```ts
import { Duration } from "effect"

Duration.isNegative(Duration.seconds(-5)) // => true
Duration.isNegative(Duration.zero) // => false
Duration.isPositive(Duration.seconds(5)) // => true
Duration.isPositive(Duration.infinity) // => true
```

### Conversions and getters

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

```ts
import { Duration } from "effect"

Duration.toMillis(Duration.seconds(5)) // => 5000
Duration.toSeconds(Duration.minutes(2)) // => 120
Duration.toMinutes(Duration.hours(1)) // => 60
Duration.toHours(Duration.days(1)) // => 24
Duration.toDays(Duration.weeks(1)) // => 7
Duration.toWeeks(Duration.days(14)) // => 2
```

#### toNanos

Get nanoseconds as `Option<bigint>` — `None` for infinite durations.

```ts
import { Duration } from "effect"

Duration.toNanos(Duration.seconds(1)) // => Some(1000000000n)
Duration.toNanos(Duration.infinity) // => None
```

#### toNanosUnsafe

Get nanoseconds as a `bigint`, throwing for infinite durations.

```ts
import { Duration } from "effect"

Duration.toNanosUnsafe(Duration.seconds(2)) // => 2000000000n
// Duration.toNanosUnsafe(Duration.infinity) // throws
```

#### toHrTime

Convert to Node-style `[seconds, nanoseconds]` high-resolution time.

```ts
import { Duration } from "effect"

Duration.toHrTime(Duration.millis(1500)) // => [1, 500000000]
```

#### parts

Decompose a finite duration into normalized signed components.

```ts
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

Render a human-readable string (used by the examples above).

```ts
import { Duration } from "effect"

Duration.format(Duration.millis(1000)) // => "1s"
Duration.format(Duration.millis(1001)) // => "1s 1ms"
Duration.format(Duration.infinity) // => "Infinity"
```

### Math

All of these are dual (data-first / data-last).

#### sum / subtract

Add or subtract two durations. The result of `subtract` can be negative.
Infinity follows IEEE-like rules (e.g. `infinity - infinity = zero`).

```ts
import { Duration } from "effect"

Duration.toSeconds(Duration.sum(Duration.seconds(5), Duration.seconds(3))) // => 8
Duration.toSeconds(Duration.subtract(Duration.seconds(10), Duration.seconds(3))) // => 7
```

#### times

Multiply a duration by a number.

```ts
import { Duration } from "effect"

Duration.toSeconds(Duration.times(Duration.seconds(5), 2)) // => 10
```

#### divide

Safely divide by a finite, non-zero number; returns `Option`.

```ts
import { Duration, Option } from "effect"

Duration.divide(Duration.seconds(10), 2).pipe(Option.map(Duration.toSeconds))
// => Some(5)
Duration.divide(Duration.seconds(10), 0) // => None
```

#### divideUnsafe

Divide using fallback rules instead of `Option` (e.g. divide-by-zero of a finite
duration yields signed infinity).

```ts
import { Duration } from "effect"

Duration.toSeconds(Duration.divideUnsafe(Duration.seconds(10), 2)) // => 5
Duration.toMillis(Duration.divideUnsafe(Duration.seconds(10), 0)) // => Infinity
```

#### negate / abs

Flip the sign, or take the absolute value.

```ts
import { Duration } from "effect"

Duration.toMillis(Duration.negate(Duration.seconds(5))) // => -5000
Duration.toMillis(Duration.abs(Duration.seconds(-5))) // => 5000
```

#### min / max

Select the shorter or longer of two durations.

```ts
import { Duration } from "effect"

Duration.toSeconds(Duration.min(Duration.seconds(5), Duration.seconds(3))) // => 3
Duration.toSeconds(Duration.max(Duration.seconds(5), Duration.seconds(3))) // => 5
```

#### clamp

Constrain a duration to an inclusive `[minimum, maximum]` range.

```ts
import { Duration } from "effect"

Duration.toSeconds(
  Duration.clamp(Duration.seconds(10), {
    minimum: Duration.seconds(2),
    maximum: Duration.seconds(5)
  })
) // => 5
```

#### between

`true` if a duration is within an inclusive range.

```ts
import { Duration } from "effect"

Duration.between(Duration.seconds(3), {
  minimum: Duration.seconds(2),
  maximum: Duration.seconds(5)
}) // => true
```

### Comparison and equivalence

#### Order

An `Order<Duration>` (`NegativeInfinity < finite < Infinity`). Use it with
sorting and `Array`/`Order` utilities.

```ts
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

Value equality (independent of internal representation).

```ts
import { Duration } from "effect"

Duration.Equivalence(Duration.seconds(5), Duration.millis(5000)) // => true
Duration.equals(Duration.seconds(5), Duration.millis(5000)) // => true
```

#### isLessThan / isLessThanOrEqualTo / isGreaterThan / isGreaterThanOrEqualTo

Pairwise comparisons (dual).

```ts
import { Duration } from "effect"

Duration.isLessThan(Duration.seconds(3), Duration.seconds(5)) // => true
Duration.isLessThanOrEqualTo(Duration.seconds(5), Duration.seconds(5)) // => true
Duration.isGreaterThan(Duration.seconds(5), Duration.seconds(3)) // => true
Duration.isGreaterThanOrEqualTo(Duration.seconds(5), Duration.seconds(5)) // => true
```

### Pattern matching

#### match

Match on the underlying representation: millis, nanos, or infinity.
`onNegativeInfinity` is optional and falls back to `onInfinity`.

```ts
import { Duration } from "effect"

Duration.match(Duration.seconds(5), {
  onMillis: (millis) => `${millis} milliseconds`,
  onNanos: (nanos) => `${nanos} nanoseconds`,
  onInfinity: () => "infinite"
}) // => "5000 milliseconds"
```

#### 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`.

```ts
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
}) // => 5000
```

### 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

Sums many durations, starting from `Duration.zero`. `combineAll` folds an
iterable; `combineAll([])` returns `Duration.zero`.

```ts
import { Duration } from "effect"

Duration.ReducerSum.combineAll([
  Duration.seconds(1),
  Duration.seconds(2),
  Duration.seconds(3)
]).pipe(Duration.toSeconds) // => 6
```

#### CombinerMax / CombinerMin

Keep the longest or shortest duration when combining.

```ts
import { Duration } from "effect"

Duration.CombinerMax.combine(Duration.seconds(2), Duration.seconds(5)).pipe(Duration.toSeconds) // => 5
Duration.CombinerMin.combine(Duration.seconds(2), Duration.seconds(5)).pipe(Duration.toSeconds) // => 2
```

### Types

- **`Duration`** — 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 a `Duration` (see
  [Duration.Input](#durationinput)).
- **`DurationObject`** — additive object input with optional `weeks`, `days`,
  `hours`, `minutes`, `seconds`, `milliseconds`, `microseconds`, `nanoseconds`.

## DateTime reference

### Construction

#### make

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

```ts
import { DateTime } from "effect"

DateTime.make("2024-01-01")._tag // => "Some"
DateTime.make("not a date")._tag // => "None"
```

#### makeUnsafe

Like `make` but throws on invalid input.

```ts
import { DateTime } from "effect"

DateTime.formatIso(DateTime.makeUnsafe({ year: 2024 }))
// => "2024-01-01T00:00:00.000Z"
```

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

```ts
import { DateTime } from "effect"

DateTime.makeZoned("2024-06-15T14:30:00Z", { timeZone: "Europe/London" })._tag
// => "Some"
```

#### makeZonedUnsafe

Like `makeZoned` but throws on invalid input.

```ts
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

Parse a zoned ISO string (offset-only or `[Time/Zone]` form) into `Option<Zoned>`.

```ts
import { DateTime } from "effect"

DateTime.makeZonedFromString("2024-01-01T12:00:00+02:00[Europe/Berlin]")._tag
// => "Some"
DateTime.makeZonedFromString("invalid")._tag // => "None"
```

#### fromDateUnsafe

Build a `Utc` from a JavaScript `Date` (throws on an invalid `Date`).

```ts
import { DateTime } from "effect"

DateTime.formatIso(DateTime.fromDateUnsafe(new Date("2024-01-01T12:00:00Z")))
// => "2024-01-01T12:00:00.000Z"
```

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

```ts
import { DateTime, Effect } from "effect"

const eff = DateTime.now // => Effect<DateTime.Utc>
const asDate = DateTime.nowAsDate // => Effect<Date>
const sync = DateTime.nowUnsafe() // => DateTime.Utc
```

#### nowInCurrentZone

`Effect<Zoned, never, CurrentTimeZone>` — current instant in the ambient zone.

```ts
import { DateTime } from "effect"

DateTime.nowInCurrentZone.pipe(DateTime.withCurrentZoneNamed("Asia/Tokyo"))
// => Effect<DateTime.Zoned>
```

### Guards

#### isDateTime / isTimeZone / isTimeZoneOffset / isTimeZoneNamed

Narrow unknown values to `DateTime` / `TimeZone` and its variants.

```ts
import { DateTime } from "effect"

DateTime.isDateTime(DateTime.makeUnsafe(0)) // => true
const zone = DateTime.zoneMakeOffset(3_600_000)
DateTime.isTimeZone(zone) // => true
DateTime.isTimeZoneOffset(zone) // => true
DateTime.isTimeZoneNamed(zone) // => false
```

#### isUtc / isZoned

Narrow a known `DateTime` to its variant.

```ts
import { DateTime } from "effect"

DateTime.isUtc(DateTime.makeUnsafe(0)) // => true
DateTime.isZoned(DateTime.makeZonedUnsafe(0, { timeZone: "UTC" })) // => true
```

### Time zones

#### setZone / setZoneOffset

Attach a `TimeZone` (or a fixed offset in ms) to a `DateTime`, returning `Zoned`.

```ts
import { DateTime } from "effect"

const z = DateTime.zoneMakeNamedUnsafe("Europe/London")
DateTime.setZone(DateTime.makeUnsafe(0), z) // => DateTime.Zoned
DateTime.setZoneOffset(DateTime.makeUnsafe(0), 3 * 60 * 60 * 1000) // => DateTime.Zoned (+03:00)
```

#### setZoneNamed / setZoneNamedUnsafe

Attach a named zone by IANA id. The safe version returns `Option`; the unsafe
version throws on an invalid id.

```ts
import { DateTime } from "effect"

DateTime.setZoneNamed(DateTime.makeUnsafe(0), "Europe/London")._tag // => "Some"
DateTime.setZoneNamedUnsafe(DateTime.makeUnsafe(0), "Europe/London") // => DateTime.Zoned
```

#### toUtc

Drop the zone, keeping the same instant, returning `Utc`.

```ts
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

Construct a `TimeZone.Named` from an IANA id — as `Option`, throwing, or as an
`Effect` failing with `IllegalArgumentError`.

```ts
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

Construct a fixed-offset zone (offset in ms), or the system's local named zone.

```ts
import { DateTime } from "effect"

DateTime.zoneToString(DateTime.zoneMakeOffset(3 * 60 * 60 * 1000)) // => "+03:00"
DateTime.zoneMakeLocal() // => TimeZone.Named (system zone)
```

#### zoneFromString / zoneToString

Parse a zone from an IANA id or `±HH:MM` offset (`Option`), and render one back.

```ts
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

Read a `Zoned` value's offset, in milliseconds or as an ISO `±HH:MM` string.

```ts
import { DateTime } from "effect"

const z = DateTime.makeZonedUnsafe("2024-01-01T12:00:00Z", {
  timeZone: DateTime.zoneMakeOffset(3 * 60 * 60 * 1000)
})
DateTime.zonedOffset(z) // => 10800000
DateTime.zonedOffsetIso(z) // => "+03:00"
```

#### Ambient current time zone

The `CurrentTimeZone` service supplies a zone to the `*Current*` helpers, so you
do not have to pass it everywhere.

##### CurrentTimeZone

The `Context.Service` holding the ambient `TimeZone`.

```ts
import { DateTime, Effect } from "effect"

Effect.gen(function* () {
  const zone = yield* DateTime.CurrentTimeZone
  console.log(DateTime.zoneToString(zone))
}).pipe(DateTime.withCurrentZoneNamed("Europe/London"))
```

##### setZoneCurrent

Attach the ambient zone to a `DateTime`.

```ts
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

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

```ts
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

`Layer` versions for app-wide wiring (the named layer can fail with
`IllegalArgumentError`).

```ts
import { DateTime } from "effect"

DateTime.layerCurrentZone(DateTime.zoneMakeNamedUnsafe("Europe/London"))
DateTime.layerCurrentZoneOffset(3 * 60 * 60 * 1000)
DateTime.layerCurrentZoneNamed("Europe/London")
DateTime.layerCurrentZoneLocal
```

### Comparison

#### Order / Equivalence

Order by epoch milliseconds; equivalence is instant-based regardless of zone.

```ts
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" })
) // => true
```

#### clamp / min / max / between

Constrain or select instants; `between` is an inclusive range check.

```ts
import { DateTime } from "effect"

const min = DateTime.makeUnsafe("2024-01-01")
const max = DateTime.makeUnsafe("2024-12-31")

DateTime.min(min, max) // => 2024-01-01
DateTime.max(min, max) // => 2024-12-31
DateTime.clamp(DateTime.makeUnsafe("2025-06-15"), { minimum: min, maximum: max }) // => 2024-12-31
DateTime.between(DateTime.makeUnsafe("2024-06-15"), { minimum: min, maximum: max }) // => true
```

#### isGreaterThan / isGreaterThanOrEqualTo / isLessThan / isLessThanOrEqualTo

Pairwise comparisons (dual).

```ts
import { DateTime } from "effect"

const a = DateTime.makeUnsafe("2024-02-01")
const b = DateTime.makeUnsafe("2024-01-01")

DateTime.isGreaterThan(a, b) // => true
DateTime.isGreaterThanOrEqualTo(b, b) // => true
DateTime.isLessThan(b, a) // => true
DateTime.isLessThanOrEqualTo(b, b) // => true
```

#### isFuture / isPast (and Unsafe)

Compare against "now". The effectful versions read the `Clock`; the `*Unsafe`
versions read `Date.now()` synchronously.

```ts
import { DateTime, Effect } from "effect"

const later = DateTime.add(DateTime.nowUnsafe(), { hours: 1 })
DateTime.isFutureUnsafe(later) // => true
DateTime.isPastUnsafe(later) // => false

Effect.gen(function* () {
  return yield* DateTime.isFuture(later) // => Effect<boolean>
})
```

#### distance

Signed `Duration` between two instants (positive when `other` is later).

```ts
import { DateTime } from "effect"

DateTime.distance(
  DateTime.makeUnsafe(0),
  DateTime.makeUnsafe(60_000)
) // => Duration "1m"
```

### Arithmetic

#### add / subtract

Calendar-aware part arithmetic. Day/week/month/year amounts respect the zone.

```ts
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

Elapsed-time math (always milliseconds, not calendar-aware). Accepts a
`Duration.Input`.

```ts
import { DateTime, Duration } from "effect"

DateTime.makeUnsafe(0).pipe(DateTime.addDuration("5 minutes")) // => +5m instant
DateTime.makeUnsafe(0).pipe(DateTime.subtractDuration(Duration.hours(1))) // => -1h instant
```

#### mutate / mutateUtc

Adjust calendar fields with a mutable `Date` copy — in the value's own zone, or
in UTC.

```ts
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

Transform the underlying epoch milliseconds directly.

```ts
import { DateTime } from "effect"

DateTime.makeUnsafe(0).pipe(DateTime.mapEpochMillis((ms) => ms + 10))
// => instant at 10ms
```

#### startOf / endOf / nearest

Round to the start, end, or nearest boundary of a unit. For `"week"`, pass
`{ weekStartsOn }` (0 = Sunday).

```ts
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

Strip the time, keeping just the (zone-adjusted) date as `Utc`.

```ts
import { DateTime } from "effect"

DateTime.makeUnsafe("2024-01-01T23:30:00Z").pipe(DateTime.removeTime, DateTime.formatIso)
// => "2024-01-01T00:00:00.000Z"
```

#### withDate / withDateUtc

Run a function against a `Date` view — zone-adjusted, or UTC — and return its
result.

```ts
import { DateTime } from "effect"

DateTime.makeUnsafe(0).pipe(DateTime.withDateUtc((d) => d.getTime())) // => 0
```

### Parts and conversion

#### toParts / toPartsUtc

Get all calendar parts (with `weekDay`) — zone-adjusted, or always UTC.

```ts
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

Read one part by key — zone-adjusted, or UTC.

```ts
import { DateTime } from "effect"

DateTime.getPartUtc(DateTime.makeUnsafe({ year: 2024 }), "year") // => 2024
```

#### setParts / setPartsUtc

Return a new value with the given parts replaced — zone-adjusted, or UTC.

```ts
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

Convert to a native `Date` — zone-adjusted wall-clock, or the raw UTC instant.

```ts
import { DateTime } from "effect"

DateTime.toDateUtc(DateTime.makeUnsafe("2024-01-01T12:00:00Z")).toISOString()
// => "2024-01-01T12:00:00.000Z"
```

#### toEpochMillis

Get milliseconds since the Unix epoch (always UTC).

```ts
import { DateTime } from "effect"

DateTime.toEpochMillis(DateTime.makeUnsafe("2024-01-01T00:00:00Z"))
// => 1704067200000
```

### Pattern matching

#### match

Handle `Utc` and `Zoned` cases separately.

```ts
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

#### 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`.

```ts
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

Format using an existing `Intl.DateTimeFormat` instance (it controls
locale/zone/options).

```ts
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

UTC ISO 8601 string.

```ts
import { DateTime } from "effect"

DateTime.formatIso(DateTime.makeUnsafe("2024-01-01T12:30:45.123Z"))
// => "2024-01-01T12:30:45.123Z"
```

#### formatIsoDate / formatIsoDateUtc

Date-only ISO string — zone-adjusted, or always UTC.

```ts
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

ISO string including the offset (same as `formatIso` for `Utc`).

```ts
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

Full zoned string: `YYYY-MM-DDTHH:mm:ss.sss+HH:MM[Time/Zone]`.

```ts
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]"
```

### Types

- **`DateTime`** — `Utc | Zoned`.
- **`Utc`** — instant with no zone (`_tag: "Utc"`).
- **`Zoned`** — instant plus a `TimeZone` (`_tag: "Zoned"`).
- **`TimeZone`** — `TimeZone.Offset` (fixed `offset` in ms) or `TimeZone.Named`
  (IANA `id`).
- **`Disambiguation`** — `"compatible" | "earlier" | "later" | "reject"` for DST
  gaps/overlaps.
- **`DateTime.Parts` / `PartsWithWeekday`** — calendar components (one-based
  `month`); `PartsWithWeekday` adds `weekDay` (0 = Sunday).
- **`DateTime.PartsForMath`** — plural amounts (`days`, `weeks`, `months`,
  `years`, …) accepted by `add` / `subtract`.

## See also

- [Clock](https://effect.plants.sh/scheduling/clock/) — read "now" deterministically.
- [TestClock](https://effect.plants.sh/testing/testclock/) — control time in tests.
- [Schedule](https://effect.plants.sh/scheduling/schedule/) — drive retries and repeats with `Duration`s.