mirror of
https://github.com/Xevion/banner.git
synced 2026-01-31 08:23:35 -06:00
79 lines
2.4 KiB
TypeScript
79 lines
2.4 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;
|
|
}
|
|
|
|
/**
|
|
* Format a duration in milliseconds as a compact human-readable string.
|
|
*
|
|
* Format tiers:
|
|
* - < 60s: seconds only ("45s")
|
|
* - < 1h: minutes + seconds ("9m 35s")
|
|
* - < 24h: hours + minutes ("1h 23m")
|
|
* - >= 24h: days only ("3d")
|
|
*/
|
|
export function formatDuration(ms: number): string {
|
|
const totalSeconds = Math.floor(Math.abs(ms) / 1000);
|
|
|
|
if (totalSeconds < 60) return `${totalSeconds}s`;
|
|
|
|
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
if (totalMinutes < 60) {
|
|
const secs = totalSeconds % 60;
|
|
return `${totalMinutes}m ${secs}s`;
|
|
}
|
|
|
|
const totalHours = Math.floor(totalMinutes / 60);
|
|
if (totalHours < 24) {
|
|
const mins = totalMinutes % 60;
|
|
return `${totalHours}h ${mins}m`;
|
|
}
|
|
|
|
const days = Math.floor(totalHours / 24);
|
|
return `${days}d`;
|
|
}
|
|
|
|
/**
|
|
* Compute a compact relative time string and the interval until it next changes.
|
|
*
|
|
* Uses {@link formatDuration} for the text, plus computes the optimal refresh
|
|
* interval so callers can schedule the next update efficiently.
|
|
*/
|
|
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 };
|
|
}
|
|
|
|
const text = formatDuration(diffMs);
|
|
|
|
// Compute optimal next-update interval based on the current tier
|
|
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
const totalHours = Math.floor(totalMinutes / 60);
|
|
|
|
let nextUpdateMs: number;
|
|
if (totalHours >= 24) {
|
|
const msIntoCurrentDay = diffMs % 86_400_000;
|
|
nextUpdateMs = 86_400_000 - msIntoCurrentDay || 86_400_000;
|
|
} else if (totalMinutes >= 60) {
|
|
const msIntoCurrentMinute = diffMs % 60_000;
|
|
nextUpdateMs = 60_000 - msIntoCurrentMinute || 60_000;
|
|
} else {
|
|
nextUpdateMs = 1000 - (diffMs % 1000) || 1000;
|
|
}
|
|
|
|
return { text, nextUpdateMs };
|
|
}
|