Temporal

Temporal types model JavaScript's Temporal API for date and time handling in Sessel. All types live under the Temporal.* namespace. They parse ISO 8601 strings, expose typed accessors, support arithmetic, convert between representations, and format output.

Type summary

Type Construction example Description
Temporal.Instant Temporal.Instant.from("2026-03-23T14:30:00Z") An exact moment in time (no timezone or calendar)
Temporal.ZonedDateTime Temporal.ZonedDateTime.from("2026-03-23T14:30:00[Europe/London]") An exact moment in a named timezone
Temporal.PlainDateTime Temporal.PlainDateTime.from("2026-03-23T14:30:00") A wall-clock date and time (no timezone)
Temporal.PlainDate Temporal.PlainDate.from("2026-03-23") A calendar date (no time, no timezone)
Temporal.PlainTime Temporal.PlainTime.from("14:30:00") A wall-clock time (no date, no timezone)
Temporal.PlainYearMonth Temporal.PlainYearMonth.from("2026-03") A year-month pair
Temporal.PlainMonthDay Temporal.PlainMonthDay.from("--03-23") A month-day pair (recurring date)
Temporal.Duration Temporal.Duration.from("P1Y2M3DT4H5M6S") A span of time

Temporal.PlainDate

A calendar date: year, month, and day, with no time component and no timezone.

Construction

Temporal.PlainDate.from("2026-03-23")
Temporal.PlainDate.from({ year: 2026, month: 3, day: 23 })

Accessors

let date = Temporal.PlainDate.from("2026-03-23");
date.year          // 2026
date.month         // 3
date.day           // 23
date.dayOfWeek     // 1 (Monday = 1, Sunday = 7, ISO 8601)
date.dayOfYear     // 82
date.weekOfYear    // 13
date.daysInMonth   // 31
date.daysInYear    // 365
date.inLeapYear    // false

Arithmetic

let date = Temporal.PlainDate.from("2026-03-23");

date.add(Temporal.Duration.from("P1M"))        // 2026-04-23
date.subtract(Temporal.Duration.from("P7D"))   // 2026-03-16
date.until(Temporal.PlainDate.from("2026-12-31"))   // Duration to end of year
date.since(Temporal.PlainDate.from("2026-01-01"))   // Duration since start of year

Field replacement

Temporal.PlainDate.from("2026-03-23").with({ day: 1 })    // 2026-03-01
Temporal.PlainDate.from("2026-03-23").with({ month: 12 }) // 2026-12-23

Conversions

let date = Temporal.PlainDate.from("2026-03-23");

date.toPlainDateTime()                                   // 2026-03-23T00:00:00
date.toPlainDateTime(Temporal.PlainTime.from("09:00"))   // 2026-03-23T09:00:00
date.toZonedDateTime("Europe/London")                    // ZonedDateTime at midnight

Comparison

Temporal.PlainDate.compare(
  Temporal.PlainDate.from("2026-01-01"),
  Temporal.PlainDate.from("2026-12-31")
)
// -1 (first is earlier), 0 (equal), or 1 (first is later)

Equality

Temporal.PlainDate.from("2026-03-23").equals(Temporal.PlainDate.from("2026-03-23"))  // true

Formatting

let date = Temporal.PlainDate.from("2026-03-23");

date.format("yyyy-MM-dd")           // "2026-03-23"
date.format("dd/MM/yyyy")           // "23/03/2026"
date.format("MMMM d, yyyy")         // "March 23, 2026"
date.toLocaleString("en-US")        // "3/23/2026"
date.toLocaleString("de-DE")        // "23.3.2026"
String(date)                        // "2026-03-23" (ISO 8601)

Testable examples

PlainDate accessors evaluated through an expression binding:

GET /temporal-ref/date-accessors.html
HTTP/1.1 200

<!DOCTYPE html>
<html>
<body>
  <div>
    <p>Date: 2026-03-23</p>
    <p>Year: 2026, Month: 3, Day: 23</p>
    <p>Day of week: 1</p>
    <p>Leap year: false</p>
  </div>
