export function relativeTime(iso: string | null): string { if (!iso) return ""; const then = new Date(iso).getTime(); const secs = Math.max(0, (Date.now() - then) / 1000); const units: [number, string][] = [ [60, "s"], [3600, "min"], [86400, "h"], [604800, "d"], [2592000, "wk"], [31536000, "mo"], ]; if (secs < 60) return "just now"; for (let i = 0; i < units.length - 1; i++) { const [, label] = units[i]; const next = units[i + 1][0]; if (secs < next) { const v = Math.floor(secs / units[i][0]); return `${v} ${label} ago`; } } const years = Math.floor(secs / 31536000); if (years >= 1) return `${years} yr ago`; const months = Math.floor(secs / 2592000); return `${months} mo ago`; } export function formatDuration(sec: number | null): string { if (sec == null) return ""; const h = Math.floor(sec / 3600); const m = Math.floor((sec % 3600) / 60); const s = Math.floor(sec % 60); const pad = (n: number) => String(n).padStart(2, "0"); return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${m}:${pad(s)}`; } /** Coarse, human ETA for a remaining duration in seconds ("~3 hours", "~2 days"). */ export function formatEta(seconds: number): string { if (seconds <= 0) return "done"; const hours = seconds / 3600; if (hours < 1) return "< 1 hour"; if (hours < 24) { const h = Math.round(hours); return `~${h} hour${h === 1 ? "" : "s"}`; } const d = Math.round(hours / 24); return `~${d} day${d === 1 ? "" : "s"}`; } export function formatViews(n: number | null): string { if (n == null) return ""; if (n >= 1e9) return `${(n / 1e9).toFixed(1).replace(/\.0$/, "")}B`; if (n >= 1e6) return `${(n / 1e6).toFixed(1).replace(/\.0$/, "")}M`; if (n >= 1e3) return `${(n / 1e3).toFixed(1).replace(/\.0$/, "")}K`; return String(n); }