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) }