lunar-lite 0.1.0

A small Rust library for Chinese lunisolar date conversion.
Documentation

lunar-lite

CI codecov

A small, table-backed Rust library for Chinese lunisolar (农历) date conversion.

What it does

lunar-lite converts between Gregorian solar dates and Chinese lunisolar dates, including leap-month handling and traditional twelve-branch time index (时辰, shíchen) calculation.

Supported lunar years: 1850..=2150

What it does not do

See Non-goals.

Design

All conversion data is stored in a generated static table compiled into the binary — there is no runtime astronomical calculation. The table is generated ahead of time from a reference implementation and committed to the crate; at runtime the crate is a pure Rust lookup with no I/O and no allocations on the hot path.

Installation

[dependencies]
lunar-lite = "0.1"

With Serde support:

[dependencies]
lunar-lite = { version = "0.1", features = ["serde"] }

Usage

Solar → lunar

use lunar_lite::{SolarDate, solar_to_lunar};

let solar = SolarDate { year: 2023, month: 1, day: 22 };
let lunar = solar_to_lunar(solar).unwrap();
// LunarDate { year: 2023, month: 1, day: 1, is_leap_month: false }

Lunar → solar

use lunar_lite::{LunarDate, lunar_to_solar};

let lunar = LunarDate { year: 2023, month: 2, day: 1, is_leap_month: true };
let solar = lunar_to_solar(lunar).unwrap();
// SolarDate { year: 2023, month: 3, day: 22 }

Leap-month normalization

use lunar_lite::{LunarDate, normalize_lunar_date};

// 2024 has no leap month 1 — the flag is silently dropped.
let date = LunarDate { year: 2024, month: 1, day: 1, is_leap_month: true };
let normalized = normalize_lunar_date(date).unwrap();
// LunarDate { year: 2024, month: 1, day: 1, is_leap_month: false }

Time index (时辰)

use lunar_lite::time_index;

assert_eq!(time_index(0, 30).unwrap(), 0);   // early Zi  00:00–00:59
assert_eq!(time_index(1, 0).unwrap(), 1);    // Chou      01:00–02:59
assert_eq!(time_index(23, 0).unwrap(), 12);  // late Zi   23:00–23:59

Index mapping:

Index Branch Hours
0 子 (early Zi) 00:00–00:59
1 丑 Chou 01:00–02:59
2 寅 Yin 03:00–04:59
3 卯 Mao 05:00–06:59
4 辰 Chen 07:00–08:59
5 巳 Si 09:00–10:59
6 午 Wu 11:00–12:59
7 未 Wei 13:00–14:59
8 申 Shen 15:00–16:59
9 酉 You 17:00–18:59
10 戌 Xu 19:00–20:59
11 亥 Hai 21:00–22:59
12 子 (late Zi) 23:00–23:59

Zi hour is split: the early half (0) begins the current day, the late half (12) closes it.

Leap months

The Chinese lunisolar calendar inserts an intercalary (leap) month roughly every three years. LunarDate carries an is_leap_month: bool field to distinguish the leap copy of a month from the regular one.

normalize_lunar_date is the safe entry point for externally-supplied dates:

  • If is_leap_month = true and the year actually has a leap month at that position, the date is kept as-is.
  • If is_leap_month = true but the year has no leap month at that position, the flag is cleared and the date is treated as the regular month.
  • After normalization the actual day count is validated; an out-of-range day returns LunarError::InvalidLunarDate.

lunar_to_solar calls normalize_lunar_date internally, so passing a fake leap flag is safe.

Error handling

All fallible functions return Result<_, LunarError>:

Variant Meaning
LunarError::InvalidSolarDate Solar date is not a valid calendar date
LunarError::InvalidLunarDate Lunar date is structurally invalid or day exceeds month length
LunarError::YearOutOfRange Year is outside 1850..=2150
LunarError::InvalidTime Hour > 23 or minute > 59

Reference data generation

The static table in src/generated/year_info.rs is produced by:

tools/lunar-lite-reference/scripts/dump-year-info.mjs

This Node.js script uses the lunar-typescript library as its reference source and writes both the Rust table and a JSON fixture used by the compatibility test suite.

Runtime users do not need Node.js or lunar-typescript. The generated file is committed to the repository and regeneration is only needed if you extend the supported year range or update the reference data.

To regenerate:

cd tools/lunar-lite-reference
npm install
node scripts/dump-year-info.mjs

Compatibility with lunar-typescript

Conversion results are generated from lunar-typescript and are expected to match it for all dates in the supported range. The crate does not embed the full astronomical engine; it is a lightweight Rust consumer of pre-computed data. Results are described as "generated from the reference implementation" rather than independently verified against historical astronomical records.

Non-goals

  • BaZi (八字) engine — pillar calculation is not included.
  • Solar terms (节气) API — not yet exposed.
  • True solar time correction — time zone offsets based on longitude are not applied.
  • Zi Wei Dou Shu (紫微斗数) charting — out of scope.
  • Runtime JavaScript dependency — the crate is pure Rust at runtime.

License

MIT — see LICENSE.