Let's say you're building a chat app. International. You're launching in Germany, Japan, and Brazil next quarter — because apparently that's the plan now, and you found out on a Tuesday afternoon.
You have a timestamp. A user was online 5 hours ago. You want to show that.
Simple.
In English: "5 hours ago"
In German: "vor 5 Stunden"
In Japanese: "5時間前"
In Brazilian Portuguese: "há 5 horas"
Or maybe you have an event. It's in two days. You want to say that.
In English: "in 2 days"
In Arabic: "خلال يومين"
In Turkish: "2 gün içinde"
Or just a date. March 25, 2026. Simple.
In English: "Mar 25, 2026"
In Russian: "25 мар. 2026 г."
In Chinese: "2026年3月25日"
None of this is hard to display. It's hard to display correctly, in the right language, without shipping a dictionary to every user.
The obvious solution that isn't great
You open dayjs. You find the locale system. You import German. Portugal... and over and over more.
import 'dayjs/locale/de'
import 'dayjs/locale/ja'
import 'dayjs/locale/pt-br'
It works. Three locales down, forty-seven to go — and you're not sure which forty-seven, because that depends on where the business decides to expand next.
Then someone asks for Arabic. Arabic has six plural forms — "1 hour ago", "2 hours ago", "3 hours ago" are all different grammatical structures. You import the locale file. Then Serbian. Then Korean.
Then there's a meeting where someone says "we should support all major languages" and you smile and nod and quietly go update your webpack config.
The locale bundle grows. The import list grows. Any better ideas?
My solution
Three functions. That's it. No classes, no configuration objects, no "please read the 40-page migration guide". Just install.
npm install anywhen
And use.
import { anydate, anywhen, anyago } from 'anywhen'
Each function answers one question you already ask yourself when writing UI.
anywhen — just make it readable
This is the one that thinks for you. You give it a date, it figures out what a human would actually say. Recent things get relative time. Yesterday gets "yesterday". Older things get a proper date. Future things get "in 2 weeks". You stop making these decisions in every component.
anywhen(date, 'en') // "just now"
anywhen(date, 'en') // "10 minutes ago"
anywhen(date, 'en') // "today, 2:35 PM"
anywhen(date, 'en') // "yesterday, 9:00 AM"
anywhen(date, 'en') // "Wednesday, 11:20 AM"
anywhen(date, 'en') // "Mar 5, 2016"
anywhen(date, 'en') // "in 2 weeks"
The switching logic:
< 45 seconds → "just now"
< 1 hour → "10 minutes ago"
future > 1h → "in 2 weeks"
same day → "today, 14:35"
yesterday → "yesterday, 09:00"
< 7 days → "Wednesday, 11:20"
older → "Mar 5, 2016"
anydate — what's the exact date, just a formatter?
Maybe, but smart. For when you need a clean, localized date string. A post timestamp. An invoice date. A birthday. Something absolute that doesn't change based on when the user is reading it.
anydate(date, 'en') // "Mar 25, 2026"
anydate(date, 'de') // "25. März 2026"
anydate(date, 'ja') // "2026年3月25日"
anydate(date, 'ar') // "٢٥ مارس ٢٠٢٦"
Need more control? Pass any Intl.DateTimeFormat options:
anydate(date, 'en', { weekday: 'long', month: 'long', day: 'numeric' })
// "Wednesday, March 25"
anydate(date, 'en', { hour: '2-digit', minute: '2-digit' })
// "2:35 PM"
anyago — how long ago? how far ahead?
For when the exact date doesn't matter — the distance does. A comment was posted. An event is coming. A subscription expires. You want to say "3 hours ago" or "in 2 days", not "March 25, 2026 at 14:35:00 UTC".
anyago(date, 'en') // "5 hours ago"
anyago(date, 'de') // "vor 5 Stunden"
anyago(date, 'tr') // "5 saat önce"
anyago(date, 'ru') // "5 часов назад"
// works for the future too
anyago(date, 'en') // "in 2 days"
anyago(date, 'ar') // "خلال يومين"
Sometimes you don't want "yesterday" — you want "1 day ago". Just add flag true:
anyago(date, 'en', true) // "1 day ago" instead of "yesterday"
anyago(date, 'en', true) // "1 week ago" instead of "last week"
anywhere — same locale everywhere
If you're formatting multiple dates in one component, passing the locale every time gets old fast. anywhere binds it once.
import { anywhere } from 'anywhen'
const d = anywhere('de')
d.anydate(date) // "25. März 2026"
d.anywhen(date) // "heute, 09:00"
d.anyago(date) // "vor 5 Stunden"
Locale set once and forgotten.
800 bytes. That includes every language.
What it doesn't do
Honest section. I like these.
No custom format strings like DD/MM/YYYY. No timezone handling (at least now).
No date arithmetic. No parsing of whatever creative format your backend decided to use in 2013.
The 7-day cutoff in anywhen — after which it shows an absolute date instead of a weekday — is fixed. If you need custom logic, use anyago and anydate directly and wire it yourself.
This library is for one specific thing:
displaying dates to humans in their language.
Chat timestamps. Event countdowns. Comment dates. Notification feeds. If that's what you need, it's 800 bytes and you're done. If you need to parse timezones or do date math — use date-fns. No shame in that.
Try it
Live demo where you can pick any method, any date, any locale and see the output: Demo page
Source is on GitHub. The whole thing is small enough to read in one sitting. If something is broken or missing — open an issue. I built this because I needed it, and I'm really curious whether other people do too.
Top comments (0)