lunar-rs

lunar-rs 设计方案

基于参考实现 lunar-javascriptlunar-go,使用 Rust 实现的高性能、高水平、高可用的中国农历 / 阳历 / 佛历 / 道历 / 八字 / 九星日历库。

本文档为“先剖析、再设计、后编码”工作流的设计产出,存放于 docs/ 目录(已加入 .gitignore,禁止提交)。


0. 文档导航

章节 内容
1. 背景与目标 项目目标、非目标、成功标准
2. 参考仓库剖析 lunar-javascript / lunar-go 的架构、模块、数据流对比
3. 核心算法原理 寿星天文历、干支推导、闰月、节气、八字、九星
4. Rust 项目结构设计 crate 布局、模块划分、依赖
5. 模块级 API 设计 每个类型的字段、构造、关键方法、Rust 化映射
6. 性能策略 Arc 缓存、Cow、常量表、零拷贝、数值算法
7. 错误处理策略 Result vs panic、错误类型分层
8. 国际化 (i18n) 策略 占位符重写表的 Rust 实现
9. 测试策略 用例来源、等价对照、模糊测试、性能基准
10. 实施路线图 分阶段交付计划与里程碑
11. 风险与决策记录 关键取舍

1. 背景与目标

1.1 项目定位

lunar-rslunar 系列(作者 6tail)的 Rust 实现。lunar 是一款无第三方依赖的中国历法工具,覆盖:

1.2 设计目标

目标 含义
高性能 (Performance) 单次 Solar → Lunar 转换 + 完整 GanZhi 计算在微秒级;常量查表零分配;天文计算结果按年缓存复用。
高水平 (Quality) 与 Go/JS 参考实现逐位对齐的输出(以参考仓库测试用例为黄金标准);#![deny(unsafe_code)]clippy::pedantic 通过;100% safe Rust。
高可用 (Usability) 符合 Rust 习惯的 API(from_ymd/next/迭代器/Display);Result 化的错误处理而非 panic;完善文档与示例;可选 serde 支持。

1.3 非目标

1.4 成功标准

  1. 移植 lunar-go 的全部 _test.go 为 Rust 集成测试,全部通过
  2. 对 1900–2100 年(含闰月、1582 历法改革、跨年边界)每日随机采样,与 Go 版输出字符串完全一致。
  3. cargo bench 下,单次 Solar::from_ymd(2020,5,1).lunar() 全量字段计算 < 5µs(命中缓存时 < 500ns)。

2. 参考仓库剖析

2.1 lunar-javascript(权威实现)

2.2 lunar-go(移植蓝本)

lunar-go/
├── ShouXingUtil/   # 寿星天文历:VSOP87/ELP 级数、ΔT、章动、节气/合朔求解器 (657 行)
├── SolarUtil/      # 阳历纯函数:闰年、月日数、儒略日、星期、节日 (294 行)
├── LunarUtil/      # 农历核心常量 + 查找表 + 纯函数 (1004 行)
├── FotoUtil/       # 佛历数据 (295 行)
├── TaoUtil/        # 道历数据 (140 行)
├── HolidayUtil/    # 法定节假日(可运行时覆盖) (198+75 行)
└── calendar/       # 领域对象:Solar/Lunar/.../Yun (577+1963+... 行)

2.3 两版对比与选型

维度 lunar-javascript lunar-go lunar-rs 取舍
模块组织 单文件 21 模块 多包清晰拆分 采用 Go 的模块拆分作为 crate 内 mod 布局
i18n 有(占位符重写) 移植 JS 的 i18n,作为可选 feature(默认中文,零开销)
运 (Yun) 类 内联在 EightChar 真类 Yun/DaYun/... 采用 Go 的真类,API 更清晰、更易测试
节气/伏/九 Lunar 方法 / 字面量对象 Lunar 方法 + JieQi/Fu/ShuJiu 结构体 采用 Go 的结构体,类型更安全
错误处理 抛异常 panic Result<T, LunarError>(Rust 习惯,不 panic)
集合 Array / 链表 container/list Vec<T> / 迭代器
缓存 单条 CACHE_YEAR 单条 CACHE_YEAR + Mutex OnceLock / RwLock<HashMap<year, Arc<LunarYear>>>(可配容量)

