format.ts (2033B)
1 // web/src/util/format.ts - timestamps, durations and sizes 2 3 const UNITS: [number, string][] = [ 4 [60, 'second'], 5 [60, 'minute'], 6 [24, 'hour'], 7 [7, 'day'], 8 [4.348, 'week'], 9 [12, 'month'], 10 ]; 11 12 // Rendered from the clock at draw time rather than baked in, so a page left 13 // open does not keep insisting something happened two seconds ago. 14 export function ago(at: number | null | undefined, now = Date.now()): string { 15 if (!at) return ''; 16 let value = (now - at) / 1000; 17 if (value < 45) return 'just now'; 18 19 for (const [step, unit] of UNITS) { 20 if (Math.abs(value) < step) { 21 const n = Math.round(value); 22 return `${n} ${unit}${n === 1 ? '' : 's'} ago`; 23 } 24 value /= step; 25 } 26 const years = Math.round(value); 27 return `${years} year${years === 1 ? '' : 's'} ago`; 28 } 29 30 export function duration(from: number | null | undefined, to: number | null | undefined): string { 31 if (!from) return ''; 32 const ms = (to ?? Date.now()) - from; 33 if (ms < 0) return ''; 34 35 const seconds = Math.floor(ms / 1000); 36 if (seconds < 60) return `${seconds}s`; 37 38 const minutes = Math.floor(seconds / 60); 39 if (minutes < 60) return `${minutes}m ${seconds % 60}s`; 40 41 return `${Math.floor(minutes / 60)}h ${minutes % 60}m`; 42 } 43 44 export function bytes(size: number | null | undefined): string { 45 if (size === null || size === undefined) return ''; 46 if (size < 1024) return `${size} B`; 47 48 const units = ['KiB', 'MiB', 'GiB', 'TiB']; 49 let value = size / 1024; 50 let unit = 0; 51 while (value >= 1024 && unit < units.length - 1) { 52 value /= 1024; 53 unit += 1; 54 } 55 return `${value.toFixed(value < 10 ? 1 : 0)} ${units[unit]}`; 56 } 57 58 export function stamp(at: number | null | undefined): string { 59 return at ? new Date(at).toLocaleString() : ''; 60 } 61 62 export function shortSha(sha: string | null | undefined): string { 63 return sha ? sha.slice(0, 8) : ''; 64 } 65 66 const TERMINAL = ['success', 'failed', 'cancelled', 'skipped', 'timeout']; 67 68 export function settled(state: string): boolean { 69 return TERMINAL.includes(state); 70 }