ente/web/packages/base/i18n-date.ts
Manav Rathi e8d9f4f6cf
Conv
2025-02-21 12:45:12 +05:30

105 lines
3.2 KiB
TypeScript

/**
* @file Various date formatters.
*
* Note that we rely on the current behaviour of a full reload on changing the
* language. See: [Note: Changing locale causes a full reload].
*/
import i18n, { t } from "i18next";
const _dateFormat = new Intl.DateTimeFormat(i18n.language, {
weekday: "short",
day: "numeric",
month: "short",
year: "numeric",
});
const _dateWithoutYearFormat = new Intl.DateTimeFormat(i18n.language, {
weekday: "short",
day: "numeric",
month: "short",
});
const _timeFormat = new Intl.DateTimeFormat(i18n.language, {
timeStyle: "short",
});
/**
* Return a locale aware formatted date from the given {@link Date}.
*
* The behaviour depends upon whether the given {@link date} falls within the
* current calendar year.
*
* - For dates in the current year, year is omitted, e.g, "Fri, 21 Feb".
*
* - Otherwise, the year is included, e.g., "Fri, 21 Feb 2025".
*/
export const formattedDate = (date: Date) =>
(isSameYear(date) ? _dateWithoutYearFormat : _dateFormat).format(date);
const isSameYear = (date: Date) =>
new Date().getFullYear() === date.getFullYear();
/**
* Return a locale aware formatted time from the given {@link Date}.
*
* Example: "11:51 AM"
*/
export const formattedTime = (date: Date) => _timeFormat.format(date);
/**
* Return a locale aware formatted date and time from the given {@link Date},
* using the year omission behavior as documented in {@link formattedDate}.
*
* Example:
* - If within year: "Fri, 21 Feb at 11:51 AM".
* - Otherwise: "Fri, 21 Feb 2025 at 11:51 AM"
*
* @param dateOrEpochMicroseconds A JavaScript Date or a numeric epoch
* microseconds value.
*
* As a convenience, this function can be either be directly passed a JavaScript
* date, or it can be given the raw epoch microseconds value and it'll convert
* internally.
*
* See: [Note: Remote timestamps are epoch microseconds]
*/
export const formattedDateTime = (dateOrEpochMicroseconds: Date | number) =>
_formattedDateTime(toDate(dateOrEpochMicroseconds));
const _formattedDateTime = (date: Date) =>
[formattedDate(date), t("at"), formattedTime(date)].join(" ");
const toDate = (dm: Date | number) =>
typeof dm == "number" ? new Date(dm / 1000) : dm;
let _relativeTimeFormat: Intl.RelativeTimeFormat | undefined;
export const formattedDateRelative = (date: Date) => {
const units: [Intl.RelativeTimeFormatUnit, number][] = [
["year", 24 * 60 * 60 * 1000 * 365],
["month", (24 * 60 * 60 * 1000 * 365) / 12],
["day", 24 * 60 * 60 * 1000],
["hour", 60 * 60 * 1000],
["minute", 60 * 1000],
["second", 1000],
];
// Math.abs accounts for both past and future scenarios.
const elapsed = Math.abs(date.getTime() - Date.now());
// Lazily created, then cached, instance of RelativeTimeFormat.
const relativeTimeFormat = (_relativeTimeFormat ??=
new Intl.RelativeTimeFormat(i18n.language, {
localeMatcher: "best fit",
numeric: "always",
style: "short",
}));
for (const [u, d] of units) {
if (elapsed > d)
return relativeTimeFormat.format(Math.round(elapsed / d), u);
}
return relativeTimeFormat.format(Math.round(elapsed / 1000), "second");
};