</body>
</html>

Date arithmetic and field replacement in a rendered page:

GET /temporal-ref/date-arithmetic.html
HTTP/1.1 200

<!DOCTYPE html>
<html>
<body>
  <div>
    <p>Plus one month: 2026-04-23</p>
    <p>Minus seven days: 2026-03-16</p>
    <p>First of month: 2026-03-01</p>
  </div>
</body>
</html>

Temporal.PlainTime

A wall-clock time: hour, minute, second, and sub-second precision. No date, no timezone.

Construction

Temporal.PlainTime.from("14:30:00")
Temporal.PlainTime.from("14:30:00.500")

Accessors

let t = Temporal.PlainTime.from("14:30:45.123456789");
t.hour          // 14
t.minute        // 30
t.second        // 45
t.millisecond   // 123
t.microsecond   // 456
t.nanosecond    // 789

Arithmetic

let t = Temporal.PlainTime.from("14:30:00");

t.add(Temporal.Duration.from("PT2H30M"))     // 17:00:00
t.subtract(Temporal.Duration.from("PT1H"))   // 13:30:00
t.until(Temporal.PlainTime.from("18:00:00")) // PT3H30M
t.since(Temporal.PlainTime.from("09:00:00")) // PT5H30M

Field replacement

Temporal.PlainTime.from("14:30:00").with({ hour: 9 })      // 09:30:00
Temporal.PlainTime.from("14:30:00").with({ minute: 0, second: 0 }) // 14:00:00

Conversions

let t = Temporal.PlainTime.from("14:30:00");
t.toPlainDateTime(Temporal.PlainDate.from("2026-03-23"))  // 2026-03-23T14:30:00

Comparison

Temporal.PlainTime.compare(
  Temporal.PlainTime.from("09:00:00"),
  Temporal.PlainTime.from("17:00:00")
)
// -1

Equality

Temporal.PlainTime.from("14:30:00").equals(Temporal.PlainTime.from("14:30:00"))  // true

Formatting

let t = Temporal.PlainTime.from("14:30:00");

t.format("HH:mm")               // "14:30"
t.format("h:mm a")              // "2:30 PM"
t.toLocaleString("en-US")       // "2:30:00 PM"
String(t)                       // "14:30:00"

Temporal.PlainDateTime

A wall-clock date and time. No timezone.

Construction

Temporal.PlainDateTime.from("2026-03-23T14:30:00")
Temporal.PlainDateTime.from("2026-03-23T14:30:00.500")

Accessors

let dt = Temporal.PlainDateTime.from("2026-03-23T14:30:45");
dt.year        // 2026
dt.month       // 3
dt.day         // 23
dt.hour        // 14
dt.minute      // 30
dt.second      // 45
dt.millisecond // 0
dt.microsecond // 0
dt.nanosecond  // 0

Arithmetic

let dt = Temporal.PlainDateTime.from("2026-03-23T14:30:00");

dt.add(Temporal.Duration.from("P1DT2H"))       // 2026-03-24T16:30:00
dt.subtract(Temporal.Duration.from("PT30M"))   // 2026-03-23T14:00:00
dt.until(Temporal.PlainDateTime.from("2026-03-30T09:00:00"))
dt.since(Temporal.PlainDateTime.from("2026-01-01T00:00:00"))

Field replacement

Temporal.PlainDateTime.from("2026-03-23T14:30:00").with({ hour: 9, minute: 0 })
// 2026-03-23T09:00:00

Conversions

let dt = Temporal.PlainDateTime.from("2026-03-23T14:30:00");

dt.toPlainDate()                        // 2026-03-23
dt.toPlainTime()                        // 14:30:00
dt.toZonedDateTime("America/New_York")  // ZonedDateTime with timezone applied

Comparison

Temporal.PlainDateTime.compare(
  Temporal.PlainDateTime.from("2026-03-23T09:00:00"),
  Temporal.PlainDateTime.from("2026-03-23T17:00:00")
)
// -1

