Files
banner/web/src/lib/time.ts

70 lines
2.0 KiB
TypeScript

/**
* Relative time formatting with adaptive refresh intervals.
*
* The key insight: a timestamp showing "3s" needs to update every second,
* but "2h 15m" only needs to update every minute. This module provides both
* the formatted string and the optimal interval until the next meaningful change.
*/
interface RelativeTimeResult {
/** Compact relative time string (e.g. "9m 35s", "1h 23m", "3d") */
text: string;
/** Milliseconds until the displayed text would change */
nextUpdateMs: number;
}
/**
* Compute a compact relative time string and the interval until it next changes.
*
* Format tiers:
* - < 60s: seconds only ("45s")
* - < 1h: minutes + seconds ("9m 35s")
* - < 24h: hours + minutes ("1h 23m")
* - >= 24h: days only ("3d")
*/
export function relativeTime(date: Date, ref: Date): RelativeTimeResult {
const diffMs = ref.getTime() - date.getTime();
const totalSeconds = Math.floor(diffMs / 1000);
if (totalSeconds < 1) {
return { text: "now", nextUpdateMs: 1000 - (diffMs % 1000) || 1000 };
}
if (totalSeconds < 60) {
const remainder = 1000 - (diffMs % 1000);
return {
text: `${totalSeconds}s`,
nextUpdateMs: remainder || 1000,
};
}
const totalMinutes = Math.floor(totalSeconds / 60);
if (totalMinutes < 60) {
const secs = totalSeconds % 60;
const remainder = 1000 - (diffMs % 1000);
return {
text: `${totalMinutes}m ${secs}s`,
nextUpdateMs: remainder || 1000,
};
}
const totalHours = Math.floor(totalMinutes / 60);
if (totalHours < 24) {
const mins = totalMinutes % 60;
const msIntoCurrentMinute = diffMs % 60_000;
const msUntilNextMinute = 60_000 - msIntoCurrentMinute;
return {
text: `${totalHours}h ${mins}m`,
nextUpdateMs: msUntilNextMinute || 60_000,
};
}
const days = Math.floor(totalHours / 24);
const msIntoCurrentDay = diffMs % 86_400_000;
const msUntilNextDay = 86_400_000 - msIntoCurrentDay;
return {
text: `${days}d`,
nextUpdateMs: msUntilNextDay || 86_400_000,
};
}