feat: 完善时标与天象几何计算并扩展输出接口
- 新增时标、ΔT 模型、质心时间与 UT1 支持 - 改进日月食、月掩、行星事件及路径边界计算 - 完善恒星三维自行与动态距离传播 - 扩展 SVG、GeoJSON、KML 输出与底层距离换算工具 - 整理中英文手册、示例资源及回归测试
This commit is contained in:
+9
-9
@@ -6,18 +6,18 @@ import (
|
||||
"b612.me/astro/basic"
|
||||
)
|
||||
|
||||
// NowJDE 当前时刻儒略日 / current Julian day.
|
||||
func NowJDE() float64 {
|
||||
return basic.GetNowJDE()
|
||||
// NowJD 当前时刻儒略日 / current Julian day.
|
||||
func NowJD() float64 {
|
||||
return basic.GetNowJD()
|
||||
}
|
||||
|
||||
// Date2JDE 日期转儒略日 / date to Julian day.
|
||||
func Date2JDE(date time.Time) float64 {
|
||||
// Date2JD 日期转儒略日 / date to Julian day.
|
||||
func Date2JD(date time.Time) float64 {
|
||||
day := float64(date.Day()) + float64(date.Hour())/24.0 + float64(date.Minute())/24.0/60.0 + float64(date.Second())/24.0/3600.0 + float64(date.Nanosecond())/1000000000.0/3600.0/24.0
|
||||
return basic.JDECalc(date.Year(), int(date.Month()), day)
|
||||
return basic.JDCalc(date.Year(), int(date.Month()), day)
|
||||
}
|
||||
|
||||
// JDE2Date 儒略日转日期 / Julian day to date.
|
||||
func JDE2Date(jde float64) time.Time {
|
||||
return basic.JDE2Date(jde)
|
||||
// JD2Date 儒略日转日期 / Julian day to date.
|
||||
func JD2Date(jd float64) time.Time {
|
||||
return basic.JD2Date(jd)
|
||||
}
|
||||
|
||||
+30
-30
@@ -73,9 +73,9 @@ func Lunar(year, month, day int, timezone float64) (int, int, int, bool, string)
|
||||
// The current GB/T 33661-2017 lunar-calendar convention is recommended for years 1929-3000.
|
||||
// For ancient dates, the result may differ from historical practice because computed new-moon and solar-term reconstructions are approximate for ancient dates.
|
||||
func Solar(year, month, day int, leap bool, timezone float64) time.Time {
|
||||
jde := basic.GetSolar(year, month, day, leap, timezone/24.0)
|
||||
jd := basic.GetSolar(year, month, day, leap, timezone/24.0)
|
||||
zone := time.FixedZone("CST", int(timezone*3600))
|
||||
return basic.JDE2DateByZone(jde, zone, true)
|
||||
return basic.JD2DateByZone(jd, zone, true)
|
||||
}
|
||||
|
||||
// SolarToLunar 公历转农历 / solar to lunar calendar.
|
||||
@@ -390,7 +390,7 @@ func julianOnlyPhantomJDE(year, month, day int) (float64, bool) {
|
||||
if !julianOnlyCivilDay(year, month, day) {
|
||||
return 0, false
|
||||
}
|
||||
return basic.JDECalc(year, month, float64(day)), true
|
||||
return basic.JDCalc(year, month, float64(day)), true
|
||||
}
|
||||
|
||||
// julianOnlyTextResult 复核字符串入口的结果是否落在儒略历独有闰日上,是则返回该闰日的结果。
|
||||
@@ -400,10 +400,10 @@ func julianOnlyTextResult(result Time, month, day int, leap bool) (Time, bool) {
|
||||
if !julianOnlyCivilDay(year, 2, 29) {
|
||||
return result, false
|
||||
}
|
||||
diff := Date2JDE(solar) - basic.JDECalc(year, 2, 29)
|
||||
diff := Date2JD(solar) - basic.JDCalc(year, 2, 29)
|
||||
switch diff {
|
||||
case 1, -1:
|
||||
// +1 表示结果是闰日的标签日,-1 表示结果是闰日的前一天。
|
||||
// +1 表示结果是闰日的规范日,-1 表示结果是闰日的前一天。
|
||||
default:
|
||||
return result, false
|
||||
}
|
||||
@@ -418,7 +418,7 @@ func julianOnlyTextResult(result Time, month, day int, leap bool) (Time, bool) {
|
||||
}
|
||||
resultDay := result.Lunar().LunarDay()
|
||||
if diff > 0 {
|
||||
// 结果是闰日的标签日,农历上比闰日晚一天。
|
||||
// 结果是闰日的规范日,农历上比闰日晚一天。
|
||||
if resultDay != day+1 && !(resultDay == 1 && day >= 29) {
|
||||
return result, false
|
||||
}
|
||||
@@ -431,23 +431,23 @@ func julianOnlyTextResult(result Time, month, day int, leap bool) (Time, bool) {
|
||||
return forward, true
|
||||
}
|
||||
|
||||
// markJulianOnly 补记儒略历独有闰日:Solar 取规范标签(后继日),精确日期放入 JDE。
|
||||
func markJulianOnly(result Time, phantomJDE float64) Time {
|
||||
label := basic.JDE2DateByZone(phantomJDE, getCst(), true)
|
||||
result.solarTime = label
|
||||
// markJulianOnly 补记儒略历独有闰日:Solar 取后继日作为规范日,精确日期放入 JDE。
|
||||
func markJulianOnly(result Time, phantomJD float64) Time {
|
||||
canonicalDate := basic.JD2DateByZone(phantomJD, getCst(), true)
|
||||
result.solarTime = canonicalDate
|
||||
for i := range result.lunars {
|
||||
result.lunars[i].solarDate = label
|
||||
result.lunars[i].solarDate = canonicalDate
|
||||
result.lunars[i].julianOnly = true
|
||||
result.lunars[i].jde = phantomJDE
|
||||
result.lunars[i].jd = phantomJD
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// markJulianOnlyLunar 反向路径用:用正向记录改写停在标签日的农历身份,并补记闰日标志。
|
||||
func markJulianOnlyLunar(result Time, year, month, day int, leap bool, phantomJDE float64) Time {
|
||||
label := basic.JDE2DateByZone(phantomJDE, getCst(), true)
|
||||
result = markJulianOnly(result, phantomJDE)
|
||||
forward, err := innerSolarToLunarByYMD(label.Year(), 2, 29)
|
||||
// markJulianOnlyLunar 反向路径用:用正向记录改写停在规范日的农历身份,并补记闰日标志。
|
||||
func markJulianOnlyLunar(result Time, year, month, day int, leap bool, phantomJD float64) Time {
|
||||
canonicalDate := basic.JD2DateByZone(phantomJD, getCst(), true)
|
||||
result = markJulianOnly(result, phantomJD)
|
||||
forward, err := innerSolarToLunarByYMD(canonicalDate.Year(), 2, 29)
|
||||
if err != nil || !lunarDateMatches(forward, year, month, day, leap) {
|
||||
return result
|
||||
}
|
||||
@@ -455,7 +455,7 @@ func markJulianOnlyLunar(result Time, year, month, day int, leap bool, phantomJD
|
||||
copy(lunars, forward.lunars)
|
||||
for i := range lunars {
|
||||
lunars[i].julianOnly = true
|
||||
lunars[i].jde = phantomJDE
|
||||
lunars[i].jd = phantomJD
|
||||
}
|
||||
result.lunars = lunars
|
||||
return result
|
||||
@@ -463,8 +463,8 @@ func markJulianOnlyLunar(result Time, year, month, day int, leap bool, phantomJD
|
||||
|
||||
// markJulianOnlyJDN 在 JDN 层面判定儒略历独有闰日(古历与秦汉反向直接由 JDN 造记录)。
|
||||
func markJulianOnlyJDN(result Time, jdn int) Time {
|
||||
label := basic.JDE2DateByZone(float64(jdn)-0.5, getCst(), true)
|
||||
phantom, ok := julianOnlyPhantomJDE(label.Year(), 2, 29)
|
||||
canonicalDate := basic.JD2DateByZone(float64(jdn)-0.5, getCst(), true)
|
||||
phantom, ok := julianOnlyPhantomJDE(canonicalDate.Year(), 2, 29)
|
||||
if !ok || int(math.Floor(phantom+0.5)) != jdn {
|
||||
return result
|
||||
}
|
||||
@@ -578,9 +578,9 @@ func lunarToSolarHanQingDefault(year, month, day int, leap bool) (Time, bool) {
|
||||
// 返回传入年份、节气对应的北京时间节气时间。
|
||||
// Returns the Beijing-time instant of the requested solar term in the supplied year.
|
||||
func JieQi(year, term int) time.Time {
|
||||
calcJde := basic.GetJQTime(year, term)
|
||||
calcJD := basic.GetJQTime(year, term)
|
||||
zone := time.FixedZone("CST", 8*3600)
|
||||
return basic.JDE2DateByZone(calcJde, zone, false)
|
||||
return basic.JD2DateByZone(calcJD, zone, false)
|
||||
}
|
||||
|
||||
// CalendricalJieQi 历法相符节气日期(北京时间当天 0 点) / calendrical solar-term date at Beijing midnight.
|
||||
@@ -610,9 +610,9 @@ func CalendricalJieQiWithCalendar(year, term int, system AncientCalendarSystem)
|
||||
// 返回传入年份、物候对应的北京时间物候时间。
|
||||
// Returns the Beijing-time instant of the requested pentad in the supplied year.
|
||||
func WuHou(year, term int) time.Time {
|
||||
calcJde := basic.GetWuHouTime(year, term)
|
||||
calcJD := basic.GetWuHouTime(year, term)
|
||||
zone := time.FixedZone("CST", 8*3600)
|
||||
return basic.JDE2DateByZone(calcJde, zone, false)
|
||||
return basic.JD2DateByZone(calcJD, zone, false)
|
||||
}
|
||||
|
||||
func rapidLunarModern(year, month, day int) (int, int, int, bool, string) {
|
||||
@@ -980,12 +980,12 @@ func GanZhiOfYear(year int) string {
|
||||
|
||||
// GanZhiOfDay 日干支 / sexagenary day name.
|
||||
func GanZhiOfDay(t time.Time) string {
|
||||
return ganZhiOfJDE(Date2JDE(time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, getCst())))
|
||||
return ganZhiOfJD(Date2JD(time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, getCst())))
|
||||
}
|
||||
|
||||
// ganZhiOfJDE 由儒略日直接求日干支(儒略历独有闰日无法表示为 time.Time)。
|
||||
func ganZhiOfJDE(jde float64) string {
|
||||
diff := int(jde - 2451550.5)
|
||||
// ganZhiOfJD 由儒略日直接求日干支(儒略历独有闰日无法表示为 time.Time)。
|
||||
func ganZhiOfJD(jd float64) string {
|
||||
diff := int(jd - 2451550.5)
|
||||
if diff >= 0 {
|
||||
return tiangan[diff%10] + dizhi[diff%12]
|
||||
}
|
||||
@@ -1009,8 +1009,8 @@ func commonGanZhiOfMonth(year, month int) string {
|
||||
}
|
||||
|
||||
func ganZhiOfDayIndex(t time.Time) (int, int) {
|
||||
jde := Date2JDE(time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, getCst()))
|
||||
diff := int(jde - 2451550.5)
|
||||
localJD := Date2JD(time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, getCst()))
|
||||
diff := int(localJD - 2451550.5)
|
||||
if diff >= 0 {
|
||||
return diff % 10, diff % 12
|
||||
}
|
||||
|
||||
@@ -709,13 +709,13 @@ func ancientCalendarName(system AncientCalendarSystem) string {
|
||||
}
|
||||
|
||||
func ancientDateJDN(year, month, day int) int {
|
||||
return int(math.Floor(basic.JDECalc(year, month, float64(day)) + 0.5))
|
||||
return int(math.Floor(basic.JDCalc(year, month, float64(day)) + 0.5))
|
||||
}
|
||||
|
||||
func ancientJDAtLocalMidnight(year, month, day int) float64 {
|
||||
return basic.JDECalc(year, month, float64(day))
|
||||
return basic.JDCalc(year, month, float64(day))
|
||||
}
|
||||
|
||||
func ancientJDNToDate(jdn int) time.Time {
|
||||
return basic.JDE2DateByZone(float64(jdn)-0.5, getCst(), true)
|
||||
return basic.JD2DateByZone(float64(jdn)-0.5, getCst(), true)
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ func hanQingCalendricalJieQiDateInRow(rowYear, termIndex int) (time.Time, error)
|
||||
for i := 0; i < termIndex; i++ {
|
||||
offset += hanQingJieQiPatternDelta(patternID, i)
|
||||
}
|
||||
baseJDN := int(math.Floor(basic.JDECalc(rowYear-1, 12, 31) + 0.5))
|
||||
return basic.JDE2DateByZone(float64(baseJDN+offset)-0.5, getCst(), true), nil
|
||||
baseJDN := int(math.Floor(basic.JDCalc(rowYear-1, 12, 31) + 0.5))
|
||||
return basic.JD2DateByZone(float64(baseJDN+offset)-0.5, getCst(), true), nil
|
||||
}
|
||||
|
||||
func calendricalJieQiTableTermIndex(term int) (int, error) {
|
||||
|
||||
@@ -166,11 +166,11 @@ func qinHanEpoch(year int) (float64, int, int) {
|
||||
}
|
||||
|
||||
func qinHanDateJDN(year, month, day int) int {
|
||||
return int(math.Floor(basic.JDECalc(year, month, float64(day)) + 0.5))
|
||||
return int(math.Floor(basic.JDCalc(year, month, float64(day)) + 0.5))
|
||||
}
|
||||
|
||||
func qinHanJDNToDate(jdn int) time.Time {
|
||||
return basic.JDE2DateByZone(float64(jdn)-0.5, getCst(), true)
|
||||
return basic.JD2DateByZone(float64(jdn)-0.5, getCst(), true)
|
||||
}
|
||||
|
||||
func floorDiv(a, b int) int {
|
||||
|
||||
@@ -858,7 +858,7 @@ func TestRapidLunarAndLunar(t *testing.T) {
|
||||
}
|
||||
t.Fatal(year, month, 24, a1, a2, a3, a4, b1, b2, b3, b4)
|
||||
}
|
||||
sol := JDE2Date(basic.GetSolar(b1, b2, b3, b4, 8.0/24))
|
||||
sol := JD2Date(basic.GetSolar(b1, b2, b3, b4, 8.0/24))
|
||||
if sol.Year() != year && int(sol.Month()) != month && sol.Day() != 24 {
|
||||
t.Fatal(year, month, sol, b1, b2, b3, b4)
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"b612.me/astro/basic"
|
||||
)
|
||||
|
||||
// L2 契约:儒略历独有闰日(1582 年前百年非 400 闰年的 2 月 29 日)的 Solar 取后继日标签,
|
||||
// JDE 取精确儒略日,JulianOnly 为真,农历身份字段描述这一天本身。
|
||||
// L2 契约:儒略历独有闰日(1582 年前百年非 400 闰年的 2 月 29 日)的 Solar 取后继日作为规范日,
|
||||
// JD 取精确儒略日,JulianOnly 为真,农历身份字段描述这一天本身。
|
||||
var julianOnlyYears = []int{
|
||||
100, 200, 300, 500, 600, 700, 900, 1000, 1100, 1300, 1400, 1500,
|
||||
-700, -600, -500, -300, -200, -100,
|
||||
@@ -40,21 +40,21 @@ func identityOf(t Time) lunarIdentity {
|
||||
|
||||
func TestJulianOnlyLeapDayForwardReverseAgree(t *testing.T) {
|
||||
for _, year := range julianOnlyYears {
|
||||
phantom := basic.JDECalc(year, 2, 29)
|
||||
phantom := basic.JDCalc(year, 2, 29)
|
||||
if phantom != phantom { // NaN 保护:这些年份必须被历法层承认
|
||||
t.Fatalf("year %d: basic.JDECalc(%d,2,29) is NaN", year, year)
|
||||
t.Fatalf("year %d: basic.JDCalc(%d,2,29) is NaN", year, year)
|
||||
}
|
||||
label := basic.JDE2DateByZone(phantom, time.FixedZone("CST", 8*3600), true)
|
||||
canonicalDate := basic.JD2DateByZone(phantom, time.FixedZone("CST", 8*3600), true)
|
||||
fwd, err := SolarToLunarByYMD(year, 2, 29)
|
||||
if err != nil {
|
||||
t.Fatalf("year %d: SolarToLunarByYMD(%d,2,29) error: %v", year, year, err)
|
||||
}
|
||||
if !fwd.JulianOnly() || fwd.JDE() != phantom {
|
||||
t.Errorf("year %d: forward JulianOnly=%v JDE=%.1f, want true %.1f",
|
||||
year, fwd.JulianOnly(), fwd.JDE(), phantom)
|
||||
if !fwd.JulianOnly() || fwd.JD() != phantom {
|
||||
t.Errorf("year %d: forward JulianOnly=%v JD=%.1f, want true %.1f",
|
||||
year, fwd.JulianOnly(), fwd.JD(), phantom)
|
||||
}
|
||||
if got := fwd.Solar().Format("2006-01-02"); got != label.Format("2006-01-02") {
|
||||
t.Errorf("year %d: forward Solar=%s, want canonical label %s", year, got, label.Format("2006-01-02"))
|
||||
if got := fwd.Solar().Format("2006-01-02"); got != canonicalDate.Format("2006-01-02") {
|
||||
t.Errorf("year %d: forward Solar=%s, want canonical day %s", year, got, canonicalDate.Format("2006-01-02"))
|
||||
}
|
||||
want := identityOf(fwd)
|
||||
|
||||
@@ -63,26 +63,26 @@ func TestJulianOnlyLeapDayForwardReverseAgree(t *testing.T) {
|
||||
t.Fatalf("year %d: LunarToSolarByYMD(%d,%d,%d,%v) error: %v",
|
||||
year, want.year, want.month, want.day, want.leap, err)
|
||||
}
|
||||
if !rev.JulianOnly() || rev.JDE() != phantom {
|
||||
t.Errorf("year %d: reverse JulianOnly=%v JDE=%.1f, want true %.1f",
|
||||
year, rev.JulianOnly(), rev.JDE(), phantom)
|
||||
if !rev.JulianOnly() || rev.JD() != phantom {
|
||||
t.Errorf("year %d: reverse JulianOnly=%v JD=%.1f, want true %.1f",
|
||||
year, rev.JulianOnly(), rev.JD(), phantom)
|
||||
}
|
||||
if got := rev.Solar().Format("2006-01-02"); got != label.Format("2006-01-02") {
|
||||
t.Errorf("year %d: reverse Solar=%s, want canonical label %s", year, got, label.Format("2006-01-02"))
|
||||
if got := rev.Solar().Format("2006-01-02"); got != canonicalDate.Format("2006-01-02") {
|
||||
t.Errorf("year %d: reverse Solar=%s, want canonical day %s", year, got, canonicalDate.Format("2006-01-02"))
|
||||
}
|
||||
if got := identityOf(rev); got != want {
|
||||
t.Errorf("year %d: reverse identity %+v, want %+v (requested lunar %d/%d/%d leap=%v)",
|
||||
year, got, want, want.year, want.month, want.day, want.leap)
|
||||
}
|
||||
info := rev.LunarInfo()[0]
|
||||
if !info.JulianOnly || info.JDE != phantom {
|
||||
t.Errorf("year %d: LunarInfo JulianOnly=%v JDE=%.1f, want true %.1f", year, info.JulianOnly, info.JDE, phantom)
|
||||
if !info.JulianOnly || info.JD != phantom {
|
||||
t.Errorf("year %d: LunarInfo JulianOnly=%v JD=%.1f, want true %.1f", year, info.JulianOnly, info.JD, phantom)
|
||||
}
|
||||
// 日干支必须取自闰日自己的儒略日,而不是标签日 Solar()。
|
||||
// The sexagenary day name must come from the leap day's own Julian day, not from the label day.
|
||||
if got, labelGanzhi := rev.Lunar().GanZhiDay(), GanZhiOfDay(rev.Solar()); got != ganZhiOfJDE(phantom) {
|
||||
t.Errorf("year %d: GanZhiDay=%s, want %s (label %s gives %s)",
|
||||
year, got, ganZhiOfJDE(phantom), rev.Solar().Format("2006-01-02"), labelGanzhi)
|
||||
// 日干支必须取自闰日自己的儒略日,而不是规范日 Solar()。
|
||||
// The sexagenary day name must come from the leap day's own Julian day, not from the canonical day.
|
||||
if got, canonicalGanzhi := rev.Lunar().GanZhiDay(), GanZhiOfDay(rev.Solar()); got != ganZhiOfJD(phantom) {
|
||||
t.Errorf("year %d: GanZhiDay=%s, want %s (canonical day %s gives %s)",
|
||||
year, got, ganZhiOfJD(phantom), rev.Solar().Format("2006-01-02"), canonicalGanzhi)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,7 @@ func TestJulianOnlyLeapDayForwardReverseAgree(t *testing.T) {
|
||||
// 儒略历闰日两侧仍是普通日子:往返正常且农历日不与闰日重复。
|
||||
func TestJulianOnlyLeapDayNeighboursStayOrdinary(t *testing.T) {
|
||||
for _, year := range julianOnlyYears {
|
||||
phantom := basic.JDECalc(year, 2, 29)
|
||||
phantom := basic.JDCalc(year, 2, 29)
|
||||
fwd, err := SolarToLunarByYMD(year, 2, 29)
|
||||
if err != nil {
|
||||
t.Fatalf("year %d: SolarToLunarByYMD(%d,2,29) error: %v", year, year, err)
|
||||
@@ -98,7 +98,7 @@ func TestJulianOnlyLeapDayNeighboursStayOrdinary(t *testing.T) {
|
||||
leapLunar := identityOf(fwd)
|
||||
for _, c := range []struct {
|
||||
month, day int
|
||||
wantJDE float64
|
||||
wantJD float64
|
||||
}{
|
||||
{2, 28, phantom - 1},
|
||||
{3, 1, phantom + 1},
|
||||
@@ -120,8 +120,8 @@ func TestJulianOnlyLeapDayNeighboursStayOrdinary(t *testing.T) {
|
||||
if rev.JulianOnly() {
|
||||
t.Errorf("year %d: %d-%02d flagged JulianOnly", year, year, c.day)
|
||||
}
|
||||
if rev.JDE() != c.wantJDE {
|
||||
t.Errorf("year %d: %d-%02d JDE=%.1f, want %.1f", year, year, c.day, rev.JDE(), c.wantJDE)
|
||||
if rev.JD() != c.wantJD {
|
||||
t.Errorf("year %d: %d-%02d JD=%.1f, want %.1f", year, year, c.day, rev.JD(), c.wantJD)
|
||||
}
|
||||
if got := identityOf(rev); got != nearID {
|
||||
t.Errorf("year %d: %d-%02d reverse identity %+v, want %+v", year, year, c.day, got, nearID)
|
||||
@@ -154,7 +154,7 @@ func TestJulianOnlyCivilDayClassification(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// 普通日期不受 L2 影响:JulianOnly 为假、JDE 与 Solar 一致、身份与正向一致。
|
||||
// 普通日期不受 L2 影响:JulianOnly 为假、JD 与 Solar 一致、身份与正向一致。
|
||||
func TestOrdinaryDatesCarryNoJulianOnlyFlag(t *testing.T) {
|
||||
cases := []struct{ y, m, d int }{
|
||||
{2025, 1, 1}, {2025, 6, 15}, {1912, 2, 12}, {1900, 5, 5}, {1582, 10, 15},
|
||||
@@ -174,8 +174,8 @@ func TestOrdinaryDatesCarryNoJulianOnlyFlag(t *testing.T) {
|
||||
if rev.JulianOnly() {
|
||||
t.Errorf("%d-%02d-%02d flagged JulianOnly", c.y, c.m, c.d)
|
||||
}
|
||||
if want := Date2JDE(rev.Solar()); rev.JDE() != want {
|
||||
t.Errorf("%d-%02d-%02d JDE=%.1f, want %.1f (Solar 推出值)", c.y, c.m, c.d, rev.JDE(), want)
|
||||
if want := Date2JD(rev.Solar()); rev.JD() != want {
|
||||
t.Errorf("%d-%02d-%02d JD=%.1f, want %.1f (Solar 推出值)", c.y, c.m, c.d, rev.JD(), want)
|
||||
}
|
||||
if got := identityOf(rev); got != id {
|
||||
t.Errorf("%d-%02d-%02d reverse identity %+v, want %+v", c.y, c.m, c.d, got, id)
|
||||
@@ -185,8 +185,8 @@ func TestOrdinaryDatesCarryNoJulianOnlyFlag(t *testing.T) {
|
||||
if v.JulianOnly {
|
||||
t.Errorf("%d-%02d-%02d LunarInfo flagged JulianOnly", c.y, c.m, c.d)
|
||||
}
|
||||
if v.JDE != rev.JDE() {
|
||||
t.Errorf("%d-%02d-%02d LunarInfo JDE=%.1f, want %.1f", c.y, c.m, c.d, v.JDE, rev.JDE())
|
||||
if v.JD != rev.JD() {
|
||||
t.Errorf("%d-%02d-%02d LunarInfo JD=%.1f, want %.1f", c.y, c.m, c.d, v.JD, rev.JD())
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -226,8 +226,8 @@ func TestLunarToSolarTextHandlesJulianOnlyLeapDays(t *testing.T) {
|
||||
{"永元十二年二月初二", 100, 2, 2, false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
phantom := basic.JDECalc(c.year, 2, 29)
|
||||
label := basic.JDE2DateByZone(phantom, time.FixedZone("CST", 8*3600), true)
|
||||
phantom := basic.JDCalc(c.year, 2, 29)
|
||||
canonicalDate := basic.JD2DateByZone(phantom, time.FixedZone("CST", 8*3600), true)
|
||||
results, err := LunarToSolar(c.desc)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: LunarToSolar error: %v", c.desc, err)
|
||||
@@ -237,11 +237,11 @@ func TestLunarToSolarTextHandlesJulianOnlyLeapDays(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
got := results[0]
|
||||
if !got.JulianOnly() || got.JDE() != phantom {
|
||||
t.Errorf("%s: JulianOnly=%v JDE=%.1f, want true %.1f", c.desc, got.JulianOnly(), got.JDE(), phantom)
|
||||
if !got.JulianOnly() || got.JD() != phantom {
|
||||
t.Errorf("%s: JulianOnly=%v JD=%.1f, want true %.1f", c.desc, got.JulianOnly(), got.JD(), phantom)
|
||||
}
|
||||
if solar := got.Solar().Format("2006-01-02"); solar != label.Format("2006-01-02") {
|
||||
t.Errorf("%s: Solar=%s, want canonical label %s", c.desc, solar, label.Format("2006-01-02"))
|
||||
if solar := got.Solar().Format("2006-01-02"); solar != canonicalDate.Format("2006-01-02") {
|
||||
t.Errorf("%s: Solar=%s, want canonical day %s", c.desc, solar, canonicalDate.Format("2006-01-02"))
|
||||
}
|
||||
lunar := got.Lunar()
|
||||
if lunar.LunarYear() != c.year || lunar.LunarMonth() != c.month || lunar.LunarDay() != c.day || lunar.IsLeap() != c.leap {
|
||||
@@ -254,10 +254,10 @@ func TestLunarToSolarTextHandlesJulianOnlyLeapDays(t *testing.T) {
|
||||
// 闰日前后的描述必须仍是普通日期。
|
||||
func TestLunarToSolarTextKeepsLeapDayNeighboursOrdinary(t *testing.T) {
|
||||
for _, year := range julianOnlyYears {
|
||||
phantom := basic.JDECalc(year, 2, 29)
|
||||
phantom := basic.JDCalc(year, 2, 29)
|
||||
for _, offset := range []int{-1, 1} {
|
||||
// 取闰日前后各一天,用它们自己的农历日构造描述。
|
||||
neighbour := basic.JDE2DateByZone(phantom+float64(offset), time.FixedZone("CST", 8*3600), true)
|
||||
neighbour := basic.JD2DateByZone(phantom+float64(offset), time.FixedZone("CST", 8*3600), true)
|
||||
fwd, err := SolarToLunarByYMD(neighbour.Year(), int(neighbour.Month()), neighbour.Day())
|
||||
if err != nil {
|
||||
t.Fatalf("year %d: SolarToLunarByYMD(%s) error: %v", year, neighbour.Format("2006-01-02"), err)
|
||||
@@ -282,8 +282,8 @@ func TestLunarToSolarTextKeepsLeapDayNeighboursOrdinary(t *testing.T) {
|
||||
if got.JulianOnly() {
|
||||
t.Errorf("%s: unexpectedly flagged JulianOnly", desc)
|
||||
}
|
||||
if want := phantom + float64(offset); got.JDE() != want {
|
||||
t.Errorf("%s: JDE=%.1f, want %.1f", desc, got.JDE(), want)
|
||||
if want := phantom + float64(offset); got.JD() != want {
|
||||
t.Errorf("%s: JD=%.1f, want %.1f", desc, got.JD(), want)
|
||||
}
|
||||
if got := identityOf(results[0]); got != id {
|
||||
t.Errorf("%s: identity %+v, want %+v", desc, got, id)
|
||||
|
||||
@@ -109,10 +109,10 @@ func TestAncientYearStartCrossingRoundTrips(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// 王莽改历的 23/24 年交界:同一套月序偏移会把"腊月三十"反解到腊月初一(标签碰撞),
|
||||
// 王莽改历的 23/24 年交界:同一套月序偏移会把"腊月三十"反解到腊月初一(规范日碰撞),
|
||||
// 此时应改用另一套偏移下能正向回到该农历日的那一天,并把主答案换成它。
|
||||
// At the Wang Mang 23/24 boundary the primary month offset resolves "12th month, day 30" onto the
|
||||
// 1st of that month (a label collision); the reverse must switch to the offset whose civil day
|
||||
// 1st of that month (a canonical-day collision); the reverse must switch to the offset whose civil day
|
||||
// converts forward to the requested lunar date and make that day the primary answer.
|
||||
func TestWangMangBoundaryReverseIsSelfConsistent(t *testing.T) {
|
||||
cases := []struct {
|
||||
@@ -147,8 +147,8 @@ func TestWangMangBoundaryReverseIsSelfConsistent(t *testing.T) {
|
||||
t.Errorf("lunar %d/%d/%d -> %s converts forward to %d/%d/%d",
|
||||
c.ly, c.lm, c.ld, c.wantSolar, l.LunarYear(), l.LunarMonth(), l.LunarDay())
|
||||
}
|
||||
if res.JDE() != fwd.JDE() {
|
||||
t.Errorf("lunar %d/%d/%d: JDE=%.1f, forward gives %.1f", c.ly, c.lm, c.ld, res.JDE(), fwd.JDE())
|
||||
if res.JD() != fwd.JD() {
|
||||
t.Errorf("lunar %d/%d/%d: JDE=%.1f, forward gives %.1f", c.ly, c.lm, c.ld, res.JD(), fwd.JD())
|
||||
}
|
||||
for _, cand := range res.SolarCandidates() {
|
||||
candFwd, err := SolarToLunarByYMD(cand.Year(), int(cand.Month()), cand.Day())
|
||||
|
||||
@@ -105,7 +105,7 @@ func TestGregorianSkippedDaysHaveNoCandidates(t *testing.T) {
|
||||
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 label %q is not a valid date", formatAncientSolar(res.Solar()))
|
||||
t.Errorf("solar canonical day %q is not a valid date", formatAncientSolar(res.Solar()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
-20
@@ -32,8 +32,8 @@ type LunarInfo struct {
|
||||
CalendarSystem AncientCalendarSystem `json:"calendarSystem"`
|
||||
// CalendarName 历法名称
|
||||
CalendarName string `json:"calendarName"`
|
||||
// JDE 该农历日精确的儒略日 / exact Julian day.
|
||||
JDE float64 `json:"jde"`
|
||||
// JD 该农历日精确的儒略日 / exact Julian day.
|
||||
JD float64 `json:"jd"`
|
||||
// JulianOnly 是否只存在于儒略历 / Julian-calendar-only date.
|
||||
JulianOnly bool `json:"julianOnly,omitempty"`
|
||||
// Dynasty 朝代,如唐、宋、元、明、清等
|
||||
@@ -174,8 +174,8 @@ func (t Time) Lunar() LunarTime {
|
||||
func (t Time) Add(d time.Duration) Time {
|
||||
newT := t.solarTime.Add(d)
|
||||
if crossesGregorianReformGap(t.solarTime, newT) {
|
||||
jde := Date2JDE(t.solarTime) + d.Seconds()/86400.0
|
||||
newT = basic.JDE2DateByZone(jde, t.solarTime.Location(), true)
|
||||
jd := Date2JD(t.solarTime) + d.Seconds()/86400.0
|
||||
newT = basic.JD2DateByZone(jd, t.solarTime.Location(), true)
|
||||
}
|
||||
rT, _ := SolarToLunar(newT)
|
||||
return rT
|
||||
@@ -213,10 +213,10 @@ type LunarTime struct {
|
||||
calendarSystem AncientCalendarSystem
|
||||
//历法名称
|
||||
calendarName string
|
||||
// julianOnly 该农历日是否只存在于儒略历(如 700-02-29);Solar 取后继日作为标签。
|
||||
// julianOnly 该农历日是否只存在于儒略历(如 700-02-29);Solar 取后继日作为规范日。
|
||||
julianOnly bool
|
||||
// jde 该农历日精确的儒略日,仅在 julianOnly 时显式保存。
|
||||
jde float64
|
||||
// jd 该农历日精确的儒略日,仅在 julianOnly 时显式保存。
|
||||
jd float64
|
||||
|
||||
eras []EraDesc
|
||||
}
|
||||
@@ -226,12 +226,12 @@ func (l LunarTime) JulianOnly() bool {
|
||||
return l.julianOnly
|
||||
}
|
||||
|
||||
// JDE 该农历日精确的儒略日;儒略历独有闰日比 Solar() 早一天 / exact Julian day.
|
||||
func (l LunarTime) JDE() float64 {
|
||||
if l.julianOnly && l.jde > 0 {
|
||||
return l.jde
|
||||
// JD 该农历日精确的儒略日;儒略历独有闰日比 Solar() 早一天 / exact Julian day.
|
||||
func (l LunarTime) JD() float64 {
|
||||
if l.julianOnly && l.jd > 0 {
|
||||
return l.jd
|
||||
}
|
||||
return Date2JDE(l.solarDate)
|
||||
return Date2JD(l.solarDate)
|
||||
}
|
||||
|
||||
// JulianOnly 主历法农历日是否只存在于儒略历 / whether the primary date is Julian-calendar-only.
|
||||
@@ -242,12 +242,12 @@ func (t Time) JulianOnly() bool {
|
||||
return t.lunars[0].JulianOnly()
|
||||
}
|
||||
|
||||
// JDE 主历法农历日精确的儒略日 / exact Julian day of the primary lunar date.
|
||||
func (t Time) JDE() float64 {
|
||||
// JD 主历法农历日精确的儒略日 / exact Julian day of the primary lunar date.
|
||||
func (t Time) JD() float64 {
|
||||
if len(t.lunars) == 0 {
|
||||
return Date2JDE(t.solarTime)
|
||||
return Date2JD(t.solarTime)
|
||||
}
|
||||
return t.lunars[0].JDE()
|
||||
return t.lunars[0].JD()
|
||||
}
|
||||
|
||||
// SolarCandidates 该农历日的全部合法公历候选,首个恒等于 Solar() / every legal civil date, Solar() first.
|
||||
@@ -287,8 +287,8 @@ func (l LunarTime) GanZhiMonth() string {
|
||||
|
||||
// GanZhiDay 日干支 / sexagenary day name.
|
||||
func (l LunarTime) GanZhiDay() string {
|
||||
if l.julianOnly && l.jde > 0 {
|
||||
return ganZhiOfJDE(l.jde)
|
||||
if l.julianOnly && l.jd > 0 {
|
||||
return ganZhiOfJD(l.jd)
|
||||
}
|
||||
return GanZhiOfDay(l.solarDate)
|
||||
}
|
||||
@@ -419,7 +419,7 @@ func (l LunarTime) LunarInfo() []LunarInfo {
|
||||
EraDesc: v.String(),
|
||||
LunarWithEraDesc: v.String() + l.desc,
|
||||
ChineseZodiac: l.ShengXiao(),
|
||||
JDE: l.JDE(),
|
||||
JD: l.JD(),
|
||||
JulianOnly: l.julianOnly,
|
||||
}
|
||||
res = append(res, li)
|
||||
@@ -445,7 +445,7 @@ func (l LunarTime) LunarInfo() []LunarInfo {
|
||||
EraDesc: lunarYearDesc(l.year) + "年",
|
||||
LunarWithEraDesc: lunarYearDesc(l.year) + "年" + l.desc,
|
||||
ChineseZodiac: l.ShengXiao(),
|
||||
JDE: l.JDE(),
|
||||
JD: l.JD(),
|
||||
JulianOnly: l.julianOnly,
|
||||
}
|
||||
res = append(res, li)
|
||||
|
||||
Reference in New Issue
Block a user