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