mrkeyoor.com_
Sun 20 Sept 06:05 UTC
npmUtilsupdated 20 Sept 2026

luxon review

Luxon wraps JavaScript dates in immutable DateTime, Duration, and Interval objects, with IANA zones and locale output supplied by the runtime's Intl implementation. That makes zone conversion and calendar arithmetic much clearer than raw Date without bundling timezone tables. Version 3.7.2 fixes ES module packaging after 3.7.1 reverted the first attempt; the 3.7 line also added Duration zero controls, relative-time rounding, and ISO precision. Our full browser import measured 69.5 KB minified and 21.5 KB gzipped.

27.4Mdownloads / wk
Verdict

Luxon is a sensible choice when named zones, durations, and intervals justify an object model and a 21.5 KB gzipped client cost. Add the separate type package, validate every parse, and test DST boundaries instead of assuming day and 24-hour arithmetic are identical.

We installed it

Lab card: what happened when we installed luxonScreenshot of luxon documentation
Install✓ · 0.6s2 packages on disk · 5 MB
ImportESM import works · require() works · CommonJS package with exports map
Browser21.5 KBgzipped (69.5 KB minified), bundled with esbuild
Typesno TypeScript types found
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does luxon install cleanly?

Yes. In a fresh container with an empty cache, npm install luxon finished in 0.6s, leaving 2 packages and 5 MB on disk. npm audit reported no known vulnerabilities.

How much does luxon add to a browser bundle?