Equality

Temporal.PlainDateTime.from("2026-03-23T14:30:00")
  .equals(Temporal.PlainDateTime.from("2026-03-23T14:30:00"))
// true

Formatting

let dt = Temporal.PlainDateTime.from("2026-03-23T14:30:00");

dt.format("yyyy-MM-dd HH:mm")          // "2026-03-23 14:30"
dt.format("MMMM d, yyyy 'at' h:mm a")  // "March 23, 2026 at 2:30 PM"
dt.toLocaleString("en-GB")             // "23/03/2026, 14:30:00"
String(dt)                             // "2026-03-23T14:30:00"

Temporal.Instant

An exact moment in time — a point on the UTC timeline with no calendar or timezone.

Construction

Temporal.Instant.from("2026-03-23T14:30:00Z")
Temporal.Instant.from("2026-03-23T14:30:00.500Z")
Temporal.Instant.fromEpochSeconds(1)              // 1970-01-01T00:00:01Z

Accessors

let instant = Temporal.Instant.from("1970-01-01T00:00:01Z");
instant.epochSeconds       // 1
instant.epochMilliseconds  // 1000
instant.epochMicroseconds  // 1000000
instant.epochNanoseconds   // 1000000000

Arithmetic

let instant = Temporal.Instant.from("2026-03-23T14:30:00Z");

instant.add(Temporal.Duration.from("PT1H"))
instant.subtract(Temporal.Duration.from("PT30M"))
instant.until(Temporal.Instant.from("2026-03-23T18:00:00Z"))   // PT3H30M
instant.since(Temporal.Instant.from("2026-03-23T12:00:00Z"))   // PT2H30M

Conversions

let instant = Temporal.Instant.from("2026-03-23T14:30:00Z");
instant.toZonedDateTimeISO("America/New_York")  // ZonedDateTime in Eastern time

Comparison

Temporal.Instant.compare(
  Temporal.Instant.from("2026-03-23T14:00:00Z"),
  Temporal.Instant.from("2026-03-23T15:00:00Z")
)
// -1

Equality

Temporal.Instant.from("2026-03-23T14:30:00Z")
  .equals(Temporal.Instant.from("2026-03-23T14:30:00Z"))
// true

Formatting

let instant = Temporal.Instant.from("2026-03-23T14:30:00Z");

instant.format("yyyy-MM-dd HH:mm 'UTC'")  // formats in UTC
instant.toLocaleString("en-US")
String(instant)                            // "2026-03-23T14:30:00Z"

Testable examples

Instant epoch accessors and arithmetic in a rendered page:

GET /temporal-ref/instant.html
HTTP/1.1 200

<!DOCTYPE html>
<html>
<body>
  <div>
    <p>Epoch seconds: 1</p>
    <p>Plus one hour: 2026-03-23T15:30:00Z</p>
    <p>Compare: -1</p>
  </div>
</body>
</html>

Temporal.ZonedDateTime

A moment in time in a named IANA timezone — combines an exact instant with a calendar representation.

Construction

Temporal.ZonedDateTime.from("2026-03-23T14:30:00[Europe/London]")
Temporal.ZonedDateTime.from("2026-03-23T14:30:00+05:30[Asia/Kolkata]")

Accessors

let zdt = Temporal.ZonedDateTime.from("2026-03-23T14:30:00[America/New_York]");
zdt.year         // 2026
zdt.month        // 3
zdt.day          // 23
zdt.hour         // 14
zdt.minute       // 30
zdt.second       // 0
zdt.millisecond  // 0
zdt.microsecond  // 0
zdt.nanosecond   // 0
zdt.timeZoneId   // "America/New_York"
zdt.offset       // "-04:00" (or "-05:00" depending on DST)

Arithmetic

let zdt = Temporal.ZonedDateTime.from("2026-03-23T14:30:00[America/New_York]");

