cheat sheet
Day.js
Parse, format, manipulate, compare, and convert timezones for dates in JavaScript using Day.js — a 2 kB Moment-compatible alternative with a plugin-based architecture.
Day.js — Tiny, immutable date library
What it is
Day.js is a minimalist date library for Node.js and browsers — roughly 2 kB gzipped — with an API deliberately compatible with Moment.js. It is maintained by iamkun and the OSS community and written in TypeScript. Reach for it when you want fluent date arithmetic, formatting, timezone handling, and relative-time strings without pulling in the 70+ kB of Moment or the 30 kB of date-fns. Alternatives to know: date-fns (tree-shakeable, function-first), Luxon (richer timezone/duration model), and the still-proposal-stage Temporal API (already polyfillable today via @js-temporal/polyfill) — the official future of JavaScript dates.
Install
npm install dayjs
# Or with other package managers
pnpm add dayjs
yarn add dayjs
bun add dayjs
Output: (none — exits 0 on success)
Syntax
dayjs(input?) creates a Day.js instance. With no argument it represents "now"; pass an ISO string, a Date, a UNIX milliseconds number, or another dayjs object. All instances are immutable — every mutator returns a new instance rather than modifying the original.
import dayjs from "dayjs";
dayjs(); // now
dayjs("2026-05-25"); // ISO date
dayjs("2026-05-25T14:30:00Z"); // ISO datetime
dayjs(new Date(2026, 4, 25)); // from Date
dayjs(1748167200000); // from UNIX ms
dayjs(dayjs()); // from another dayjs
Output: (none — exits 0 on success)
Essential API surface
| Method | Returns | Purpose |
|---|---|---|
dayjs(input) | instance | Construct |
.format(template) | string | Render to string |
.add(n, unit) | new instance | Add time |
.subtract(n, unit) | new instance | Subtract time |
.startOf(unit) | new instance | Floor to unit boundary |
.endOf(unit) | new instance | Ceil to unit boundary |
.isBefore(other) | boolean | Strict less-than |
.isAfter(other) | boolean | Strict greater-than |
.isSame(other, unit?) | boolean | Equality at granularity |
.diff(other, unit?, float?) | number | Difference in unit |
.toDate() | Date | Escape hatch to native |
.toISOString() | string | RFC 3339 string |
.unix() / .valueOf() | number | UNIX seconds / ms |
.isValid() | boolean | Sanity check after parse |
Parsing
Day.js's default parser only handles ISO 8601 strings reliably; for any other format you must load the customParseFormat plugin and pass an explicit pattern. Always call .isValid() on the result before using a parsed date — invalid inputs return an instance whose every operation silently produces NaN/"Invalid Date".
import dayjs from "dayjs";
import customParseFormat from "dayjs/plugin/customParseFormat";
dayjs.extend(customParseFormat);
// ISO — works without plugin
const a = dayjs("2026-05-25");
console.log(a.isValid(), a.format("YYYY-MM-DD"));
// Custom format — needs the plugin
const b = dayjs("25/05/2026", "DD/MM/YYYY");
console.log(b.isValid(), b.format("YYYY-MM-DD"));
// Multiple candidate formats
const c = dayjs("May 25, 2026", ["MMM D, YYYY", "MMMM D, YYYY"]);
console.log(c.isValid(), c.format("YYYY-MM-DD"));
// Strict mode — fails if input doesn't exactly match
const d = dayjs("2026-13-99", "YYYY-MM-DD", true);
console.log(d.isValid());
Output:
true 2026-05-25
true 2026-05-25
true 2026-05-25
false
Common parse tokens
| Token | Meaning |
|---|---|
YYYY | 4-digit year |
YY | 2-digit year |
MM / M | Month 01–12 / 1–12 |
MMM / MMMM | Short / full month name |
DD / D | Day of month |
HH / H | Hour 00–23 / 0–23 |
hh / h | Hour 01–12 / 1–12 (with A/a) |
mm / m | Minute |
ss / s | Second |
SSS | Millisecond |
A / a | AM/PM |
Z / ZZ | TZ offset +05:00 / +0500 |
Formatting
.format(template) renders the instance to a string. Without arguments, it produces an ISO 8601 string. Tokens are substituted; literal text can be escaped with [brackets].
import dayjs from "dayjs";
const d = dayjs("2026-05-25T14:30:45Z");
console.log(d.format()); // default ISO
console.log(d.format("YYYY-MM-DD")); // date only
console.log(d.format("HH:mm:ss")); // time only
console.log(d.format("dddd, MMMM D, YYYY")); // long date
console.log(d.format("h:mm A")); // 12-hour
console.log(d.format("[Today is] dddd")); // literal text
console.log(d.format("YYYY-MM-DDTHH:mm:ssZ")); // RFC 3339
Output:
2026-05-25T14:30:45+00:00
2026-05-25
14:30:45
Monday, May 25, 2026
2:30 PM
Today is Monday
2026-05-25T14:30:45+00:00
Relative time (fromNow, to)
The relativeTime plugin adds human-readable phrases like "5 minutes ago" or "in 2 hours". Useful for activity feeds, comment timestamps, and "last seen" indicators.
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
dayjs.extend(relativeTime);
const now = dayjs("2026-05-25T12:00:00Z");
console.log(now.subtract(5, "minute").from(now)); // "5 minutes ago"
console.log(now.add(2, "hour").from(now)); // "in 2 hours"
console.log(now.subtract(3, "day").fromNow(true)); // "3 days" (no prefix)
console.log(now.to(now.add(1, "year"))); // "in a year"
Output:
5 minutes ago
in 2 hours
3 days
in a year
Calendar plugin
calendar() produces context-sensitive labels: "Today", "Yesterday", "Last Wednesday", "[absolute date]".
import dayjs from "dayjs";
import calendar from "dayjs/plugin/calendar";
dayjs.extend(calendar);
const now = dayjs();
console.log(now.calendar());
console.log(now.subtract(1, "day").calendar());
console.log(now.subtract(5, "day").calendar());
console.log(now.subtract(2, "month").calendar());
Output:
Today at 12:00 PM
Yesterday at 12:00 PM
Last Wednesday at 12:00 PM
03/25/2026
Manipulation
.add(), .subtract(), .startOf(), and .endOf() all return new instances — the original is never mutated. Supported units (singular or plural): year, month, week, day, hour, minute, second, millisecond, plus short forms (y, M, w, d, h, m, s, ms).
import dayjs from "dayjs";
const d = dayjs("2026-05-25T14:30:45.123Z");
console.log(d.add(1, "week").format()); // +7 days
console.log(d.subtract(3, "month").format()); // -3 months
console.log(d.add(90, "minute").format()); // +90 min
console.log(d.startOf("day").format()); // 00:00:00
console.log(d.endOf("day").format()); // 23:59:59.999
console.log(d.startOf("month").format("YYYY-MM-DD")); // 1st of month
console.log(d.startOf("week").format("ddd YYYY-MM-DD")); // first of week
// Chained
const lastFriday = dayjs()
.startOf("week")
.add(5, "day")
.subtract(1, "week");
console.log(lastFriday.format("ddd, MMM D"));
Output:
2026-06-01T14:30:45+00:00
2026-02-25T14:30:45+00:00
2026-05-25T16:00:45+00:00
2026-05-25T00:00:00+00:00
2026-05-25T23:59:59+00:00
2026-05-01
Sun 2026-05-24
Fri, May 22
Setters
.year(), .month(), .date(), .hour(), .minute(), .second(), .millisecond(), and the catch-all .set(unit, value) return a new instance with that field replaced (others preserved). .month() is 0-indexed (January = 0) — a perennial source of off-by-one bugs.
import dayjs from "dayjs";
const d = dayjs("2026-05-25T14:30:00Z");
console.log(d.year(2027).format("YYYY-MM-DD"));
console.log(d.month(0).format("YYYY-MM-DD")); // month is 0-indexed!
console.log(d.set("month", 11).format("YYYY-MM-DD"));
console.log(d.date(1).format("YYYY-MM-DD")); // day-of-month
console.log(d.hour(9).minute(0).second(0).format());
Output:
2027-05-25
2026-01-25
2026-12-25
2026-05-01
2026-05-25T09:00:00+00:00
Comparison
Use .isBefore(), .isAfter(), and .isSame() for boolean comparisons. Each accepts an optional unit argument that compares at that granularity (e.g. "same day, ignoring time"). For "between" semantics, install the isBetween plugin.
import dayjs from "dayjs";
import isBetween from "dayjs/plugin/isBetween";
dayjs.extend(isBetween);
const a = dayjs("2026-05-25T10:00:00Z");
const b = dayjs("2026-05-25T22:00:00Z");
const c = dayjs("2026-05-26T10:00:00Z");
console.log(a.isBefore(b)); // true
console.log(c.isAfter(b)); // true
console.log(a.isSame(b)); // false (different time)
console.log(a.isSame(b, "day")); // true (same calendar day)
console.log(a.isSame(c, "month")); // true
console.log(a.isBetween("2026-05-01", "2026-06-01")); // true
console.log(a.isBetween("2026-05-01", "2026-06-01", "day", "[]")); // inclusive both ends
Output:
true
true
false
true
true
true
true
.diff() — durations
Returns the difference between two instances as a number, in the requested unit. By default .diff() truncates toward zero; pass true as the third argument for a floating-point result.
import dayjs from "dayjs";
const start = dayjs("2026-01-01");
const end = dayjs("2026-05-25");
console.log(end.diff(start)); // ms
console.log(end.diff(start, "day")); // 144
console.log(end.diff(start, "month")); // 4
console.log(end.diff(start, "month", true)); // 4.78... (float)
console.log(end.diff(start, "year", true)); // 0.398... (float)
Output:
12441600000
144
4
4.774193548387097
0.39780821917808217
Timezones
Day.js handles timezones via two plugins that must be loaded together: utc (UTC parsing/formatting) and timezone (IANA zone conversion). Conversion is not free — internally it uses Intl.DateTimeFormat, which is built into modern Node/browsers but adds work per call.
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
dayjs.extend(utc);
dayjs.extend(timezone);
// Construct in UTC
const u = dayjs.utc("2026-05-25T14:30:00Z");
console.log(u.format());
// Convert to a named zone
console.log(u.tz("America/New_York").format("YYYY-MM-DD HH:mm Z"));
console.log(u.tz("Asia/Tokyo").format("YYYY-MM-DD HH:mm Z"));
console.log(u.tz("Europe/London").format("YYYY-MM-DD HH:mm Z"));
// Parse a string as if in a specific zone (no offset in string)
const ny = dayjs.tz("2026-05-25 14:30", "America/New_York");
console.log(ny.format()); // local
console.log(ny.utc().format()); // converted to UTC
// Default zone for the whole app
dayjs.tz.setDefault("America/Los_Angeles");
console.log(dayjs.tz("2026-05-25 09:00").format());
Output:
2026-05-25T14:30:00Z
2026-05-25 10:30 -04:00
2026-05-25 23:30 +09:00
2026-05-25 15:30 +01:00
2026-05-25T14:30:00-04:00
2026-05-25T18:30:00Z
2026-05-25T09:00:00-07:00
UTC mode
The utc plugin alone lets you keep all dates in UTC without involving IANA timezone data. Use .utc() to switch an instance into UTC mode (formatting/printing) and .local() to convert back. Storing UTC and converting at the edge (display) is the recommended pattern for any app with users in multiple regions.
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
dayjs.extend(utc);
const local = dayjs("2026-05-25T14:30:00"); // parsed as local time
console.log(local.format()); // local
console.log(local.utc().format()); // converted to UTC
console.log(local.utc().local().format()); // back to local
// Construct directly in UTC
const u = dayjs.utc("2026-05-25 14:30");
console.log(u.format("YYYY-MM-DDTHH:mm:ss[Z]"));
Output: (assuming machine is in Europe/London)
2026-05-25T14:30:00+01:00
2026-05-25T13:30:00Z
2026-05-25T14:30:00+01:00
2026-05-25T14:30:00Z
Locales
Day.js ships with English (en) by default. Load a locale module to get translated month names, weekday names, and fromNow() phrases. Locales can be set globally or per-instance with .locale().
import dayjs from "dayjs";
import "dayjs/locale/fr";
import "dayjs/locale/ja";
import "dayjs/locale/de";
// Global locale
dayjs.locale("fr");
console.log(dayjs("2026-05-25").format("dddd D MMMM YYYY"));
// Per-instance — does not change global
console.log(dayjs("2026-05-25").locale("ja").format("YYYY年M月D日(ddd)"));
console.log(dayjs("2026-05-25").locale("de").format("dddd, D. MMMM YYYY"));
// Reset to English
dayjs.locale("en");
console.log(dayjs("2026-05-25").format("dddd, MMMM D, YYYY"));
Output:
lundi 25 mai 2026
2026年5月25日(月)
Montag, 25. Mai 2026
Monday, May 25, 2026
Library comparison
| Feature | Day.js | date-fns | Luxon | Temporal (polyfill) |
|---|---|---|---|---|
| Bundle size (gzipped) | ~2 kB | ~13 kB (tree-shaken) | ~22 kB | ~30 kB |
| API style | Fluent, immutable | Functional | Fluent, immutable | Fluent, immutable |
| Plugin model | Yes | N/A — direct imports | Builtin | Builtin |
| Timezones | Plugin | Plugin | Builtin | Builtin |
| TypeScript types | Yes | Yes | Yes | Yes |
| Tree-shakeable | Partial (plugins) | Yes (per function) | No | No |
| Future-proof | Stable | Stable | Stable | Becomes native JS |
Use Day.js when you want a small dependency and a Moment-like API. Use date-fns when you want to tree-shake aggressively (pull in just format, parseISO, addDays). Use Luxon when timezone math is central to your app. Watch Temporal — once it ships natively, it will replace all of the above.
Common pitfalls
- Forgetting
.extend()—dayjs("25/05/2026", "DD/MM/YYYY")returns Invalid Date until youdayjs.extend(customParseFormat). The validator silently returnsisValid: false; always check. .month()is 0-indexed —dayjs().month(5)is June, not May. Use the format strings ("MMMM") and.set("month", n)carefully.- String parsing without format — non-ISO strings like
"05/25/2026"give inconsistent results across browsers/Node versions. Always pass a format. - Assuming mutation —
d.add(1, "day")does not modifyd; reassign or chain (d = d.add(...)or use the returned value). - Timezones without plugins —
.tz("America/New_York")silently fails (returnsundefinedmethod) unless bothutcandtimezoneare extended. - Tree-shaking — Day.js's main bundle and plugins are designed to be small, but importing every plugin "just in case" defeats the size win. Import only what you use.
.format()token confusion with Moment — Day.js is 99% Moment-compatible, but advanced tokens (Wo,wo) require theweekOfYearplugin. Check the docs page before assuming.- Comparing with
===— Day.js instances are objects;a === bis reference equality. Use.isSame()for value comparison. new Date()parsing ofYYYY-MM-DD— the underlyingnew Date("2026-05-25")is parsed as UTC, butnew Date("2026-05-25T14:30:00")(no Z) is parsed as local time. This inconsistency leaks through Day.js too; always include aZor explicit offset.- Locale not reset —
dayjs.locale("fr")is global; subsequent code expecting English will get French. Either set per-instance (.locale("fr")) or reset.
Real-world recipes
Convert mixed-timezone timestamps to user's local time
A log table from a database has UTC timestamps; render them in the user's browser timezone for an activity feed.
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
import relativeTime from "dayjs/plugin/relativeTime";
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(relativeTime);
const userTz = Intl.DateTimeFormat().resolvedOptions().timeZone; // browser zone
const events = [
{ kind: "login", at: "2026-05-25T08:00:00Z" },
{ kind: "purchase", at: "2026-05-25T13:42:11Z" },
{ kind: "logout", at: "2026-05-25T17:30:00Z" },
];
const rows = events.map((e) => {
const local = dayjs.utc(e.at).tz(userTz);
return {
kind: e.kind,
local: local.format("YYYY-MM-DD HH:mm"),
relative: local.fromNow(),
};
});
console.table(rows);
Output: (assuming userTz = "America/Los_Angeles", current time ≈ 2026-05-25 12:00 PT)
┌─────────┬──────────┬──────────────────┬───────────────┐
│ (index) │ kind │ local │ relative │
├─────────┼──────────┼──────────────────┼───────────────┤
│ 0 │ 'login' │ '2026-05-25 01:00' │ '11 hours ago'│
│ 1 │'purchase'│ '2026-05-25 06:42' │ '5 hours ago' │
│ 2 │ 'logout' │ '2026-05-25 10:30' │ 'an hour ago' │
└─────────┴──────────┴──────────────────┴───────────────┘
Compute business-day intervals
Skip weekends when adding N working days — a common scheduling problem.
import dayjs from "dayjs";
function addBusinessDays(start: dayjs.Dayjs, days: number): dayjs.Dayjs {
let d = start;
let remaining = days;
while (remaining > 0) {
d = d.add(1, "day");
const dow = d.day(); // 0 = Sun, 6 = Sat
if (dow !== 0 && dow !== 6) remaining--;
}
return d;
}
const ship = addBusinessDays(dayjs("2026-05-22"), 5); // Friday + 5 business days
console.log(ship.format("dddd, MMM D"));
Output:
Friday, May 29
Group events into "today / this week / older" buckets
A common UI pattern: bucket records by recency for a feed view.
import dayjs from "dayjs";
type Item = { id: number; at: string };
function bucket(items: Item[]) {
const now = dayjs();
return items.reduce<Record<string, Item[]>>((acc, it) => {
const d = dayjs(it.at);
const key = d.isSame(now, "day")
? "Today"
: d.isSame(now, "week")
? "This week"
: d.isSame(now.subtract(1, "week"), "week")
? "Last week"
: "Older";
(acc[key] ||= []).push(it);
return acc;
}, {});
}
console.log(
bucket([
{ id: 1, at: dayjs().subtract(2, "hour").toISOString() },
{ id: 2, at: dayjs().subtract(3, "day").toISOString() },
{ id: 3, at: dayjs().subtract(10, "day").toISOString() },
{ id: 4, at: dayjs().subtract(2, "month").toISOString() },
]),
);
Output:
{
Today: [ { id: 1, at: '…' } ],
'This week': [ { id: 2, at: '…' } ],
'Last week': [ { id: 3, at: '…' } ],
Older: [ { id: 4, at: '…' } ]
}
Generate ISO-week reports
ISO weeks (YYYY-Www) are useful for KPI dashboards where weeks are the natural reporting unit. Requires the isoWeek plugin.
import dayjs from "dayjs";
import isoWeek from "dayjs/plugin/isoWeek";
dayjs.extend(isoWeek);
const sales = [
{ day: "2026-05-22", amount: 120 },
{ day: "2026-05-25", amount: 80 },
{ day: "2026-05-26", amount: 200 },
{ day: "2026-06-01", amount: 50 },
];
const byWeek = sales.reduce<Record<string, number>>((acc, s) => {
const week = dayjs(s.day).format("YYYY-[W]WW"); // 2026-W21
acc[week] = (acc[week] ?? 0) + s.amount;
return acc;
}, {});
console.log(byWeek);
Output:
{ '2026-W21': 120, '2026-W22': 280, '2026-W23': 50 }
Looking ahead: the Temporal API
Temporal is the official future of JavaScript dates — already polyfillable today and slated for native shipping. The API is richer (separate PlainDate, PlainTime, PlainDateTime, ZonedDateTime, Instant, Duration types) and avoids most Day.js/Moment gotchas (no 0-indexed months, no silent invalid dates, builtin timezones). When it ships natively, expect to migrate Day.js code to it.
npm install @js-temporal/polyfill
Output: (none — exits 0 on success)
import { Temporal } from "@js-temporal/polyfill";
const today = Temporal.Now.plainDateISO();
console.log(today.toString()); // 2026-05-25
console.log(today.add({ days: 7 }).toString()); // 2026-06-01
console.log(today.with({ month: 12, day: 25 }).toString()); // 2026-12-25
const meeting = Temporal.ZonedDateTime.from(
"2026-05-25T14:30:00-04:00[America/New_York]",
);
console.log(meeting.withTimeZone("Asia/Tokyo").toString());
Output:
2026-05-25
2026-06-01
2026-12-25
2026-05-26T03:30:00+09:00[Asia/Tokyo]