Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | 309x 3x 306x 306x 306x 306x 306x 306x 306x 18x 288x 1x 287x 1x 286x 306x 23x 23x 12x 18x 2x 16x 16x 1x 15x 3x 12x 5x 7x 8x 7x 7x 1x 6x 2x 4x 3x 1x 62x 10x 52x 62x 50x 50x 2x | /**
* Time and duration utility functions for formatters.
*
* Extracted to break the circular import between `human.ts` and `trace.ts`:
* both modules need these utilities but neither should depend on the other.
*
* Also provides generic compact/verbose duration formatters (seconds-based)
* used by replay commands and any future duration display.
*/
import type { TraceSpan } from "../../types/index.js";
import { colorTag } from "./markdown.js";
/**
* Format a date string as a relative time label.
*
* - Under 60 minutes: "5m ago"
* - Under 24 hours: "3h ago"
* - Under 3 days: "2d ago"
* - Otherwise: short date like "Jan 18"
*
* Returns a muted "—" when the input is undefined.
*
* @param dateString - ISO date string or undefined
* @returns Human-readable relative time string
*/
export function formatRelativeTime(dateString: string | undefined): string {
if (!dateString) {
return colorTag("muted", "—");
}
const date = new Date(dateString);
const now = Date.now();
// Clamp to >= 0 so clock skew, scheduled/future timestamps, or bad API data
// render as "0m ago" instead of a negative duration like "-5m ago".
const diffMs = Math.max(0, now - date.getTime());
const diffMins = Math.floor(diffMs / 60_000);
const diffHours = Math.floor(diffMs / 3_600_000);
const diffDays = Math.floor(diffMs / 86_400_000);
let text: string;
if (diffMins < 60) {
text = `${diffMins}m ago`;
} else if (diffHours < 24) {
text = `${diffHours}h ago`;
} else if (diffDays < 3) {
text = `${diffDays}d ago`;
} else {
// Short date: "Jan 18"
text = date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
}
return text;
}
// ---------------------------------------------------------------------------
// Generic duration formatting (seconds-based)
// ---------------------------------------------------------------------------
/**
* Split a duration in seconds into days, hours, minutes, and seconds.
* Rounds to the nearest second and clamps to non-negative.
*/
function splitDuration(totalSeconds: number): {
days: number;
hours: number;
minutes: number;
seconds: number;
} {
const rounded = Math.max(0, Math.round(totalSeconds));
return {
days: Math.floor(rounded / 86_400),
hours: Math.floor((rounded % 86_400) / 3600),
minutes: Math.floor((rounded % 3600) / 60),
seconds: rounded % 60,
};
}
/**
* Pluralize a value with its singular unit name.
*
* @example pluralize(1, "minute") → "1 minute"
* @example pluralize(3, "hour") → "3 hours"
*/
function pluralize(value: number, singular: string): string {
return `${value} ${singular}${value === 1 ? "" : "s"}`;
}
/**
* Format a duration (in seconds) as a compact string for table/list output.
*
* Shows at most two adjacent units: `2m 5s`, `1h 1m`, `1d 1h`.
* Returns `"—"` when the input is null or undefined.
*
* @param seconds - Duration in seconds, or null/undefined
* @returns Compact duration string (e.g., `"2m 5s"`, `"1d"`, `"—"`)
*/
export function formatDurationCompact(
seconds: number | null | undefined
): string {
if (seconds === null || seconds === undefined) {
return "—";
}
const parts = splitDuration(seconds);
if (parts.days > 0) {
return parts.hours > 0
? `${parts.days}d ${parts.hours}h`
: `${parts.days}d`;
}
if (parts.hours > 0) {
return parts.minutes > 0
? `${parts.hours}h ${parts.minutes}m`
: `${parts.hours}h`;
}
if (parts.minutes > 0) {
return parts.seconds > 0
? `${parts.minutes}m ${parts.seconds}s`
: `${parts.minutes}m`;
}
return `${parts.seconds}s`;
}
/**
* Format a duration (in milliseconds) as a compact string.
*
* Converts ms → seconds and delegates to {@link formatDurationCompact}.
* Useful for activity offsets and other ms-based durations.
*
* @param milliseconds - Duration in milliseconds
* @returns Compact duration string (e.g., `"2m 5s"`, `"1h"`)
*/
export function formatDurationCompactMs(milliseconds: number): string {
return formatDurationCompact(milliseconds / 1000);
}
/**
* Format a duration (in seconds) as a verbose human-readable string.
*
* Uses full unit names with "and" joining the two most significant units:
* `"2 minutes and 5 seconds"`, `"1 hour and 1 minute"`, `"1 day"`.
*
* @param seconds - Duration in seconds
* @returns Verbose duration string
*/
export function formatDurationVerbose(seconds: number): string {
const parts = splitDuration(seconds);
if (parts.days > 0) {
return parts.hours > 0
? `${pluralize(parts.days, "day")} and ${pluralize(parts.hours, "hour")}`
: pluralize(parts.days, "day");
}
if (parts.hours > 0) {
return parts.minutes > 0
? `${pluralize(parts.hours, "hour")} and ${pluralize(parts.minutes, "minute")}`
: pluralize(parts.hours, "hour");
}
if (parts.minutes > 0) {
return parts.seconds > 0
? `${pluralize(parts.minutes, "minute")} and ${pluralize(parts.seconds, "second")}`
: pluralize(parts.minutes, "minute");
}
return pluralize(parts.seconds, "second");
}
// ---------------------------------------------------------------------------
// Span duration
// ---------------------------------------------------------------------------
/**
* Compute the duration of a span in milliseconds.
* Prefers the API-provided `duration` field, falls back to timestamp arithmetic.
*
* @returns Duration in milliseconds, or undefined if not computable
*/
export function computeSpanDurationMs(span: TraceSpan): number | undefined {
if (span.duration !== undefined && Number.isFinite(span.duration)) {
return span.duration;
}
const endTs = span.end_timestamp || span.timestamp;
if (endTs !== undefined && Number.isFinite(endTs)) {
const ms = (endTs - span.start_timestamp) * 1000;
return ms >= 0 ? ms : undefined;
}
return;
}
|