astro/basic/calendar.go

447 lines
14 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 (
"fmt"
"math"
"strings"
"time"
)
var defDeltaTFn = DefaultDeltaTv2
// Date2JDE 日期转儒略日
func Date2JDE(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 JDECalc(date.Year(), int(date.Month()), day)
}
/*
@name: 儒略日计算
@dec: 计算给定时间的儒略日1582年改力后为格里高利历之前为儒略历
@ 请注意,传入的时间在天文计算中一般为力学时,应当注意和世界时的转化
*/
func JDECalc(Year, Month int, Day float64) float64 {
if Month == 1 || Month == 2 {
Year--
Month += 12
}
var tmpvarB int
tmpvar := fmt.Sprintf("%04d-%02d-%2d", Year, Month, int(math.Floor(Day)))
if strings.Compare(tmpvar, `1582-10-04`) != 1 {
tmpvarB = 0
} else {
tmpvarA := int(Year / 100)
tmpvarB = 2 - tmpvarA + int(tmpvarA/4)
}
return (math.Floor(365.25*(float64(Year)+4716.0)) + math.Floor(30.6001*float64(Month+1)) + Day + float64(tmpvarB) - 1524.5)
}
/*
@name: 获得当前儒略日时间:当地世界时,非格林尼治时间
*/
func GetNowJDE() (NowJDE float64) {
Time := float64(time.Now().Second())/3600.0/24.0 + float64(time.Now().Minute())/60.0/24.0 + float64(time.Now().Hour())/24.0
NowJDE = JDECalc(time.Now().Year(), int(time.Now().Month()), float64(time.Now().Day())+Time)
return
}
func DeltaT(date float64, isJDE bool) float64 {
return defDeltaTFn(date, isJDE)
}
func SetDeltaTFn(fn func(float64, bool) float64) {
if fn != nil {
defDeltaTFn = fn
}
}
func GetDeltaTFn() func(float64, bool) float64 {
return defDeltaTFn
}
func DefaultDeltaTv2(date float64, isJd bool) float64 { //传入年或儒略日,传出为秒
if !isJd {
date = JDECalc(int(date), int((date-math.Floor(date))*12)+1, (date-math.Floor(date))*365.25+1)
}
return DeltaTv2(date)
}
// 使用Stephenson等人(2016)和Morrison等人(2021)的拟合和外推公式计算Delta T
// http://astro.ukho.gov.uk/nao/lvm/
// 2010年后的系数已修改以包含2019年后的数据
// 返回Delta T单位为秒
func DeltaTSplineY(y float64) float64 {
// 积分lod平均太阳日偏离86400秒的偏差方程
// 来自 http://astro.ukho.gov.uk/nao/lvm/
// lod = 1.72 t 3.5 sin(2*pi*(t+0.75)/14) 单位ms/day其中 t = (y - 1825)/100
// 是从1825年开始的世纪数
// 使用 1ms = 1e-3s 和 1儒略年 = 365.25天,
// lod = 6.2823e-3 * Delta y - 1.278375*sin(2*pi/14*(Delta y /100 + 0.75) 单位s/year
// 其中 Delta y = y - 1825。积分该方程得到
// Integrate[lod, y] = 3.14115e-3*(Delta y)^2 + 894.8625/pi*cos(2*pi/14*(Delta y /100 + 0.75)
// 单位为秒。积分常数设为0。
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 {
// 使用积分lod + 常数
const c = 1.007739546148514
return integratedLod(y) + c
}
if y > 2025 {
// 使用积分lod + 常数
const c = -150.56787057979514
return integratedLod(y) + c
}
// 使用三次样条拟合
y0 := []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}
y1 := []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}
a0 := []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}
a1 := []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}
a2 := []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}
a3 := []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}
n := len(y0)
var i int
for i = n - 1; i >= 0; i-- {
if y >= y0[i] {
break
}
}
t := (y - y0[i]) / (y1[i] - y0[i])
dT := a0[i] + t*(a1[i]+t*(a2[i]+t*a3[i]))
return dT
}
func DeltaTv2(jd float64) float64 {
if jd > 2461041.5 || jd < 2441317.5 {
var y float64
if jd >= 2299160.5 {
y = (jd-2451544.5)/365.2425 + 2000
} else {
y = (jd+0.5)/365.25 - 4712
}
return DeltaTSplineY(y)
}
// 闰秒JD值
jdLeaps := []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}
n := len(jdLeaps)
DT := 42.184
for i := 0; i < n; i++ {
if jd > jdLeaps[i] {
DT += float64(n - i - 1)
break
}
}
return DT
}
func TD2UT(JDE float64, UT2TD bool) float64 { // true 世界时转力学时CCfalse 力学时转世界时VV
Deltat := DeltaT(JDE, true)
if UT2TD {
return JDE + Deltat/3600/24
} else {
return JDE - Deltat/3600/24
}
}
func JDE2Date(JD float64) time.Time {
JD = JD + 0.5
Z := float64(int(JD))
F := JD - Z
var A, B, Years, Months, Days float64
if Z < 2299161.0 {
A = Z
} else {
alpha := math.Floor((Z - 1867216.25) / 36524.25)
A = Z + 1 + alpha - math.Floor(alpha/4)
}
B = A + 1524
C := math.Floor((B - 122.1) / 365.25)
D := math.Floor(365.25 * C)
E := math.Floor((B - D) / 30.6001)
Days = B - D - math.Floor(30.6001*E) + F
if E < 14 {
Months = E - 1
}
if E == 14 || E == 15 {
Months = E - 13
}
if Months > 2 {
Years = C - 4716
}
if Months == 1 || Months == 2 {
Years = C - 4715
}
tms := (Days - math.Floor(Days)) * 24 * 3600
Days = math.Floor(Days)
tz, _ := time.LoadLocation("Local")
dates := time.Date(int(Years), time.Month(int(Months)), int(Days), 0, 0, 0, 0, tz)
return time.Unix(dates.Unix()+int64(tms), int64((tms-math.Floor(tms))*1000000000))
}
// JDE2DateByZone JDE儒略日转日期
// JD: 儒略日
// tz: 目标时区
// byZone: (true: 传入的儒略日视为目标时区当地时间的儒略日false: 传入的儒略日视为UTC时间的儒略日)
// 回参:转换后的日期,时区始终为目标时区
func JDE2DateByZone(JD float64, tz *time.Location, byZone bool) time.Time {
JD = JD + 0.5
Z := float64(int(JD))
F := JD - Z
var A, B, Years, Months, Days float64
if Z < 2299161.0 {
A = Z
} else {
alpha := math.Floor((Z - 1867216.25) / 36524.25)
A = Z + 1 + alpha - math.Floor(alpha/4)
}
B = A + 1524
C := math.Floor((B - 122.1) / 365.25)
D := math.Floor(365.25 * C)
E := math.Floor((B - D) / 30.6001)
Days = B - D - math.Floor(30.6001*E) + F
if E < 14 {
Months = E - 1
}
if E == 14 || E == 15 {
Months = E - 13
}
if Months > 2 {
Years = C - 4716
}
if Months == 1 || Months == 2 {
Years = C - 4715
}
tms := (Days - math.Floor(Days)) * 24 * 3600
Days = math.Floor(Days)
var transTz = tz
if !byZone {
transTz = time.UTC
}
return time.Date(int(Years), time.Month(int(Months)), int(Days), 0, 0, 0, 0, transTz).
Add(time.Duration(int64(1000000000 * tms))).In(tz)
}
func GetLunar(year, month, day int, tz float64) (lyear, lmonth, lday int, leap bool, result string) {
julianDayEpoch := JDECalc(year, month, float64(day))
// 确定农历年份
lyear = year
adjustedYear := year
if month == 11 || month == 12 {
winterSolsticeDay := GetJQTime(year, 270) + tz
firstNewMoonDay := TD2UT(CalcMoonS(float64(year)+11.0/12.0+5.0/30.0/12.0, 0), true) + tz
nextNewMoonDay := TD2UT(CalcMoonS(float64(year)+1.0, 0), true) + tz
firstNewMoonDay = normalizeTimePoint(firstNewMoonDay)
nextNewMoonDay = normalizeTimePoint(nextNewMoonDay)
if winterSolsticeDay >= firstNewMoonDay && winterSolsticeDay < nextNewMoonDay && julianDayEpoch <= firstNewMoonDay {
adjustedYear--
}
if winterSolsticeDay >= nextNewMoonDay && julianDayEpoch < nextNewMoonDay {
adjustedYear--
}
} else {
adjustedYear--
}
// 获取节气和朔望月数据
solarTerms := GetJieqiLoops(adjustedYear, 25)
newMoonDays := GetMoonLoops(float64(adjustedYear), 17)
// 计算冬至日期
winterSolsticeFirst := solarTerms[0] - 8.0/24 + tz
winterSolsticeSecond := solarTerms[24] - 8.0/24 + tz
// 规范化时间点
normalizeTimeArray(newMoonDays, tz)
normalizeTimeArray(solarTerms, tz)
// 计算朔望月范围
minMoonIndex, maxMoonIndex := 20, 0
moonCount := 0
for i := 0; i < 15; i++ {
if newMoonDays[i] >= winterSolsticeFirst && newMoonDays[i] < winterSolsticeSecond {
if i <= minMoonIndex {
minMoonIndex = i
}
if i >= maxMoonIndex {
maxMoonIndex = i
}
moonCount++
}
}
// 确定闰月位置
leapMonthPos := 20
if moonCount == 13 {
solarTermIndex, i := 2, 0
for i = minMoonIndex; i <= maxMoonIndex; i++ {
if !(newMoonDays[i] <= solarTerms[solarTermIndex] && newMoonDays[i+1] > solarTerms[solarTermIndex]) {
break
}
solarTermIndex += 2
}
leapMonthPos = i - minMoonIndex + 1
}
// 找到当前月相索引
currentMoonIndex := 0
for currentMoonIndex = minMoonIndex - 1; currentMoonIndex <= maxMoonIndex; currentMoonIndex++ {
if newMoonDays[currentMoonIndex] > julianDayEpoch {
break
}
}
// 计算农历月份
lmonth = currentMoonIndex - minMoonIndex
shouldAdjustLeap := false
leap = false
if lmonth >= leapMonthPos {
shouldAdjustLeap = true
}
if lmonth == leapMonthPos {
leap = true
}
if lmonth < 2 {
lmonth += 11
} else {
lmonth--
}
if shouldAdjustLeap {
lmonth--
}
if lmonth <= 0 {
lmonth += 12
}
// 计算农历日期
lday = int(julianDayEpoch-newMoonDays[currentMoonIndex-1]) + 1
// 生成农历日期字符串
result = formatLunarDateString(lmonth, lday, leap)
if lmonth >= 10 && month < 3 {
lyear--
}
return
}
func GetSolar(year, month, day int, leap bool, tz float64) float64 {
adjustedYear := year
if month < 11 {
adjustedYear--
}
// 获取节气和朔望月数据
solarTerms := GetJieqiLoops(adjustedYear, 25)
newMoonDays := GetMoonLoops(float64(adjustedYear), 17)
// 计算冬至日期
winterSolsticeFirst := solarTerms[0] - 8.0/24 + tz
winterSolsticeSecond := solarTerms[24] - 8.0/24 + tz
// 规范化时间点
normalizeTimeArray(newMoonDays, tz)
normalizeTimeArray(solarTerms, tz)
// 计算朔望月范围
minMoonIndex, maxMoonIndex := 20, 0
moonCount := 0
for i := 0; i < 15; i++ {
if newMoonDays[i] >= winterSolsticeFirst && newMoonDays[i] < winterSolsticeSecond {
if i <= minMoonIndex {
minMoonIndex = i
}
if i >= maxMoonIndex {
maxMoonIndex = i
}
moonCount++
}
}
// 确定闰月位置
leapMonthPos := 20
if moonCount == 13 {
solarTermIndex, i := 2, 0
for i = minMoonIndex; i <= maxMoonIndex; i++ {
if !(newMoonDays[i] <= solarTerms[solarTermIndex] && newMoonDays[i+1] > solarTerms[solarTermIndex]) {
break
}
solarTermIndex += 2
}
leapMonthPos = i - minMoonIndex + 1
}
// 计算实际月份索引
actualMonth := month
if leap {
actualMonth++
}
if actualMonth > 10 {
actualMonth -= 11
} else {
actualMonth++
}
if actualMonth >= leapMonthPos && !leap {
actualMonth++
}
return newMoonDays[minMoonIndex-1+actualMonth] + float64(day) - 1
}
func normalizeTimeArray(timeArray []float64, tz float64) {
for idx, timeValue := range timeArray {
adjustedTime := timeValue
if tz != 8.0/24 {
adjustedTime = timeValue - 8.0/24 + tz
}
timeArray[idx] = normalizeTimePoint(adjustedTime)
}
}
func normalizeTimePoint(timePoint float64) float64 {
if timePoint-math.Floor(timePoint) > 0.5 {
return math.Floor(timePoint) + 0.5
}
return math.Floor(timePoint) - 0.5
}
func formatLunarDateString(lunarMonth, lunarDay int, isLeap bool) string {
monthNames := []string{"十", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "冬", "腊"}
dayPrefixes := []string{"初", "十", "廿", "三"}
var dateString string
if isLeap {
dateString += "闰"
}
if lunarMonth == 1 {
dateString += "正月"
} else {
dateString += monthNames[lunarMonth] + "月"
}
if lunarDay == 20 {
dateString += "二十"
} else if lunarDay == 10 {
dateString += "初十"
} else {
dateString += dayPrefixes[lunarDay/10] + monthNames[lunarDay%10]
}
return dateString
}