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

calendaryjs-plugin-lunar

v0.3.0

Published

Lunar (lunisolar) calendar plugin for calendaryjs — solar ↔ lunar date conversion (1900–2100) and lunar recurring events using the standard astronomical lunisolar table.

Readme

calendaryjs-plugin-lunar

New to calendaryjs? Start with the core README — this plugin builds on its engine and builder.

Adds the East Asian lunisolar calendar to calendaryjs: declare events by lunar date, and convert solar ↔ lunar (1900–2100) with leap months handled. Ships two national variants — Chinese (default, the published almanac table) and Vietnamese (âm lịch, astronomical at UTC+7) — selected like an Intl/Temporal calendar id.

Install

npm i calendaryjs calendaryjs-plugin-lunar

Use it

Register the plugin, then declare lunar events with the lunar.date() selector — the engine converts them to solar dates automatically:

import { calendary } from "calendaryjs";
import { every } from "calendaryjs/builder";
import { lunar } from "calendaryjs-plugin-lunar";

const cal = calendary().use(lunar());

cal.addGroup({
  id: "lunar-holidays",
  events: [
    every("year").on(lunar.date(1, 1)).title("Lunar New Year"),
    every("year").on(lunar.date(8, 15)).title("Mid-Autumn Festival"),
  ],
});

cal.getEventsInRange("2025-01-01", "2025-12-31"); // → solar dates

Prefer raw config? The selector compiles to a plain lunar event:

{ type: "lunar", id: "tet", lunarMonth: 1, lunarDay: 1, title: "Lunar New Year" }

Calendar variants — Chinese vs Vietnamese

The Chinese calendar is computed at 120°E (UTC+8), the Vietnamese one at 105°E (UTC+7). They are distinct national standards, not timezones: whenever a new moon falls between 23:00 and 24:00 Vietnam time, the Vietnamese month starts a day earlier — occasionally moving Tết (1968, 1985, 2007, 2030) or a leap-month boundary (1984, 1985, 1987, 1995, 2031).

The default is "chinese" (the standard almanac table). Opt into Vietnamese âm lịch per plugin instance — it applies to events and day enrichment alike:

const cal = calendary().use(lunar({ calendar: "vietnamese" }));

// 2019-03-17 is 12/2 âm lịch (Vietnam) but 11/2 in the Chinese calendar:
solarToLunar({ year: 2019, month: 3, day: 17 }, { calendar: "vietnamese" });
// → { year: 2019, month: 2, day: 12, isLeapMonth: false }
solarToLunar({ year: 2019, month: 3, day: 17 });
// → { year: 2019, month: 2, day: 11, isLeapMonth: false }

The Vietnamese variant uses the Ho Ngoc Duc astronomical algorithm — the de-facto Vietnamese standard. The Chinese variant keeps the published table (deliberately not "the same algorithm at UTC+8", which would drift from the official Chinese calendar on 155 days in 1900–2100).

Sharing events? Pin the calendar on the event. The instance option is your app's default; a stored/shared event can carry its own calendar field, which wins over the instance default — so a .cdy collection is self-describing and renders the same dates for every consumer:

// Builder: pin per event
every("year")
  .on(lunar.date(2, 12, { calendar: "vietnamese" }))
  .title("Giỗ bà");
// .cdy — self-describing, regardless of how the consumer registers lunar()
{
  "plugins": ["calendaryjs-plugin-lunar"],
  "events": [
    {
      "type": "lunar",
      "id": "gio-ba",
      "title": "Giỗ bà",
      "lunarMonth": 2,
      "lunarDay": 12,
      "calendar": "vietnamese"
    }
  ]
}

Convert dates

Every conversion takes an optional { calendar } as the last argument (default "chinese"):

import { lunarToSolar, solarToLunar } from "calendaryjs-plugin-lunar";

lunarToSolar({ year: 2025, month: 1, day: 1, isLeapMonth: false });
// → { year: 2025, month: 1, day: 29 }

solarToLunar({ year: 2025, month: 1, day: 29 }, { calendar: "vietnamese" });
// → { year: 2025, month: 1, day: 1, isLeapMonth: false }

Leap months

In a leap year a lunar month repeats (2023 has a leap 2nd month — two “month 2”s). Events resolve to the regular month by default; pass { leap: true } (raw: isLeapMonth: true) to prefer the leap month. In years without it the event falls back to the regular month, matching Temporal's monthCode.

every("year")
  .on(lunar.date(2, 15, { leap: true }))
  .title("Leap 2/15");

Working from a known solar date (a death date for a giỗ, a birthday)? Let lunar.fromSolar compute the flag instead of guessing it:

lunar.fromSolar(new Date(2023, 3, 5)); // Apr 5, 2023
// → { lunarMonth: 2, lunarDay: 15, isLeapMonth: true }

Reference

Event type lunarlunarMonth (1–12) · lunarDay (1–30) · isLeapMonth? (prefer the leap month; see above) · calendar? (pin the variant per event — wins over the instance default), plus every standard event property.

Plugin optionsenrichDays?: boolean · calendar?: "chinese" | "vietnamese" (default "chinese").

Exportslunar() · lunar.date(month, day, { leap?, calendar? }) · lunar.fromSolar(date, { calendar? }) · lunarToSolar · solarToLunar · isValidLunarDate (each takes an optional { calendar }).

License

MIT. This plugin requires the calendaryjs core, which is licensed PolyForm Noncommercial — so commercial use of the combined work requires a commercial license for the core. See Commercial licensing.