/**
* Centralized relative - time formatting utilities .
*
* Consolidates 7 + scattered implementations ( formatAge , formatAgeShort , formatAgo ,
* formatRelativeTime , formatElapsedTime ) into two functions :
*
* - ` formatTimeAgo ( durationMs ) ` — format a duration as " 5 m ago " / " 5 m " ( for known elapsed time )
* - ` formatRelativeTimestamp ( epochMs ) ` — format an epoch timestamp relative to now ( handles future )
*/
export type FormatTimeAgoOptions = {
/** Append "ago" suffix. Default: true. When false, returns bare unit: "5m", "2h" */
suffix?: boolean ;
/** Return value for invalid/null/negative input. Default: "unknown" */
fallback?: string;
};
/**
* Format a duration ( in ms ) as a human - readable relative time .
*
* Input : how many milliseconds ago something happened .
*
* With suffix ( default ) : " just now " , " 5 m ago " , " 3 h ago " , " 2 d ago "
* Without suffix : " 0 s " , " 5 m " , " 3 h " , " 2 d "
*/
export function formatTimeAgo(
durationMs: number | null | undefined,
options?: FormatTimeAgoOptions,
): string {
const suffix = options?.suffix !== false ;
const fallback = options?.fallback ?? "unknown" ;
if (durationMs == null || !Number.isFinite(durationMs) || durationMs < 0 ) {
return fallback;
}
const totalSeconds = Math.round(durationMs / 1000 );
const minutes = Math.round(totalSeconds / 60 );
if (minutes < 1 ) {
return suffix ? "just now" : `${totalSeconds}s`;
}
if (minutes < 60 ) {
return suffix ? `${minutes}m ago` : `${minutes}m`;
}
const hours = Math.round(minutes / 60 );
if (hours < 48 ) {
return suffix ? `${hours}h ago` : `${hours}h`;
}
const days = Math.round(hours / 24 );
return suffix ? `${days}d ago` : `${days}d`;
}
export type FormatRelativeTimestampOptions = {
/** If true, fall back to short date (e.g. "Oct 5") for timestamps >7 days. Default: false */
dateFallback?: boolean ;
/** IANA timezone for date fallback display */
timezone?: string;
/** Return value for invalid/null input. Default: "n/a" */
fallback?: string;
};
/**
* Format an epoch timestamp relative to now .
*
* Handles both past ( " 5 m ago " ) and future ( " in 5 m " ) timestamps .
* Optionally falls back to a short date for timestamps older than 7 days .
*/
export function formatRelativeTimestamp(
timestampMs: number | null | undefined,
options?: FormatRelativeTimestampOptions,
): string {
const fallback = options?.fallback ?? "n/a" ;
if (timestampMs == null || !Number.isFinite(timestampMs)) {
return fallback;
}
const diff = Date.now() - timestampMs;
const absDiff = Math.abs(diff);
const isPast = diff >= 0 ;
const sec = Math.round(absDiff / 1000 );
if (sec < 60 ) {
return isPast ? "just now" : "in <1m" ;
}
const min = Math.round(sec / 60 );
if (min < 60 ) {
return isPast ? `${min}m ago` : `in ${min}m`;
}
const hr = Math.round(min / 60 );
if (hr < 48 ) {
return isPast ? `${hr}h ago` : `in ${hr}h`;
}
const day = Math.round(hr / 24 );
if (!options?.dateFallback || day <= 7 ) {
return isPast ? `${day}d ago` : `in ${day}d`;
}
// Fall back to short date display for old timestamps
try {
return new Intl.DateTimeFormat("en-US" , {
month: "short" ,
day: "numeric" ,
...(options.timezone ? { timeZone: options.timezone } : {}),
}).format(new Date(timestampMs));
} catch {
return `${day}d ago`;
}
}
Messung V0.5 in Prozent C=99 H=99 G=98
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet am 2026-06-10)
¤
*© Formatika GbR, Deutschland