calendaryjs-plugin-lunarv0.3.0
Solar ↔ lunar date conversion (1900–2100) plus lunar recurring events. Ships two
national variants of the East Asian lunisolar calendar — Chinese (default)
and Vietnamese (âm lịch) — selected like an Intl/Temporal calendar id.
Install
Section titled “Install”npm i calendaryjs calendaryjs-plugin-lunarThe plugin adds a lunar.date(month, day) builder selector:
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"), ],});lunar.date(month, day) takes lunarMonth (1–12) and lunarDay (1–30) and compiles
to a plain lunar event ({ type: "lunar", lunarMonth, lunarDay }) — the storage form.
Pass { leap: true } to target a leap month.
Calendar variants — Chinese vs Vietnamese
Section titled “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". Opt into Vietnamese âm lịch per plugin instance —
it applies to lunar 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 }The Vietnamese variant uses the Ho Ngoc Duc astronomical algorithm (the de-facto Vietnamese standard); the Chinese variant keeps the published almanac table — deliberately not “one algorithm at UTC+8”, which would drift from the official Chinese calendar on 155 days in 1900–2100.
Date conversion
Section titled “Date conversion”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
Section titled “Leap months”In a leap year a lunar month repeats — 2023 has a leap 2nd month, so 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");Have a known solar date (a death date for a giỗ, a birthday)? lunar.fromSolar
computes the flag for you:
import { lunar } from "calendaryjs-plugin-lunar";
lunar.fromSolar(new Date(2023, 3, 5)); // Apr 5, 2023// → { lunarMonth: 2, lunarDay: 15, isLeapMonth: true }
// Pin the variant when your source date is Vietnamese:lunar.fromSolar(deathDate, { calendar: "vietnamese" });Day enrichment
Section titled “Day enrichment”Opt in with enrichDays: true and every day from getDay() / getDays()
carries a lunar object (computed with the instance’s calendar variant):
const cal = calendary().use(lunar({ enrichDays: true, calendar: "vietnamese" }));
cal.getDay("2025-01-29").lunar;// → { year: 2025, month: 1, day: 1, isLeapMonth: false } — mùng 1 TếtReference
Section titled “Reference”Plugin options — enrichDays?: boolean (default false) ·
calendar?: "chinese" | "vietnamese" (default "chinese" — the instance-wide
default; a per-event calendar field wins over it).
Event type lunar
| Field | Type | Meaning |
|---|---|---|
lunarMonth |
1–12 |
Lunar month |
lunarDay |
1–30 |
Lunar day |
isLeapMonth |
boolean? |
Prefer the leap month (falls back when the year has none) |
calendar |
"chinese" | "vietnamese"? |
Pin the variant per event — wins over the instance default |
…plus every standard event property.
Exports — lunar() · lunar.date(month, day, { leap?, calendar? }) ·
lunar.fromSolar(date, { calendar? }) · solarToLunar · lunarToSolar ·
isValidLunarDate (each conversion takes an optional { calendar }).