2.4 数据流(Solar ⇄ Lunar 的核心闭环)

                    ┌─────────────────────────────────────────────────────┐
                    │  LunarYear::compute()  (每年仅算一次,结果缓存)       │
                    │                                                       │
   year ──────────▶ │  1. 估冬至儒略日 jd                                    │
                    │  2. CalcQi × 26 → 24 节气儒略日(每 15.2184 天一格)    │
                    │  3. CalcShuo × 16 → 合朔儒略日(每 29.5306 天)        │
                    │  4. hs[i+1]-hs[i] → 每月天数(29/30)                   │
                    │  5. 无中气置闰 / LEAP_11 / LEAP_12 → 定闰月             │
                    │  6. 产出 15 个 LunarMonth(跨年边界各延伸一月)          │
                    │  7. 产出 31 个节气儒略日(对齐 JIE_QI_IN_USE)          │
                    └─────────────────────────────────────────────────────┘
                                            │
        ┌───────────────────────────────────┼───────────────────────────────────┐
        ▼                                   ▼                                   ▼
  Solar → Lunar                        Lunar → Solar                      JieQi / GanZhi
  遍历 15 月,找到           取当月 firstJulianDay + (day-1)            compute():
  firstJulianDay ≤ JD <                  的正午 Solar,                      jieqi → year → month
  firstJulianDay + dayCount 的月           补回原始时分秒                       → day → time → week

3. 核心算法原理

本节是为编码实现提供“为什么这样算”的理论底座。所有公式与 Go 参考实现逐行对齐。

3.1 寿星天文历(ShouXing / VSOP87+ELP)

这是整个库的精度根基。它由许剑伟先生提出,是中文历算圈广泛使用的高精度天文算法。

核心组件

  1. 太阳黄经级数 XL0(VSOP87 截断版):形如 A·cos(φ + ω·t) 的数千项三角级数,按精度分 6 档(eLonn 参数控制取项数)。
  2. 月亮黄经级数 XL1(ELP 类似):4×2652 的二维数组,分 4 档。
  3. 章动 NUT_B:10 项 IAU 风格的黄经章动模型。
  4. ΔT 表 DT_AT:从公元前 4000 到公元 2050 的分段三次多项式拟合(TT−UT),近期项用 skyfield 的 DE440s 预测数据拟合。
  5. 根求解器:牛顿法 + 二分回退。
    • saLonT(w) / saLonT2(w):解“太阳视黄经 = w”。节气即太阳黄经走到 k·15°
    • msaLonT(w) / msaLonT2(w):解“日月黄经差 = w”。合朔(初一)即差为 0。
  6. 历史长历查表 QI_KB / SHUO_KB:经典长历的周期+余数表,让中段历史年份无需展开级数即可秒算。
  7. 修正位串 QB / SB:用 decode() 函数把紧缩字母串展开为 0/1/2 位串,对低精度区间的节气/合朔做 ±1 天修正。

三段精度策略CalcQi / CalcShuo):

if jd < f1 或 jd >= f3:   用 qiHigh(全级数,高精度)        ← 远古代 / 远未来
elif jd < f2:             用 QI_KB 长历查表(历史区间)       ← 公元 ~1600 之前
elif jd < f3:             用 qiLow(低精度级数)+ QB 修正     ← 近现代

节气精化 QiAccurate2(jd):以 π/12(15°)为步长,找到最近的节气锚点,再做一次 SaLonT 高精度求解。

⚠️ 移植要点XL0/XL1/NUT_B/DT_AT/QI_KB/SHUO_KB/QB/SB 全部数据必须逐字符原样复制;浮点运算顺序必须与 Go 一致(否则末位差异会导致节气分钟级偏移)。Rust 中用 f64%rem_euclid 对齐 Go 的负数取模行为需逐处核对。

