/** * Date formatting utilities for consistent display across the app. * Works in all JavaScript environments (browser, Node.js, Bun, etc.) */ type DateInput = string | Date; /** * Safely convert a date input to a Date object. */ function toDate(date: DateInput): Date { return typeof date === "string" ? new Date(date) : date; } /** * Calculate the difference in days between two dates. */ function daysDiff(from: Date, to: Date): number { const diffMs = to.getTime() - from.getTime(); return Math.floor(diffMs / (1000 * 60 * 60 * 24)); } /** * Format a date for display in tables and lists. * @example formatDate("2024-01-15") // "Jan 15, 2024" */ export function formatDate(date: DateInput): string { const d = toDate(date); return d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", }); } /** * Format a date with time for detailed views. * @example formatDateTime("2024-01-15T15:30:00") // "Jan 15, 2024, 3:30 PM" */ export function formatDateTime(date: DateInput): string { const d = toDate(date); return d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", hour: "numeric", minute: "2-digit", }); } /** * Format a date in long form. * @example formatLongDate("2024-01-15") // "January 15, 2024" */ export function formatLongDate(date: DateInput): string { const d = toDate(date); return d.toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric", }); } /** * Options for relative date formatting. */ export interface FormatRelativeDateOptions { /** * Reference date to compare against. Defaults to current date. */ now?: Date; } /** * Format a date as a relative time string. * @example * formatRelativeDate("2024-01-15") // "Today" (if today is Jan 15) * formatRelativeDate("2024-01-14") // "Yesterday" (if today is Jan 15) * formatRelativeDate("2024-01-10") // "5 days ago" (if today is Jan 15) * formatRelativeDate("2024-01-01") // "2 weeks ago" (if today is Jan 15) * formatRelativeDate("2023-06-15") // "Jun 15, 2023" (older dates) */ export function formatRelativeDate( date: DateInput, options?: FormatRelativeDateOptions, ): string { const d = toDate(date); const now = options?.now ?? new Date(); const diffDays = daysDiff(d, now); if (diffDays === 0) { return "Today"; } if (diffDays === 1) { return "Yesterday"; } if (diffDays < 7) { return `${diffDays.toLocaleString()} days ago`; } if (diffDays < 30) { const weeks = Math.floor(diffDays / 7); return weeks === 1 ? "1 week ago" : `${weeks.toLocaleString()} weeks ago`; } // For older dates, show the actual date return d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: d.getFullYear() !== now.getFullYear() ? "numeric" : undefined, }); } /** * Format a date as a relative time string, with "Never" for null values. * Useful for displaying "last used" timestamps. * @example * formatRelativeTime("2024-01-15") // "Today" * formatRelativeTime(null) // "Never" */ export function formatRelativeTime( date: DateInput | null | undefined, options?: FormatRelativeDateOptions, ): string { if (date === null || date === undefined) { return "Never"; } return formatRelativeDate(date, options); }