npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

japan-calendar

v0.1.2

Published

Zero-dependency TypeScript library for Japanese holidays, business days, and wareki (era) date conversion

Readme

japan-calendar

npm CI License: MIT Bundled data: CC BY 4.0 Node.js 20+ dependencies: none

English | 日本語

A zero-dependency TypeScript library for Japanese holidays, business-day arithmetic, and wareki (Japanese era) date conversion — shipped as an npm package, and usable as an HTTP API you deploy yourself on Cloudflare Workers (there is no hosted endpoint; see Cloudflare Workers).

Most free holiday libraries only answer "is this a holiday?". This one also treats business-day arithmetic as a first-class feature (isBusinessDay / addBusinessDays / businessDaysBetween), and it's the only one that flags Vernal/Autumnal Equinox Day as confirmed: true or false — those two holidays aren't legally fixed until the Official Gazette publishes the following year's "Calendrical Data" each February, so any date beyond that is inherently a forecast, not a fact.

Install

npm install japan-calendar
import { isHoliday, addBusinessDays } from 'japan-calendar';

isHoliday('2026-05-05');
// → { date: { year: 2026, month: 5, day: 5 }, name: 'こどもの日',
//     category: 'statutory', confirmed: true }

isHoliday('2026-05-07');
// → null

addBusinessDays('2026-05-01', 3);
// → { year: 2026, month: 5, day: 11 }
//   Three business days after Friday 5/1 lands on Monday 5/11: Golden Week
//   eats 5/3–5/6 (including the substitute holiday for 5/3 falling on a Sunday).

Node.js 20+. No dependencies, no runtime data fetching, and the ESM bundle runs unchanged in browsers and on Cloudflare Workers.

Maintenance posture. The version is 0.x, so the API may still change between minor releases; anything that changes is recorded in CHANGELOG.md. This is a personal project maintained on a best-effort basis. Read the next section before relying on it for anything that matters.

Support scope and disclaimer

What this library covers, and what it deliberately does not:

  • Supported years. Holiday and business-day functions cover 1949-2099; anything outside raises OutOfRangeError. Wareki conversion covers Meiji 6-1-1 (1873-01-01) onward.
  • Equinox dates beyond equinoxConfirmedThrough are forecasts, not facts. They are returned with confirmed: false. Don't treat them as settled dates — check the flag.
  • Wareki conversions of future dates assume the current era continues. An era's end is not knowable in advance — the 2019 Heisei → Reiwa change is the standing precedent — so converting a future date to or from wareki is a forecast in the same sense as an unconfirmed equinox, not a settled fact. There is no upper bound on the supported range; the assumption just grows with the distance.
  • 1949-1954 cannot be independently verified. Those six years fall outside the official data, so they rely on the approximation formula's extrapolation. They are pinned by tests derived from the text of the 1948 Public Holiday Law, which is the best available check, not a confirmation against published dates.
  • Only two calendars are provided, 'national' and 'bank'. Company- or industry-specific closure days are out of scope.
  • No warranty. The software is provided "AS IS" under the MIT License. Holiday and business-day results are not guaranteed to be fit for legal, financial, or regulatory decisions; verify against the Cabinet Office's own publication where correctness is load-bearing.

Design principles

  • No data fetching at runtime. The Cabinet Office's syukujitsu.csv is normalized once and baked into the repo as a static TypeScript module.
  • A rule engine does the computing; the official data is the ground truth used to verify it. The official CSV only covers 1955-2027, so shipping data alone would make the library falsely claim "not a holiday" for 2028 onward. The Public Holiday Law and its amendments are implemented in code, and every date within the covered range is checked against the official data.
  • All date arithmetic is pinned to JST. Date's local-timezone APIs are never used; everything is computed from civil dates and an integer day count instead.
  • Zero runtime dependencies. Runs on Cloudflare Workers.

Confirmed vs. tentative

Vernal Equinox Day and Autumnal Equinox Day are only officially finalized when the National Astronomical Observatory of Japan's "Calendrical Data" is published in the Official Gazette each February, for the following year. So the latest year covered by the official data is exactly the confirmed/tentative boundary.

That boundary isn't a hand-maintained constant — a generator script computes it from the real data:

