16c62a97d5
- 新增时标、ΔT 模型、质心时间与 UT1 支持 - 改进日月食、月掩、行星事件及路径边界计算 - 完善恒星三维自行与动态距离传播 - 扩展 SVG、GeoJSON、KML 输出与底层距离换算工具 - 整理中英文手册、示例资源及回归测试
186 lines
5.5 KiB
Go
186 lines
5.5 KiB
Go
package basic
|
|
|
|
import (
|
|
"errors"
|
|
"math"
|
|
"time"
|
|
)
|
|
|
|
var ErrInvalidCivilDate = errors.New("invalid civil date")
|
|
var timeNow = time.Now
|
|
|
|
// Date2JD 日期转儒略日
|
|
func Date2JD(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 JDCalc(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
|
|
}
|
|
|
|
// JDCalc 由公历年月日(day 可含小数)计算儒略日 / Julian day from a civil year, month and fractional day.
|
|
// 1582 年 10 月 15 日起按格里高利历,之前按儒略历;日期非法时返回 NaN。
|
|
// Gregorian from 1582-10-15 onward and Julian before it; an invalid civil date yields NaN.
|
|
func JDCalc(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)
|
|
}
|
|
|
|
// GetNowJD 按当前时区的日历字段取儒略日 / Julian day from the current clock's calendar fields.
|
|
// 读的是当前时区的年月日时分秒,不做时区归算。
|
|
// It reads the calendar fields in the current location without any zone conversion.
|
|
func GetNowJD() (nowJD float64) {
|
|
now := timeNow()
|
|
dayFraction := float64(now.Second())/3600.0/24.0 + float64(now.Minute())/60.0/24.0 + float64(now.Hour())/24.0
|
|
nowJD = JDCalc(now.Year(), int(now.Month()), float64(now.Day())+dayFraction)
|
|
return
|
|
}
|
|
|
|
// JD2Date 儒略日转 Local 时区的时刻 / Local-zone instant from a Julian day.
|
|
// 儒略日的日历字段按 Local 时区解释,1582 年 10 月 15 日之前按儒略历。
|
|
// The calendar fields are read in the Local zone, and dates before 1582-10-15 are read in the Julian calendar.
|
|
func JD2Date(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))
|
|
}
|
|
|
|
// JD2DateByZone 儒略日转日期
|
|
// jd: 儒略日
|
|
// tz: 目标时区
|
|
// byZone: (true: 传入的儒略日视为目标时区当地时间的儒略日,false: 传入的儒略日视为UTC时间的儒略日)
|
|
// 回参:转换后的日期,时区始终为目标时区
|
|
func JD2DateByZone(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)
|
|
date := time.Date(int(years), time.Month(int(months)), int(days), 0, 0, 0, 0, time.UTC).
|
|
Add(time.Duration(int64(1000000000 * tms)))
|
|
if !byZone {
|
|
return date.In(tz)
|
|
}
|
|
// 当地 JD 的小数部分是钟表读数,不能按夏令时午夜后的实际时长累加。
|
|
year, month, day := date.Date()
|
|
hour, minute, second := date.Clock()
|
|
return time.Date(year, month, day, hour, minute, second, date.Nanosecond(), tz)
|
|
}
|