$ snippet format-date
Format a date without a library
YYYY-MM-DD and ISO-style output, plus a 'time ago' helper, using only built-in APIs.
/** "2026-08-28" in local time (toISOString() would give UTC). */
function toYMD(date = new Date()) {
const p = (n) => String(n).padStart(2, "0");
return `${date.getFullYear()}-${p(date.getMonth() + 1)}-${p(date.getDate())}`;
}
/** "2026-08-28 14:05" */
function toYMDHM(date = new Date()) {
const p = (n) => String(n).padStart(2, "0");
return `${toYMD(date)} ${p(date.getHours())}:${p(date.getMinutes())}`;
}
/** "3 hours ago", "in 2 days" — localized via Intl.RelativeTimeFormat. */
function timeAgo(date, locale = "en") {
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
const diff = (date.getTime() - Date.now()) / 1000;
const units = [["year", 31536000], ["month", 2592000], ["week", 604800],
["day", 86400], ["hour", 3600], ["minute", 60], ["second", 1]];
for (const [unit, secs] of units) {
if (Math.abs(diff) >= secs || unit === "second")
return rtf.format(Math.round(diff / secs), unit);
}
}How it works
The most common bug is calling toISOString().slice(0, 10) for a date string: it converts to UTC first, so late-evening dates shift to tomorrow for users west of Greenwich. Build the string from the local getters instead. For human-friendly relative times, Intl.RelativeTimeFormat handles pluralization and language for you.
Gotchas
getMonth()is zero-based;getDate()is the day of the month,getDay()is the weekday.- For full locale-aware formatting ("28 Aug 2026", "28/08/2026") use
Intl.DateTimeFormatrather than string-building. - Parsing
new Date("2026-08-28")treats the string as UTC midnight, whilenew Date("2026-08-28T00:00")is local midnight. Include a time to get local.
Ad slot