Skip to content

Common Patterns

Framework-agnostic patterns for real-world use cases.

ISO 8601 Timestamps for APIs

When sending dates over a network or storing them in a database, always use ISO 8601. Temporal makes this trivial — every Temporal type has a .toString() that produces a valid ISO 8601 string.

ts
import { format } from 'moment-less'

// For APIs: Temporal.Instant → UTC ISO string (most portable)
const now = Temporal.Now.instant()
now.toString()
// → "2026-04-09T14:05:30.042Z"

// For APIs that want date-only
const today = Temporal.Now.plainDateISO()
today.toString()
// → "2026-04-09"

// For APIs that want datetime without timezone (local server time)
const dt = Temporal.Now.plainDateTimeISO()
dt.toString()
// → "2026-04-09T14:05:30.042"

// For APIs that want datetime with timezone offset
const zdt = Temporal.Now.zonedDateTimeISO()
zdt.toString()
// → "2026-04-09T14:05:30.042+05:30[Asia/Kolkata]"

// For log files / audit trails (custom format)
format(Temporal.Now.zonedDateTimeISO('UTC'), 'YYYY-MM-DDTHH:mm:ss.SSS[Z]')
// → "2026-04-09T14:05:30.042Z"

Human-Readable Dates for UI

ts
import { format } from 'moment-less'

const date = Temporal.PlainDate.from('2026-04-09')

// Locale: en-US
format(date, 'MMMM D, YYYY')         // → "April 9, 2026"
format(date, 'dddd, MMMM Do, YYYY')  // → "Thursday, April 9th, 2026"
format(date, 'D MMMM YYYY')          // → "9 April 2026"

// Locale: fr
format(date, 'D MMMM YYYY', { locale: 'fr' })   // → "9 avril 2026"
format(date, 'dddd D MMMM', { locale: 'fr' })   // → "jeudi 9 avril"

// Short formats
format(date, 'MMM D')   // → "Apr 9"
format(date, 'M/D/YY')  // → "4/9/26"
format(date, 'D/M/YY')  // → "9/4/26"

File / Upload Timestamps

Show upload time with appropriate granularity depending on how long ago it happened.

ts
import { fromNow, format, calendar, fromDate } from 'moment-less'

function fileTimestamp(uploadedAt: Date): { relative: string; absolute: string } {
  const inst = fromDate(uploadedAt)
  const now  = Temporal.Now.instant()

  const zdt = inst.toZonedDateTimeISO(Temporal.Now.timeZoneId())

  return {
    relative: fromNow(inst, now),
    absolute: format(zdt, 'MMM D, YYYY [at] h:mm A'),
  }
}

// Example:
fileTimestamp(new Date(Date.now() - 5 * 60_000))
// → { relative: "5 minutes ago", absolute: "Apr 9, 2026 at 2:00 PM" }

fileTimestamp(new Date('2026-03-01T10:00:00Z'))
// → { relative: "a month ago", absolute: "Mar 1, 2026 at 10:00 AM" }

Chat Message Timestamps

Combine calendar() for the date label and format() for the inline time in a thread UI.

ts
import { calendar, format, fromDate } from 'moment-less'

interface Message {
  id: string
  body: string
  sentAt: Date
}

function groupMessagesByDay(messages: Message[]) {
  const now = Temporal.Now.instant()
  const groups = new Map<string, { dayLabel: string; messages: Message[] }>()

  for (const msg of messages) {
    const inst = fromDate(msg.sentAt)
    // Use PlainDate for the group key (no time — groups by calendar day)
    const localDate = inst
      .toZonedDateTimeISO(Temporal.Now.timeZoneId())
      .toPlainDate()
    const dayKey   = localDate.toString()

    if (!groups.has(dayKey)) {
      groups.set(dayKey, {
        dayLabel: calendar(localDate, now.toZonedDateTimeISO(Temporal.Now.timeZoneId()).toPlainDate()),
        messages: [],
      })
    }
    groups.get(dayKey)!.messages.push(msg)
  }

  return [...groups.values()]
}

// Render the inline time for each bubble
function messageTime(msg: Message): string {
  const inst = fromDate(msg.sentAt)
  const zdt  = inst.toZonedDateTimeISO(Temporal.Now.timeZoneId())
  return format(zdt, 'h:mm A')  // → "2:05 PM"
}

Countdown Timers with humanizeDuration()

ts
import { humanizeDuration, fromNow } from 'moment-less'

const launchAt = Temporal.Instant.from('2026-05-01T00:00:00Z')

function getCountdown(): string {
  const now = Temporal.Now.instant()
  const remaining = now.until(launchAt)

  if (remaining.sign <= 0) return 'Launched!'

  // humanizeDuration gives magnitude: "3 days"
  // fromNow gives direction: "in 3 days"
  return fromNow(launchAt, now)  // → "in 22 days"
}

// For a more granular countdown, use Temporal arithmetic directly
function preciseCountdown(): string {
  const now = Temporal.Now.instant()
  const dur = now.until(launchAt, { largestUnit: 'hours' })

  if (dur.sign <= 0) return '00:00:00'

  const h = String(dur.hours + dur.days * 24).padStart(2, '0')
  const m = String(dur.minutes).padStart(2, '0')
  const s = String(dur.seconds).padStart(2, '0')
  return `${h}:${m}:${s}`  // → "527:43:21"
}