21.5 KB gzipped (69.5 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does luxon work with both ESM and CommonJS?

Yes. Both import 'luxon' and require('luxon') worked in Node 22 in our run. The package is published as CommonJS with an exports map.

Does luxon include TypeScript types?

No type declarations were found in our install, so TypeScript users need their own declarations.

luxon or date-fns: which should you use?

date-fns: Choose it for tree-shakeable functions over Date when only selected operations should enter the bundle. Luxon is a sensible choice when named zones, durations, and intervals justify an object model and a 21.5 KB gzipped client cost.

When should you not use luxon?

You need function-level tree shaking: importing DateTime brought our browser build to 21.5 KB gzipped, while date-fns can ship selected functions

API stability5/5The DateTime, Duration, and Interval model has remained steady through the 3.x line. Version 3.7.2 repaired ES module packaging without changing user calls, while 3.7.0 added options and methods rather than replacing core operations. A version 4 discussion exists, but the current package exposes both require and import paths that passed our checks.
Docs5/5The project site links a guided tour, install help, generated API pages, a Moment migration guide, and focused explanations of zones, formatting, math, and invalid values. Its examples distinguish parser options from setZone behavior and document interval semantics. The main remaining burden is that Intl capabilities differ by runtime, which no static API page can fully guarantee.
Maintenance3/5Version 3.7.2 has been published since 2025-09-05 and its changelog describes a packaging correction. GitHub shows the repository was pushed on 2026-08-09 and is not archived, though the open counter combines 179 issues and pull requests. Work continues, but releases are less frequent than the package's download volume might suggest.
Ecosystem4/5The npm endpoint recorded 37,725,578 downloads for the week ending 2026-08-22, and GitHub reports 16,445 stars. DefinitelyTyped supplies the missing declarations, while Intl provides zones and locales without Luxon plugins. The ecosystem is mature, though date-fns has a broader function-and-plugin orbit and Temporal is the longer-term platform direction.

Discussed on

  1. hnLuxon – A library for working with dates and times in JS185 points
  2. hnNew Zealand's prime minister proposes social media ban for under-16s6 points
  3. hnLuxon is over 500 times faster than MongoDB for timeseries data4 points
  4. hnLuxon3 points

Use it if

  • Your product schedules or displays events in named IANA time zones and daylight-saving changes matter
  • You need intervals and durations as objects with overlap, splitting, shifting, and ISO conversion methods
  • Locale-aware date and relative-time output should use the runtime's Intl data instead of shipped locale files
  • A team migrating from Moment wants immutable values and explicit zone handling with a familiar object API
Skip it if

Setup reality

Our clean Node 22 install of 3.7.2 finished in 0.6 seconds. It left 2 packages and used 5 MB on disk. Luxon itself has no direct or peer dependencies and is 4,608 KB unpacked. npm audit reported 0 known vulnerabilities. require() and ESM import both worked through the exports map. We found no TypeScript declarations in the package.

Install @types/luxon separately for TypeScript. There is no native build, zone file, or locale pack to configure. Luxon asks Intl for both. Current standard Node builds include broad ICU data, but slim or unusual runtimes can have missing locale behavior. Test every deployment runtime with the zones and languages the product promises.

DateTime.fromISO and the other parsers return an object even when input is bad. Check isValid, invalidReason, and invalidExplanation at the boundary. Settings.throwOnInvalid changes that behavior process-wide; it can also affect a dependency that uses Luxon, so enabling it inside a shared library is unfriendly. Set defaultZone and defaultLocale only at application startup if machine defaults must not leak into output.

Calendar math and elapsed math are different around daylight-saving transitions. plus({ days: 1 }) keeps local clock time, while plus({ hours: 24 }) advances an exact duration and may land an hour away. Intervals include the start and exclude the end. Our browser bundle was 69.5 KB minified and 21.5 KB gzipped, so high-traffic clients should compare that cost with smaller function-based libraries.

Patterns

Parse ISO input and reject invalid values parse-iso

import { DateTime } from 'luxon';

const value = DateTime.fromISO(input, { setZone: true });
if (!value.isValid) {
  throw new Error(`${value.invalidReason}: ${value.invalidExplanation}`);
}

Parsing does not throw by default. setZone preserves an offset or zone present in the input instead of converting to the local zone.

Format for a user's locale format-localized

const label = DateTime.now()
  .setLocale('fr-FR')
  .toLocaleString(DateTime.DATETIME_MED);

Locale presets follow local ordering and names. toFormat uses fixed tokens and is better reserved for machine or prescribed display formats.

Display the same instant in another zone convert-zone

const utc = DateTime.fromISO('2026-11-05T15:00:00Z');
const local = utc.setZone('America/New_York');
console.log(local.toFormat('yyyy-LL-dd HH:mm ZZZZ'));

setZone changes the display zone while preserving the instant. keepLocalTime: true changes the instant and needs a specific scheduling reason.

Choose calendar days or elapsed hours add-calendar-time

const start = DateTime.fromISO('2026-03-07T12:00', { zone: 'America/New_York' });
const sameWallTime = start.plus({ days: 1 });
const exactElapsed = start.plus({ hours: 24 });

The two results may differ across a daylight-saving boundary. Tests should cover the zones used in production.

Parse a known non-ISO format parse-custom-format

const date = DateTime.fromFormat(
  '24/08/2026 18:30',
  'dd/LL/yyyy HH:mm',
  { zone: 'Asia/Kolkata', locale: 'en-IN' }
);

Luxon tokens differ from Moment tokens. Always inspect isValid after parsing external text.

Return a difference in named units measure-difference

const opened = DateTime.fromISO(openedAt);
const closed = DateTime.fromISO(closedAt);
const elapsed = closed.diff(opened, ['days', 'hours', 'minutes']).toObject();

diff returns a Duration. Pass the desired units or convert it with as() before treating the result as a number.

Normalize duration units for display normalize-duration

import { Duration } from 'luxon';

const duration = Duration.fromObject({ hours: 2, minutes: 95 });
const normalized = duration.shiftTo('hours', 'minutes');
console.log(normalized.toHuman());

toHuman reflects stored units. Use shiftTo or normalize when 95 minutes should be carried into hours.

Check whether two intervals overlap test-interval-overlap

import { Interval } from 'luxon';

const booking = Interval.fromDateTimes(start, end);
if (!booking.isValid) throw new Error(booking.invalidExplanation);
const conflicts = booking.overlaps(existingBooking);

Intervals are half-open: start is included and end is excluded, so adjacent bookings do not overlap.

Split a range into calendar slots split-interval

const slots = Interval.fromDateTimes(dayStart, dayEnd)
  .splitBy({ minutes: 30 })
  .filter(slot => slot.length('minutes') === 30);

A final partial interval can be returned when the range is not evenly divisible. Filter it only if partial slots are invalid for the product.

Create a relative-time label render-relative-time

const label = DateTime.fromISO(createdAt)
  .setLocale(userLocale)
  .toRelative({ base: DateTime.now() });

The result depends on the base clock and becomes stale. Refresh it on the client or regenerate it when caching HTML.

Cross the native Date boundary convert-js-date

const luxonValue = DateTime.fromJSDate(nativeDate, { zone: 'utc' });
const nativeAgain = luxonValue.toJSDate();
const epochMs = luxonValue.toMillis();

A Date stores an instant without a named zone. Supply the intended zone when wrapping it for display or calendar work.

Set deterministic process defaults set-application-defaults

import { Settings } from 'luxon';

Settings.defaultZone = 'UTC';
Settings.defaultLocale = 'en-GB';

Set globals once during application startup. Shared libraries should accept zone and locale inputs instead of changing process-wide settings.

Alternatives

PackageRegistryPick it when
date-fnsnpmChoose it for tree-shakeable functions over Date when only selected operations should enter the bundle.
dayjsnpmChoose it for a small Moment-like surface when plugins can cover the required behavior.
@js-temporal/polyfillnpmChoose it to adopt the Temporal data model before every target runtime ships it.
date-fns-tznpmChoose it when a date-fns codebase only needs named-zone formatting and conversion.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.