equinoxConfirmedThrough = the latest year that includes both "Vernal Equinox Day" and "Autumnal Equinox Day"

Requiring both to be present avoids mistakenly treating a year that was only partially appended mid-year as finalized. Equinox dates up to and including this year are confirmed: true; beyond it, confirmed: false.

API

import {
  isHoliday,
  holidaysForYear,
  isBusinessDay,
  addBusinessDays,
  businessDaysBetween,
  toWareki,
  formatWareki,
  fromWareki,
} from 'japan-calendar';

isHoliday('2026-09-22');
// => { date: {year:2026,month:9,day:22}, name: '国民の休日', category: 'bridge', confirmed: true }

holidaysForYear(2026).length;
// => 18 (every holiday in the year, in date order, substitute and bridge
//        holidays included; statutoryHolidaysForYear omits those two kinds)

holidaysForYear(2026)[0];
// => { date: {year:2026,month:1,day:1}, name: '元日', category: 'statutory', confirmed: true }

isBusinessDay('2026-12-31', 'bank');
// => false (true for 'national'; the year-end/New Year bank holiday window only applies to 'bank')

addBusinessDays('2026-12-30', 1, 'bank');
// => { year: 2027, month: 1, day: 4 } (skips 12/31 and 1/1-1/3)

businessDaysBetween('2026-08-03', '2026-08-08');
// => 5 (half-open interval [from, to); negative if to < from, 0 if from === to)

formatWareki(toWareki('2019-05-01'));
// => '令和元年5月1日'

fromWareki('令和', 1, 5, 1);
// => { year: 2019, month: 5, day: 1 }

isHoliday / isBusinessDay / addBusinessDays / businessDaysBetween support years 1949-2099 (OutOfRangeError outside that). Wareki conversion supports Meiji 6-1-1 (1873-01-01) onward (UnsupportedWarekiRangeError outside that range; MeijiReformError for the 29 days lost to the 1873 calendar reform, Meiji 5, month 12, days 3-31).

CalendarKind is either 'national' (only holidays are non-business days) or 'bank' (holidays plus 12/31, 1/2, and 1/3; 1/1 is already non-business on both calendars as New Year's Day). Weekends are non-business days on both calendars. addBusinessDays(date, 0) returns date unchanged even if it isn't itself a business day.

Accepted date input

Every function that takes a date accepts three forms:

isHoliday('2026-09-22');                      // YYYY-MM-DD — a calendar date, used as-is
isHoliday({ year: 2026, month: 9, day: 22 }); // a plain object — same, no timezone involved
isHoliday(new Date());                        // an instant — reduced to the date it is *in JST*
isHoliday('2026-09-22T00:00:00Z');            // an instant too (offset required — see below)

A date-time string must carry an explicit UTC offset (Z, +09:00, or +0900). Anything else is rejected with InvalidDateInputError:

isHoliday('2026-09-22T00:00:00');  // ✗ InvalidDateInputError — no offset
isHoliday('2026/09/22');           // ✗ InvalidDateInputError — not YYYY-MM-DD
isHoliday('2026-9-22');            // ✗ InvalidDateInputError — not zero-padded

This is deliberate. Date.parse resolves an offset-less date-time using the host machine's timezone, so '2026-09-22T00:00:00' would mean a different day depending on where the code runs — and for a holiday library a different day can mean a different answer. Rather than guess, the library refuses the ambiguous input: pass a plain YYYY-MM-DD string if you mean a calendar date, or add an offset if you mean an instant.

Other arguments are checked too

The same rule applies to every argument, not just dates. A wrong argument raises InvalidArgumentError rather than producing a plausible answer:

isBusinessDay('2026-12-31', 'Bank');   // ✗ InvalidArgumentError — only 'national' | 'bank'
addBusinessDays('2026-08-03', NaN);    // ✗ InvalidArgumentError — days must be a safe integer
addBusinessDays('2026-08-03', 1.5);    // ✗ InvalidArgumentError
holidaysForYear(2026.5);               // ✗ InvalidArgumentError — year must be an integer
formatWareki(w, 'JA');                 // ✗ InvalidArgumentError — unknown format

This matters most from plain JavaScript, and from TypeScript whenever the value arrives as a string from JSON, a query parameter, or a form field — the type annotation isn't there at runtime. A capitalization slip like 'Bank' is the dangerous case: it isn't the bank calendar, and answering as if it were the national one would be a wrong answer delivered with confidence.

Errors

Every exception extends JapanCalendarError, so one catch covers them all.

| Error | Raised when | |---|---| | InvalidDateInputError | A date argument can't be interpreted, or names a day that doesn't exist | | InvalidArgumentError | A non-date argument has the wrong type or isn't an accepted value | | OutOfRangeError | A date is outside 1949–2099 | | UnsupportedWarekiRangeError | A wareki conversion before Meiji 6-1-1 (1873-01-01) | | MeijiReformError | Meiji 5, month 12, days 3–31 — the 29 days the 1873 reform removed | | InvalidWarekiDateError | A wareki date outside its own era's span, e.g. Shōwa 64-1-8 |

isHoliday returns null rather than throwing when the date is simply not a holiday; it throws only when the input itself is unusable.

Error messages quote the offending value, capped at 200 characters — the Worker copies them into its 400 bodies, and a message that reflects a caller's entire input turns the API into an echo service.

About the equinox approximation formula

The approximation formula in src/rules/equinox.ts has been verified against the Cabinet Office's official data (1955-2027, 146 vernal/autumnal dates in total) with zero discrepancies. Years 1949-1954 (outside the official data's coverage) have no way to be verified and rely purely on this formula's extrapolation.

