16c62a97d5
- 新增时标、ΔT 模型、质心时间与 UT1 支持 - 改进日月食、月掩、行星事件及路径边界计算 - 完善恒星三维自行与动态距离传播 - 扩展 SVG、GeoJSON、KML 输出与底层距离换算工具 - 整理中英文手册、示例资源及回归测试
119 lines
4.5 KiB
Go
119 lines
4.5 KiB
Go
package calendar
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// L3 契约:Solar() 保持库内默认选择,SolarCandidates() 给出全部合法候选(主答案在首),
|
|
// 且每个候选都能正向回到该农历日。
|
|
func TestSolarCandidatesForReformWindows(t *testing.T) {
|
|
cases := []struct {
|
|
year, month, day int
|
|
leap bool
|
|
want []string
|
|
}{
|
|
{762, 4, 1, false, []string{"0762-03-01", "0762-04-29"}}, // 唐肃宗改制
|
|
{700, 11, 1, false, []string{"0700-12-15", "0700-10-17"}}, // 武则天改制
|
|
{700, 12, 1, false, []string{"0700-11-15", "0701-01-14"}}, // 武则天改制
|
|
{23, 12, 1, false, []string{"0023-12-02", "0023-12-31"}}, // 王莽改制
|
|
{239, 12, 1, false, []string{"0239-12-13", "0240-01-12"}}, // 魏明帝改制
|
|
{-104, 10, 1, false, []string{"-0104-11-26", "-0105-11-08"}}, // 太初改历交接
|
|
}
|
|
for _, c := range cases {
|
|
res, err := LunarToSolarByYMD(c.year, c.month, c.day, c.leap)
|
|
if err != nil {
|
|
t.Fatalf("LunarToSolarByYMD(%d,%d,%d,%v) error: %v", c.year, c.month, c.day, c.leap, err)
|
|
}
|
|
candidates := res.SolarCandidates()
|
|
if len(candidates) != len(c.want) {
|
|
t.Errorf("%d/%d/%d: got %d candidates %v, want %d %v",
|
|
c.year, c.month, c.day, len(candidates), formatCandidates(candidates), len(c.want), c.want)
|
|
continue
|
|
}
|
|
if !candidates[0].Equal(res.Solar()) {
|
|
t.Errorf("%d/%d/%d: first candidate %s is not Solar() %s",
|
|
c.year, c.month, c.day, formatAncientSolar(candidates[0]), formatAncientSolar(res.Solar()))
|
|
}
|
|
for i, v := range candidates {
|
|
if got := formatAncientSolar(v); got != c.want[i] {
|
|
t.Errorf("%d/%d/%d: candidate %d = %s, want %s", c.year, c.month, c.day, i, got, c.want[i])
|
|
}
|
|
for j := 0; j < i; j++ {
|
|
if candidates[j].Equal(v) {
|
|
t.Errorf("%d/%d/%d: candidate %d duplicates candidate %d (%s)",
|
|
c.year, c.month, c.day, i, j, formatAncientSolar(v))
|
|
}
|
|
}
|
|
fwd, err := SolarToLunarByYMD(v.Year(), int(v.Month()), v.Day())
|
|
if err != nil {
|
|
t.Errorf("%d/%d/%d: candidate %s does not convert forward: %v",
|
|
c.year, c.month, c.day, formatAncientSolar(v), err)
|
|
continue
|
|
}
|
|
l := fwd.Lunar()
|
|
if l.LunarYear() != c.year || l.LunarMonth() != c.month || l.LunarDay() != c.day || l.IsLeap() != c.leap {
|
|
t.Errorf("%d/%d/%d: candidate %s converts back to lunar %d/%d/%d leap=%v",
|
|
c.year, c.month, c.day, formatAncientSolar(v),
|
|
l.LunarYear(), l.LunarMonth(), l.LunarDay(), l.IsLeap())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 改历窗口外只有一个候选,且 SolarCandidates() 返回的是副本。
|
|
func TestSolarCandidatesAreSingleOutsideReformWindows(t *testing.T) {
|
|
cases := []struct{ year, month, day int }{
|
|
{763, 4, 1}, {701, 11, 1}, {24, 12, 1}, {241, 12, 1}, {-103, 10, 1},
|
|
{-105, 10, 1}, {2025, 1, 1}, {1912, 1, 1}, {1000, 7, 7}, {1, 1, 1},
|
|
}
|
|
for _, c := range cases {
|
|
res, err := LunarToSolarByYMD(c.year, c.month, c.day, false)
|
|
if err != nil {
|
|
t.Fatalf("LunarToSolarByYMD(%d,%d,%d) error: %v", c.year, c.month, c.day, err)
|
|
}
|
|
candidates := res.SolarCandidates()
|
|
if len(candidates) != 1 {
|
|
t.Errorf("%d/%d/%d: got %d candidates %v, want a single one",
|
|
c.year, c.month, c.day, len(candidates), formatCandidates(candidates))
|
|
continue
|
|
}
|
|
if !candidates[0].Equal(res.Solar()) {
|
|
t.Errorf("%d/%d/%d: single candidate %s is not Solar() %s",
|
|
c.year, c.month, c.day, formatAncientSolar(candidates[0]), formatAncientSolar(res.Solar()))
|
|
}
|
|
// 返回值必须是副本。
|
|
candidates[0] = candidates[0].AddDate(1, 1, 1)
|
|
if again := res.SolarCandidates(); !again[0].Equal(res.Solar()) {
|
|
t.Errorf("%d/%d/%d: SolarCandidates() is not a copy (%s)", c.year, c.month, c.day, formatAncientSolar(again[0]))
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1582 改历跳过的十天本身不存在,与 L2/L3 无关。
|
|
func TestGregorianSkippedDaysHaveNoCandidates(t *testing.T) {
|
|
for day := 5; day <= 14; day++ {
|
|
if _, err := SolarToLunarByYMD(1582, 10, day); err == nil {
|
|
t.Errorf("SolarToLunarByYMD(1582,10,%d) unexpectedly succeeded", day)
|
|
}
|
|
}
|
|
res, err := LunarToSolarByYMD(1582, 9, 18, false)
|
|
if err != nil {
|
|
t.Fatalf("LunarToSolarByYMD(1582,9,18) error: %v", err)
|
|
}
|
|
if got := len(res.SolarCandidates()); got != 1 {
|
|
t.Errorf("lunar 1582/9/18 has %d candidates, want 1", got)
|
|
}
|
|
if _, err := time.Parse("2006-01-02", formatAncientSolar(res.Solar())); err != nil {
|
|
t.Errorf("solar canonical day %q is not a valid date", formatAncientSolar(res.Solar()))
|
|
}
|
|
}
|
|
|
|
func formatCandidates(candidates []time.Time) []string {
|
|
out := make([]string, 0, len(candidates))
|
|
for _, v := range candidates {
|
|
out = append(out, formatAncientSolar(v))
|
|
}
|
|
return out
|
|
}
|