# anywhen — dates & times
> Formats a date as the string a reader expects — "yesterday, 2:35 PM", "3 hours ago", "5 Feb 2016" — choosing the form from how far away the date is.
- wraps: Intl.DateTimeFormat + Intl.RelativeTimeFormat
- install: npm install anywhen
- size: 1.4 kB gzip, zero dependencies, ESM + CJS, TypeScript types included
- runtime: Node 18+, every modern browser, Deno, Bun, edge runtimes.
- docs: https://anyfamily.site/docs/anywhen
- demo: https://anyfamily.site/anywhen
- playground: https://stackblitz.com/github/kirilinsky/anyfamily/tree/main/examples/vanilla?file=src/main.ts
- npm: https://www.npmjs.com/package/anywhen
- source: https://github.com/kirilinsky/anyfamily/tree/main/packages/anywhen
- family: https://anyfamily.site/llms.txt
## why
Relative time by hand is a threshold table plus a per-locale plural mess; dayjs and date-fns ship locale files and a hundred functions to get one string. anywhen is one call, ~1.4 kB, every locale the runtime knows, and SSR-stable when you pin `now`.
## usage
```ts
anywhen(date) // "yesterday, 2:35 PM"
anywhen(date, { mode: "relative" }) // "3 hours ago"
anywhen(date, { mode: "absolute", locale: "ja" }) // "2016年2月5日"
anywhen(date, { locale: "en", now: requestTime }) // SSR-safe: fixed anchor
anywhen.parts(date, { mode: "relative" }) // [{ type: "integer", value: "3", unit: "hour" }, …]
anywhen.range(from, to, { format: { day: "numeric", month: "short" } }) // "Sep 12 – 15"
```
## not for
Parsing dates, date arithmetic, or custom pattern strings like DD/MM/YYYY — use a date library for those and hand the result here.
---
# anywhen
Tiny human-readable date formatter built on native Intl. Turn dates into "now", "yesterday, 2:35 PM", "через 3 часа", or "2016年2月5日".
▸ live demo · ▸ full docs · ▸ any family
---
**One export. Smart defaults. Any locale. ~1.4kb gzip. Zero dependencies.**
`Intl` is powerful. anywhen makes it usable. Built for feeds, chats,
notifications, dashboards and docs — anywhere a raw timestamp should read like a
person wrote it. No locale files, no plugins, no config.
```ts
import { anywhen } from "anywhen";
anywhen(date); // "yesterday, 2:35 PM" — smart (default)
anywhen(date, { mode: "absolute", locale: "en" }); // "Feb 5, 2016"
anywhen(date, { mode: "relative", locale: "en" }); // "3 hours ago"
anywhen(date, { mode: "relative", locale: "ru" }); // "3 часа назад"
anywhen(date, { mode: "absolute", locale: "ja" }); // "2016年2月5日"
```
---
## install
```bash
npm install anywhen
```
---
## usage
```ts
anywhen(input);
anywhen(input, options);
```
`input` is a `Date`, a unix timestamp in milliseconds, or an ISO 8601 string.
```ts
anywhen(new Date());
anywhen(Date.now());
anywhen("2016-02-05T14:00:00Z");
```
`anywhen.parts()` takes the same arguments and returns
`{ type, value, unit? }` parts instead of a string — style the number apart from
the unit, or rebuild the output your own way.
```tsx
anywhen.parts(date, { mode: "relative", locale: "en" });
// [{ type: "integer", value: "3", unit: "hour" }, { type: "literal", value: " hours ago" }]
anywhen.parts(date, { mode: "relative" }).map((p, i) =>
p.type === "integer" ? {p.value} : p.value,
);
```
`anywhen.range()` formats two dates as one range — delivery windows, event
dates, opening hours — the way the locale writes it, shared parts collapsed.
Absolute only: `locale`, `timeZone` and `format` apply. Order-independent.
It says *when*; for *how long* the span is (`"3 days"`) use
[anylong](https://www.npmjs.com/package/anylong)`(from, to)`.
```ts
anywhen.range(from, to, { locale: "en", format: { day: "numeric", month: "short" } }); // "Sep 12 – 15"
anywhen.range(from, to, { locale: "de" }); // "12.–15. Sept. 2026"
anywhen.range(open, close, { locale: "en", format: { hour: "numeric", minute: "2-digit" } }); // "9:00 – 11:30 AM"
```
---
## recipes
Copy, paste, move on.
```tsx
// Blog post date
// Chat message
// 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"
// SSR-safe: freeze the anchor and the zone
anywhen(createdAt, { locale: "en", now: requestTime, timeZone: "Europe/Belgrade" });
// Delivery window
anywhen.range(order.earliest, order.latest, { locale: "en", format: { day: "numeric", month: "short" } });
// "Sep 12 – 15"
```
In React, `now` is what keeps server and client output identical across the
hydration boundary. `timeZone` controls both the printed clock and the smart
calendar boundaries. If you would rather have relative output refresh itself,
[`anyfamily-react`](https://www.npmjs.com/package/anyfamily-react)'s `useAnywhen`
ticks so `"3 minutes ago"` never goes stale.
---
## modes
`mode` picks the rendering strategy. Each mode reads only the options that apply
to it; the rest are ignored.
| Mode | Does | Reads |
| --- | --- | --- |
| `"smart"` (default) | relative when near, calendar labels for nearby days, absolute when far | `locale`, `now`, `time`, `timeZone`, `style`, `thresholds` |
| `"absolute"` | plain `Intl.DateTimeFormat` output, shaped by `format` | `locale`, `format`, `timeZone` |
| `"relative"` | always relative, past and future, never falls back | `locale`, `now`, `numeric`, `style`, `thresholds` |
Smart mode is symmetric — past and future read the same way:
```ts
anywhen(date, { locale: "en" });
// < 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"
```
→ [Full breakdown of every mode](https://anyfamily.site/docs/anywhen#modes)
---
## options
| Option | Type | Default | Used by |
| --- | --- | --- | --- |
| `mode` | `"smart" \| "absolute" \| "relative"` | `"smart"` | — |
| `locale` | `string \| string[]` | runtime locale | all |
| `now` | `Date \| number \| string` | current time | smart, relative |
| `timeZone` | `string` | runtime timezone | smart, absolute |
| `time` | `boolean` | `true` | smart |
| `numeric` | `boolean` | `false` | relative |
| `style` | `"long" \| "short" \| "narrow"` | `"long"` | smart, relative |
| `format` | `Intl.DateTimeFormatOptions` | `{ day, month, year }` | absolute |
| `thresholds` | `Partial>` | built-in table | smart, relative |
→ [What each option does, with examples](https://anyfamily.site/docs/anywhen#options)
---
## locales
Any valid BCP 47 tag, including regional variants and fallback arrays. When
omitted, native `Intl` uses the runtime locale.
```ts
anywhen(date, { locale: "de" }); // "gestern, 14:35"
anywhen(date, { locale: "ru" }); // "вчера, 14:35"
anywhen(date, { locale: ["sr-Latn-RS", "en"] });
anywhen(date, { mode: "absolute", locale: "ja" }); // "2016年2月5日"
```
Non-Gregorian calendars need no extra API — pick one with the `-u-ca-` extension
on `locale`, and ask for the era through `format`.
```ts
anywhen(date, { mode: "absolute", locale: "th-TH-u-ca-buddhist" });
// "5 ก.พ. 2559"
```
→ [Calendars, eras and their limits](https://anyfamily.site/docs/anywhen#calendars)
---
## vs the alternatives
| | anywhen | dayjs | date-fns |
| --- | :---: | :---: | :---: |
| gzip | **~1.4kb** | ~7kb | ~20kb |
| locale data bundled | **no** | yes | yes |
| locales | **200+** | 140 | 100 |
| dependencies | **0** | 0 | 0 |
---
## stability
anywhen follows [semver](https://semver.org/). The public API is a single
export — `anywhen`, with `anywhen.parts` on it — plus `AnywhenOptions` and the
exported types. It only changes shape in a major release. New options arrive in
minors; exact formatted strings come from `Intl` and may vary between ICU
versions, so never assert on them across environments.
### migrating from 1.x
2.0 removed the separate `anywhenParts` export. It is the same function, now
reached through the one name the package exports:
```diff
- 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.
---
## compatibility
Node.js 18+ · Chrome 71+ · Firefox 65+ · Safari 14+ · Edge Runtime · Cloudflare
Workers · Deno
CI runs the full suite on Node 20, 22 and 24.
---
## the any family
anywhen is part of **any family** — tiny, zero-dependency wrappers over native
`Intl`, one API per package.
| | | |
| --- | --- | --- |
| [**anywhen**](https://anyfamily.site/anywhen) | dates & relative time | `Intl.DateTimeFormat` |
| [anyamount](https://anyfamily.site/anyamount) | numbers, currency, units | `Intl.NumberFormat` |
| [anymany](https://anyfamily.site/anymany) | lists | `Intl.ListFormat` |
| [anyaround](https://anyfamily.site/anyaround) | names & flags | `Intl.DisplayNames` |
| [anylong](https://anyfamily.site/anylong) | durations | `Intl.DurationFormat` |
| [anyplural](https://anyfamily.site/anyplural) | plurals | `Intl.PluralRules` |
| [anyword](https://anyfamily.site/anyword) | words & graphemes | `Intl.Segmenter` |
Want all of them? [`anyfamily`](https://www.npmjs.com/package/anyfamily) is one
install for the lot, and [`anyfamily-react`](https://www.npmjs.com/package/anyfamily-react)
wraps each as a hook with a shared locale provider.
```bash
npm install anyfamily
```
---
MIT © [kirilinsky](https://github.com/kirilinsky)