Relativize.js
Made for use on this website.
See the Pen Relativize Lib by j0lol (@j0lol) on CodePen.
Code
// Relativize library
// Authored by: Josie
// Licensed: CC0 || Unlicense
// Calculates "N months/days/hours ago" strings with Temporal.
// feel free to tweak this, i guess.
// i doubt you want ms precision, or weeks separate from days
const units = ["years", "months", "days", "hours", "minutes", "seconds"];
// Finds the largest unit (y, m, d, etc.)
function findLargestUnit(duration) {
return units.find((unit) => (duration[unit] !== 0));
}
// 2 months, 3 days, 1 minute, 5 seconds, ... -> 2 months
function plainSimplify(duration) {
const largestUnit = findLargestUnit(duration);
return (new Temporal.Duration(0)).with({
[largestUnit]: duration[largestUnit]
});
}
// i don't have the ability to translate this, sorry.
function formatRelString(duration, ...localeArgs) {
const sign = duration.sign;
if (sign === 1) {
const localeString = duration.toLocaleString(...localeArgs);
return `${localeString} ago`
} else if (sign === -1) {
const localeString = duration.abs().toLocaleString(...localeArgs);
return `in ${localeString}`;
} else {
return "now";
}
}
const defaultRelativizeOptions = {
locale: "en-US",
largestFormattableUnit: "years",
durationFormattingOptions: { style: "long" },
plainDateFormattingOptions: { dateStyle: "short" }
};
const dRO = defaultRelativizeOptions; // alias
// `locale` is used for `toLocaleString` calls.
// (Realistically only English is useful here... weh)
//
// `largestFormattableUnit` is the unit where relativize will stop bothering.
// (this defaults to a year, set it to null/undefined to disable this behavior)
//
// `durationFormattingOptions` and `plainDateFormattingOptions` are both passed to
// `toLocaleString`, in case you want to change how it looks.
// See `Intl.DateTimeFormat()` and `Intl.DurationFormat()` constructors for more info.
export function relativize({
locale = dRO.locale,
largestFormattableUnit = dRO.largestFormattableUnit,
durationFormattingOptions = dRO.durationFormattingOptions,
plainDateFormattingOptions = dRO.plainDateFormattingOptions
} = defaultRelativizeOptions) {
// upgrade your browser!
// in theory this could be used with a Temporal polyfill.
// don't do this. or do. i'm not your boss.
if (!Temporal) { return; }
document.querySelectorAll("time[data-relative]").forEach((el) => {
el.innerHTML = "processing...";
// we have to make sure both dates are in the same
// timezone to do calendar-aware math with Duration
const then = Temporal.ZonedDateTime.from(
el.attributes.datetime.value
).withTimeZone("UTC");
const now = Temporal.Now.zonedDateTimeISO().withTimeZone("UTC");
// specifying largestUnit years puts the Duration into
// calendar-aware mode. weird behavior, i know.
// if not specified, all the time will be put into "hours".
const dur = now.since(then, { largestUnit: "years" });
if (largestFormattableUnit != null) {
if (dur.abs()[largestFormattableUnit] >= 1) {
el.innerHTML = "on " + then.toPlainDate().toLocaleString(locale, plainDateFormattingOptions);
return;
}
}
el.innerHTML = formatRelString(plainSimplify(dur), locale, durationFormattingOptions);
});
}