Jordan Smalls

Software Engineer

Format a relative time string

Turn a date into "3 hours ago" style text using Intl.RelativeTimeFormat.

Intl.RelativeTimeFormat handles the pluralization and locale text; the function just needs to pick the right unit and value. Divisions are ordered from largest to smallest and stop at the first unit under its threshold.

typescript
const units: { unit: Intl.RelativeTimeFormatUnit; ms: number }[] = [
  { unit: 'year', ms: 1000 * 60 * 60 * 24 * 365 },
  { unit: 'month', ms: 1000 * 60 * 60 * 24 * 30 },
  { unit: 'week', ms: 1000 * 60 * 60 * 24 * 7 },
  { unit: 'day', ms: 1000 * 60 * 60 * 24 },
  { unit: 'hour', ms: 1000 * 60 * 60 },
  { unit: 'minute', ms: 1000 * 60 },
  { unit: 'second', ms: 1000 },
]

const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })

export function formatRelativeTime(date: Date | string) {
  const target = typeof date === 'string' ? new Date(date) : date
  const diffMs = target.getTime() - Date.now()

  for (const { unit, ms } of units) {
    if (Math.abs(diffMs) >= ms || unit === 'second') {
      return rtf.format(Math.round(diffMs / ms), unit)
    }
  }
  return rtf.format(0, 'second')
}

Negative diffs read naturally as past tense, positive diffs as future. "numeric: auto" lets Intl swap in words like "yesterday" and "tomorrow" where the locale supports it.

typescript
formatRelativeTime(new Date(Date.now() - 1000 * 60 * 5)) // "5 minutes ago"
formatRelativeTime(new Date(Date.now() + 1000 * 60 * 60 * 24)) // "tomorrow"