// Locale-independent post-date formatting. Uses a fixed month table and // UTC getters so the rendered string never varies by browser/CI locale // or timezone (toLocaleDateString would make tests flaky). const MONTHS = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ] export function formatPostDate(iso) { if (!iso) return null const d = new Date(iso) if (isNaN(d.getTime())) return null return `${MONTHS[d.getUTCMonth()]} ${d.getUTCDate()}, ${d.getUTCFullYear()}` } // Compact relative formatter: "5m ago" (past) or "in 3h" (future). Null / // invalid input returns `nullText`. Pass { future: true } for upcoming // timestamps; a future time already elapsed reads as "imminent". export function formatRelative(iso, opts = {}) { const { future = false, nullText = future ? '—' : 'Never' } = opts if (!iso) return nullText const then = new Date(iso).getTime() if (isNaN(then)) return nullText const diff = (then - Date.now()) / 1000 // +ve = future const abs = Math.abs(diff) let body if (abs < 60) body = `${Math.floor(abs)}s` else if (abs < 3600) body = `${Math.floor(abs / 60)}m` else if (abs < 86400) body = `${Math.floor(abs / 3600)}h` else body = `${Math.floor(abs / 86400)}d` if (future) return diff <= 0 ? 'imminent' : `in ${body}` return `${body} ago` } // Parse a backend timestamp as a UTC instant. Backend serializes tz-aware // UTC (…+00:00); if a value ever arrives without a tz designator we treat // it as UTC so it still converts to the viewer's zone correctly. function _parseUtc (iso) { if (typeof iso !== 'string' || !iso) return null const hasTz = /([zZ])$|([+-]\d{2}:?\d{2})$/.test(iso) const d = new Date(hasTz ? iso : `${iso}Z`) return isNaN(d.getTime()) ? null : d } // Local-timezone date + time, e.g. "May 28, 7:30 PM". Use for absolute // timestamps that were previously shown as raw UTC ISO. export function formatDateTime (iso) { const d = _parseUtc(iso) if (!d) return '' return d.toLocaleString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', }) } // Local-timezone date only, e.g. "May 28, 2026". export function formatLocalDate (iso) { const d = _parseUtc(iso) if (!d) return '' return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric', }) }