1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
const CURRENCY_SYMBOLS: Record<string, string> = {
USD: "$",
CAD: "C$",
GBP: "£",
EUR: "€",
JPY: "¥",
AUD: "A$",
CHF: "CHF ",
CNY: "CN¥",
HKD: "HK$",
};
function currencySymbol(currency = "USD"): string {
return CURRENCY_SYMBOLS[currency.toUpperCase()] || `${currency.toUpperCase()} `;
}
export function fmtCurrency(value?: number | null, decimals = 2, currency = "USD"): string {
if (value === null || value === undefined || Number.isNaN(value)) return "-";
return `${currencySymbol(currency)}${value.toLocaleString(undefined, { maximumFractionDigits: decimals, minimumFractionDigits: decimals })}`;
}
export function fmtNumber(value?: number | null, decimals = 2): string {
if (value === null || value === undefined || Number.isNaN(value)) return "-";
return value.toLocaleString(undefined, { maximumFractionDigits: decimals, minimumFractionDigits: decimals });
}
export function fmtLarge(value?: number | null, currency = "USD"): string {
if (value === null || value === undefined || Number.isNaN(value)) return "-";
const abs = Math.abs(value);
const sign = value < 0 ? "-" : "";
const symbol = currencySymbol(currency);
if (abs >= 1e12) return `${sign}${symbol}${(abs / 1e12).toFixed(2)}T`;
if (abs >= 1e9) return `${sign}${symbol}${(abs / 1e9).toFixed(2)}B`;
if (abs >= 1e6) return `${sign}${symbol}${(abs / 1e6).toFixed(1)}M`;
return `${sign}${symbol}${abs.toLocaleString(undefined, { maximumFractionDigits: 0 })}`;
}
export function fmtPct(value?: number | null, decimals = 2, signed = false): string {
if (value === null || value === undefined || Number.isNaN(value)) return "-";
const pct = value * 100;
const sign = signed && pct > 0 ? "+" : "";
return `${sign}${pct.toFixed(decimals)}%`;
}
export function deltaClass(value?: number | null): string {
if (value === null || value === undefined) return "neutral";
return value >= 0 ? "positive" : "negative";
}
export function formatNewsTimestamp(iso: string): { display: string; iso: string } {
const date = new Date(iso);
const now = new Date();
const tz: Intl.DateTimeFormatOptions = { timeZone: "America/Los_Angeles" };
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60_000);
const diffHours = Math.floor(diffMs / 3_600_000);
if (diffMins < 1) {
return { display: "Just now", iso };
}
if (diffMins < 60) {
return { display: `${diffMins}m ago`, iso };
}
if (diffHours < 24) {
return { display: `${diffHours}h ago`, iso };
}
const inTz = (options: Intl.DateTimeFormatOptions) =>
date.toLocaleString("en-US", { ...options, ...tz });
if (diffHours < 48) {
const time = inTz({ hour: "numeric", minute: "2-digit" });
return { display: `Yesterday ${time}`, iso };
}
const yearNow = Number(now.toLocaleString("en-US", { year: "numeric", ...tz }));
const yearDate = Number(inTz({ year: "numeric" }));
if (yearDate === yearNow) {
return { display: inTz({ month: "short", day: "numeric" }), iso };
}
return { display: inTz({ month: "short", day: "numeric", year: "numeric" }), iso };
}
|