Files
astro/lite/internal/time.go
T

115 lines
3.1 KiB
Go
Raw Normal View History

package internal
import (
"errors"
"math"
"time"
)
// ErrInvalidCivilDate 日期非法 / invalid civil date.
var ErrInvalidCivilDate = errors.New("invalid civil date")
// Date2JD 日期转儒略日 / Julian day from a civil time.
func Date2JD(date time.Time) float64 {
day := float64(date.Day()) + float64(date.Hour())/24.0 + float64(date.Minute())/1440.0 + float64(date.Second())/86400.0 + float64(date.Nanosecond())/86400000000000.0
return JDCalc(date.Year(), int(date.Month()), day)
}
// JDCalc 由公历年月日计算儒略日 / Julian day from a civil date.
func JDCalc(year, month int, day float64) float64 {
if !validCivilDate(year, month, day) {
return math.NaN()
}
effectiveYear, effectiveMonth, effectiveDay := year, month, int(math.Floor(day))
if month == 1 || month == 2 {
year--
month += 12
}
correction := 0
if effectiveYear > 1582 || (effectiveYear == 1582 && (effectiveMonth > 10 || (effectiveMonth == 10 && effectiveDay >= 15))) {
century := int(year / 100)
correction = 2 - century + int(century/4)
}
return math.Floor(365.25*(float64(year)+4716.0)) + math.Floor(30.6001*float64(month+1)) + day + float64(correction) - 1524.5
}
func validCivilDate(year, month int, day float64) bool {
if math.IsNaN(day) || math.IsInf(day, 0) || month < 1 || month > 12 || day < 1 {
return false
}
dayInt := int(math.Floor(day))
if dayInt > daysInCivilMonth(year, month) {
return false
}
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
}
// JD2DateByZone 儒略日转目标时区日期 / convert a Julian day to a target timezone.
func JD2DateByZone(jd float64, tz *time.Location, byZone bool) time.Time {
jd += 0.5
z := float64(int(jd))
fraction := jd - z
a := z
if z >= 2299161.0 {
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) + fraction
month := e - 1
if e >= 14 {
month = e - 13
}
year := c - 4716
if month <= 2 {
year = c - 4715
}
seconds := (days - math.Floor(days)) * 86400
day := math.Floor(days)
date := time.Date(int(year), time.Month(int(month)), int(day), 0, 0, 0, 0, time.UTC).
Add(time.Duration(int64(1000000000 * seconds)))
if !byZone {
return date.In(tz)
}
y, m, dayInt := date.Date()
h, minute, second := date.Clock()
return time.Date(y, m, dayInt, h, minute, second, date.Nanosecond(), tz)
}
// UTC2UT1 轻量民用时转世界时,忽略 DUT1(|UT1−UTC| < 0.9 s) / lightweight UTC to UT1, DUT1 ignored.
func UTC2UT1(jd float64) float64 {
return jd
}