3.2 干支(GanZhi)推导

六十甲子 = 10 天干 × 12 地支的最小公倍循环。索引体系(注意 1-based vs 0-based 混用):

维度 天干索引 地支索引 锚点 / 公式
年柱 (year − 4) mod 10 (year − 4) mod 12 以公元 4 年 = 甲子为锚;按正月初一 / 立春日 / 立春时刻三种 sect 在立春前后 ±1 修正
月柱 ((yearGanIdx mod 5 + 1)·2 + monthOffset) mod 10 (monthOffset + 2) mod 12 BASE_MONTH_ZHI_INDEX = 2(寅为正月);monthOffset 由沿 JIE_QI_IN_USE 的“节”序列定位(起始 index = −3
日柱 (⌊JD_noon⌋ − 11) mod 10 (⌊JD_noon⌋ − 11) mod 12 以正午为日界;“晚子时”(23:00–23:59)Exact 变体 +1 进入次日
时柱 (dayGanExact mod 5 · 2 + timeZhi) mod 10 GetTimeZhiIndex("HH:MM") “五鼠遁时”规则

三种年起算约定 sect1 = 正月初一、2 = 立春当日(默认)、3 = 立春交接时刻。这是命理 / 民俗分歧的根源,库需全部支持并暴露切换。

3.3 闰月(Leap Month)

农历闰月由 “无中气置闰” 规则决定:一个朔望月(初一到下一个初一)内若不含中气(即第 2i 个节气),则为闰月。

leapIndex 默认 = 16(无闰)
if year ∈ LEAP_11:        leapIndex = 13      # 历史/未来兜底
elif year ∈ LEAP_12:      leapIndex = 14
elif hs[13] ≤ jq[24]:                          # 冬至前有 13 个朔 → 必须插闰
    找首个 i 使 hs[i+1] ≤ jq[2·i] → leapIndex = i

闰月用负数月份表示(month < 0IsLeap),全代码库统一;查表时一律取 |month|

3.4 节气表(JieQi)

JIE_QI_IN_USE 是长度 31 的查找表,顺序是承载逻辑的

["DA_XUE","冬至","小寒",...,"大雪","DONG_ZHI","XIAO_HAN","DA_HAN","LI_CHUN","YU_SHUI","JING_ZHE"]

3.5 八字(BaZi / EightChar)

四柱 = 年柱 + 月柱 + 日柱 + 时柱,每柱 = 天干 + 地支。EightCharLunar 之上提供:

3.6 运程(Yun / 大运 / 流年 / 流月 / 小运)

大运顺逆阳男 / 阴女 → 顺行;阴男 / 阳女 → 逆行yang = yearGanIdxExact % 2 == 0man = gender == 1)。

起运岁(出生到最近一个“节”的时间距离,换算成岁):

大运干支:从月柱的六十甲子索引,按顺 / 逆每 10 年进一格;LiuNian(流年)每年一格、LiuYue(流月)每月一格、XiaoYun(小运)从时柱起每年一格。

3.7 九星(NineStar / 九宫飞星)


4. Rust 项目结构设计

4.1 Crate 形态

单 crate 库 lunar-rs[lib]),edition 2024。未来若拆分(如 lunar-rs-core + lunar-rs-chrono)再演进。

4.2 目录布局

