Files
astro/basic/timescale.go
T
b612 16c62a97d5 feat: 完善时标与天象几何计算并扩展输出接口
- 新增时标、ΔT 模型、质心时间与 UT1 支持
- 改进日月食、月掩、行星事件及路径边界计算
- 完善恒星三维自行与动态距离传播
- 扩展 SVG、GeoJSON、KML 输出与底层距离换算工具
- 整理中英文手册、示例资源及回归测试
2026-09-23 18:55:12 +08:00

449 lines
17 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package basic
import (
"math"
"sync"
)
// TimeScaleFuturePolicy 民用时标换算政策,零值为闰秒情景外推 / civil-time conversion policy, defaulting to leap-second extrapolation.
type TimeScaleFuturePolicy int
const (
// TimeScaleLeapSecond 默认按 ΔT 越限施加整数秒校正,不代表闰秒公告 / default integer-second corrections driven by extrapolated ΔT, not announcements.
TimeScaleLeapSecond TimeScaleFuturePolicy = iota
// TimeScaleAssumeUT1Tracking 窗口外固定末端 DUT1,平滑跟随 UT1 / holds the last observed DUT1 beyond the window.
TimeScaleAssumeUT1Tracking
// TimeScaleFreezeUTCOffset 窗口外固定末端 TT−UTC / holds the last TT−UTC offset beyond the window.
TimeScaleFreezeUTCOffset
// TimeScaleLeapHour 按 ΔT 越限施加整小时校正,仅作情景演算 / applies hour-sized corrections as a scenario assumption.
TimeScaleLeapHour
// TimeScaleUT1Civil 全时轴民用时标等同 UT1,TT−UTC 覆盖仍优先 / uses UT1 as civil time everywhere unless TT−UTC is overridden.
TimeScaleUT1Civil
)
// 闰时情景的校正步长与容限,单位秒。
const timeScaleLeapHourSeconds = 3600.0
// 闰秒情景的 DUT1 容限,单位秒。
const utcDUT1ToleranceSeconds = 0.9
// utcEraStartJDE 是 UTC 按 SI 秒运行的起点(1972-01-01),此前民用时标即 UT1。
const utcEraStartJDE = 2441317.5
// 实测窗口末端,必须与月度 ΔT 表末项同步。
const timeScaleExactEndJDE = 2461284.5
// 闰秒生效的儒略日按降序排列,与 utcLeapOffsets 一一对应。
var utcLeapJDEs = [...]float64{
2457754.5, 2457204.5, 2456109.5, 2454832.5,
2453736.5, 2451179.5, 2450630.5, 2450083.5,
2449534.5, 2449169.5, 2448804.5, 2448257.5,
2447892.5, 2447161.5, 2446247.5, 2445516.5,
2445151.5, 2444786.5, 2444239.5, 2443874.5,
2443509.5, 2443144.5, 2442778.5, 2442413.5,
2442048.5, 2441683.5, 2441499.5, 2441133.5,
}
// utcLeapOffsets[i] 是 utcLeapJDEs[i] 起生效的 TT−UTC(秒),与上表顺序一致。
var utcLeapOffsets = [...]float64{
69.184, 68.184, 67.184, 66.184,
65.184, 64.184, 63.184, 62.184,
61.184, 60.184, 59.184, 58.184,
57.184, 56.184, 55.184, 54.184,
53.184, 52.184, 51.184, 50.184,
49.184, 48.184, 47.184, 46.184,
45.184, 44.184, 43.184, 42.184,
}
// utcLeapBaseSeconds 是 1972-01-01 起算的 TT−UTC 基值:32.184 + (TAI−UTC 的 10 秒)。
const utcLeapBaseSeconds = 42.184
var (
timeScaleMu sync.RWMutex
ttMinusUTCFn func(float64) float64
futurePolicy TimeScaleFuturePolicy
)
// SetTTMinusUTCFn 覆盖 TT−UTC,nil 恢复内置表与政策 / overrides TT−UTC; nil restores the built-in table and policy.
func SetTTMinusUTCFn(fn func(float64) float64) {
timeScaleMu.Lock()
ttMinusUTCFn = fn
timeScaleMu.Unlock()
deltaTFnMu.Lock()
deltaTGeneration++
deltaTFnMu.Unlock()
}
// GetTTMinusUTCFn 返回当前 TT−UTC 覆盖函数,未设置时为 nil / current TT−UTC override, or nil.
func GetTTMinusUTCFn() func(float64) float64 {
timeScaleMu.RLock()
defer timeScaleMu.RUnlock()
return ttMinusUTCFn
}
// SetTimeScaleFuturePolicy 设置民用时标换算政策 / sets the civil-time conversion policy.
func SetTimeScaleFuturePolicy(policy TimeScaleFuturePolicy) {
timeScaleMu.Lock()
futurePolicy = policy
timeScaleMu.Unlock()
deltaTFnMu.Lock()
deltaTGeneration++
deltaTFnMu.Unlock()
}
// GetTimeScaleFuturePolicy 返回当前民用时标换算政策 / current civil-time conversion policy.
func GetTimeScaleFuturePolicy() TimeScaleFuturePolicy {
timeScaleMu.RLock()
defer timeScaleMu.RUnlock()
return futurePolicy
}
// TTMinusUTCSeconds 返回 TT−UTC 覆盖值或内置表值,不应用未来政策 / TT−UTC override or leap-table seconds, without future policy.
func TTMinusUTCSeconds(jd float64) float64 {
timeScaleMu.RLock()
fn := ttMinusUTCFn
timeScaleMu.RUnlock()
if fn != nil {
return fn(jd)
}
return ttMinusUTCSecondsDefault(jd)
}
// UTC2TT 民用时刻转 TT,1972 年前按 UT1、之后按覆盖或所选政策 / converts civil time to TT using UT1 before 1972, then the override or selected policy.
func UTC2TT(jd float64) float64 {
return utcToTTJDE(jd)
}
// TT2UTC 反解 UTC2TT,阶跃区间内往返不唯一 / inverts UTC2TT, with ambiguous round trips within step intervals.
func TT2UTC(ttJDE float64) float64 {
return ttToUTCJDE(ttJDE)
}
// UT12TT 按当前 ΔT 模型将 UT1 转为 TT / converts UT1 to TT using the active ΔT model.
func UT12TT(jd float64) float64 {
return ut1ToTTJDE(jd)
}
// TT2UT1 是 UT12TT 的逆 / inverts UT12TT.
func TT2UT1(ttJDE float64) float64 {
return ttToUT1JDE(ttJDE)
}
// UTC2UT1 民用时刻转 UT1,窗口之后按当前未来政策 / converts civil time to UT1 under the active future policy.
func UTC2UT1(jd float64) float64 {
return ttToUT1JDE(utcToTTJDE(jd))
}
// UT12UTC 是 UTC2UT1 的逆 / inverts UTC2UT1.
func UT12UTC(ut1JDE float64) float64 {
return ttToUTCJDE(ut1ToTTJDE(ut1JDE))
}
// DUT1Seconds 返回 UT1−UTC(秒),采用当前覆盖与政策 / UT1−UTC in seconds under the active overrides and policy.
func DUT1Seconds(jd float64) float64 {
return utcToTTOffsetSeconds(jd) - ut1ToTTOffsetSeconds(jd)
}
// 表外沿用最近的已知偏移,不应用未来政策。
func ttMinusUTCSecondsDefault(jd float64) float64 {
for i := range utcLeapJDEs {
if jd >= utcLeapJDEs[i] {
return utcLeapOffsets[i]
}
}
return utcLeapBaseSeconds
}
// TTMinusUTCSecondsDefault 返回内置闰秒表的 TT−UTC(秒),忽略覆盖 / built-in TT−UTC seconds, ignoring overrides.
func TTMinusUTCSecondsDefault(jd float64) float64 {
return ttMinusUTCSecondsDefault(jd)
}
// deltaTModelSecondsAtUT 是内置 ΔT 模型(秒):覆盖期内用逐月实测表(毫秒级),表外用外推样条。
func deltaTModelSecondsAtUT(jd float64) float64 {
if seconds, ok := deltaTMonthlyAt(jd); ok {
return seconds
}
// 两端按常值锚定,避免 ΔT 跳变使反解落到窗口另一侧。
if jd < deltaTMonthlyJDE[0] {
return deltaTSplineAtJDE(jd) + deltaTMonthlyObserved[0] - deltaTSplineAtJDE(deltaTMonthlyJDE[0])
}
last := len(deltaTMonthlyJDE) - 1
return deltaTSplineAtJDE(jd) + deltaTMonthlyObserved[last] - deltaTSplineAtJDE(deltaTMonthlyJDE[last])
}
// deltaTMonthlyAt 在逐月实测表覆盖范围内线性插值,范围外 ok 为 false。
func deltaTMonthlyAt(jd float64) (float64, bool) {
count := len(deltaTMonthlyJDE)
if count == 0 || jd < deltaTMonthlyJDE[0] || jd > deltaTMonthlyJDE[count-1] {
return 0, false
}
low, high := 0, count-1
for high-low > 1 {
mid := (low + high) / 2
if deltaTMonthlyJDE[mid] <= jd {
low = mid
} else {
high = mid
}
}
span := deltaTMonthlyJDE[high] - deltaTMonthlyJDE[low]
if span <= 0 {
return deltaTMonthlyObserved[low], true
}
ratio := (jd - deltaTMonthlyJDE[low]) / span
return deltaTMonthlyObserved[low] + ratio*(deltaTMonthlyObserved[high]-deltaTMonthlyObserved[low]), true
}
// deltaTYearAtJDE 把 UT 儒略日换成十进制年:1582 改历之后按格里高利年平均长度,之前按儒略年。
func deltaTYearAtJDE(jd float64) float64 {
if jd >= 2299160.5 {
return (jd-2451544.5)/365.2425 + 2000
}
return (jd+0.5)/365.25 - 4712
}
// deltaTSplineAtJDE 用外推样条求 TT−UT1(秒),覆盖表外的古代与未来。
func deltaTSplineAtJDE(jd float64) float64 {
year := deltaTYearAtJDE(jd)
return DeltaTSplineY(year)
}
// 直接求秒差,避免两个大 JD 相减导致精度损失。
func utcToTTOffsetSeconds(jd float64) float64 {
if jd < utcEraStartJDE {
// 1972 前民用时标即 UT1。
return ut1ToTTOffsetSeconds(jd)
}
// 1972 年后显式覆盖优先于全部政策。
if fn := GetTTMinusUTCFn(); fn != nil {
return fn(jd)
}
// UT1Civil 替换全时轴定义,须先于窗口内闰秒表判断。
if GetTimeScaleFuturePolicy() == TimeScaleUT1Civil {
return ut1ToTTOffsetSeconds(jd)
}
if jd <= timeScaleExactEndJDE {
return TTMinusUTCSeconds(jd)
}
switch GetTimeScaleFuturePolicy() {
case TimeScaleAssumeUT1Tracking:
return ut1ToTTOffsetSeconds(jd) + dut1AtTimeScaleExactEnd()
case TimeScaleFreezeUTCOffset:
return TTMinusUTCSeconds(timeScaleExactEndJDE)
case TimeScaleLeapHour:
return steppedUTCOffsetSeconds(jd, timeScaleLeapHourSeconds, timeScaleLeapHourSeconds)
}
return steppedUTCOffsetSeconds(jd, 1, utcDUT1ToleranceSeconds)
}
// 相对窗口末端偏移施加最少整数步校正,使 DUT1 落回容限内。
func steppedUTCOffsetSeconds(jd, step, tolerance float64) float64 {
base := TTMinusUTCSeconds(timeScaleExactEndJDE)
drift := base - ut1ToTTOffsetSeconds(jd)
if math.IsNaN(drift) || math.IsInf(drift, 0) {
return math.NaN()
}
switch {
case drift <= -tolerance:
return base + step*(math.Floor((-drift-tolerance)/step)+1)
case drift >= tolerance:
return base - step*(math.Floor((drift-tolerance)/step)+1)
}
return base
}
// dut1AtTimeScaleExactEnd 是窗口末端的实测 UT1−UTC(秒),TimeScaleAssumeUT1Tracking 沿用此值。
func dut1AtTimeScaleExactEnd() float64 {
return TTMinusUTCSeconds(timeScaleExactEndJDE) - ut1ToTTOffsetSeconds(timeScaleExactEndJDE)
}
func ut1ToTTOffsetSeconds(jd float64) float64 {
return DeltaT(jd, true)
}
func utcToTTJDE(jd float64) float64 {
return jd + utcToTTOffsetSeconds(jd)/86400
}
func ut1ToTTJDE(jd float64) float64 {
return jd + ut1ToTTOffsetSeconds(jd)/86400
}
// ttToUT1JDE 解 TT = UT12TT(ut),与正向使用同一模型,保证往返一致。
func ttToUT1JDE(ttJDE float64) float64 {
ut := ttJDE - ut1ToTTOffsetSeconds(ttJDE)/86400
for iteration := 0; iteration < 4; iteration++ {
next := ttJDE - ut1ToTTOffsetSeconds(ut)/86400
if next == ut {
break
}
ut = next
}
return ut
}
// ttToUTCJDE 解 TT = UTC2TT(utc):分支由候选时刻判定,否则边界处会来回振荡。
func ttToUTCJDE(ttJDE float64) float64 {
if math.IsNaN(ttJDE) || math.IsInf(ttJDE, 0) {
return math.NaN()
}
utc := ttJDE - utcToTTOffsetSeconds(ttJDE)/86400
for iteration := 0; iteration < 4; iteration++ {
next := ttJDE - utcToTTOffsetSeconds(utc)/86400
if next == utc {
break
}
utc = next
}
return utc
}
var defDeltaTFn = DefaultDeltaTv2
var deltaTFnMu sync.RWMutex
var activeDeltaTModel = DeltaTModelDefault
var activeDeltaTKeepObserved = true
// 配置变化使缓存世代递增,起始为 1 以排除零值缓存。
var deltaTGeneration uint64 = 1
// 两种输入口径共用的 ΔT 模型年限。
const deltaTValidYearSpan = 40000.0
// DeltaT 返回 TT−UT1(秒),julianDay 为真取 UT JD、否则取十进制年,超出 ±40000 年返回 NaN / TT−UT1 seconds from a UT JD if julianDay, otherwise a decimal year; NaN beyond ±40000 years.
func DeltaT(date float64, julianDay bool) float64 {
if !math.IsNaN(date) && !math.IsInf(date, 0) && math.Abs(deltaTArgumentYear(date, julianDay)) > deltaTValidYearSpan {
return math.NaN()
}
deltaTFnMu.RLock()
fn := defDeltaTFn
deltaTFnMu.RUnlock()
return fn(date, julianDay)
}
// deltaTArgumentYear 把两种自变量口径统一成十进制年,只用于越界判定。
func deltaTArgumentYear(date float64, julianDay bool) float64 {
if julianDay {
return deltaTYearAtJDE(date)
}
return math.Floor(date)
}
// SetDeltaTFn 注入秒单位的 ΔT 模型并标记为 Manual,nil 恢复默认 / installs a manual ΔT model in seconds; nil restores the default.
func SetDeltaTFn(fn func(float64, bool) float64) {
if fn == nil {
installDeltaTFn(DefaultDeltaTv2, DeltaTModelDefault, true)
return
}
installDeltaTFn(fn, DeltaTModelManual, false)
}
// installDeltaTFn 是 ΔT 钩子的唯一写入口:函数、模型标记与世代号一起更新。
func installDeltaTFn(fn func(float64, bool) float64, model DeltaTModel, keepObserved bool) {
deltaTFnMu.Lock()
defDeltaTFn = fn
activeDeltaTModel = model
activeDeltaTKeepObserved = keepObserved
deltaTGeneration++
deltaTFnMu.Unlock()
}
func deltaTGenerationValue() uint64 {
deltaTFnMu.RLock()
value := deltaTGeneration
deltaTFnMu.RUnlock()
return value
}
// GetDeltaTFn 返回当前生效的 ΔT 计算函数,其入参为年或儒略日 / current ΔT function, taking a year or a Julian day.
func GetDeltaTFn() func(float64, bool) float64 {
deltaTFnMu.RLock()
fn := defDeltaTFn
deltaTFnMu.RUnlock()
return fn
}
// DefaultDeltaTv2 库内默认 ΔT 计算,date 为年或儒略日(isJd),返回秒 / default ΔT computation in seconds.
func DefaultDeltaTv2(date float64, isJd bool) float64 {
if math.IsNaN(date) || math.IsInf(date, 0) {
return math.NaN()
}
if !isJd {
year := math.Floor(date)
start := JDCalc(int(year), 1, 1)
end := JDCalc(int(year)+1, 1, 1)
date = start + (date-year)*(end-start)
}
return DeltaTv2(date)
}
// DeltaTSplineY 按十进制年计算样条与长期外推 ΔT(秒)/ spline and long-term ΔT in seconds for a decimal year.
func DeltaTSplineY(y float64) float64 {
if math.IsNaN(y) || math.IsInf(y, 0) {
return math.NaN()
}
// 日长偏差积分后的秒差,积分常数在两侧分别锚定。
integratedLod := func(x float64) float64 {
u := x - 1825
return 3.14115e-3*u*u + 284.8435805251424*math.Cos(0.4487989505128276*(0.01*u+0.75))
}
if y < -720 {
const c = 1.007739546148514
return integratedLod(y) + c
}
if y > 2025 {
const c = -150.56787057979514
return integratedLod(y) + c
}
n := len(deltaTSplineY0)
var i int
for i = n - 1; i >= 0; i-- {
if y >= deltaTSplineY0[i] {
break
}
}
t := (y - deltaTSplineY0[i]) / (deltaTSplineY1[i] - deltaTSplineY0[i])
dT := deltaTSplineA0[i] + t*(deltaTSplineA1[i]+t*(deltaTSplineA2[i]+t*deltaTSplineA3[i]))
return dT
}
// 区间内 t = (year−Y0)/(Y1−Y0),ΔT = A0 + t·(A1 + t·(A2 + t·A3))。
var deltaTSplineY0 = [...]float64{-720, -100, 400, 1000, 1150, 1300, 1500, 1600, 1650, 1720, 1800, 1810, 1820, 1830, 1840, 1850, 1855, 1860, 1865, 1870, 1875, 1880, 1885, 1890, 1895, 1900, 1905, 1910, 1915, 1920, 1925, 1930, 1935, 1940, 1945, 1950, 1953, 1956, 1959, 1962, 1965, 1968, 1971, 1974, 1977, 1980, 1983, 1986, 1989, 1992, 1995, 1998, 2001, 2004, 2007, 2010, 2013, 2016, 2019, 2022}
var deltaTSplineY1 = [...]float64{-100, 400, 1000, 1150, 1300, 1500, 1600, 1650, 1720, 1800, 1810, 1820, 1830, 1840, 1850, 1855, 1860, 1865, 1870, 1875, 1880, 1885, 1890, 1895, 1900, 1905, 1910, 1915, 1920, 1925, 1930, 1935, 1940, 1945, 1950, 1953, 1956, 1959, 1962, 1965, 1968, 1971, 1974, 1977, 1980, 1983, 1986, 1989, 1992, 1995, 1998, 2001, 2004, 2007, 2010, 2013, 2016, 2019, 2022, 2025}
var deltaTSplineA0 = [...]float64{20371.848, 11557.668, 6535.116, 1650.393, 1056.647, 681.149, 292.343, 109.127, 43.952, 12.068, 18.367, 15.678, 16.516, 10.804, 7.634, 9.338, 10.357, 9.04, 8.255, 2.371, -1.126, -3.21, -4.388, -3.884, -5.017, -1.977, 4.923, 11.142, 17.479, 21.617, 23.789, 24.418, 24.164, 24.426, 27.05, 28.932, 30.002, 30.76, 32.652, 33.621, 35.093, 37.956, 40.951, 44.244, 47.291, 50.361, 52.936, 54.984, 56.373, 58.453, 60.678, 62.898, 64.083, 64.553, 65.197, 66.061, 66.919, 68.130, 69.250, 69.296}
var deltaTSplineA1 = [...]float64{-9999.586, -5822.27, -5671.519, -753.21, -459.628, -421.345, -192.841, -78.697, -68.089, 2.507, -3.481, 0.021, -2.157, -6.018, -0.416, 1.642, -0.486, -0.591, -3.456, -5.593, -2.314, -1.893, 0.101, -0.531, 0.134, 5.715, 6.828, 6.33, 5.518, 3.02, 1.333, 0.052, -0.419, 1.645, 2.499, 1.127, 0.737, 1.409, 1.577, 0.868, 2.275, 3.035, 3.157, 3.199, 3.069, 2.878, 2.354, 1.577, 1.648, 2.235, 2.324, 1.804, 0.674, 0.466, 0.804, 0.839, 1.005, 1.348, 0.594, -0.227}
var deltaTSplineA2 = [...]float64{776.247, 1303.151, -298.291, 184.811, 108.771, 61.953, -6.572, 10.505, 38.333, 41.731, -1.126, 4.629, -6.806, 2.944, 2.658, 0.261, -2.389, 2.284, -5.148, 3.011, 0.269, 0.152, 1.842, -2.474, 3.138, 2.443, -1.329, 0.831, -1.643, -0.856, -0.831, -0.449, -0.022, 2.086, -1.232, 0.22, -0.61, 1.282, -1.115, 0.406, 1.002, -0.242, 0.364, -0.323, 0.193, -0.384, -0.14, -0.637, 0.708, -0.121, 0.21, -0.729, -0.402, 0.194, 0.144, -0.109, 0.275, 0.068, -0.822, 0.001}
var deltaTSplineA3 = [...]float64{409.16, -503.433, 1085.087, -25.346, -24.641, -29.414, 16.197, 3.018, -2.127, -37.939, 1.918, -3.812, 3.25, -0.096, -0.539, -0.883, 1.558, -2.477, 2.72, -0.914, -0.039, 0.563, -1.438, 1.871, -0.232, -1.257, 0.72, -0.825, 0.262, 0.008, 0.127, 0.142, 0.702, -1.106, 0.614, -0.277, 0.631, -0.799, 0.507, 0.199, -0.414, 0.202, -0.229, 0.172, -0.192, 0.081, -0.165, 0.448, -0.276, 0.11, -0.313, 0.109, 0.199, -0.017, -0.084, 0.128, -0.069, -0.297, 0.274, 0.086}
// DeltaTv2 返回内置模型的 TT−UT1(秒),实测表外采用外推样条 / built-in TT−UT1 seconds, with spline extrapolation outside the observed table.
func DeltaTv2(jd float64) float64 {
if math.IsNaN(jd) || math.IsInf(jd, 0) {
return math.NaN()
}
return deltaTModelSecondsAtUT(jd)
}
// DeltaTSecondsAt 返回 TT 时刻的 ΔT,有限覆盖值优先,NaN/Inf 选择当前模型 / ΔT at a TT instant, using a finite override or the active model.
func DeltaTSecondsAt(jdeTT, overrideSeconds float64) float64 {
if !math.IsNaN(overrideSeconds) && !math.IsInf(overrideSeconds, 0) {
return overrideSeconds
}
return deltaTModelSecondsAtTT(jdeTT)
}
func deltaTModelSecondsAtTT(jdeTT float64) float64 {
// ΔT 模型以 UT 为自变量,不能直接传 TT。
return ut1ToTTOffsetSeconds(ttToUT1JDE(jdeTT))
}
// DeltaTGroundShiftKM 将 ΔT 误差换算为站点相对影子的地面横移距离(千米)/ ground displacement in kilometres from a ΔT error.
func DeltaTGroundShiftKM(deltaTSeconds, latitudeDeg float64) float64 {
if math.IsNaN(deltaTSeconds) || math.IsInf(deltaTSeconds, 0) ||
math.IsNaN(latitudeDeg) || math.IsInf(latitudeDeg, 0) {
return math.NaN()
}
return solarEclipseEarthEquatorialRotationKMPerSecond * math.Abs(deltaTSeconds) * math.Cos(latitudeDeg*rad)
}