1
7
8const BYTES_UNITS = ["B", "KB", "MB", "GB", "TB", "PB"] as const;
9
10
15export function formatBytes(n: number): string {
16 if (!Number.isFinite(n) || n < 0) return "0 B";
17 if (n < 1024) return `${Math.round(n)} B`;
18 let value = n;
19 let unit = 0;
20 while (value >= 1024 && unit < BYTES_UNITS.length - 1) {
21 value /= 1024;
22 unit += 1;
23 }
24 const label = BYTES_UNITS[unit] ?? "B";
25
26 const text =
27 value < 10 && !Number.isInteger(value) ? value.toFixed(1) : String(Math.round(value));
28 return `${text} ${label}`;
29}
30
31
32export function formatNumber(n: number, locale?: string): string {
33 return new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }).format(n);
34}
35
36const MINUTE_MS = 60_000;
37const HOUR_MS = 60 * MINUTE_MS;
38const DAY_MS = 24 * HOUR_MS;
39
40function parseDate(iso: string): number {
41 const t = Date.parse(iso);
42 return Number.isNaN(t) ? 0 : t;
43}
44
45
50export function formatRelativeTime(iso: string, now?: number): string {
51 const nowMs = now ?? Date.now();
52 const thenMs = parseDate(iso);
53 if (thenMs <= 0) return "";
54 const diff = nowMs - thenMs;
55 if (diff < MINUTE_MS) return "just now";
56 if (diff < HOUR_MS) return `${Math.floor(diff / MINUTE_MS)}m ago`;
57 if (diff < DAY_MS) return `${Math.floor(diff / HOUR_MS)}h ago`;
58 if (diff < 7 * DAY_MS) return `${Math.floor(diff / DAY_MS)}d ago`;
59 return formatDate(iso);
60}
61
62
63export function formatDurationMs(ms: number): string {
64 if (!Number.isFinite(ms) || ms < 0) return "0s";
65 const totalSeconds = Math.round(ms / 1000);
66 if (totalSeconds < 60) {
67 const seconds = ms / 1000;
68 return seconds < 10 ? `${seconds.toFixed(1)}s` : `${Math.round(seconds)}s`;
69 }
70 const hours = Math.floor(totalSeconds / 3600);
71 const minutes = Math.floor((totalSeconds % 3600) / 60);
72 const seconds = totalSeconds % 60;
73 if (hours > 0) {
74 return `${hours}h ${String(minutes).padStart(2, "0")}m ${String(seconds).padStart(2, "0")}s`;
75 }
76 return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
77}
78
79
84export function initials(name: string): string {
85 const words = name.trim().split(/\s+/).filter(Boolean);
86 if (words.length === 0) return "";
87 if (words.length === 1) {
88 const w = words[0] ?? "";
89 return w.slice(0, 2).toUpperCase();
90 }
91 let out = "";
92 for (const w of words) {
93 if (out.length >= 2) break;
94 out += (w[0] ?? "").toUpperCase();
95 }
96 return out;
97}
98
99
100export function truncate(s: string, n: number): string {
101 if (n < 0 || s.length <= n) return s;
102 return `${s.slice(0, n)}…`;
103}
104
105
106export function formatDate(iso: string, opts?: { locale?: string }): string {
107 const t = parseDate(iso);
108 if (t <= 0) return "";
109 return new Date(t).toLocaleDateString(opts?.locale, {
110 year: "numeric",
111 month: "short",
112 day: "numeric",
113 });
114}
115