lunar-rs/
├── Cargo.toml
├── src/
│   ├── lib.rs                     # 顶层 mod 声明 + 重导出 + crate 文档
│   ├── error.rs                   # LunarError 错误类型
│   │
│   ├── shou_xing/                 # 寿星天文历(精度根基,无业务依赖)
│   │   ├── mod.rs
│   │   ├── data.rs                # XL0/XL1/NUT_B/DT_AT/QI_KB/SHUO_KB/QB/SB
│   │   └── engine.rs              # calc_qi / calc_shuo / qi_accurate2 / sa_lon_t ...
│   │
│   ├── solar_util.rs              # 阳历纯函数 + 常量
│   ├── lunar_util/                # 农历常量 + 查找表 + 纯函数
│   │   ├── mod.rs                 # 公开 API + 公开常量
│   │   ├── tables.rs              # GAN/ZHI/JIA_ZI/NAYIN/positions/pengzu/chong/...
│   │   └── packed.rs              # day_yi_ji / time_yi_ji / day_shen_sha 解码
│   ├── foto_util.rs               # 佛历数据
│   ├── tao_util.rs                # 道历数据
│   ├── nine_star_util.rs          # 九星查找表(i18n 占位符版)
│   ├── holiday_util.rs            # 法定节假日(可运行时覆盖,内部 RwLock)
│   │
│   ├── solar.rs                   # Solar 领域对象
│   ├── lunar.rs                   # Lunar 领域对象(核心 god-class)
│   ├── lunar_time.rs              # 时辰
│   ├── lunar_month.rs             # 农历月
│   ├── lunar_year.rs              # 农历年(天文计算 + 缓存)
│   ├── eight_char.rs              # 八字
│   ├── nine_star.rs               # 九星
│   ├── foto.rs                    # 佛历
│   ├── tao.rs                     # 道历
│   ├── jieqi.rs                   # 节气包装
│   ├── fu.rs                      # 三伏
│   ├── shu_jiu.rs                 # 数九
│   ├── solar_week.rs              # 阳历周
│   ├── solar_month.rs             # 阳历月
│   ├── solar_year.rs              # 阳历年
│   ├── solar_half_year.rs         # 阳历半年
│   ├── solar_season.rs            # 阳历季节
│   ├── holiday.rs                 # 节假日对象
│   │
│   ├── yun/                       # 运程
│   │   ├── mod.rs                 # Yun
│   │   ├── da_yun.rs              # DaYun
│   │   ├── liu_nian.rs            # LiuNian
│   │   ├── liu_yue.rs             # LiuYue
│   │   └── xiao_yun.rs            # XiaoYun
│   │
│   └── i18n.rs                    # 国际化(可选 feature)
│
├── tests/                         # 集成测试(移植自 lunar-go 的 *_test.go)
│   ├── solar.rs
│   ├── lunar.rs
│   ├── jieqi.rs
│   ├── eightchar.rs
│   └── ...
└── benches/                       # 性能基准
    └── convert.rs

4.3 依赖策略

[dependencies]
# 默认零依赖,保证库轻量、可嵌入/no_std 友好

[dev-dependencies]
# 仅测试期:可引入对照实现做差分测试

[features]
default = ["i18n-zh"]
i18n       = []        # 启用 i18n 框架
i18n-zh    = ["i18n"]  # 默认中文
i18n-en    = ["i18n"]  # 英文消息
serde      = ["dep:serde", ...]  # 可选序列化

决策:移除现有 Cargo.toml 中的 chrono(仅用于 from_date,可改为接收 i64 时间戳或独立 feature)与无用的 wifiscanner。农历计算本身不需要任何时间库——Solar 用 6 个 i32 字段表示,儒略日自算。

4.4 编译期保证


5. 模块级 API 设计

命名约定:Go 的 GetXxx() → Rust 的 xxx()NewXxx()Xxx::from_*() 或关联函数;IsLeapis_leapString()/ToFullString() → 实现 Display + to_full_string()

5.1 Solar

pub struct Solar { year: i32, month: u32, day: u32, hour: u32, minute: u32, second: u32 }

impl Solar {
    // 构造(返回 Result,非法日期不 panic)
    pub fn from_ymd_hms(y, mo, d, h, mi, s) -> Result<Solar, LunarError>;
    pub fn from_ymd(y, mo, d) -> Result<Solar, LunarError>;
    pub fn from_julian_day(jd: f64) -> Solar;
    pub fn from_unix_instant(...) -> Solar;     // 可选

