dayjs
Day.js is a tiny date library for parsing, formatting, comparing, and doing arithmetic on dates. It deliberately clones the Moment.js API (chainable calls like dayjs().add(1, 'day').format('YYYY-MM-DD')) but ships as an immutable core of about 3 KB gzipped with zero dependencies. Everything beyond the basics (timezones, relative time, strict parsing, week numbers) lives in optional plugins you load one at a time. It is the default answer for teams migrating off Moment without rewriting call sites.
Still the best pick for Moment migrations and for apps that just need small, familiar date handling. If timezones are central to your product, or you are starting fresh in 2026, look at Luxon or the Temporal polyfill instead.
Use it if
- You are migrating a codebase off Moment.js and want a near drop-in API without rewriting every date call
- You need basic parse, format, add/subtract, and compare operations in the browser and care about bundle size
- You need i18n formatting: 100+ locales exist and each one is only loaded when you import it
- You want an immutable API so passing date objects around cannot mutate them, which was a classic Moment bug source
- You need serious timezone work: the timezone plugin is built on the Intl API, is slow when converting many dates, and has long-standing open DST edge case bugs in the issue tracker
- You want tree-shakeable functions instead of a chainable object; date-fns gives you per-function imports and works better with strict typing
- You are starting a greenfield project targeting modern runtimes: the Temporal API (and its polyfill) is the standards-track answer and dayjs has sat on 1.11.x for years with a large open-issue backlog (1,292 open issues and PRs)
- You expect every feature to be built in; in dayjs almost anything past format/add/diff means finding, importing, and registering another plugin, and forgetting the extend() call fails at runtime, not build time
Setup reality
npm install dayjs and the core works immediately with no config and no dependencies. The friction is plugins: UTC, timezone, customParseFormat, relativeTime, and advancedFormat all need a separate import plus dayjs.extend() before first use, and if a module runs before the extend call you get runtime errors like 'dayjs.tz is not a function'. Locales are also manual imports. TypeScript types are bundled, but plugin types only appear after you import the plugin module.
Patterns
Parse a date string and format itparse-and-format
import dayjs from 'dayjs'
const d = dayjs('2026-08-04')
d.format('DD MMM YYYY') // '04 Aug 2026'
d.format('YYYY-MM-DDTHH:mm') // '2026-08-04T00:00'Only ISO 8601 strings parse reliably; anything else falls back to the native Date constructor, which differs across engines. Use the customParseFormat plugin for other formats.
Add or subtract timeadd-subtract-time
import dayjs from 'dayjs'
const nextWeek = dayjs().add(7, 'day')
const lastMonth = dayjs().subtract(1, 'month')
const combo = dayjs().add(1, 'year').subtract(2, 'hour')Every call returns a new instance; dayjs objects are immutable, so chaining never mutates the original.
Get the difference between two datesdiff-between-dates
import dayjs from 'dayjs'
const a = dayjs('2026-01-01')
const b = dayjs('2026-08-04')
b.diff(a, 'day') // 215
b.diff(a, 'month') // 7
b.diff(a, 'month', true) // 7.09... (float instead of truncation)diff truncates toward zero by default; pass true as the third argument to get the fractional value.
Show 'x minutes ago' stringsrelative-time
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
dayjs.extend(relativeTime)
dayjs('2026-08-01').fromNow() // '3 days ago'
dayjs().to(dayjs('2027-01-01')) // 'in 5 months'fromNow does not exist until you extend with the plugin; forgetting extend() throws at runtime, not at build time.
Work in UTC instead of local timeutc-mode
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
dayjs.extend(utc)
dayjs.utc('2026-08-04T12:00:00Z').format() // stays in UTC
dayjs('2026-08-04T12:00:00Z').utc().local() // convert back to localServer code that formats timestamps should use utc mode; otherwise output silently depends on the host machine timezone.
Convert a date into a named timezonetimezone-convert
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import timezone from 'dayjs/plugin/timezone'
dayjs.extend(utc)
dayjs.extend(timezone)
dayjs('2026-08-04T12:00:00Z').tz('America/New_York').format('HH:mm') // '08:00'
dayjs.tz('2026-08-04 12:00', 'Asia/Kolkata') // parse as Kolkata wall timeThe timezone plugin requires the utc plugin to be extended first, relies on the runtime Intl API, and is slow in loops over many dates.
Strictly parse and validate a custom formatstrict-parse-validate
import dayjs from 'dayjs'
import customParseFormat from 'dayjs/plugin/customParseFormat'
dayjs.extend(customParseFormat)
dayjs('04/08/2026', 'DD/MM/YYYY', true).isValid() // true
dayjs('31/02/2026', 'DD/MM/YYYY', true).isValid() // falseWithout the third argument (strict mode) partial matches can still pass; always pass true when validating user input.
Compare two datescompare-dates
import dayjs from 'dayjs'
const deadline = dayjs('2026-12-31')
dayjs().isBefore(deadline) // true
dayjs().isAfter('2026-01-01') // true
dayjs().isSame('2026-08-04', 'day') // compare at day granularityThe second argument sets granularity; without it isSame compares to the millisecond, which is almost never what you want for calendar logic.
Snap to the start or end of a periodstart-end-of-period
import dayjs from 'dayjs'
dayjs().startOf('month').format('YYYY-MM-DD') // first of month, 00:00
dayjs().endOf('day') // today 23:59:59.999
dayjs().startOf('week') // depends on locale's first day of weekstartOf('week') is locale dependent (Sunday vs Monday); load the right locale or use isoWeek from the isoWeek plugin for ISO Monday weeks.
Format in another languageswitch-locale
import dayjs from 'dayjs'
import 'dayjs/locale/es'
dayjs.locale('es') // global default
dayjs('2026-08-04').format('dddd D MMMM') // 'martes 4 agosto'
dayjs('2026-08-04').locale('en').format('dddd') // per-instance overrideLocales are not bundled by default; each one must be imported explicitly or format falls back to English silently.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| date-fns | npm | You want tree-shakeable pure functions that operate on native Date objects instead of a wrapper class |
| luxon | npm | Timezone and Intl-heavy work matters more than bundle size; its zone support is first class rather than a plugin |
| moment | npm | Only for maintaining legacy code; Moment is in maintenance mode and its own docs point new projects elsewhere |