anywhen API reference
Overview
anywhen is a tiny date formatter built entirely on the native Intl browser API. One export, one options object, three modes. The bare call returns the string; anywhen.parts hangs off the same name. Follows semver — see Migrating from 1.x if you are coming from the old two-export API.
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 anywhenOr 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"Migrating from 1.x
2.0 removed the separate anywhenParts export. It is the same function, reached through the one name the package exports.
- import { anywhen, anywhenParts } from 'anywhen'
+ import { anywhen } from 'anywhen'
- anywhenParts(date, { mode: 'relative' })
+ anywhen.parts(date, { mode: 'relative' })Arguments, return value and throwing behaviour are unchanged, and nothing else in the API moved. Every any* package follows this shape from 2.0 on: the bare call does the job, everything else hangs off the same name.
anywhen.parts()
Same arguments as calling anywhen() directly, 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. Joining every part's valuereproduces the plain call's output.
import { anywhen } from 'anywhen'
anywhen.parts(date, { mode: 'relative', locale: 'en' })
// [
// { type: 'integer', value: '3', unit: 'hour' },
// { type: 'literal', value: ' hours ago' },
// ]
// React: bold the number
anywhen.parts(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.
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
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 minutesIn 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.
Options
mode'smart' | 'absolute' | 'relative'default: 'smart'Rendering strategy. Each mode reads only the options that apply to it.
localestring | string[]default: runtime localeAny valid BCP 47 locale tag, or a fallback array — 'en', 'en-US', 'zh-TW', ['sr-Latn-RS', 'en'].
nowDate | number | stringdefault: current timeReference time for smart and relative modes. Pass this in SSR to keep server and client output stable.
timeZonestringdefault: runtime timezoneIANA time zone for the displayed clock and smart day boundaries (today, yesterday, weekday). Used by smart and absolute modes.
timebooleandefault: trueSmart mode only. Whether to include clock time in today/yesterday/weekday output.
numericbooleandefault: falseRelative 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 tableSmart and relative modes. Per-unit cutoffs (in seconds) for picking the display unit. Override any subset; the rest keep their defaults.
What breaks without this
Every one of these is a line people write by hand, and each is wrong somewhere.
"1 hours ago"
Relative time built by subtracting timestamps and dividing gets the plural wrong at 1, prints "0 hours ago" for anything under an hour, and never decides on its own that a week-old post should show a date instead.
The server formats for the wrong reader
Rendered on a server, a date takes the server's time zone. A reader eight hours away sees the wrong day for anything near midnight. The time zone is an option because it has to be a decision, not an accident of where the code ran.
Relative time and hydration disagree
The server renders "5 seconds ago", the browser hydrates two seconds later and renders "7 seconds ago" — React reports a mismatch. Relative output is a clock read, so either render an absolute date on the server or let the first tick settle it.
Month names are not a translation table
Locales disagree on the order of the fields, on the separators between them, and sometimes on the calendar itself — fa-IR counts Persian years, th-TH Buddhist ones. Swapping English month names for translated ones fixes none of that.
Recipes
Copy, paste, move on.
// Blog post date
<time dateTime={post.createdAt}>
{anywhen(post.createdAt, { locale: 'en', time: false })}
</time>
// Chat message
<time dateTime={message.sentAt}>
{anywhen(message.sentAt, { locale: 'en' })}
</time>
// Notification
anywhen(notification.createdAt, { mode: 'relative', locale: 'en' })
// "3 minutes ago"
// Settings screen / invoice date
anywhen(invoice.date, {
mode: 'absolute',
locale: 'en',
format: { month: 'long', day: 'numeric', year: 'numeric' },
})
// "February 5, 2016"
// Compact UI — label without the clock
anywhen(date, { locale: 'en', time: false })
// "yesterday"
// SSR-safe: freeze the anchor and the zone
anywhen(createdAt, { locale: 'en', now: requestTime, timeZone: 'Europe/Belgrade' })React / Next.js
Wrap the output in <time> so machines still get the exact timestamp while people get the readable one.
import { anywhen } from 'anywhen'
export function PostMeta({ createdAt }: { createdAt: string }) {
return <time dateTime={createdAt}>{anywhen(createdAt)}</time>
}Relative output goes stale the moment the component stops re-rendering — "3 minutes ago" stays frozen at three minutes. anyfamily-react solves that with useAnywhen, which re-renders on an interval and reads the locale from a shared provider.
import { AnyfamilyProvider, useAnywhen } from 'anyfamily-react'
function PostMeta({ createdAt }: { createdAt: string }) {
const when = useAnywhen(createdAt, { mode: 'relative' }) // ticks itself
return <time dateTime={createdAt}>{when}</time>
}
<AnyfamilyProvider locale="en">
<PostMeta createdAt={post.createdAt} />
</AnyfamilyProvider>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.
Calendars & eras
Non-Gregorian calendars need no extra API. Pick the calendar with the BCP 47 -u-ca- extension on locale, and ask for the era through format — both go straight to Intl.DateTimeFormat.
anywhen(date, {
mode: 'absolute',
locale: 'ja-JP-u-ca-japanese',
format: { era: 'short', year: 'numeric', month: 'short', day: 'numeric' },
})
// "平成28年2月5日"
anywhen(date, { mode: 'absolute', locale: 'th-TH-u-ca-buddhist' })
// "5 ก.พ. 2559"
anywhen(date, { mode: 'absolute', locale: 'en-US-u-ca-islamic-umalqura' })
// "Rab. II 26, 1437 AH"
anywhen(date, {
mode: 'absolute',
locale: 'zh-TW-u-ca-roc',
format: { era: 'short', year: 'numeric', month: 'short', day: 'numeric' },
})
// "民國105年2月5日"anywhen.parts reports the era as its own part, so it can be styled apart from the year:
anywhen.parts(date, {
mode: 'absolute',
locale: 'en-u-ca-gregory',
format: { era: 'short', day: 'numeric', month: 'short', year: 'numeric' },
})
// [… { type: 'year', value: '2016' }, { type: 'literal', value: ' ' }, { type: 'era', value: 'AD' }]Two limits worth knowing:
Absolute mode only
Smart mode uses its own fixed date shape and ignores format, so eras never appear in its absolute fallback.
eraDisplay is not usable yet
The option is still Stage 2 and current engines ignore it silently — it never reaches resolvedOptions(). Once it ships it will work through format with no change to anywhen.
Smart-mode day boundaries (today, yesterday, weekday) are computed on Gregorian days. Every calendar Intl ships switches days at local midnight too, so the boundaries line up — only the printed labels differ.
vs the alternatives
What you would otherwise reach for, and what changes if you do.
| anywhen | dayjs | date-fns | |
|---|---|---|---|
| gzip | ~1.4kb | ~7kb | ~20kb |
| locale data bundled | no | yes | yes |
| locales | 200+ | 140 | 100 |
| dependencies | 0 | 0 | 0 |
anywhen formats. It does not parse loose date strings, do calendar arithmetic, or diff two dates into a structure — reach for date-fns or Temporal when the job is manipulating dates rather than writing them down.
Compatibility
anywhen uses Intl.RelativeTimeFormat and Intl.DateTimeFormat — both widely supported.
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.