zdt.add(Temporal.Duration.from("P1DT2H"))
zdt.subtract(Temporal.Duration.from("PT1H"))
zdt.until(Temporal.ZonedDateTime.from("2026-04-01T00:00:00[America/New_York]"))
zdt.since(Temporal.ZonedDateTime.from("2026-01-01T00:00:00[America/New_York]"))

Field replacement

Temporal.ZonedDateTime.from("2026-03-23T14:30:00[America/New_York]")
  .with({ hour: 9, minute: 0 })
// 2026-03-23T09:00:00[America/New_York]

Temporal.ZonedDateTime.from("2026-03-23T14:30:00[America/New_York]")
  .with({ timeZone: "Europe/London" })
// re-interprets the wall clock time in the new timezone

Conversions

let zdt = Temporal.ZonedDateTime.from("2026-03-23T14:30:00[America/New_York]");

zdt.toInstant()                          // the underlying Instant
zdt.toPlainDate()                        // 2026-03-23
zdt.toPlainTime()                        // 14:30:00
zdt.toPlainDateTime()                    // 2026-03-23T14:30:00
zdt.withTimeZone("Europe/London")        // same instant, different timezone

Comparison

Temporal.ZonedDateTime.compare(
  Temporal.ZonedDateTime.from("2026-03-23T09:00:00[America/New_York]"),
  Temporal.ZonedDateTime.from("2026-03-23T14:00:00[Europe/London]")
)
// compares the underlying instants

Equality

ZonedDateTime.equals() requires both the same instant AND the same timezone:

let a = Temporal.ZonedDateTime.from("2026-03-23T14:30:00[America/New_York]");
let b = Temporal.ZonedDateTime.from("2026-03-23T19:30:00[Europe/London]");
a.equals(b)  // false — different timezones, even though same instant

Formatting

let zdt = Temporal.ZonedDateTime.from("2026-03-23T14:30:00[America/New_York]");

zdt.format("yyyy-MM-dd HH:mm z")   // "2026-03-23 14:30 EDT"
zdt.toLocaleString("en-US")
String(zdt)                         // "2026-03-23T14:30:00-04:00[America/New_York]"

Testable examples

ZonedDateTime timezone conversion in a rendered page:

GET /temporal-ref/zoned.html
HTTP/1.1 200

<!DOCTYPE html>
<html>
<body>
  <div>
    <p>Timezone: UTC</p>
    <p>New York hour: 6</p>
    <p>Date: 2026-03-23</p>
  </div>
</body>
</html>

Temporal.Duration

A span of time with both calendar components (years, months, weeks, days) and time components (hours, minutes, seconds, sub-seconds).

Construction

ISO 8601 duration format: P<date>T<time>. The P prefix is required. The T separator is required if any time component is present.

Temporal.Duration.from("P1Y2M3DT4H5M6S")   // 1 year, 2 months, 3 days, 4 hours, 5 minutes, 6 seconds
Temporal.Duration.from("P1M")               // 1 month
Temporal.Duration.from("PT30M")             // 30 minutes
Temporal.Duration.from("P7D")               // 7 days
Temporal.Duration.from("-PT1H")             // negative 1 hour

Map construction:

Temporal.Duration.from({ years: 1, months: 6 })
Temporal.Duration.from({ hours: 2, minutes: 30 })
Temporal.Duration.from({ days: 7 })

Accessors

let dur = Temporal.Duration.from("P1Y2M3DT4H5M6S");
dur.years        // 1
dur.months       // 2
dur.weeks        // 0
dur.days         // 3
dur.hours        // 4
dur.minutes      // 5
dur.seconds      // 6
dur.milliseconds // 0
dur.microseconds // 0
dur.nanoseconds  // 0
dur.sign         // 1 (positive), -1 (negative), or 0 (zero)
dur.blank        // false (true only when all components are zero)

Field replacement

Temporal.Duration.from("P1Y2M3DT4H5M6S").with({ years: 2, hours: 0 })
// P2Y2M3DT5M6S

.with() takes a map of the same keys as map construction above (years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds) and replaces only the given components, leaving the rest unchanged.

