playbook/outfitter-agents/plugins/outfitter/skills/check-status/scripts/lib/time.ts

118 lines
3.1 KiB
TypeScript

/**
* Time parsing utilities for status gatherers
*/
const TIME_UNITS: Record<string, number> = {
h: 60 * 60 * 1000,
d: 24 * 60 * 60 * 1000,
w: 7 * 24 * 60 * 60 * 1000,
};
/**
* Parse time constraint string to milliseconds
* @example parseTimeConstraint("24h") → 86400000
* @example parseTimeConstraint("7d") → 604800000
* @example parseTimeConstraint("2w") → 1209600000
*/
export function parseTimeConstraint(input: string): number {
const match = input.match(/^(\d+)([hdw])$/i);
if (!match) {
throw new Error(
`Invalid time constraint: ${input}. Use format like "24h", "7d", or "2w"`,
);
}
const [, value, unit] = match;
const multiplier = TIME_UNITS[unit.toLowerCase()];
if (!multiplier) {
throw new Error(`Unknown time unit: ${unit}`);
}
return parseInt(value, 10) * multiplier;
}
/**
* Get cutoff Date from milliseconds offset
*/
export function toCutoffDate(ms: number): Date {
return new Date(Date.now() - ms);
}
/**
* Convert to git --since format
* @example toGitSince(86400000) → "2024-12-21T12:00:00"
*/
export function toGitSince(ms: number): string {
return toCutoffDate(ms).toISOString().replace("Z", "");
}
/**
* Convert to ISO 8601 duration for Linear
* @example toISOPeriod(86400000) → "-P1D" (1 day)
* @example toISOPeriod(604800000) → "-P7D" (7 days)
*/
export function toISOPeriod(ms: number): string {
const hours = ms / (60 * 60 * 1000);
if (hours < 24) {
return `-PT${Math.round(hours)}H`;
}
const days = Math.round(hours / 24);
return `-P${days}D`;
}
/**
* Convert to human-readable relative time
* @example toRelativeTime(new Date(Date.now() - 3600000)) → "1 hour ago"
*/
export function toRelativeTime(date: Date | string): string {
const d = typeof date === "string" ? new Date(date) : date;
const ms = Date.now() - d.getTime();
if (ms < 60 * 1000) return "just now";
if (ms < 60 * 60 * 1000) {
const mins = Math.floor(ms / (60 * 1000));
return `${mins} minute${mins === 1 ? "" : "s"} ago`;
}
if (ms < 24 * 60 * 60 * 1000) {
const hours = Math.floor(ms / (60 * 60 * 1000));
return `${hours} hour${hours === 1 ? "" : "s"} ago`;
}
const days = Math.floor(ms / (24 * 60 * 60 * 1000));
return `${days} day${days === 1 ? "" : "s"} ago`;
}
/**
* Filter items by updated_at field within time window
*/
export function filterByTime<
T extends { updated_at?: string; updatedAt?: string },
>(items: T[], ms: number): T[] {
const cutoff = toCutoffDate(ms);
return items.filter((item) => {
const updatedAt = item.updated_at || item.updatedAt;
if (!updatedAt) return false;
return new Date(updatedAt) >= cutoff;
});
}
/**
* Format time constraint for display
* @example formatTimeConstraint("24h") → "last 24 hours"
*/
export function formatTimeConstraint(input: string): string {
const match = input.match(/^(\d+)([hdw])$/i);
if (!match) return input;
const [, value, unit] = match;
const num = parseInt(value, 10);
switch (unit.toLowerCase()) {
case "h":
return `last ${num} hour${num === 1 ? "" : "s"}`;
case "d":
return `last ${num} day${num === 1 ? "" : "s"}`;
case "w":
return `last ${num} week${num === 1 ? "" : "s"}`;
default:
return input;
}
}