@burglekitt/gmt
Version:
Temporal-based date and time utilities with timezone support and polyfill integration
58 lines (57 loc) • 2.38 kB
JavaScript
import { Temporal } from "@js-temporal/polyfill";
import { normalizeDateTime, resolveRelativeRounding } from "../../internal/index.js";
import { isValidDate } from "../validate/index.js";
const AUTO_UNITS = [
{ unit: "day", maxDays: 7 },
{ unit: "week", maxDays: 28 },
{ unit: "month", maxDays: 365 },
{ unit: "year", maxDays: Infinity },
];
/**
* Format the relative time between a plain date and a reference date.
*
* - Auto-picks the display unit (day/week/month/year) based on the distance, unless
* `largestUnit` forces one.
* - `roundingMethod` controls how the distance rounds to the display unit.
*
* @param value ISO date string to format
* @param locale optional: BCP 47 locale tag
* @param options optional: { style, numeric, largestUnit, roundingMethod, reference }
* @returns the formatted relative-time string, or "" on invalid input
*
* @example formatRelativeDate("2026-01-15", "en-US", { reference: "2026-04-15" }) // "3 months ago"
* @example formatRelativeDate(value, "en-US", { roundingMethod: "floor" }) // rounds toward the earlier boundary
* @example formatRelativeDate("not-a-date") // ""
*/
export function formatRelativeDate(value, locale, options = {}) {
if (!isValidDate(value))
return "";
if (options.reference !== undefined && !isValidDate(options.reference))
return "";
try {
const target = Temporal.PlainDate.from(value);
const reference = options.reference
? Temporal.PlainDate.from(options.reference)
: Temporal.Now.plainDateISO();
const diff = target.since(reference);
const absDays = Math.abs(diff.total("day"));
const unit = options.largestUnit ??
AUTO_UNITS.find((t) => absDays < t.maxDays)?.unit ??
"year";
let amount;
try {
amount = resolveRelativeRounding(diff.total(unit), options.roundingMethod);
}
catch {
// month/year are calendrical and need a relativeTo anchor
amount = resolveRelativeRounding(diff.total({ unit, relativeTo: reference }), options.roundingMethod);
}
return normalizeDateTime(new Intl.RelativeTimeFormat(locale, {
numeric: options.numeric ?? "auto",
style: options.style ?? "long",
}).format(amount, unit));
}
catch {
return "";
}
}