Skip to content

formatCalendar

formatCalendar(value: string, locale?: string | string[], options?: FormatCalendarOptions): string

Format a plain date-time as a relative day label plus time-of-day, e.g. “Tomorrow at 2:30 PM”. The formatRelativeDateTime family does not cover this: it renders “in 1 day”, an elapsed-time phrase, not a day label + clock time.

  • Within ±6 days of reference (default: now), renders <day label> joined to the localized time using the locale’s own connector — never a hardcoded “at”. The day label comes from Intl.RelativeTimeFormat (numeric: "auto"), so it reads “Today”/“Tomorrow”/“Yesterday” near the boundary and “in N days”/“N days ago” further out, all locale-native.
  • Beyond ±6 days, falls back to an absolute dateStyle: "long" + timeStyle string with no relative wording. The one-week limit is GMT’s own rule, not a standard’s: from 7 days out a date is easier to place than a day count.
  • Comparison: Moment’s .calendar() switches to a date at the same distance (its sameElse case); its wording inside the week differs.
  • The connector between the day label and the time is read from CLDR’s own combined date+time pattern for the locale (see internal/joinDateTimeConnector.ts), not hardcoded — this is what lets formatCalendar avoid the i18n objection that excludes a token formatter.
  • The time never carries a zone name: a plain value has none.
  • Use formatCalendar for user-facing schedules (“Tomorrow at 2:30 PM”); use formatRelativeDateTime for elapsed-time displays (“in 1 day”).
  • options must be an object or omitted: null or any other primitive returns "", as Temporal’s GetOptionsObject rejects it.
ParameterTypeDescription
valuestringISO PlainDateTime string to format
localestring | string[]optional: BCP 47 locale tag, or a preference list of tags (ECMA-402)

options

What the value is compared with and how the time is written

Options for formatCalendar: the date-time the value is compared with, and the style of the time-of-day half.

import type { FormatCalendarOptions } from "@northguild/gmt";
OptionTypeDefaultDescription
timeStyle?"short" | "medium""short"The length of the time-of-day part, as the timeStyle of Intl.DateTimeFormat. “long” and “full” are not offered: they differ from “medium” only by a timeZoneName, and a plain value has no zone. Passed anyway (from untyped code), they format as “medium” — Temporal’s PlainDateTime format drops timeZoneName (AdjustDateTimeStyleFormat).
reference?stringThe current date and time.The moment the day label is measured from, as an ISO string in the same form as the value being formatted. A value on the same calendar day reads “today”, one on the next day “tomorrow”.

the formatted calendar string, or "" on invalid input

import { formatCalendar } from "@northguild/gmt/plain/format";
formatCalendar("2026-03-16T14:30:00", "en-US", { reference: "2026-03-15T09:00:00" }) // "tomorrow at 2:30 PM"
output 

plain/format/formatCalendar.ts