moment
Moment is the date library that JavaScript grew up on: parse a string or a Date, then chain methods to add days, compare, and format the result into human text. One object type (a Moment) wraps everything, and it covers parsing dozens of input shapes, formatting tokens, relative time like 'in 3 hours', durations, and localized output in over 100 locales. It is also, by the maintainers' own statement in the README, a legacy project in maintenance mode: it takes security fixes but no new features, and the docs tell you to pick a different library for new code. It still ships to millions of installs a week because it is buried inside older apps and transitive dependencies, not because anyone recommends starting with it.
Keep it alive in old code, do not put it in new code. The library still works and still gets security patches, but its own maintainers point you at Day.js, date-fns, Luxon, or the coming Temporal API, and every one of those is smaller or safer to reason about.
Use it if
- You already have a large codebase full of moment() calls and the migration cost is real: it still works, it is still patched for security issues, and rewriting date logic under time pressure causes bugs
- You need moment-timezone's IANA database handling in a legacy build where swapping to Luxon or Temporal means touching hundreds of call sites
- You need Moment's forgiving parser for messy human-entered date strings across many locales, and you are willing to pass an explicit format string to keep it predictable
- You are maintaining an old build target (IE11-era bundles, no ES2015 output) where newer date libraries' module formats cause trouble
- You are starting anything new: the project README says it is legacy and in maintenance mode, and the last release, 2.30.1, shipped in December 2023
- Bundle size matters at all: about 75 KB gzipped before locales, with no tree shaking, versus roughly 3 KB for Day.js; webpack pulls in every locale file unless you add IgnorePlugin or ContextReplacementPlugin yourself
- Your team keeps hitting mutation bugs: add(), startOf(), and friends mutate the Moment in place, so passing one into a helper silently rewrites the caller's value unless everyone remembers .clone()
- You want timezone support without a second install: moment alone has only UTC and local, and moment-timezone adds a large IANA data payload on top of the already large core
- You can target modern runtimes: Temporal is landing in browsers and Node, and date-fns or Luxon give you immutable values plus tree shaking today
Setup reality
npm install moment and you are done: no peer dependencies, no config, TypeScript types ship in the package. The pain comes at build time and at runtime, not at install. Bundlers see moment's dynamic require of locale files and pull in all of them, so a plain webpack build adds tens of kilobytes of Serbian and Burmese translations you never asked for, and the fix is a manual IgnorePlugin or ContextReplacementPlugin rule. Timezone work means installing moment-timezone separately and choosing between the full data build and a trimmed date range. Parsing a non-ISO string without a format falls back to the browser's Date constructor and prints a deprecation warning to the console, so most real projects end up passing explicit format strings everywhere.
Patterns
Parse a date and format itparse-and-format
const moment = require('moment');
const m = moment('2026-08-05T14:30:00Z');
console.log(m.format('YYYY-MM-DD HH:mm')); // local time
console.log(m.format('dddd, MMMM Do YYYY')); // Wednesday, August 5th 2026Formatting always renders in local time unless you switch the Moment to UTC or a named zone first.
Clone before adding or subtractingclone-before-mutating
const start = moment('2026-01-01');
// wrong: this changes `start` itself
// const end = start.add(7, 'days');
// right:
const end = start.clone().add(7, 'days');
console.log(start.format('YYYY-MM-DD'), end.format('YYYY-MM-DD'));Every mutating method returns the same object, so a missing .clone() rewrites values held elsewhere. This is the single most common Moment bug.
Parse a known format strictlystrict-parsing
const m = moment('05/08/2026', 'DD/MM/YYYY', true);
console.log(m.isValid()); // true
moment('not a date', 'DD/MM/YYYY', true).isValid(); // falseThe third argument turns on strict mode. Without a format string, non-ISO input falls through to the native Date constructor and logs a deprecation warning.
Check whether a parse succeededvalidate-input
const m = moment(userInput, 'YYYY-MM-DD', true);
if (!m.isValid()) {
console.log(m.invalidAt()); // index of the failing unit
console.log(m.parsingFlags().overflow);
throw new Error('Bad date');
}An invalid Moment does not throw; format() on it returns the string 'Invalid date'. Always call isValid() on user input.
Measure the gap between two datesdiff-two-dates
const a = moment('2026-08-05');
const b = moment('2026-12-25');
console.log(b.diff(a, 'days')); // 142
console.log(b.diff(a, 'months')); // 4 (truncated)
console.log(b.diff(a, 'months', true)); // 4.65... (float)diff truncates toward zero by default; pass true as the third argument for a fractional result.
Render relative time like 'an hour ago'relative-time
moment('2026-08-05T10:00:00Z').fromNow(); // '4 hours ago'
moment().add(3, 'days').fromNow(); // 'in 3 days'
moment().add(3, 'days').fromNow(true); // '3 days'
moment().add(3, 'days').from(moment('2026-08-01'));Thresholds are coarse by design: 45 seconds rounds to 'a minute'. Adjust with moment.relativeTimeThreshold if the fuzziness matters.
Snap to the start or end of a periodstart-and-end-of-period
const day = moment().startOf('day').clone();
const monthEnd = moment().endOf('month');
console.log(day.toISOString(), monthEnd.toISOString());startOf and endOf mutate in place too. endOf('day') lands on 23:59:59.999, so use a half-open range comparison rather than <= for queries.
Work in UTC instead of local timeutc-mode
const u = moment.utc('2026-08-05 14:30', 'YYYY-MM-DD HH:mm');
console.log(u.format()); // 2026-08-05T14:30:00Z
console.log(u.local().format()); // shifted to the machine zone
console.log(u.toISOString()); // always UTCmoment.utc() controls display and parsing, not storage. toISOString() is UTC regardless of the mode, which is what you want in a database.
Convert to a named IANA timezonenamed-timezone
// npm install moment-timezone
const moment = require('moment-timezone');
const t = moment.tz('2026-08-05 09:00', 'Asia/Kolkata');
console.log(t.clone().tz('America/New_York').format('YYYY-MM-DD HH:mm z'));
console.log(moment.tz.guess()); // browser's zonemoment-timezone is a separate install that bundles the IANA database. Require it instead of moment; requiring both in one build wastes bytes.
Build and read a durationdurations
const d = moment.duration(93, 'minutes');
console.log(d.hours(), d.minutes()); // 1 33 (components)
console.log(d.asMinutes()); // 93 (total)
console.log(d.humanize()); // 'an hour'hours() gives the component inside the duration, asHours() gives the total. Mixing them up produces wrong totals for anything over a day.
Switch locale for formattingset-locale
require('moment/locale/de');
moment.locale('de');
console.log(moment().format('dddd')); // Mittwoch
// per-instance, without changing the global default:
console.log(moment().locale('fr').format('dddd'));moment.locale() is global state shared by the whole process; in a server rendering pages for many users, set the locale per instance instead.
Stop webpack from bundling every localeshrink-bundle
// webpack.config.js
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment$/,
}),
],
};Without this rule moment's dynamic locale require pulls in all locale files. Import the few you need explicitly after adding the plugin.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| dayjs | npm | You want the same chained API in about 3 KB and immutable objects, with plugins for the extras |
| date-fns | npm | You prefer plain functions on native Date objects that tree shake down to only what you import |
| luxon | npm | You need real timezone and Intl handling and are willing to learn a different, immutable API |
| @js-joda/core | npm | You want a strict, typed date model borrowed from Java's java.time rather than a loose parser |