    // 转换
    pub fn lunar(&self) -> Lunar;
    pub fn julian_day(&self) -> f64;

    // 推移
    pub fn next_year(&self, n: i32) -> Solar;
    pub fn next_month(&self, n: i32) -> Solar;
    pub fn next_day(&self, n: i32) -> Solar;
    pub fn next(&self, n: i32, only_workday: bool) -> Solar;
    pub fn next_hour(&self, n: i32) -> Solar;
    pub fn subtract(&self, other: &Solar) -> i32;       // 天差
    pub fn subtract_minute(&self, other: &Solar) -> i32;

    // 查询
    pub fn is_leap_year(&self) -> bool;
    pub fn week(&self) -> u32;       // 0=周日
    pub fn xingzuo(&self) -> &'static str;  // 星座
    pub fn festivals(&self) -> Vec<&'static str>;
    pub fn salary_rate(&self) -> u8;        // 1/2/3 倍薪资

    // 格式
    pub fn to_ymd(&self) -> String;         // "2020-05-01"
    pub fn to_ymd_hms(&self) -> String;
}
impl Display for Solar { ... }              // to_ymd

字段类型决策:用 i32(年)/ u32(月日时分秒)而非全 i32,在类型层挡住负月日,但保留 i32 年以表达公元前。月份 / 日有效性在构造期校验并返回 Err

5.2 Lunar(核心)

pub struct Lunar {
    year: i32, month: i32 /* 负=闰 */, day: i32, hour, minute, second,
    // 预计算并缓存的干支索引(构造时一次算清)
    year_gan_index: i8, year_zhi_index: i8,
    year_gan_index_by_li_chun: i8, year_zhi_index_by_li_chun: i8,
    year_gan_index_exact: i8, year_zhi_index_exact: i8,
    month_gan_index: i8, month_zhi_index: i8,
    month_gan_index_exact: i8, month_zhi_index_exact: i8,
    day_gan_index: i8, day_zhi_index: i8,
    day_gan_index_exact: i8, day_zhi_index_exact: i8,
    day_gan_index_exact2: i8, day_zhi_index_exact2: i8,
    time_gan_index: i8, time_zhi_index: i8,
    week_index: i8,
    jie_qi: Box<[(JieQiKey, Solar); 31]>,   // 借自 LunarYear 的 Arc,或克隆
    solar: Solar,
}

关键决策

方法域(对应 Go 的 ~200 方法,节选):

代表方法
转换 lunar(), solar(), next(n), time(), times()
干支 year_gan/zhi(), year_in_gan_zhi(), ..._by_li_chun(), ..._exact(), month_*, day_*(_exact/_exact2), time_*, 各 *_index()
纳音 year_nayin(), month_nayin(), day_nayin(), time_nayin()
节气 jie(), qi(), jie_qi(), jie_qi_table(), next_jie(), prev_jie(), next_qi(), prev_qi(), next_jie_qi(), *by_whole_day()
节日 festivals(), other_festivals()
生肖 year_sheng_xiao(), ..._by_li_chun(), ..._exact(), month/day/time_sheng_xiao()
彭祖 peng_zu_gan(), peng_zu_zhi()
宜忌 day_yi/ji(), day_yi/ji_by_sect(sect), day_ji_shen(), day_xiong_sha(), time_yi/ji()
方位 day_position_xi/yang_gui/yin_gui/fu/cai()(+_desc), year/month/day_position_tai_sui(), 胎神 day/month_position_tai()
冲煞 day_chong(), day_chong_gan(), day_chong_gan_tie(), day_chong_sheng_xiao(), day_sha(), time_*
天神 day_tian_shen(), day_tian_shen_type(), day_tian_shen_luck(), time_*
zhi_xing()(建除), xiu/xiu_luck/xiu_song(), zheng/animal/gong/shou(), yue_xiang()(月相), liu_yao()(六曜), shu_jiu()(数九), fu()(三伏), hou()/wu_hou()(物候), day_lu()(日禄), *_nine_star()(九星)
旬空 *_xun(), *_xun_kong()
包装 eight_char(), foto(), tao()

