mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 19:18:26 +00:00
Centralize date/time formatting utilities (#11831)
This commit is contained in:
103
src/infra/format-time/format-duration.ts
Normal file
103
src/infra/format-time/format-duration.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
export type FormatDurationSecondsOptions = {
|
||||
decimals?: number;
|
||||
unit?: "s" | "seconds";
|
||||
};
|
||||
|
||||
export type FormatDurationCompactOptions = {
|
||||
/** Add space between units: "2m 5s" instead of "2m5s". Default: false */
|
||||
spaced?: boolean;
|
||||
};
|
||||
|
||||
export function formatDurationSeconds(
|
||||
ms: number,
|
||||
options: FormatDurationSecondsOptions = {},
|
||||
): string {
|
||||
if (!Number.isFinite(ms)) {
|
||||
return "unknown";
|
||||
}
|
||||
const decimals = options.decimals ?? 1;
|
||||
const unit = options.unit ?? "s";
|
||||
const seconds = Math.max(0, ms) / 1000;
|
||||
const fixed = seconds.toFixed(Math.max(0, decimals));
|
||||
const trimmed = fixed.replace(/\.0+$/, "").replace(/(\.\d*[1-9])0+$/, "$1");
|
||||
return unit === "seconds" ? `${trimmed} seconds` : `${trimmed}s`;
|
||||
}
|
||||
|
||||
/** Precise decimal-seconds output: "500ms" or "1.23s". Input is milliseconds. */
|
||||
export function formatDurationPrecise(
|
||||
ms: number,
|
||||
options: FormatDurationSecondsOptions = {},
|
||||
): string {
|
||||
if (!Number.isFinite(ms)) {
|
||||
return "unknown";
|
||||
}
|
||||
if (ms < 1000) {
|
||||
return `${ms}ms`;
|
||||
}
|
||||
return formatDurationSeconds(ms, {
|
||||
decimals: options.decimals ?? 2,
|
||||
unit: options.unit ?? "s",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact compound duration: "500ms", "45s", "2m5s", "1h30m".
|
||||
* With `spaced`: "45s", "2m 5s", "1h 30m".
|
||||
* Omits trailing zero components: "1m" not "1m 0s", "2h" not "2h 0m".
|
||||
* Returns undefined for null/undefined/non-finite/non-positive input.
|
||||
*/
|
||||
export function formatDurationCompact(
|
||||
ms?: number | null,
|
||||
options?: FormatDurationCompactOptions,
|
||||
): string | undefined {
|
||||
if (ms == null || !Number.isFinite(ms) || ms <= 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (ms < 1000) {
|
||||
return `${Math.round(ms)}ms`;
|
||||
}
|
||||
const sep = options?.spaced ? " " : "";
|
||||
const totalSeconds = Math.round(ms / 1000);
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const seconds = totalSeconds % 60;
|
||||
if (hours >= 24) {
|
||||
const days = Math.floor(hours / 24);
|
||||
const remainingHours = hours % 24;
|
||||
return remainingHours > 0 ? `${days}d${sep}${remainingHours}h` : `${days}d`;
|
||||
}
|
||||
if (hours > 0) {
|
||||
return minutes > 0 ? `${hours}h${sep}${minutes}m` : `${hours}h`;
|
||||
}
|
||||
if (minutes > 0) {
|
||||
return seconds > 0 ? `${minutes}m${sep}${seconds}s` : `${minutes}m`;
|
||||
}
|
||||
return `${seconds}s`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rounded single-unit duration for display: "500ms", "5s", "3m", "2h", "5d".
|
||||
* Returns fallback string for null/undefined/non-finite input.
|
||||
*/
|
||||
export function formatDurationHuman(ms?: number | null, fallback = "n/a"): string {
|
||||
if (ms == null || !Number.isFinite(ms) || ms < 0) {
|
||||
return fallback;
|
||||
}
|
||||
if (ms < 1000) {
|
||||
return `${Math.round(ms)}ms`;
|
||||
}
|
||||
const sec = Math.round(ms / 1000);
|
||||
if (sec < 60) {
|
||||
return `${sec}s`;
|
||||
}
|
||||
const min = Math.round(sec / 60);
|
||||
if (min < 60) {
|
||||
return `${min}m`;
|
||||
}
|
||||
const hr = Math.round(min / 60);
|
||||
if (hr < 24) {
|
||||
return `${hr}h`;
|
||||
}
|
||||
const day = Math.round(hr / 24);
|
||||
return `${day}d`;
|
||||
}
|
||||
Reference in New Issue
Block a user