Working with Timezones

ts
import { format } from 'moment-less'

const instant = Temporal.Instant.from('2026-04-09T14:00:00Z')

// Convert to multiple timezones
const zones = ['America/New_York', 'Europe/London', 'Asia/Tokyo', 'Australia/Sydney']

zones.map((tz) => {
  const zdt = instant.toZonedDateTimeISO(tz)
  return {
    timezone: tz,
    display: format(zdt, 'ddd, MMM D YYYY h:mm A z'),
  }
})
// [
//   { timezone: 'America/New_York', display: 'Thu, Apr 9 2026 10:00 AM EDT' },
//   { timezone: 'Europe/London',    display: 'Thu, Apr 9 2026 3:00 PM BST'  },
//   { timezone: 'Asia/Tokyo',       display: 'Thu, Apr 9 2026 11:00 PM JST' },
//   { timezone: 'Australia/Sydney', display: 'Fri, Apr 10 2026 12:00 AM AEST' },
// ]

Converting User Input to UTC

ts
// User types "2026-04-09 14:00" in a form, in their local timezone
function localInputToInstant(localISO: string, timeZone: string): Temporal.Instant {
  const zdt = Temporal.ZonedDateTime.from(`${localISO}[${timeZone}]`)
  return zdt.toInstant()
}

const instant = localInputToInstant('2026-04-09T14:00:00', 'America/Chicago')
instant.toString()
// → "2026-04-09T19:00:00Z"  (UTC-5 in April → +5 hours to UTC)

Sorting Temporal Objects

Temporal provides a static compare() method on every type that works with Array.prototype.sort() directly.

ts
const dates = [
  Temporal.PlainDate.from('2026-03-15'),
  Temporal.PlainDate.from('2026-01-01'),
  Temporal.PlainDate.from('2026-04-09'),
  Temporal.PlainDate.from('2025-12-31'),
]

// Ascending (oldest first)
dates.sort(Temporal.PlainDate.compare)

// Descending (newest first)
dates.sort((a, b) => Temporal.PlainDate.compare(b, a))

For Instant objects (e.g., sorted feed items):

ts
import { fromDate } from 'moment-less'

interface Post {
  title: string
  publishedAt: Date
}

function sortPostsNewestFirst(posts: Post[]): Post[] {
  return [...posts].sort((a, b) => {
    const ia = fromDate(a.publishedAt)
    const ib = fromDate(b.publishedAt)
    return Temporal.Instant.compare(ib, ia)  // descending
  })
}

Comparing Dates

ts
const a = Temporal.PlainDate.from('2026-04-01')
const b = Temporal.PlainDate.from('2026-04-09')

// Equality
a.equals(b)                               // → false
a.equals(Temporal.PlainDate.from('2026-04-01'))  // → true

// Ordering
Temporal.PlainDate.compare(a, b)  // → -1 (a is before b)
Temporal.PlainDate.compare(b, a)  // →  1 (b is after a)
Temporal.PlainDate.compare(a, a)  // →  0 (equal)

// Convenience wrappers (if you want named predicates)
const isBefore = (x: Temporal.PlainDate, y: Temporal.PlainDate) =>
  Temporal.PlainDate.compare(x, y) < 0

const isAfter = (x: Temporal.PlainDate, y: Temporal.PlainDate) =>
  Temporal.PlainDate.compare(x, y) > 0

const isSameOrBefore = (x: Temporal.PlainDate, y: Temporal.PlainDate) =>
  Temporal.PlainDate.compare(x, y) <= 0

isBefore(a, b)       // → true
isAfter(b, a)        // → true
isSameOrBefore(a, a) // → true

// Is date in range?
function isInRange(
  date: Temporal.PlainDate,
  start: Temporal.PlainDate,
  end: Temporal.PlainDate
): boolean {
  return (
    Temporal.PlainDate.compare(date, start) >= 0 &&
    Temporal.PlainDate.compare(date, end)   <= 0
  )
}

const today = Temporal.PlainDate.from('2026-04-09')
const rangeStart = Temporal.PlainDate.from('2026-04-01')
const rangeEnd   = Temporal.PlainDate.from('2026-04-30')

isInRange(today, rangeStart, rangeEnd)  // → true

Parsing Dates Safely

Temporal throws on invalid input. Wrap in a try/catch for user input:

ts
import { format } from 'moment-less'

function safeParsePlainDate(input: string): Temporal.PlainDate | null {
  try {
    return Temporal.PlainDate.from(input)
  } catch {
    return null
  }
}

function formatUserInput(input: string): string {
  const date = safeParsePlainDate(input)
  if (!date) return 'Invalid date'
  return format(date, 'MMMM Do, YYYY')
}

formatUserInput('2026-04-09')        // → "April 9th, 2026"
formatUserInput('not-a-date')        // → "Invalid date"
formatUserInput('2026-13-01')        // → "Invalid date"  (month 13 doesn't exist)

Released under the MIT License.