anywhen API reference

Overview

anywhen is a tiny date formatter built entirely on the native Intl browser API. One function, one options object, three modes. Stable since 1.0 — the public API follows semver.

The browser already knows how to format dates in 200+ languages. anywhen just makes that API pleasant to use.

import { anywhen } from 'anywhen'

anywhen(date)
// "yesterday, 2:35 PM"  — smart mode (default)

anywhen(date, { mode: 'absolute', locale: 'en' })
// "Feb 5, 2016"

anywhen(date, { mode: 'relative', locale: 'en' })
// "3 hours ago"

Install

npm install anywhen
# or
pnpm add anywhen
# or
yarn add anywhen

Or take the whole family at once with npm install anyfamily.

anywhen()

The single entry point. Pass a date, optionally pass options.

anywhen(input)
anywhen(input, options?)

anywhen(date)
// runtime locale, smart mode

anywhen(date, { locale: 'en' })
// "yesterday, 2:35 PM"

anywhen(date, { mode: 'relative', locale: 'en', numeric: true })
// "1 day ago"

anywhen(date, {
  mode: 'absolute',
  locale: 'en',
  format: { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' },
})
// "Friday, February 5, 2016"

anywhenParts()

Same arguments as anywhen(), but returns the output as { type, value, unit? } parts instead of a string — style the number apart from the unit, or rebuild the output your own way. New in 1.0.

import { anywhenParts } from 'anywhen'

anywhenParts(date, { mode: 'relative', locale: 'en' })
// [
//   { type: 'integer', value: '3', unit: 'hour' },
//   { type: 'literal', value: ' hours ago' },
// ]

// React: bold the number
anywhenParts(date, { mode: 'relative' }).map((p, i) =>
  p.type === 'integer' ? <b key={i}>{p.value}</b> : p.value,
)

Note: part values keep the original Intl characters — the space before AM/PM can be U+202F (narrow no-break space), which some engines replace with a regular space in the joined string.

Modes

The mode option picks the rendering strategy. Each mode reads only the options that apply to it — the rest are ignored.

smart (default)

Context-aware. Picks the most readable format based on distance from now — covers past and future.

< 45s→ "now"
< 1 hour→ "10 minutes ago / in 10 minutes"
same day→ "today, 14:35"
yesterday→ "yesterday, 09:00"
tomorrow→ "tomorrow, 09:00"
within 7 days→ "Wednesday, 11:20"
older / further→ "Feb 5, 2016"

reads: locale, now, time, timeZone, style, thresholds

absolute

Plain date formatting via Intl.DateTimeFormat. Pass format to control the output shape.

anywhen(date, { mode: 'absolute', locale: 'en' })
// "Feb 5, 2016"

anywhen(date, {
  mode: 'absolute',
  locale: 'en',
  format: { hour: '2-digit', minute: '2-digit' },
})
// "2:35 PM"

anywhen(date, {
  mode: 'absolute',
  locale: 'en',
  format: { month: 'long', year: 'numeric' },
  timeZone: 'Europe/Belgrade',
})
// "February 2016"

reads: locale, format, timeZone

relative

Always relative. Past and future. Never falls back to an absolute date.

anywhen(date, { mode: 'relative', locale: 'en' })
// "3 hours ago"
// "yesterday"
// "in 2 weeks"

anywhen(date, { mode: 'relative', locale: 'en', numeric: true })
// "1 day ago"   — disables auto-phrases
// "1 week ago"

anywhen(date, { mode: 'relative', locale: 'en', style: 'short' })
// "3 hr. ago"

anywhen(date, { mode: 'relative', locale: 'en', style: 'narrow' })
// "3h ago"

reads: locale, now, numeric, style, thresholds

Options

mode'smart' | 'absolute' | 'relative'default: 'smart'

Rendering strategy. Each mode reads only the options that apply to it.

localestring | string[]default: runtime locale

Any valid BCP 47 locale tag, or a fallback array — 'en', 'en-US', 'zh-TW', ['sr-Latn-RS', 'en'].

nowDate | number | stringdefault: current time

Reference time for smart and relative modes. Pass this in SSR to keep server and client output stable.

timeZonestringdefault: runtime timezone

IANA time zone for the displayed clock and smart day boundaries (today, yesterday, weekday). Used by smart and absolute modes.

timebooleandefault: true

Smart mode only. Whether to include clock time in today/yesterday/weekday output.

numericbooleandefault: false

Relative mode only. Force numeric output — disables auto-phrases like 'yesterday' or 'last week'.

style'long' | 'short' | 'narrow'default: 'long'

Smart and relative modes. Maps to Intl.RelativeTimeFormat and shortens the relative phrasing — '10 min. ago', '3h ago'. Calendar labels keep their clock.

formatIntl.DateTimeFormatOptionsdefault: { day, month, year }

Absolute mode only. Any options accepted by Intl.DateTimeFormat. Defaults to a short date.

thresholdsPartial<Record<unit, number>>default: built-in table

Smart and relative modes. Per-unit cutoffs (in seconds) for picking the display unit. Override any subset; the rest keep their defaults.

Thresholds

Each unit is shown while the distance from now is below its cutoff, in seconds. Defaults: second: 45, minute: 2700, hour: 79200, day: 518400, week: 2160000, month: 28512000. New in 1.0.

anywhen(date, { mode: 'relative', locale: 'en', thresholds: { minute: 5400 } })
// 50 minutes ago → "50 minutes ago" instead of "1 hour ago"

anywhen(date, { locale: 'en', thresholds: { second: 120 } })
// smart mode: "now" covers the first 2 minutes

In smart mode thresholds.secondwidens the "now" window and thresholds.minute the sub-hour minutes window, symmetrically in both directions. Calendar labels (today, yesterday, tomorrow, weekday) are not affected.

SSR

By default, smart and relative modes use the current time. In React SSR or Next.js, pass a stable now value to avoid hydration drift.

import { anywhen } from 'anywhen'

export function PostMeta({ createdAt, requestTime }: {
  createdAt: string
  requestTime: string
}) {
  return (
    <time dateTime={createdAt}>
      {anywhen(createdAt, {
        locale: 'en',
        now: requestTime,
        timeZone: 'Europe/Belgrade',
      })}
    </time>
  )
}

timeZone controls both the displayed clock and the smart calendar boundaries for today, yesterday, and weekday output.

In React, anyfamily-react's useAnywhen also keeps relative output from going stale — it re-renders on an interval so "3 minutes ago" stays true.

Input types

All inputs accept three formats interchangeably.

// Date object
anywhen(new Date())

// Unix timestamp (milliseconds)
anywhen(Date.now())
anywhen(1704499200000)

// ISO string
anywhen('2016-02-05T14:00:00Z')
anywhen('2016-02-05')

Locales

Same calls in a few languages — no extra setup, no locale files.

// smart mode
anywhen(date, { locale: 'de' })   // "gestern, 14:35"
anywhen(date, { locale: 'ru' })   // "вчера, 14:35"
anywhen(date, { locale: 'fr' })   // "hier, 14:35"

// absolute mode
anywhen(date, { mode: 'absolute', locale: 'ja' })   // "2016年2月5日"
anywhen(date, { mode: 'absolute', locale: 'ar' })   // "٥ فبراير ٢٠١٦"
anywhen(date, { mode: 'absolute', locale: 'ru' })   // "5 февр. 2016 г."

// relative mode
anywhen(date, { mode: 'relative', locale: 'de' })   // "vor 3 Stunden"
anywhen(date, { mode: 'relative', locale: 'fr' })   // "il y a 3 heures"
anywhen(date, { mode: 'relative', locale: 'tr' })   // "3 saat önce"

Pass any valid BCP 47 language tag — including regional variants like en-GB, zh-TW, or pt-BR. Locale is optional; when omitted, native Intl uses the runtime locale. Fallback arrays like ['sr-Latn-RS', 'en'] also work.

Compatibility

anywhen uses Intl.RelativeTimeFormat and Intl.DateTimeFormat — both widely supported.

Node.js18+CI runs the suite on Node 20, 22, 24
Chrome71+
Firefox65+
Safari14+
Edge79+
Vercel Edge Runtime
Cloudflare Workers
Deno

Limitations

A few things worth knowing before you ship:

Output depends on the runtime's Intl data

anywhen delegates all formatting to native Intl. Exact output — punctuation, spacing, abbreviated month names — may vary between Node versions, browsers, and OSes. Don't hardcode expected strings in tests; use pattern matching instead.

No custom format strings

Absolute mode accepts Intl.DateTimeFormat options, so you control the pieces. But if you need 'DD/MM/YYYY' with literal slashes — use a formatting library with explicit pattern strings instead.

Smart calendar cutoff is fixed at 7 days

Unit cutoffs (seconds → minutes → hours…) are configurable via the thresholds option since 1.0. The calendar switch from weekday ('Wednesday, 11:20') to absolute date still happens at 7 days and is not configurable.

Node.js < 18

The package declares engines.node >= 18 and CI tests Node 20/22/24. Older versions down to 13 will usually work — the required Intl APIs are there — but they are unsupported and untested.