javascript
33 lines · 6 steps
Building a human-friendly timeAgo formatter
A relative-time helper walks a table of unit divisors to pick the largest fitting unit and format it with Intl.
Explained by
highlit
1const DIVISIONS = [
2 { amount: 60, unit: 'seconds' },
3 { amount: 60, unit: 'minutes' },
4 { amount: 24, unit: 'hours' },
5 { amount: 7, unit: 'days' },
6 { amount: 4.34524, unit: 'weeks' },
7 { amount: 12, unit: 'months' },
8 { amount: Number.POSITIVE_INFINITY, unit: 'years' },
9];
10
11export function timeAgo(input, locale = 'en') {
12 const date = input instanceof Date ? input : new Date(input);
13
14 if (Number.isNaN(date.getTime())) {
15 throw new TypeError('timeAgo expects a valid date');
16 }
17
18 const formatter = new Intl.RelativeTimeFormat(locale, {
19 numeric: 'auto',
20 style: 'long',
21 });
22
23 let duration = (date.getTime() - Date.now()) / 1000;
24
25 for (const { amount, unit } of DIVISIONS) {
26 if (Math.abs(duration) < amount) {
27 return formatter.format(Math.round(duration), unit);
28 }
29 duration /= amount;
30 }
31
32 return formatter.format(Math.round(duration), 'years');
33}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A ladder of divisors lets you promote a raw second count into the largest natural unit without a chain of if-statements.
- 2Intl.RelativeTimeFormat handles pluralization, sign, and words like 'yesterday' so you never hand-build strings.
- 3Keeping the sign on duration means both past and future dates format correctly from the same loop.
Related explainers
javascript
const cache = new Map(); const inflight = new Map(); async function fetchResults(query) {
Building a typeahead with an LRU cache
caching
lru-eviction
request-deduplication
Intermediate
9 steps
javascript
export function createSearchClient(baseUrl) { let inFlight = null; async function search(query, { signal } = {}) {
Cancelling stale requests in a search client
closures
abortcontroller
async
Intermediate
8 steps
javascript
export function cloneState(state) { if (typeof structuredClone !== "function") { throw new Error("structuredClone is not available in this runtime"); }
Deep cloning with structuredClone
deep-copy
error-handling
immutability
Intermediate
7 steps
javascript
function validateSignup({ email, password, confirmPassword, username, age }) { const errors = {}; if (!email) {
Building a signup validator in JavaScript
validation
regex
guard-clauses
Beginner
7 steps
java
package com.example.billing; import java.math.BigDecimal; import java.math.RoundingMode;
Locale-aware currency formatting in Java
internationalization
rounding
immutability
Intermediate
6 steps
javascript
const { validationResult, matchedData } = require('express-validator'); function validate(schema) { const runners = schema.map((rule) => rule.run.bind(rule));
A reusable validation middleware in Express
middleware
validation
higher-order-functions
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-a-human-friendly-timeago-formatter-explained-javascript-68cc/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.