mrkeyoor.com_
Tue 22 Sept 22:37 UTC
npmUtilsupdated 22 Sept 2026

@js-joda/core review

@js-joda/core 6.1.0 models dates and time with immutable Java-style value classes instead of putting every concept into `Date`. `LocalDate` stores a calendar day, `LocalDateTime` stores wall time, `Instant` marks a timeline point, `Period` does calendar arithmetic, and `Duration` measures elapsed time. This release fixes offset parsing in `Instant.parse`, negative duration strings, pre-epoch instant truncation, overlapping zone-ID prefixes, and declaration errors. Named IANA zones and localized text still require separate js-joda packages. Our full import weighed 193 KB minified and 40.3 KB gzipped.

Verdict

Our @js-joda/core 6.1.0 install had zero dependencies and zero audit findings, but the full browser import measured 40.3 KB gzipped. Install it when separate date, local-time, instant, period, and duration types prevent real domain mistakes; use native Date helpers or Luxon for a smaller conceptual job.

We installed it

Lab card: what happened when we installed @js-joda/coreScreenshot of @js-joda/core documentation
Install✓ · 1.4s1 package on disk · 8 MB
ImportESM import works · require() works · CommonJS package
Browser40.3 KBgzipped (193 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does @js-joda/core install cleanly?

Yes. In a fresh container with an empty cache, npm install @js-joda/core finished in 1 seconds, leaving 1 package and 8 MB on disk. npm audit reported no known vulnerabilities.

How much does @js-joda/core add to a browser bundle?

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

Does @js-joda/core work with both ESM and CommonJS?

Yes. Both import '@js-joda/core' and require('@js-joda/core') worked in Node 22 in our run. The package is published as CommonJS.

Does @js-joda/core include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

@js-joda/core or luxon: which should you use?

luxon: Choose it for immutable date-times with zones and locales exposed through an API shaped more like ordinary JavaScript. Our @js-joda/core 6.1.0 install had zero dependencies and zero audit findings, but the full browser import measured 40.3 KB gzipped.

When should you not use @js-joda/core?

You only need a few operations on native Date. A 40.3 KB gzipped full import and a broad class model are hard to justify for simple display formatting.

API stability4/5The central types and method names follow the long-lived ThreeTen and Java `java.time` design: `LocalDate`, `Instant`, `Period`, `Duration`, formatters, fields, units, and adjusters remain recognizable across releases. Major 6 still changed integration details by simplifying `nativeJs`, dropping IE11, and coordinating a different Locale import. Existing core arithmetic is steady, but applications using browser support or companion packages must read major-version notes.
Docs4/5The project site links a getting-started manual and generated API pages, while the README explains each principal temporal type and the separate timezone, locale, and extra packages. It also states the ISO calendar scope and the ThreeTen source lineage. The landing page still carries old browser and performance language, and several 6.1 fixes appear under an `Unreleased` changelog heading dated the npm publish day, which makes release archaeology less clear than the API reference.
Maintenance5/5npm published core 6.1.0 on July 10, 2026, and GitHub shows a repository push on August 24, 2026. The release work corrected offset timestamps, negative duration output, pre-epoch rounding, zone-ID parsing, and type declarations. GitHub's combined counter lists 10 open issues and pull requests across the monorepo. The repository is active, and its companion timezone data reached the 2026a IANA release earlier this year.
Ecosystem4/5npm recorded 5,054,521 downloads for the latest completed week and GitHub reports 1,663 stars. The package family adds IANA data, locale registration, extra temporal types, and examples across Node and browsers. Bundled declarations and working CommonJS and ESM loading help mixed codebases. The strongest interoperability is conceptual, especially for Java teams; JavaScript projects centered on native Date or Temporal use a different set of types and plugins.

Use it if

  • Your database must keep a birthday, billing day, local appointment, and timestamp as different domain values.
  • Developers already know Java's `java.time` API and want the same mental model in JavaScript or TypeScript.
  • Calendar calculations need immutable `Period`, adjusters, explicit clocks, or nanosecond-capable `Instant` values.
  • ISO parsing should reject impossible dates instead of relying on `Date` normalization and machine-local defaults.
Skip it if

Setup reality

Our fresh install of @js-joda/core 6.1.0 took 1.4 seconds and put one package on disk using 8 MB. Its unpacked contents total 7800 KB, with zero direct dependencies and zero peers. npm audit returned zero known vulnerabilities. The CommonJS package has no exports map; require() and ESM import worked in Node 22. TypeScript declarations are bundled.

The first setup decision is the value type, not a config file. LocalDateTime has no offset and cannot identify a unique instant during a daylight-saving overlap. Instant is absolute but has no human calendar context. Attach an explicit ZoneId or ZoneOffset at the boundary where local input becomes a timestamp. Avoid letting the server's system zone decide business data unless that is the written rule.

Core carries fixed offsets and a system zone, while IANA rules come from @js-joda/timezone. Import that package for its registration side effect before resolving named zones. Locale formatting follows a similar split: import Locale from @js-joda/locale, then import a locale data package to register names. Version 6 changed that locale import arrangement and removed IE11. No credentials or native compilation are involved, but deployed bundles must include every registration import.

All arithmetic returns another value. plusMonths can adjust an invalid target day to the last valid day, while Duration counts time and Period counts calendar units. Parsing and invalid-field errors throw js-joda exceptions. Our browser check reached 193 KB minified and 40.3 KB gzipped for import *, so browser code should import only the classes it uses and confirm the actual production chunk. Native Date conversions also discard Instant precision below 1 millisecond.

Patterns

Keep a date without time or zone parse-calendar-date

import { LocalDate } from '@js-joda/core'

const dueDate = LocalDate.parse('2026-09-30')
console.log(dueDate.year(), dueDate.monthValue(), dueDate.dayOfMonth())
console.log(dueDate.toString())

`LocalDate` stores 2026-09-30 without inventing midnight or a machine time zone.

Construct a checked calendar value reject-invalid-date

import { LocalDate } from '@js-joda/core'

const releaseDate = LocalDate.of(2026, 8, 25)

`LocalDate.of` throws `DateTimeException` for an impossible combination such as month 2 and day 30.

Move a billing date by calendar months add-calendar-months

const opened = LocalDate.parse('2026-01-31')
const nextCycle = opened.plusMonths(1)

console.log(opened.toString())
console.log(nextCycle.toString())

The original value stays 2026-01-31; `plusMonths(1)` resolves to the last valid day of February.

Compare two LocalDate values compare-dates

const today = LocalDate.parse('2026-08-25')
const cutoff = LocalDate.parse('2026-09-01')

if (today.isBefore(cutoff)) console.log('accepting entries')

Use `isBefore`, `isAfter`, `isEqual`, or `compareTo` instead of JavaScript object comparison.

Choose calendar units or total days measure-calendar-gap

import { ChronoUnit, LocalDate, Period } from '@js-joda/core'

const start = LocalDate.parse('2026-01-31')
const end = LocalDate.parse('2026-03-02')
const calendarGap = Period.between(start, end)
const totalDays = start.until(end, ChronoUnit.DAYS)

`Period` preserves year, month, and day components; `ChronoUnit.DAYS` returns one total day count.

Represent a local appointment parse-wall-time

import { LocalDateTime } from '@js-joda/core'

const appointment = LocalDateTime.parse('2026-10-25T09:30:00')
const reminder = appointment.minusMinutes(20)

A `LocalDateTime` has no offset, so this 09:30 value is not an absolute timestamp until a zone is attached.

Resolve an offset timestamp to an Instant parse-offset-instant

import { Instant } from '@js-joda/core'

const event = Instant.parse('2026-08-25T14:00:00+02:00')
console.log(event.toString())

Core 6.1.0 fixed offset acceptance in `Instant.parse`; the value is resolved onto the UTC timeline.

Register and use IANA zone rules use-named-zone

import '@js-joda/timezone'
import { LocalDateTime, ZoneId } from '@js-joda/core'

const meeting = LocalDateTime.parse('2026-11-02T09:00')
  .atZone(ZoneId.of('Europe/Berlin'))

Install and import `@js-joda/timezone` before calling `ZoneId.of` with an IANA name; core alone does not carry that database.

Use an explicit numeric pattern format-numeric-date

import { DateTimeFormatter, LocalDate } from '@js-joda/core'

const format = DateTimeFormatter.ofPattern('dd/MM/uuuu')
const date = LocalDate.parse('25/08/2026', format)
console.log(date.format(format))

Core can format numeric ISO fields. Localized names need `@js-joda/locale` and registered locale data.

Apply a calendar adjuster find-month-end

import { LocalDate, TemporalAdjusters } from '@js-joda/core'

const anyDay = LocalDate.parse('2028-02-10')
const monthEnd = anyDay.with(TemporalAdjusters.lastDayOfMonth())
console.log(monthEnd.toString())

The leap-year result is 2028-02-29, and `anyDay` remains unchanged because the values are immutable.

Make now deterministic in a test freeze-clock

import { Clock, Instant, LocalDate, ZoneOffset } from '@js-joda/core'

const clock = Clock.fixed(
  Instant.parse('2026-08-25T23:45:00Z'),
  ZoneOffset.UTC
)
const today = LocalDate.now(clock)

Passing a fixed `Clock` controls both the instant and zone without patching global timers.

Cross the native Date boundary convert-native-date

import { convert, nativeJs } from '@js-joda/core'

const zoned = nativeJs(new Date('2026-08-25T12:00:00Z'))
const dateAgain = convert(zoned).toDate()

Native Date stores milliseconds, so a round trip cannot preserve `Instant` precision finer than 1 millisecond.

Alternatives

PackageRegistryPick it when
luxonnpmChoose it for immutable date-times with zones and locales exposed through an API shaped more like ordinary JavaScript.
date-fnsnpmChoose individual functions around native Date when bundle selection and straightforward helpers matter most.
dayjsnpmChoose a compact chainable API when plugins can supply the exact parsing and zone features you need.
momentnpmKeep it in an established Moment codebase where migration risk exceeds the cost of its mutable legacy model.

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.