5.3 LunarTime(时辰)

pub struct LunarTime { gan_index: i8, zhi_index: i8, lunar: Lunar }
// 方法镜像 Lunar 的 day/time 域:gan_zhi, nayin, tian_shen(_type/_luck),
// chong/sha, yi/ji, nine_star, xun/xun_kong, position_*, min_hm/max_hm

5.4 LunarMonth / LunarYear

pub struct LunarMonth {
    year: i32, month: i32, day_count: u8, index: i8,
    zhi_index: i8, first_julian_day: f64,
}
impl LunarMonth { /* gan_zhi, position_*, nine_star, next(n) */ }

pub struct LunarYear {
    year: i32, gan_index: i8, zhi_index: i8,
    months: Vec<LunarMonth>,           // 15 个(跨年边界)
    jie_qi_julian_days: [f64; 31],
}
impl LunarYear {
    pub fn from_year(year: i32) -> Arc<LunarYear>;   // 命中缓存
    fn compute(&mut self);                            // 天文 master 算法
    pub fn months_in_year(&self) -> impl Iterator + '_;
    pub fn month(&self, m: i32) -> Option<&LunarMonth>;
    pub fn leap_month(&self) -> i32;
    /* 元/运/九星/方位/杂占(get_tou_liang/get_cao_zi/...) */
}

5.5 EightChar + 运程

pub struct EightChar { lunar: Lunar, sect: u8 /* 1|2 */ }
impl EightChar {
    pub fn from_lunar(lunar: Lunar) -> EightChar;
    pub fn year/month/day/time() -> &'static str;        // gan_zhi
    pub fn *_hide_gan/wu_xing/nayin/shi_shen_gan/shi_shen_zhi/dishi/xun/xun_kong()
    pub fn tai_yuan/tai_xi/ming_gong/shen_gong()(+_nayin)
    pub fn yun(gender: u8, sect: u8) -> Yun;
}

pub struct Yun { gender: u8, start_year/month/day/hour: i32, forward: bool, lunar: Lunar }
impl Yun { pub fn da_yun(n) -> Vec<DaYun>; pub fn start_solar() -> Solar; }
pub struct DaYun { ... }   // gan_zhi/lis_nian(n)/xiao_yun(n)
pub struct LiuNian { ... } // gan_zhi/liu_yue()
pub struct LiuYue { ... }  // gan_zhi (月飞星式)
pub struct XiaoYun { ... } // gan_zhi

5.6 NineStar / Foto / Tao / 节气 / 节假日


6. 性能策略

