Skip to content

calendar()

Returns a context-aware calendar label in the style of messaging apps and file managers: "Today at 2:05 PM", "Yesterday at 9:30 AM", "Monday at 3:00 PM", "Apr 9, 2026".

Signature

ts
function calendar(
  temporalObj: CalendarInput,
  reference?: Temporal.PlainDate,
  options?: CalendarOptions
): string

type CalendarInput =
  | Temporal.PlainDate
  | Temporal.PlainDateTime
  | Temporal.ZonedDateTime
  | Temporal.Instant

interface CalendarOptions {
  /** BCP 47 locale for weekday/month names and the time portion. */
  locale?: string
  /**
   * Moment.js-style token string for the time suffix.
   * @default 'h:mm A'
   */
  timeFormat?: string
  /** Override the day words and the "at" connector. */
  labels?: {
    today?: string      // @default 'Today'
    yesterday?: string  // @default 'Yesterday'
    tomorrow?: string   // @default 'Tomorrow'
    at?: string         // @default 'at'
  }
}

Parameters

ParameterTypeDefaultDescription
temporalObjCalendarInputrequiredThe date (or datetime) to label. PlainTime is unsupported.
referenceTemporal.PlainDateTemporal.Now.plainDateISO()The "today" to compare against.
options.localestringruntime defaultLocale for names and the time suffix.
options.timeFormatstring'h:mm A'Token string for the appended time.
options.labelsobjectEnglish defaultsOverride the day words and at connector.

Day-range logic

calendar() compares calendar days between temporalObj and reference (via PlainDate.until), not elapsed seconds.

Day differenceOutputExample
0today label + at + timeToday at 2:05 PM
-1yesterday label + at + timeYesterday at 9:30 AM
+1tomorrow label + at + timeTomorrow at 3:00 PM
-6-2 and +2+6weekday name + at + timeMonday at 2:05 PM
≤ -7 or ≥ +7MMM D, YYYY (date only)Apr 9, 2026

The far-past/future fallback always includes the year. When the input is a PlainDate there is no time component, so the at + time suffix is omitted entirely (Today, Monday, …).

Examples

PlainDate (no time suffix)

ts
import { calendar } from 'moment-less'

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

calendar(Temporal.PlainDate.from('2026-04-09'), ref) // → "Today"
calendar(Temporal.PlainDate.from('2026-04-08'), ref) // → "Yesterday"
calendar(Temporal.PlainDate.from('2026-04-10'), ref) // → "Tomorrow"
calendar(Temporal.PlainDate.from('2026-04-06'), ref) // → "Monday"
calendar(Temporal.PlainDate.from('2025-11-15'), ref) // → "Nov 15, 2025"

PlainDateTime (with time)

ts
import { calendar } from 'moment-less'

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

calendar(Temporal.PlainDateTime.from('2026-04-09T11:00'), ref) // → "Today at 11:00 AM"
calendar(Temporal.PlainDateTime.from('2026-04-08T09:30'), ref) // → "Yesterday at 9:30 AM"
calendar(Temporal.PlainDateTime.from('2026-04-07T15:00'), ref) // → "Tuesday at 3:00 PM"

Instant (UTC)

An Instant is compared and displayed in UTC. For local time, convert to a ZonedDateTime first.

ts
import { calendar } from 'moment-less'

const ref  = Temporal.PlainDate.from('2026-04-09')
const sent = Temporal.Instant.from('2026-04-09T09:15:00Z')

calendar(sent, ref) // → "Today at 9:15 AM"

Custom time format

timeFormat takes any format() token string.

ts
import { calendar } from 'moment-less'

const ref = Temporal.PlainDate.from('2026-04-09')
const dt  = Temporal.PlainDateTime.from('2026-04-09T14:05')

calendar(dt, ref, { timeFormat: 'HH:mm' })    // → "Today at 14:05"
calendar(dt, ref, { timeFormat: 'h:mm:ss A' }) // → "Today at 2:05:00 PM"

Localization

Set locale for names and time, and override labels (including the at connector) for the surrounding copy.

ts
import { calendar } from 'moment-less'

const ref = Temporal.PlainDate.from('2026-04-09')
const dt  = Temporal.PlainDateTime.from('2026-04-08T14:05')

calendar(dt, ref, {
  locale: 'fr',
  timeFormat: 'HH:mm',
  labels: { yesterday: 'Hier', at: 'à' },
})
// → "Hier à 14:05"

Throws for PlainTime

A Temporal.PlainTime has no calendar component, so calendar() cannot place it on a day:

ts
calendar(Temporal.PlainTime.from('14:05'))
// Error: calendar() cannot be used with Temporal.PlainTime — it has no date context

Released under the MIT License.