/** * Get the number of days between two dates (calendar days in the viewer's * local timezone, not 24-hour periods). Uses local days so the bucket a * message falls into always agrees with the locale-formatted output * (time of day, weekday name, date), which is also rendered in local time. */ const getDaysDifference = (date1: Date, date2: Date): number => { const d1 = Date.UTC(date1.getFullYear(), date1.getMonth(), date1.getDate()) const d2 = Date.UTC(date2.getFullYear(), date2.getMonth(), date2.getDate()) return Math.floor((d2 - d1) / (1000 * 60 * 60 * 24)) } /** * Format a date - shows time for today, relative time for older messages * (e.g., "Just now", "2:08 PM" for today, "Yesterday" for yesterday, * "Mon" for 2-6 days ago, "Jul 14" for older dates this year, * "Jul 14, 2025" for previous years) */ export const formatRelativeTime = (date: Date): string => { const now = new Date() const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000) // Less than 1 minute if (diffInSeconds < 60) { return 'Just now' } // Check if it's today (same calendar day) const daysDiff = getDaysDifference(date, now) // If today, show time in 12-hour format (e.g., "8:40 PM") if (daysDiff === 0) { return date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit', hour12: true, }) } // Yesterday if (daysDiff === 1) { return 'Yesterday' } // 2-6 days ago - show short weekday name (e.g., "Mon") if (daysDiff < 7) { return date.toLocaleDateString(undefined, { weekday: 'short' }) } // 7+ days ago, same local year - show month and day (e.g., "Jul 14") if (date.getFullYear() === now.getFullYear()) { return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', }) } // Previous years - include the year (e.g., "Jul 14, 2025") return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric', }) }