手段 落点 收益
常量查表用 &'static [&'static str] 所有 tables.rs 零分配,getter 返回 &'static str
构造期预计算 + 紧凑字段 (i8) Lunar 23 个索引 getter O(1);Lunar 约 32 字节(不含 Arc
Arc<LunarYear> 年级缓存 LunarYear::from_year + 全局 OnceLock<RwLock<LruCache>> 同年重复构建的天文 pass 只算一次
jie_qiArc 共享 LunarArc<LunarYear> 而非克隆 31×Solar 不复制
浮点避免 Math.random/Date.now 天文引擎纯函数 可缓存、确定性、可并行
迭代器而非 Vec 收集 months_in_year, festivals 调用方按需消费,省分配
#[inline] 热路径 julian_day, next_day, 索引 getter 跨 crate 内联
Cow<'static, str> i18n 开启时部分名称需格式化 默认 Borrowed 零分配
LTO + codegen-units=1 release profile 全局优化

缓存设计:默认一个有界 LRU(容量 64 年)。Solar→Lunar 连续遍历同一年的日期时,天文 pass 仅触发一次。线程安全用 RwLock(读多写少);无锁路径在 try_write 失败时回退为直接计算(不阻塞)。

正确性优先:缓存仅是加速,不可缓存任何会因 i18n / 节假日覆盖而变的内容(那些在 Lunar 方法层动态查表)。


7. 错误处理策略

7.1 错误类型

#[non_exhaustive]
pub enum LunarError {
    InvalidSolar { year: i32, month: i32, day: i32, /* ... */ },
    InvalidSolarHour { /* ... */ },
    GregorianGap { y: i32, m: i32, d: i32 },   // 1582-10-05..14 不存在
    InvalidLunar { year: i32, month: i32, day: i32 },
    LunarDayOverflow { year: i32, month: i32, day: i32, max: i32 },
    LeapMonthAbsent { year: i32, month: i32 }, // 指定闰月但该年无此闰月
}

7.2 策略


8. 国际化 (i18n) 策略

8.1 移植 JS 的占位符重写

JS 版的精妙处:查找表存占位符('{tg.jia}'),setLanguage 时正则替换。Rust 移植:

// 默认:tables.rs 直接存中文 &'static str(零开销,90%+ 用户场景)
// feature = "i18n":
//   - tables 存占位符模板
//   - I18n 持消息字典(LazyLock<RwLock<HashMap<Lang, HashMap<&str, &str>>>>)
//   - 取值走 message(key) → 启动时或 setLanguage 时把模板重写进运行期数组

8.2 取舍


9. 测试策略

9.1 黄金用例:移植 _test.go

将 lunar-go 的 test/*_test.go(Lunar/Solar/JieQi/EightChar/NineStar/Foto/Tao/Yun/XingZuo/WuHou/LunarMonth/ShuJiu/Fu/LiuYao/Holiday/GanZhi/Year/SolarUtil/SolarWeek 等)逐条翻译为 Rust 集成测试,断言字符串完全相等。这是首要验收标准

例(来自 Lunar_test.go::TestLunar1):

#[test]
fn lunar_full_string() {
    let lunar = Lunar::from_ymd(2019, 3, 27).unwrap();
    assert_eq!(lunar.to_string(), "二〇一九年三月廿七");
    assert_eq!(lunar.to_full_string(),
        "二〇一九年三月廿七 己亥(猪)年 戊辰(龙)月 戊戌(狗)日 子(鼠)时 \
         纳音[平地木 大林木 平地木 桑柘木] 星期三 西方白虎 \
         星宿[参水猿](吉) 彭祖百忌[戊不受田田主不祥 戌不吃犬作怪上床] \
         喜神方位[巽](东南) 阳贵神方位[艮](东北) 阴贵神方位[坤](西南) \
         福神方位[艮](东北) 财神方位[坎](正北) 冲[(壬辰)龙] 煞[北]");
}

9.2 差分测试 (Differential Testing)

写一个 tests/diff.rs#[ignore] 默认不跑,CI 定期触发):对 1900–2100 年每日 + 随机历史日期,调用 Go 参考实现(通过子进程 / FFI / 预生成 JSON 快照)比对全字段输出。

9.3 边界用例

9.4 性能基准 (benches/)

criterion 基准:Solar::from_ymd().lunar() 冷启动 vs 热缓存、to_full_string()JieQi 表构建。


10. 实施路线图

分 5 个里程碑(M),每个 M 可独立编译、测试、交付。

里程碑 内容 验收
M1 基础设施 error.rsCargo.toml、mod 骨架、solar_util.rsSolar(构造 / 推移 / 儒略日 / 星期 / 星座 / 节日) Solar_test.go 全过
M2 天文引擎 shou_xing/(data + engine,calc_qi/calc_shuo/qi_accurate2 单测:已知节气儒略日对照(如 2012 白露 = 2012-09-07 13:29:01
M3 农历核心 lunar_util/(常量 + 解码)、LunarYear/LunarMonth(master compute + 缓存)、Lunar(转换 + 干支 + 节气 + 节日 + 生肖 + 位置 + 冲煞 + 宜忌 + 天神 + 建除 + 宿 + 月相 + 数九 + 三伏 + 物候 + 九星)、LunarTime Lunar_test.go / JieQi_test.go / GanZhi / XingZuo / WuHou / ShuJiu / Fu / LiuYao / LunarMonth / Year 全过
M4 八字 + 运程 EightCharyun/* EightChar_test.go / Yun_test.go 全过
M5 周边 + i18n + 交付 NineStarFoto/FotoUtilTao/TaoUtilSolarWeek/Month/Year/HalfYear/SeasonHoliday/HolidayUtili18n、serde、文档、bench 全部 _test.go 移植过;差分测试;README + doc-tests

10.1 本轮编码交付范围

本轮(本次会话)将完成 M1–M3 的核心并打通 M4 关键路径,确保:

M5 的佛历 / 道历 / 节假日 / Solar 区间类型 / i18n / serde 在结构就绪后按文档快速补齐(数据为主,逻辑简单)。


11. 风险与决策记录

# 议题 决策 理由
R1 浮点末位差异致节气分钟偏移 f64 + 严格对齐 Go 运算顺序;用黄金用例回归 历算对精度极敏感,差 1 秒可能跨分钟
R2 Go 负数取模 vs Rust % 逐处用 rem_euclid 或手动 +n 修正 Rust % 对负数向零取整,与 Go 不同
R3 XL0/XL1 巨型数据搬移 const 数组 + include! 或独立 data.rs 避免污染主逻辑,便于校对
R4 紧缩串 dayYiJi/timeYiJi/dayShenSha 原样复制 + 复刻 hex/解码逻辑 宜忌 / 神煞全靠它,不可简化
R5 i18n 是否默认开 默认关(中文直存) 满足性能目标与 90% 用户;i18n 作 feature
R6 缓存线程安全粒度 全局 LruCache<year, Arc<LunarYear>> + RwLock 读多写极少;Arc 共享避免深拷贝
R7 错误用 Result 还是 Option 构造 Result,查表 Option,getter 不失败 符合 Rust 习惯,调用方灵活
R8 是否引入 chrono 否(默认),from_unixi64 减依赖;Solar 自带儒略日计算
R9 JS 内联 Yun vs Go 真类 采 Go 真类 API 清晰、可测、符合 Rust 习惯
R10 docs/ 提交 已在 .gitignore,禁止提交 用户明确要求

附录 A:术语速查

附录 B:参考文件索引(移植源头)

Rust 模块 Go 源文件 JS 源(行)
shou_xing/ ShouXingUtil/ShouXingUtil.go lunar.js ShouXingUtil (2970–3590)
solar_util.rs / solar.rs SolarUtil/ + calendar/Solar.go Solar (13–629)
lunar_util/ LunarUtil/LunarUtil.go LunarUtil (3883–5349)
lunar_year.rs / lunar_month.rs calendar/LunarYear.go / LunarMonth.go LunarYear (2459–2788) / LunarMonth (2789–2969)
lunar.rs / lunar_time.rs calendar/Lunar.go / LunarTime.go Lunar (630–1972) / LunarTime (6038–6131)
eight_char.rs / yun/ calendar/EightChar.go / Yun.go / DaYun.go / LiuNian.go / LiuYue.go / XiaoYun.go EightChar (5636–6037)
nine_star.rs / nine_star_util.rs calendar/NineStar.go NineStar (5569–5635) / NineStarUtil (6741–6810)
foto.rs / foto_util.rs calendar/Foto.go / FotoUtil/FotoUtil.go Foto (6465–6590) / FotoUtil (6132–6464)
tao.rs / tao_util.rs calendar/Tao.go / TaoUtil/TaoUtil.go Tao (6811–6913) / TaoUtil (6614–6740)
holiday.rs / holiday_util.rs HolidayUtil/ HolidayUtil (5350–5568)
solar_week/month/year/... calendar/SolarWeek.go SolarWeek… (1973–2458)
i18n.rs (Go 无) I18n (6914–8513)