Calendar arithmetic and relativeTo

Operations involving years, months, or weeks are calendar-relative — the number of days in a month varies. These operations require a relativeTo argument providing a reference date:

let oneMonth = Temporal.Duration.from("P1M");

// Add two durations with calendar components — need relativeTo
oneMonth.add(
  Temporal.Duration.from("P1M"),
  Temporal.PlainDate.from("2026-01-31")   // relativeTo
)
// P2M (relative to Jan 31, gives Mar 31)

// total() converts to a specific unit — always needs relativeTo for calendar units
Temporal.Duration.from("P1Y").total("days", Temporal.PlainDate.from("2026-01-01"))
// 365

Pure time durations (hours, minutes, seconds) do not need relativeTo:

Temporal.Duration.from("PT2H").add(Temporal.Duration.from("PT30M"))
// PT2H30M

Negation and absolute value

Temporal.Duration.from("P1M").negated()    // -P1M
Temporal.Duration.from("-PT2H").abs()      // PT2H
Temporal.Duration.from("-P1M").sign        // -1

Equality

Temporal.Duration.from("P1M").equals(Temporal.Duration.from("P1M"))   // true
Temporal.Duration.from("P30D").equals(Temporal.Duration.from("P1M"))  // false (different components)

Testable examples

Duration accessors and negation in a rendered page:

GET /temporal-ref/duration.html
HTTP/1.1 200

<!DOCTYPE html>
<html>
<body>
  <div>
    <p>Duration: P1Y2M3DT4H5M6S</p>
    <p>Years: 1</p>
    <p>Sign: 1</p>
    <p>Negated: -PT1H</p>
    <p>Blank: true</p>
  </div>
</body>
</html>

Temporal.PlainYearMonth

A year and month without a day. Useful for billing periods, monthly reports, or month-level comparisons.

Construction

Temporal.PlainYearMonth.from("2026-03")
Temporal.PlainYearMonth.from({ year: 2026, month: 3 })

Accessors

let ym = Temporal.PlainYearMonth.from("2026-03");
ym.year         // 2026
ym.month        // 3
ym.daysInMonth  // 31
ym.daysInYear   // 365
ym.inLeapYear   // false

Arithmetic

let ym = Temporal.PlainYearMonth.from("2026-03");

ym.add(Temporal.Duration.from("P3M"))              // 2026-06
ym.subtract(Temporal.Duration.from("P1Y"))         // 2025-03
ym.until(Temporal.PlainYearMonth.from("2027-01"))  // P10M
ym.since(Temporal.PlainYearMonth.from("2026-01"))  // P2M

Field replacement

Temporal.PlainYearMonth.from("2026-03").with({ month: 12 })  // 2026-12

Conversions

Temporal.PlainYearMonth.from("2026-03").toPlainDate(15)  // 2026-03-15

Comparison

Temporal.PlainYearMonth.compare(
  Temporal.PlainYearMonth.from("2026-01"),
  Temporal.PlainYearMonth.from("2026-06")
)
// -1

Equality

Temporal.PlainYearMonth.from("2026-03").equals(Temporal.PlainYearMonth.from("2026-03"))  // true

Formatting

Temporal.PlainYearMonth.from("2026-03").format("MMMM yyyy")   // "March 2026"
Temporal.PlainYearMonth.from("2026-03").toLocaleString("en-US")
String(Temporal.PlainYearMonth.from("2026-03"))                // "2026-03"

Temporal.PlainMonthDay

A month and day without a year. Useful for recurring dates such as birthdays or anniversaries.

Construction

ISO 8601 month-day format: --MM-DD.

Temporal.PlainMonthDay.from("--03-23")
Temporal.PlainMonthDay.from({ month: 3, day: 23 })

Accessors

let md = Temporal.PlainMonthDay.from("--03-23");
md.month  // 3
md.day    // 23

Field replacement

Temporal.PlainMonthDay.from("--03-23").with({ day: 1 })   // --03-01

