(
ms: number,
options?: { hideTrailingZeros?: boolean; mostSignificantOnly?: boolean },
)
| 32 | } |
| 33 | |
| 34 | export function formatDuration( |
| 35 | ms: number, |
| 36 | options?: { hideTrailingZeros?: boolean; mostSignificantOnly?: boolean }, |
| 37 | ): string { |
| 38 | if (ms < 60000) { |
| 39 | // Special case for 0 |
| 40 | if (ms === 0) { |
| 41 | return '0s' |
| 42 | } |
| 43 | // For durations < 1s, show 1 decimal place (e.g., 0.5s) |
| 44 | if (ms < 1) { |
| 45 | const s = (ms / 1000).toFixed(1) |
| 46 | return `${s}s` |
| 47 | } |
| 48 | const s = Math.floor(ms / 1000).toString() |
| 49 | return `${s}s` |
| 50 | } |
| 51 | |
| 52 | let days = Math.floor(ms / 86400000) |
| 53 | let hours = Math.floor((ms % 86400000) / 3600000) |
| 54 | let minutes = Math.floor((ms % 3600000) / 60000) |
| 55 | let seconds = Math.round((ms % 60000) / 1000) |
| 56 | |
| 57 | // Handle rounding carry-over (e.g., 59.5s rounds to 60s) |
| 58 | if (seconds === 60) { |
| 59 | seconds = 0 |
| 60 | minutes++ |
| 61 | } |
| 62 | if (minutes === 60) { |
| 63 | minutes = 0 |
| 64 | hours++ |
| 65 | } |
| 66 | if (hours === 24) { |
| 67 | hours = 0 |
| 68 | days++ |
| 69 | } |
| 70 | |
| 71 | const hide = options?.hideTrailingZeros |
| 72 | |
| 73 | if (options?.mostSignificantOnly) { |
| 74 | if (days > 0) return `${days}d` |
| 75 | if (hours > 0) return `${hours}h` |
| 76 | if (minutes > 0) return `${minutes}m` |
| 77 | return `${seconds}s` |
| 78 | } |
| 79 | |
| 80 | if (days > 0) { |
| 81 | if (hide && hours === 0 && minutes === 0) return `${days}d` |
| 82 | if (hide && minutes === 0) return `${days}d ${hours}h` |
| 83 | return `${days}d ${hours}h ${minutes}m` |
| 84 | } |
| 85 | if (hours > 0) { |
| 86 | if (hide && minutes === 0 && seconds === 0) return `${hours}h` |
| 87 | if (hide && seconds === 0) return `${hours}h ${minutes}m` |
| 88 | return `${hours}h ${minutes}m ${seconds}s` |
| 89 | } |
| 90 | if (minutes > 0) { |
| 91 | if (hide && seconds === 0) return `${minutes}m` |
no test coverage detected