astro/basic/julian.go
starainrt 3ffdbe0034
feat: 扩展天文计算能力
- 新增日食、月食、本地可见性、中心线、半影区域、SVG 图示与沙罗周期信息
- 新增行星冲合、留、方照、物理星历、视直径、相位、亮肢角、轨道节点等计算
- 新增木星伽利略卫星位置、现象与接触事件计算
- 新增恒星星表、星座判定、自行修正与观测辅助能力
- 新增 coord、formula、orbit、sundial、lite/sun、lite/moon 等扩展包
- 完善农历年号、月相英文别名、视差角、大气质量、折射、日晷与双星计算
- 增加 NASA、JPL Horizons、IMCCE 等回归测试数据与基线测试
- 重构基础算法文件组织,补充大量公开 API 注释和语义回归测试
- 更新中文和英文 README,补充示例、精度说明、SVG 配图
2026-05-01 22:38:44 +08:00

182 lines
4.7 KiB
Go
Raw 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 (
"errors"
"math"
"time"
)
var ErrInvalidCivilDate = errors.New("invalid civil date")
var timeNow = time.Now
// 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)
}
func ValidateCivilDate(year, month int, day float64) error {
if math.IsNaN(day) || math.IsInf(day, 0) {
return ErrInvalidCivilDate
}
if month < 1 || month > 12 {
return ErrInvalidCivilDate
}
if day < 1 {
return ErrInvalidCivilDate
}
dayInt := int(math.Floor(day))
if dayInt < 1 || dayInt > daysInCivilMonth(year, month) {
return ErrInvalidCivilDate
}
if isGregorianReformGap(year, month, day) {
return ErrInvalidCivilDate
}
return nil
}
func isGregorianReformGap(year, month int, day float64) bool {
return year == 1582 && month == 10 && day >= 5 && day < 15
}
func daysInCivilMonth(year, month int) int {
switch month {
case 1, 3, 5, 7, 8, 10, 12:
return 31
case 4, 6, 9, 11:
return 30
case 2:
if isCivilLeapYear(year, month, 1) {
return 29
}
return 28
default:
return 0
}
}
func isCivilLeapYear(year, month int, day float64) bool {
if year < 1582 || (year == 1582 && (month < 10 || (month == 10 && day <= 4))) {
return year%4 == 0
}
if year%400 == 0 {
return true
}
if year%100 == 0 {
return false
}
return year%4 == 0
}
/*
@name: 儒略日计算
@dec: 计算给定时间的儒略日1582年改力后为格里高利历之前为儒略历
@ 请注意,传入的时间在天文计算中一般为力学时,应当注意和世界时的转化
*/
func JDECalc(year, month int, day float64) float64 {
if err := ValidateCivilDate(year, month, day); err != nil {
return math.NaN()
}
effectiveYear, effectiveMonth, effectiveDay := year, month, int(math.Floor(day))
if month == 1 || month == 2 {
year--
month += 12
}
var gregorianCorrection int
if effectiveYear < 1582 || (effectiveYear == 1582 && (effectiveMonth < 10 || (effectiveMonth == 10 && effectiveDay <= 4))) {
gregorianCorrection = 0
} else {
century := int(year / 100)
gregorianCorrection = 2 - century + int(century/4)
}
return (math.Floor(365.25*(float64(year)+4716.0)) + math.Floor(30.6001*float64(month+1)) + day + float64(gregorianCorrection) - 1524.5)
}
/*
@name: 获得当前儒略日时间:当地世界时,非格林尼治时间
*/
func GetNowJDE() (nowJDE float64) {
now := timeNow()
dayFraction := float64(now.Second())/3600.0/24.0 + float64(now.Minute())/60.0/24.0 + float64(now.Hour())/24.0
nowJDE = JDECalc(now.Year(), int(now.Month()), float64(now.Day())+dayFraction)
return
}
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)
}