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