Formatting
Use these functions when you need to render a date, time, or datetime as a string for a user, or encode it into a fixed interchange grammar.
Absolute formatting with locale
Section titled “Absolute formatting with locale”import { formatDate, formatTime, formatDateTime } from "@northguild/gmt";
formatDate("2024-03-15", "en-US"); // "3/15/2024"formatDate("2024-03-15", "en-GB"); // "15/03/2024"formatDate("2024-03-15", "de-DE"); // "15.3.2024"
formatTime("14:30:45"); // "2:30:45 PM"formatTime("14:30:45", "en-GB"); // "14:30:45"
formatDateTime("2024-03-15T14:30:45", "en-US"); // "3/15/2024, 2:30:45 PM"Pass Intl.DateTimeFormatOptions for full control:
formatDate("2024-03-15", "en-US", { year: "numeric", month: "long", day: "numeric" });// "March 15, 2024"Locale-elided ranges
Section titled “Locale-elided ranges”formatDateRange and formatDateTimeRange are the plain counterparts of
formatZonedRange. They take the same parameter order and option shape, and write a
locale-elided range between two timezone-free values:
import { formatDateRange, formatDateTimeRange } from "@northguild/gmt";import { formatZonedRange } from "@northguild/gmt/zoned";
formatDateRange("2024-02-03", "2024-02-05", "en-US", { dateStyle: "long" });// "February 3 - 5, 2024"
formatDateTimeRange("2024-02-03T09:00:00", "2024-02-03T17:00:00", "en-US", { dateStyle: "long", timeStyle: "short",});// "February 3, 2024, 9:00 AM - 5:00 PM"
formatZonedRange( "2024-02-03T09:00:00-05:00[America/New_York]", "2024-02-03T17:00:00-05:00[America/New_York]", "en-US", { dateStyle: "long", timeStyle: "short" },);// "February 3, 2024, 9:00 AM - 5:00 PM"Locale-ordered parts instead of a finished string
Section titled “Locale-ordered parts instead of a finished string”formatDateToParts/formatDateTimeToParts/formatZonedToParts return the
locale-ordered Array<{ type, value }> behind the string — GMT’s substitute for a
token formatter:
import { formatDateToParts } from "@northguild/gmt";
formatDateToParts("2024-03-15", "en-US");// [// { type: "month", value: "3" },// { type: "literal", value: "/" },// { type: "day", value: "15" },// { type: "literal", value: "/" },// { type: "year", value: "2024" },// ]
formatDateToParts("2024-03-15", "fr-FR");// [// { type: "day", value: "15" },// { type: "literal", value: "/" },// { type: "month", value: "03" },// { type: "literal", value: "/" },// { type: "year", value: "2024" },// ] — day comes before month — same locale-order guarantee, as partsformatZonedToParts takes the same Intl.DateTimeFormatOptions, so it can return the zone
as a part too:
import { formatZonedToParts } from "@northguild/gmt";
formatZonedToParts("2024-03-15T14:30:00-04:00[America/New_York]", "en-US", { timeZoneName: "longOffset",});// [// { type: "month", value: "3" },// { type: "literal", value: "/" },// { type: "day", value: "15" },// { type: "literal", value: "/" },// { type: "year", value: "2024" },// { type: "literal", value: ", " },// { type: "hour", value: "2" },// { type: "literal", value: ":" },// { type: "minute", value: "30" },// { type: "literal", value: ":" },// { type: "second", value: "00" },// { type: "literal", value: " " },// { type: "dayPeriod", value: "PM" },// { type: "literal", value: " " },// { type: "timeZoneName", value: "GMT-04:00" },// ]Iterate the array as returned. Do not pick out .find(p => p.type === "month")
and reassemble parts into a fixed order — that reintroduces the locale-ordering bug
formatToParts exists to avoid.
Relative formatting (“yesterday”, “in 2 hours”)
Section titled “Relative formatting (“yesterday”, “in 2 hours”)”import { formatRelativeDate, formatRelativeTime, formatRelativeDateTime,} from "@northguild/gmt";
const ref = "2024-03-15";formatRelativeDate("2024-03-14", "en-US", { reference: ref }); // "yesterday"formatRelativeDate("2024-03-18", "en-US", { reference: ref }); // "in 3 days"
formatRelativeTime("11:30:00", "en-US", { reference: "12:00:00" }); // "30 minutes ago"formatRelativeDateTime("2024-03-15T10:00:00", "en-US", { reference: "2024-03-15T12:00:00" });// "2 hours ago"reference is optional and defaults to now — pass it, in the same shape as the value,
whenever the output must be deterministic. There are
formatRelativeZoned, formatRelativeUnix, and formatRelativeUtc siblings for
their respective value types. See the Relative Time guide for
the full option set and DST-safe zoned behavior.
Calendar-day label + time-of-day (Moment’s .calendar())
Section titled “Calendar-day label + time-of-day (Moment’s .calendar())”formatCalendar renders a day-granularity label plus the time-of-day, joined with
the locale’s own connector (never a hardcoded “at”):
import { formatCalendar, formatCalendarZoned } from "@northguild/gmt";
const ref = "2026-03-15T09:00:00";formatCalendar("2026-03-16T14:30:00", "en-US", { reference: ref });// "tomorrow at 2:30 PM"
formatCalendar("2026-03-08T14:30:00", "en-US", { reference: ref });// "March 8, 2026 at 2:30 PM" — beyond ±6 days, falls back to absoluteWithin ±6 days of now (or of an explicit reference) the label stays relative. Beyond
that it falls back to an absolute date plus time, with no relative wording.
Distinct from formatRelativeDateTime (which always renders an elapsed-time phrase
like “in 1 day” and never includes a clock time). This is Moment’s .calendar(): use it
for user-facing schedules, not for elapsed-time displays.
formatCalendarZoned does the same for a zoned value, and takes the locale’s own
connector too:
import { formatCalendarZoned } from "@northguild/gmt";
formatCalendarZoned("2026-03-16T14:30:00-04:00[America/New_York]", "de-DE", { reference: "2026-03-15T09:00:00-04:00[America/New_York]",});// "morgen um 14:30" — the locale's own connector, not "at"Standalone locale calendar names (no date value needed)
Section titled “Standalone locale calendar names (no date value needed)”import { getLocaleEraNames, getLocaleMonthNames, getLocaleWeekdayNames, getLocaleMeridiems,} from "@northguild/gmt";
getLocaleEraNames("en-US"); // ["Before Christ", "Anno Domini"]getLocaleMonthNames("en-US");// ["January", "February", "March", "April", "May", "June",// "July", "August", "September", "October", "November", "December"]getLocaleWeekdayNames("en-US");// ["Sunday", "Monday", "Tuesday", "Wednesday",// "Thursday", "Friday", "Saturday"] (locale-first-day order)getLocaleMeridiems("en-US"); // ["AM", "PM"]
getLocaleEraNames("ja-JP", "short"); // ["紀元前", "西暦"]getLocaleMonthNames("de-DE", "short");// ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"]getLocaleWeekdayNames("fr-FR");// ["lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche"]getLocaleMeridiems("zh-CN"); // ["上午", "下午"]These are the GMT equivalent of Luxon’s Info.eras/Info.months/Info.weekdays/Info.meridiems.
All four return [] for an invalid BCP 47 locale tag. The era, month and weekday names take an
optional second argument, the style, such as "short". getLocaleWeekdayNames returns names
in the locale’s first-day order, consistent with getLocaleDayOfWeek: index 0 is the locale’s
first day of the week. All four delegate to the host runtime’s Intl data, so their output
depends on the runtime’s ICU build.
Named machine formats (email, HTTP, SQL, RFC 3339)
Section titled “Named machine formats (email, HTTP, SQL, RFC 3339)”Fixed, non-locale-adaptive grammars for interchange with email, HTTP, SQL and RFC 3339
consumers. None of these take a locale argument, since the grammar itself is constant
(English weekday and month names where the format mandates them):
import { formatRfc2822, formatHttpDate, formatSqlDateTime, formatRfc3339 } from "@northguild/gmt";
formatRfc2822("2024-03-15T14:30:00-04:00[America/New_York]");// "Fri, 15 Mar 2024 14:30:00 -0400"
formatHttpDate("2024-03-15T14:30:00Z");// "Fri, 15 Mar 2024 14:30:00 GMT"
formatSqlDateTime("2024-03-15T14:30:00");// "2024-03-15 14:30:00"
formatRfc3339("2024-03-15T14:30:00-04:00[America/New_York]");// "2024-03-15T14:30:00-04:00" — strips the bracketed zone, which RFC 3339 does not permitThe formatters return "" for a value their grammar cannot express, rather than writing
a string no conforming parser accepts. That means a year outside 0000–9999 for RFC 3339
and HTTP-date, or outside 0001–9999 for SQL:
import { formatRfc3339, formatSqlDateTime } from "@northguild/gmt";
formatRfc3339("10000-01-01T00:00:00Z"); // ""formatSqlDateTime("0000-01-01T00:00:00"); // ""Use Temporal’s own toString() when you need any year.
Each has a parse* counterpart in the Parsing guide, and a validator that
agrees with that parser: isValidRfc2822DateTime, isValidHttpDate, isValidSqlDateTime and
isValidRfc3339DateTime. Check a value with the validator, not with the rfc3339DateTime,
rfc2822DateTime, httpDate or sqlDateTime pattern. A pattern matches a day that does not
exist, such as 29 February 2023, and the rfc2822DateTime and httpDate patterns do not match
the obsolete forms the parsers read.
See also
Section titled “See also”- Relative Time — the full relative-formatter option set
- Parsing — decoding strings, including the
parse*counterparts - Zoned Operations —
formatZonedDateTime,formatZonedRange formatDatereferenceformatRelativeDatereferenceformatRfc2822reference