Files
astro/calendar/era_fallback_test.go
T
b612 2bf8478639 feat: 完善日月食与月掩几何链路并扩展历法接口
- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑
- 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口
- 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界
- 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名
- 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验
- 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口
- 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
2026-09-17 12:27:40 +08:00

81 lines
3.7 KiB
Go

package calendar
import (
"strings"
"testing"
)
// 年份早于年号表首个条目时,innerEras 曾追加零值 EraDesc,描述退化成空年号的“年十月廿五”。
// 兜底档(≤ −104 送进明清表)与三国/南北朝/南明的补充分支都会命中,这里锁住修复后的不变量:
// 任何农历记录的年号描述都非空,且不会以“年”开头(即不存在空年号)。
// When a year precedes the first entry of an era table, innerEras used to append a zero-valued
// EraDesc and the description degenerated to an empty era name ("年十月廿五"). The catch-all branch
// (years <= -104 sent to the Ming/Qing table) and the Three Kingdoms / Northern-Southern dynasties /
// Southern Ming supplements all hit it. These assertions pin the invariant after the fix: every lunar
// record has a non-empty era description that never starts with "年" alone.
func TestEraDescriptionNeverEmpty(t *testing.T) {
cases := []struct {
year, month, day int
primaryEra string
}{
{-104, 12, 20, "前一零五年"}, // 报告里的样例:太初改历交接年
{-103, 1, 5, "前一零五年"}, // 同上,跨公历年
{-104, 1, 1, "元封六年"}, // 同一年里年号仍正常
{221, 1, 5, "延康元年"}, // 三国补充分支(农历年 220 早于魏表首年)
{384, 1, 5, "太元八年"}, // 南北朝补充分支
{397, 1, 5, "太元二十一年"}, // 北凉补充分支
{534, 1, 5, "中大通五年"}, // 东魏北齐补充分支
{1647, 1, 5, "顺治三年"}, // 南明补充分支(农历年 1646 早于永历表首年)
{1648, 1, 5, "顺治四年"},
{1912, 12, 25, "民国元年"},
}
for _, c := range cases {
fwd, err := SolarToLunarByYMD(c.year, c.month, c.day)
if err != nil {
t.Fatalf("SolarToLunarByYMD(%d,%d,%d) error: %v", c.year, c.month, c.day, err)
}
if len(fwd.Lunars()) == 0 {
t.Fatalf("%d-%02d-%02d: no lunar records", c.year, c.month, c.day)
}
for i, l := range fwd.Lunars() {
for _, desc := range l.LunarDescWithDynastyAndEmperor() {
if strings.TrimSpace(desc) == "" || strings.HasPrefix(strings.TrimSpace(desc), "年") {
t.Errorf("%d-%02d-%02d record %d: empty-era description %q", c.year, c.month, c.day, i, desc)
}
}
for _, info := range l.LunarInfo() {
if strings.TrimSpace(info.EraDesc) == "" || strings.TrimSpace(info.EraDesc) == "年" {
t.Errorf("%d-%02d-%02d record %d: empty era description %q", c.year, c.month, c.day, i, info.EraDesc)
}
if strings.TrimSpace(info.LunarWithEraDesc) == "" || strings.HasPrefix(strings.TrimSpace(info.LunarWithEraDesc), "年") {
t.Errorf("%d-%02d-%02d record %d: empty-era description %q", c.year, c.month, c.day, i, info.LunarWithEraDesc)
}
}
}
if got := fwd.LunarInfo()[0].EraDesc; got != c.primaryEra {
t.Errorf("%d-%02d-%02d primary era = %q, want %q", c.year, c.month, c.day, got, c.primaryEra)
}
}
}
// 抽样式全库扫描:跨朝代边界的年份最容易踩到零值年号,这里按区间取样确认没有空年号描述。
// A sampled sweep: years at dynasty boundaries are the ones that hit the zero-valued era, so sample
// each band and confirm no record carries an empty era description.
func TestEraDescriptionSampledSweep(t *testing.T) {
for year := -200; year <= 1920; year += 3 {
for _, md := range [][2]int{{1, 5}, {5, 20}, {11, 25}} {
fwd, err := SolarToLunarByYMD(year, md[0], md[1])
if err != nil {
continue
}
for i, l := range fwd.Lunars() {
for _, info := range l.LunarInfo() {
if strings.TrimSpace(info.EraDesc) == "" || strings.TrimSpace(info.EraDesc) == "年" {
t.Fatalf("%d-%02d-%02d record %d: empty era description (desc %q)", year, md[0], md[1], i, info.LunarWithEraDesc)
}
}
}
}
}
}