Conversions

Temporal.PlainMonthDay.from("--03-23").toPlainDate(2026)  // 2026-03-23

Equality

Temporal.PlainMonthDay.from("--03-23").equals(Temporal.PlainMonthDay.from("--03-23"))  // true

Formatting

Temporal.PlainMonthDay.from("--03-23").format("MMMM d")       // "March 23"
Temporal.PlainMonthDay.from("--03-23").toLocaleString("en-US")
String(Temporal.PlainMonthDay.from("--03-23"))                  // "--03-23"

Temporal.Now

Temporal.Now provides current date/time values. These are non-deterministic — calls return different values on each evaluation.

Temporal.Now.instant()            // current Instant (UTC timestamp)
Temporal.Now.zonedDateTimeISO()   // ZonedDateTime in the system timezone
Temporal.Now.zonedDateTimeISO("America/New_York")  // ZonedDateTime in a specific timezone
Temporal.Now.plainDateISO()       // PlainDate in the system timezone
Temporal.Now.plainDateISO("Europe/London")
Temporal.Now.plainTimeISO()       // PlainTime in the system timezone
Temporal.Now.plainTimeISO("Asia/Tokyo")
Temporal.Now.plainDateTimeISO()   // PlainDateTime in the system timezone
Temporal.Now.plainDateTimeISO("Australia/Sydney")

Because Temporal.Now is non-deterministic, avoid it in constraint expressions where repeatable evaluation is required. Use it in mutation handlers and response expressions where the current time at execution is meaningful.


String coercion

String(value) on any Temporal value returns its ISO 8601 canonical form:

String(Temporal.PlainDate.from("2026-03-23"))       // "2026-03-23"
String(Temporal.PlainTime.from("14:30:00"))         // "14:30:00"
String(Temporal.PlainDateTime.from("2026-03-23T14:30:00"))  // "2026-03-23T14:30:00"
String(Temporal.Instant.from("2026-03-23T14:30:00Z"))       // "2026-03-23T14:30:00Z"
String(Temporal.Duration.from("P1Y2M3D"))           // "P1Y2M3D"
String(Temporal.PlainYearMonth.from("2026-03"))     // "2026-03"
String(Temporal.PlainMonthDay.from("--03-23"))      // "--03-23"

.toString() is equivalent to String(value).


Type checking

The isa operator checks whether a value belongs to a Temporal type:

let date = Temporal.PlainDate.from("2026-03-23");
date isa Temporal.PlainDate      // true
date isa Temporal.PlainDateTime  // false
date isa Temporal               // true (any Temporal type)

Formatting patterns

.format(pattern) accepts CLDR-style patterns:

Token Meaning Example
yyyy 4-digit year 2026
yy 2-digit year 26
MMMM Full month name March
MMM Short month name Mar
MM 2-digit month 03
M Month number 3
dd 2-digit day 23
d Day number 23
HH 24-hour hour (0-padded) 14
H 24-hour hour 14
hh 12-hour hour (0-padded) 02
h 12-hour hour 2
mm Minutes (0-padded) 30
ss Seconds (0-padded) 00
a AM/PM PM
'...' Literal text 'at'at

Testable examples

Pattern-based formatting with .format() in a rendered page:

GET /temporal-ref/formatting.html
HTTP/1.1 200

<!DOCTYPE html>
<html>
<body>
  <div>
    <p>ISO: 2026-03-23</p>
    <p>European: 23/03/2026</p>
    <p>Long: March 23, 2026</p>
    <p>12-hour time: 2:30 PM</p>
  </div>
</body>
</html>

.toLocaleString(locale) uses ICU4X locale-aware formatting:

Temporal.PlainDate.from("2026-03-23").toLocaleString("en-US")   // "3/23/2026"
Temporal.PlainDate.from("2026-03-23").toLocaleString("de-DE")   // "23.3.2026"
Temporal.PlainDate.from("2026-03-23").toLocaleString("ja-JP")   // "2026/3/23"

See also