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

bash
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.

typescript
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

MethodReturnsPurpose
dayjs(input)instanceConstruct
.format(template)stringRender to string
.add(n, unit)new instanceAdd time
.subtract(n, unit)new instanceSubtract time
.startOf(unit)new instanceFloor to unit boundary
.endOf(unit)new instanceCeil to unit boundary
.isBefore(other)booleanStrict less-than
.isAfter(other)booleanStrict greater-than
.isSame(other, unit?)booleanEquality at granularity
.diff(other, unit?, float?)numberDifference in unit
.toDate()DateEscape hatch to native
.toISOString()stringRFC 3339 string
.unix() / .valueOf()numberUNIX seconds / ms
.isValid()booleanSanity 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".

typescript
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:

text
true 2026-05-25
true 2026-05-25
true 2026-05-25
false

Common parse tokens

TokenMeaning
YYYY4-digit year
YY2-digit year
MM / MMonth 01–12 / 1–12
MMM / MMMMShort / full month name
DD / DDay of month
HH / HHour 00–23 / 0–23
hh / hHour 01–12 / 1–12 (with A/a)
mm / mMinute
ss / sSecond
SSSMillisecond
A / aAM/PM
Z / ZZTZ 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].

typescript
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:

text
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.

typescript
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:

text
5 minutes ago
in 2 hours
3 days
in a year

Calendar plugin

calendar() produces context-sensitive labels: "Today", "Yesterday", "Last Wednesday", "[absolute date]".

typescript
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:

text
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).

typescript
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:

text
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.

typescript
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:

text
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.

typescript
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:

text
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.

typescript
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:

text
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.

typescript
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:

text
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.

typescript
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)

text
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().

typescript
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:

text
lundi 25 mai 2026
2026年5月25日(月)
Montag, 25. Mai 2026
Monday, May 25, 2026

Library comparison

FeatureDay.jsdate-fnsLuxonTemporal (polyfill)
Bundle size (gzipped)~2 kB~13 kB (tree-shaken)~22 kB~30 kB
API styleFluent, immutableFunctionalFluent, immutableFluent, immutable
Plugin modelYesN/A — direct importsBuiltinBuiltin
TimezonesPluginPluginBuiltinBuiltin
TypeScript typesYesYesYesYes
Tree-shakeablePartial (plugins)Yes (per function)NoNo
Future-proofStableStableStableBecomes 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

  1. Forgetting .extend()dayjs("25/05/2026", "DD/MM/YYYY") returns Invalid Date until you dayjs.extend(customParseFormat). The validator silently returns isValid: false; always check.
  2. .month() is 0-indexeddayjs().month(5) is June, not May. Use the format strings ("MMMM") and .set("month", n) carefully.
  3. String parsing without format — non-ISO strings like "05/25/2026" give inconsistent results across browsers/Node versions. Always pass a format.
  4. Assuming mutationd.add(1, "day") does not modify d; reassign or chain (d = d.add(...) or use the returned value).
  5. Timezones without plugins.tz("America/New_York") silently fails (returns undefined method) unless both utc and timezone are extended.
  6. 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.
  7. .format() token confusion with Moment — Day.js is 99% Moment-compatible, but advanced tokens (Wo, wo) require the weekOfYear plugin. Check the docs page before assuming.
  8. Comparing with === — Day.js instances are objects; a === b is reference equality. Use .isSame() for value comparison.
  9. new Date() parsing of YYYY-MM-DD — the underlying new Date("2026-05-25") is parsed as UTC, but new Date("2026-05-25T14:30:00") (no Z) is parsed as local time. This inconsistency leaks through Day.js too; always include a Z or explicit offset.
  10. Locale not resetdayjs.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.

typescript
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)

text
┌─────────┬──────────┬──────────────────┬───────────────┐
│ (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.

typescript
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:

text
Friday, May 29

Group events into "today / this week / older" buckets

A common UI pattern: bucket records by recency for a feed view.

typescript
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:

text
{
  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.

typescript
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:

text
{ '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.

bash
npm install @js-temporal/polyfill

Output: (none — exits 0 on success)

typescript
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:

text
2026-05-25
2026-06-01
2026-12-25
2026-05-26T03:30:00+09:00[Asia/Tokyo]