Cloudflare Workers

There is no hosted instance of this API. worker/index.ts is a thin HTTP layer over the library that you deploy to your own Cloudflare account; the URLs below are relative to wherever you deploy it. Zero runtime dependencies, same as the library itself.

GET /v1/meta
GET /v1/holidays/:year                 e.g. /v1/holidays/2026
GET /v1/holidays/:date                 e.g. /v1/holidays/2026-09-22
GET /v1/business-days/add?date=&days=&calendar=
GET /v1/business-days/between?from=&to=&calendar=
GET /v1/wareki?date=
GET /v1/wareki/reverse?era=&year=&month=&day=

Responses where every holiday is finalized (confirmed: true) get a long cache lifetime; responses with a tentative holiday get a short one. Wareki responses get the long lifetime only for dates that have already passed — a future date's conversion assumes the current era continues, so it gets the short one. Errors are the library's own exceptions, passed straight through as { error: { type, message } } with a 4xx status.

Deploying it is a maintainer/operator task — see For maintainers for the commands.

For maintainers

The rest of this file is about working on the library rather than using it. Contributors should start from CONTRIBUTING.md.

Working on this repository needs Node.js 22+, even though the published package supports Node.js 20+. The scripts/ entry points below are run as .ts files through Node's type stripping, and wrangler also requires 22. The 20+ promise covers the published artifact, and every release verifies it by installing the packed tarball on a real Node 20 runtime.

Updating the official data

The Cabinet Office's site is blocked by the dev environment's egress policy, so the CSV is fetched via GitHub Actions instead.

# Local (requires network access)
node scripts/fetch-syukujitsu.ts

# View a summary of the already-baked-in data (no network needed)
node scripts/report.ts

The Update holiday data GitHub Actions workflow runs monthly (the 1st at 21:00 UTC, which is the 2nd at 06:00 JST) and pushes any diff to the chore/update-holiday-data branch. It can also be run manually via workflow_dispatch.

Running and deploying the Worker

npm run worker:dev      # run locally (wrangler dev)
npm run worker:deploy   # deploy to your own Cloudflare account

Test suite

npm test               # run all tests
npm run test:tz        # run all tests under 4 timezones and confirm identical results
npm run typecheck      # type-check all 3 projects: the library, scripts, and the Worker

What each test file covers, and which of them you need to re-run after touching a given area, is in CONTRIBUTING.md.

Build & package layout

npm run build           # emits dist/esm (ESM + type declarations) and dist/cjs (CommonJS)

package.json's exports field serves ESM, CJS, and type declarations separately. dist/cjs/package.json ({"type":"commonjs"}) is written during the build so the CJS output doesn't clash with the repo root's "type": "module".

Contributing

See CONTRIBUTING.md.

License

The software is MIT licensed. See NOTICE for the bundled data's source and terms.