feat: 完善时标与天象几何计算并扩展输出接口
- 新增时标、ΔT 模型、质心时间与 UT1 支持 - 改进日月食、月掩、行星事件及路径边界计算 - 完善恒星三维自行与动态距离传播 - 扩展 SVG、GeoJSON、KML 输出与底层距离换算工具 - 整理中英文手册、示例资源及回归测试
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
package civiltime
|
||||
|
||||
import "time"
|
||||
|
||||
// Event 合并变长民用日的固定偏移候选,handled=false 表示普通日期 / merges fixed-offset candidates on a civil day with an offset change.
|
||||
func Event(date time.Time, missing error, calculate func(time.Time) (time.Time, error)) (result time.Time, err error, handled bool) {
|
||||
year, month, day := date.Date()
|
||||
start := time.Date(year, month, day, 0, 0, 0, 0, date.Location())
|
||||
end := time.Date(year, month, day+1, 0, 0, 0, 0, date.Location())
|
||||
_, firstOffset := start.Zone()
|
||||
_, lastOffset := end.Add(-time.Nanosecond).Zone()
|
||||
if firstOffset == lastOffset {
|
||||
return time.Time{}, nil, false
|
||||
}
|
||||
var firstErr error
|
||||
for _, offset := range []int{firstOffset, lastOffset} {
|
||||
fixed := time.Date(year, month, day, 0, 0, 0, 0, time.FixedZone("", offset))
|
||||
candidate, candidateErr := calculate(fixed)
|
||||
if candidateErr != nil {
|
||||
if firstErr == nil {
|
||||
firstErr = candidateErr
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !candidate.Before(start) && candidate.Before(end) && (result.IsZero() || candidate.Before(result)) {
|
||||
result = candidate.In(date.Location())
|
||||
}
|
||||
}
|
||||
if !result.IsZero() {
|
||||
return result, nil, true
|
||||
}
|
||||
if firstErr != nil {
|
||||
return time.Time{}, firstErr, true
|
||||
}
|
||||
return time.Time{}, missing, true
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package civiltime
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestEventFiltersByActualCivilDay(t *testing.T) {
|
||||
loc, err := time.LoadLocation("America/New_York")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
missing := errors.New("no event on requested day")
|
||||
cases := []struct {
|
||||
month, day, offset, hour int
|
||||
wantFound bool
|
||||
}{
|
||||
{3, 8, -5, 23, false},
|
||||
{11, 1, -5, 23, true},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
date := time.Date(2026, time.Month(tc.month), tc.day, 12, 0, 0, 0, loc)
|
||||
candidate := time.Date(2026, time.Month(tc.month), tc.day, tc.hour, 30, 0, 0, time.FixedZone("", tc.offset*3600))
|
||||
calls := 0
|
||||
got, err, handled := Event(date, missing, func(fixed time.Time) (time.Time, error) {
|
||||
calls++
|
||||
if fixed.Location() == loc {
|
||||
t.Fatal("calculation must use a fixed offset")
|
||||
}
|
||||
return candidate, nil
|
||||
})
|
||||
if !handled || calls != 2 {
|
||||
t.Fatalf("handled=%t calls=%d", handled, calls)
|
||||
}
|
||||
if tc.wantFound {
|
||||
if err != nil || !got.Equal(candidate) || got.Location() != loc {
|
||||
t.Errorf("got %s, %v; want %s", got, err, candidate)
|
||||
}
|
||||
} else if !got.IsZero() || !errors.Is(err, missing) {
|
||||
t.Errorf("outside-day result %s, %v", got, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventLeavesFixedOffsetToCaller(t *testing.T) {
|
||||
date := time.Date(2026, 11, 1, 12, 0, 0, 0, time.FixedZone("EST", -5*3600))
|
||||
_, _, handled := Event(date, nil, func(time.Time) (time.Time, error) {
|
||||
t.Fatal("ordinary day invoked fallback")
|
||||
return time.Time{}, nil
|
||||
})
|
||||
if handled {
|
||||
t.Fatal("ordinary day reported handled")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user