| 1 | const UNITS = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"]; |
| 2 | |
| 3 | /** Binary units, matching what ZFS and the kernel report. */ |
| 4 | export function bytes(value: number) { |
| 5 | let unit = 0; |
| 6 | while (Math.abs(value) >= 1024 && unit < UNITS.length - 1) { |
| 7 | value /= 1024; |
| 8 | unit++; |
| 9 | } |
| 10 | return `${value.toFixed(unit === 0 ? 0 : value < 10 ? 1 : 0)} ${UNITS[unit]}`; |
| 11 | } |
| 12 | |
| 13 | export const rate = (value: number) => bytes(value) + "/s"; |
| 14 | export const percent = (value: number) => `${value.toFixed(Number.isInteger(value) || value >= 10 ? 0 : 1)}%`; |
| 15 | export const cores = (value: number) => value.toFixed(value < 10 ? 2 : 1); |
| 16 | export const watts = (value: number) => `${Math.round(value)} W`; |
| 17 | export const count = (value: number) => value.toLocaleString(); |
| 18 | export const plural = (n: number, one: string, many = one + "s") => `${count(n)} ${n === 1 ? one : many}`; |
| 19 | export const celsius = (value: number) => `${Math.round(value)} °C`; |
| 20 | |
| 21 | const SPANS = [["mo", 30 * 86400], ["d", 86400], ["h", 3600], ["m", 60]] as const; |
| 22 | |
| 23 | /** Up to two units and no seconds past the first minute: "45s", "12m", "3h 5m", "19d 5h", "3mo 22d". */ |
| 24 | export function duration(seconds: number) { |
| 25 | const first = SPANS.findIndex(([, size]) => seconds >= size); |
| 26 | if (first === -1) return `${Math.floor(Math.max(0, seconds))}s`; |
| 27 | const [label, size] = SPANS[first]!; |
| 28 | const next = SPANS[first + 1]; |
| 29 | const rest = next ? Math.floor((seconds % size) / next[1]) : 0; |
| 30 | return `${Math.floor(seconds / size)}${label}${rest ? ` ${rest}${next![0]}` : ""}`; |
| 31 | } |
| 32 | |
| 33 | /** One unit while under a week old ("22h ago"), then the date. */ |
| 34 | export function ago(t: number) { |
| 35 | const seconds = Date.now() / 1000 - t; |
| 36 | if (seconds >= 7 * 86400) return date(t); |
| 37 | const unit = SPANS.find(([, size]) => seconds >= size); |
| 38 | return unit ? `${Math.floor(seconds / unit[1])}${unit[0]} ago` : "just now"; |
| 39 | } |
| 40 | |
| 41 | /** How long until `t`, from `now`: "in 3h 5m", or "soon" once it's due. */ |
| 42 | export function until(t: number, now = Date.now() / 1000) { |
| 43 | return t > now ? `in ${duration(t - now)}` : "soon"; |
| 44 | } |
| 45 | |
| 46 | export function clock(t: number) { |
| 47 | return new Date(t * 1000).toLocaleTimeString(undefined, { hour12: false }); |
| 48 | } |
| 49 | |
| 50 | export const date = (t: number) => new Date(t * 1000).toLocaleDateString(undefined, { dateStyle: "medium" }); |
| 51 | export const datetime = (t: number) => new Date(t * 1000).toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" }); |