relative-time.ts2.6 KBView on GitHub /**
* Ages, for a file row's date column.
*
* Promoted out of `ConversationFileTree`, where `shortAgo`/`longAgo` were module-private
* and therefore re-implemented (or simply skipped) by every other file surface.
*
* Two forms, because a column and a sentence want different things:
* - `shortAgo` for the Modified column — "3m", "5h", "2d", then an absolute date past a
* week, which is what Google Drive does and what makes a column of dates scannable.
* A file from March reading "24w" tells you nothing you wanted to know.
* - `longAgo` for prose inside a tooltip or an activity string — "8h", "3 days", "2 weeks".
*/
function minutesSince(date: Date, now: number): number {
return Math.max(0, Math.round((now - date.getTime()) / 60000));
}
function toDate(at: Date | string | number | null | undefined): Date | null {
if (at === null || at === undefined) return null;
const date = at instanceof Date ? at : new Date(at);
return Number.isNaN(date.getTime()) ? null : date;
}
/**
* The Modified column: "now", "42m", "5h", "2d", then "12 Mar" / "12 Mar 2024".
*
* `now` is injectable so a test can pin it; every caller in the app omits it.
*/
export function shortAgo(
at: Date | string | number | null | undefined,
now: number = Date.now(),
): string {
const date = toDate(at);
if (!date) return '—';
const mins = minutesSince(date, now);
if (mins < 1) return 'now';
if (mins < 60) return `${mins}m`;
const hours = Math.round(mins / 60);
if (hours < 24) return `${hours}h`;
const days = Math.round(hours / 24);
if (days < 7) return `${days}d`;
// Past a week the age stops being the useful fact and the date starts being it.
// The year is only shown when it is not the current one — an extra four characters
// on every row to disambiguate the handful that need it is a bad trade.
const sameYear = date.getFullYear() === new Date(now).getFullYear();
return date.toLocaleDateString(undefined, {
day: 'numeric',
month: 'short',
...(sameYear ? {} : { year: 'numeric' }),
});
}
/** Spelled-out age for prose: "moments", "42m", "8h", "3 days", "2 weeks". */
export function longAgo(
at: Date | string | number | null | undefined,
now: number = Date.now(),
): string {
const date = toDate(at);
if (!date) return '—';
const mins = minutesSince(date, now);
if (mins < 1) return 'moments';
if (mins < 60) return `${mins}m`;
const hours = Math.round(mins / 60);
if (hours < 24) return `${hours}h`;
const days = Math.round(hours / 24);
if (days < 7) return `${days} ${days === 1 ? 'day' : 'days'}`;
const weeks = Math.round(days / 7);
return `${weeks} ${weeks === 1 ? 'week' : 'weeks'}`;
}