16c62a97d5
- 新增时标、ΔT 模型、质心时间与 UT1 支持 - 改进日月食、月掩、行星事件及路径边界计算 - 完善恒星三维自行与动态距离传播 - 扩展 SVG、GeoJSON、KML 输出与底层距离换算工具 - 整理中英文手册、示例资源及回归测试
462 lines
16 KiB
Go
462 lines
16 KiB
Go
package calendar
|
|
|
|
import (
|
|
"time"
|
|
|
|
"b612.me/astro/basic"
|
|
)
|
|
|
|
type LunarInfo struct {
|
|
// SolarDate 公历日期
|
|
SolarDate time.Time `json:"solarDate"`
|
|
// LunarYear 农历年的公历映射,如2025
|
|
LunarYear int `json:"lunarYear"`
|
|
// LunarYearChn 农历年公历映射中文表示,比如二零二五
|
|
LunarYearChn string `json:"lunarYearChn"`
|
|
// LunarMonth 农历月,表示以当时的历法推定的农历月与正月的距离,正月为1,二月为2,依次类推
|
|
// 武则天改历时期,正月为1, 十二月为2,一月为3,二月为4,以此类推
|
|
LunarMonth int `json:"lunarMonth"`
|
|
// LunarDay 农历日,[1-30]
|
|
LunarDay int `json:"lunarDay"`
|
|
// IsLeap 是否闰月
|
|
IsLeap bool `json:"isLeap"`
|
|
// LunarMonthDayDesc 农历月日描述,如正月初一。此处,十一月表示为冬月,十二月表示为腊月
|
|
LunarMonthDayDesc string `json:"lunarMonthDayDesc"`
|
|
// GanzhiYear 农历年干支
|
|
GanzhiYear string `json:"ganzhiYear"`
|
|
// GanzhiMonth 农历月干支,闰月从上一个月
|
|
GanzhiMonth string `json:"ganzhiMonth"`
|
|
// GanzhiDay 农历日干支
|
|
GanzhiDay string `json:"ganzhiDay"`
|
|
// CalendarSystem 历法系统
|
|
CalendarSystem AncientCalendarSystem `json:"calendarSystem"`
|
|
// CalendarName 历法名称
|
|
CalendarName string `json:"calendarName"`
|
|
// JD 该农历日精确的儒略日 / exact Julian day.
|
|
JD float64 `json:"jd"`
|
|
// JulianOnly 是否只存在于儒略历 / Julian-calendar-only date.
|
|
JulianOnly bool `json:"julianOnly,omitempty"`
|
|
// Dynasty 朝代,如唐、宋、元、明、清等
|
|
Dynasty string `json:"dynasty"`
|
|
// Emperor 皇帝姓名(仅供参考,多个皇帝用同一个年号的场景,此处不准)
|
|
Emperor string `json:"emperor"`
|
|
// Nianhao 年号 如"开元"
|
|
Nianhao string `json:"nianhao"`
|
|
// YearOfNianhao 该年号的第几年
|
|
YearOfNianhao int `json:"yearOfNianhao"`
|
|
// EraDesc 年代描述,如唐玄宗开元二年
|
|
EraDesc string `json:"eraDesc"`
|
|
// LunarWithEraDesc 农历日期加上年代描述,如开元二年正月初一
|
|
LunarWithEraDesc string `json:"lunarWithNianhaoDesc"`
|
|
// ChineseZodiac 生肖
|
|
ChineseZodiac string `json:"chineseZodiac"`
|
|
}
|
|
|
|
type Time struct {
|
|
solarTime time.Time
|
|
lunars []LunarTime
|
|
// solarCandidates 是该农历日的全部合法公历候选,首个元素恒等于 solarTime;单候选时为 nil。
|
|
solarCandidates []time.Time
|
|
}
|
|
|
|
// Solar 公历时间 / solar time.
|
|
//
|
|
// 返回内部保存的公历 `time.Time`,不做时区或历法再计算。
|
|
// Returns the stored civil `time.Time` directly, without any further time-zone or calendar conversion.
|
|
func (t Time) Solar() time.Time {
|
|
return t.solarTime
|
|
}
|
|
|
|
// Time 公历时间 / solar time.
|
|
//
|
|
// 是 `Solar` 的同义接口,便于把 `calendar.Time` 当作普通时间对象使用。
|
|
// This is an alias of `Solar`, convenient when `calendar.Time` is used as a regular time object.
|
|
func (t Time) Time() time.Time {
|
|
return t.solarTime
|
|
}
|
|
|
|
// Lunars 农历候选结果 / lunar candidates.
|
|
//
|
|
// 返回全部候选农历结果。
|
|
// Returns all candidate lunar-calendar results.
|
|
func (t Time) Lunars() []LunarTime {
|
|
return t.lunars
|
|
}
|
|
|
|
// LunarDesc 农历描述 / lunar descriptions.
|
|
//
|
|
// 返回全部候选结果的农历描述,如开元二年正月初一;若无年号,则返回年份描述,如二零二五年正月初一。
|
|
// Returns the lunar-date descriptions for all candidates. If no era name is available, the year is described directly.
|
|
func (t Time) LunarDesc() []string {
|
|
var res []string
|
|
for _, v := range t.lunars {
|
|
res = append(res, v.LunarDesc()...)
|
|
}
|
|
return res
|
|
}
|
|
|
|
// LunarDescWithEmperor 含君主信息的农历描述 / lunar descriptions with emperor.
|
|
//
|
|
// 返回全部候选结果中含有君主信息的农历描述,如唐玄宗 开元二年正月初一;若无年号,则返回年份描述,如二零二五年正月初一。
|
|
// Returns the candidate descriptions with emperor names when available. If no era name is available, the year is described directly.
|
|
func (t Time) LunarDescWithEmperor() []string {
|
|
var res []string
|
|
for _, v := range t.lunars {
|
|
res = append(res, v.LunarDescWithEmperor()...)
|
|
}
|
|
return res
|
|
}
|
|
|
|
// LunarDescWithDynasty 含朝代信息的农历描述 / lunar descriptions with dynasty.
|
|
//
|
|
// 返回全部候选结果中含有朝代信息的农历描述,如唐 开元二年正月初一;若无年号,则返回年份描述,如二零二五年正月初一。
|
|
// Returns the candidate descriptions with dynasty names when available. If no era name is available, the year is described directly.
|
|
func (t Time) LunarDescWithDynasty() []string {
|
|
var res []string
|
|
for _, v := range t.lunars {
|
|
res = append(res, v.LunarDescWithDynasty()...)
|
|
}
|
|
return res
|
|
}
|
|
|
|
// LunarDescWithDynastyAndEmperor 含朝代与君主信息的农历描述 / lunar descriptions with dynasty and emperor.
|
|
//
|
|
// 返回全部候选结果中含有朝代和君主信息的农历描述,如唐 唐玄宗 开元二年正月初一;若无年号,则返回年份描述,如二零二五年正月初一。
|
|
// Returns the candidate descriptions with both dynasty and emperor names when available. If no era name is available, the year is described directly.
|
|
func (t Time) LunarDescWithDynastyAndEmperor() []string {
|
|
var res []string
|
|
for _, v := range t.lunars {
|
|
res = append(res, v.LunarDescWithDynastyAndEmperor()...)
|
|
}
|
|
return res
|
|
}
|
|
|
|
// LunarInfo 农历结构化信息 / structured lunar information.
|
|
//
|
|
// 返回全部候选结果对应的结构化农历信息切片。
|
|
// Returns the structured lunar-calendar information for all candidates.
|
|
func (t Time) LunarInfo() []LunarInfo {
|
|
var res []LunarInfo
|
|
for _, v := range t.lunars {
|
|
res = append(res, v.LunarInfo()...)
|
|
}
|
|
return res
|
|
}
|
|
|
|
// Eras 朝代、皇帝、年号信息 / era information.
|
|
//
|
|
// 返回全部候选结果对应的朝代、皇帝、年号信息。
|
|
// Returns the dynasty, emperor, and era-name records associated with all candidates.
|
|
func (t Time) Eras() []EraDesc {
|
|
var res []EraDesc
|
|
for _, v := range t.lunars {
|
|
res = append(res, v.eras...)
|
|
}
|
|
return res
|
|
}
|
|
|
|
// Lunar 首个农历结果 / first lunar result.
|
|
//
|
|
// 若存在多个候选结果,只返回第一个;无结果时返回零值 `LunarTime`。
|
|
// Returns only the first candidate when multiple results exist. A zero-value `LunarTime` is returned when no result is available.
|
|
func (t Time) Lunar() LunarTime {
|
|
if len(t.lunars) > 0 {
|
|
return t.lunars[0]
|
|
}
|
|
return LunarTime{}
|
|
}
|
|
|
|
// Add 时间偏移 / add a duration.
|
|
//
|
|
// 按公历时间轴精确相加;区间跨过 1582 改历空窗时改走儒略日轴,跳过不存在的 10 天。
|
|
// Adds exactly along the civil timeline; when the interval crosses the 1582 reform gap it moves
|
|
// along the Julian-day axis instead, skipping the ten days that do not exist.
|
|
func (t Time) Add(d time.Duration) Time {
|
|
newT := t.solarTime.Add(d)
|
|
if crossesGregorianReformGap(t.solarTime, newT) {
|
|
jd := Date2JD(t.solarTime) + d.Seconds()/86400.0
|
|
newT = basic.JD2DateByZone(jd, t.solarTime.Location(), true)
|
|
}
|
|
rT, _ := SolarToLunar(newT)
|
|
return rT
|
|
}
|
|
|
|
// crossesGregorianReformGap 报告两个时刻之间是否包含 1582 改历跳过的 10 天(公历 1582-10-05..14)。
|
|
func crossesGregorianReformGap(from, to time.Time) bool {
|
|
start := time.Date(1582, 10, 5, 0, 0, 0, 0, from.Location())
|
|
end := time.Date(1582, 10, 15, 0, 0, 0, 0, from.Location())
|
|
if to.Before(from) {
|
|
from, to = to, from
|
|
}
|
|
return from.Before(end) && !to.Before(start)
|
|
}
|
|
|
|
type LunarTime struct {
|
|
solarDate time.Time
|
|
//农历年
|
|
year int
|
|
//农历月,表示以当时的历法推定的农历月与正月的距离,正月为1,二月为2,依次类推,闰月显示所闰月
|
|
month int
|
|
//农历日
|
|
day int
|
|
//是否闰月
|
|
leap bool
|
|
//农历描述
|
|
desc string
|
|
//备注
|
|
comment string
|
|
//ganzhi of month 月干支
|
|
ganzhiMonth string
|
|
//后九月
|
|
houMonth bool
|
|
//历法系统
|
|
calendarSystem AncientCalendarSystem
|
|
//历法名称
|
|
calendarName string
|
|
// julianOnly 该农历日是否只存在于儒略历(如 700-02-29);Solar 取后继日作为规范日。
|
|
julianOnly bool
|
|
// jd 该农历日精确的儒略日,仅在 julianOnly 时显式保存。
|
|
jd float64
|
|
|
|
eras []EraDesc
|
|
}
|
|
|
|
// JulianOnly 该农历日是否只存在于儒略历 / whether the date exists only in the Julian calendar.
|
|
func (l LunarTime) JulianOnly() bool {
|
|
return l.julianOnly
|
|
}
|
|
|
|
// JD 该农历日精确的儒略日;儒略历独有闰日比 Solar() 早一天 / exact Julian day.
|
|
func (l LunarTime) JD() float64 {
|
|
if l.julianOnly && l.jd > 0 {
|
|
return l.jd
|
|
}
|
|
return Date2JD(l.solarDate)
|
|
}
|
|
|
|
// JulianOnly 主历法农历日是否只存在于儒略历 / whether the primary date is Julian-calendar-only.
|
|
func (t Time) JulianOnly() bool {
|
|
if len(t.lunars) == 0 {
|
|
return false
|
|
}
|
|
return t.lunars[0].JulianOnly()
|
|
}
|
|
|
|
// JD 主历法农历日精确的儒略日 / exact Julian day of the primary lunar date.
|
|
func (t Time) JD() float64 {
|
|
if len(t.lunars) == 0 {
|
|
return Date2JD(t.solarTime)
|
|
}
|
|
return t.lunars[0].JD()
|
|
}
|
|
|
|
// SolarCandidates 该农历日的全部合法公历候选,首个恒等于 Solar() / every legal civil date, Solar() first.
|
|
func (t Time) SolarCandidates() []time.Time {
|
|
if len(t.solarCandidates) == 0 {
|
|
return []time.Time{t.solarTime}
|
|
}
|
|
out := make([]time.Time, len(t.solarCandidates))
|
|
copy(out, t.solarCandidates)
|
|
return out
|
|
}
|
|
|
|
// ShengXiao 生肖 / Chinese zodiac.
|
|
func (l LunarTime) ShengXiao() string {
|
|
shengxiao := []string{"猴", "鸡", "狗", "猪", "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊"}
|
|
diff := l.LunarYear() % 12
|
|
if diff < 0 {
|
|
diff += 12
|
|
}
|
|
return shengxiao[diff]
|
|
}
|
|
|
|
// Zodiac 生肖别名 / zodiac alias.
|
|
func (l LunarTime) Zodiac() string {
|
|
return l.ShengXiao()
|
|
}
|
|
|
|
// GanZhiYear 年干支 / sexagenary year name.
|
|
func (l LunarTime) GanZhiYear() string {
|
|
return GanZhiOfYear(l.year)
|
|
}
|
|
|
|
// GanZhiMonth 月干支 / sexagenary month name.
|
|
func (l LunarTime) GanZhiMonth() string {
|
|
return l.ganzhiMonth
|
|
}
|
|
|
|
// GanZhiDay 日干支 / sexagenary day name.
|
|
func (l LunarTime) GanZhiDay() string {
|
|
if l.julianOnly && l.jd > 0 {
|
|
return ganZhiOfJD(l.jd)
|
|
}
|
|
return GanZhiOfDay(l.solarDate)
|
|
}
|
|
|
|
// LunarYear 农历年 / lunar year.
|
|
func (l LunarTime) LunarYear() int {
|
|
return l.year
|
|
}
|
|
|
|
// LunarMonth 农历月 / lunar month.
|
|
func (l LunarTime) LunarMonth() int {
|
|
return l.month
|
|
}
|
|
|
|
// LunarDay 农历日 / lunar day.
|
|
func (l LunarTime) LunarDay() int {
|
|
return l.day
|
|
}
|
|
|
|
// IsLeap 是否闰月 / whether the month is leap.
|
|
func (l LunarTime) IsLeap() bool {
|
|
return l.leap
|
|
}
|
|
|
|
// CalendarSystem 历法系统 / calendar system.
|
|
func (l LunarTime) CalendarSystem() AncientCalendarSystem {
|
|
return l.calendarSystem
|
|
}
|
|
|
|
// CalendarName 历法名称 / calendar name.
|
|
func (l LunarTime) CalendarName() string {
|
|
return l.calendarName
|
|
}
|
|
|
|
// Eras 朝代、皇帝、年号信息 / era information.
|
|
//
|
|
// 返回该农历结果对应的朝代、皇帝、年号信息。
|
|
// Returns the dynasty, emperor, and era-name records associated with this lunar result.
|
|
func (l LunarTime) Eras() []EraDesc {
|
|
return l.eras
|
|
}
|
|
|
|
// MonthDay 农历月日描述 / lunar month-day description.
|
|
//
|
|
// 获取农历月日描述,如正月初一。此处,十一月表示为冬月,十二月表示为腊月。
|
|
// Returns the lunar month-day description, such as `正月初一`. In this package, month 11 is written as `冬月` and month 12 as `腊月`.
|
|
func (l LunarTime) MonthDay() string {
|
|
return l.desc
|
|
}
|
|
|
|
// LunarDesc 农历描述 / lunar descriptions.
|
|
//
|
|
// 获取农历描述,如开元二年正月初一,若无年号,则返回年份描述,如二零二五年正月初一。
|
|
// Returns the lunar-date descriptions for this result. If no era name is available, the year is described directly.
|
|
func (l LunarTime) LunarDesc() []string {
|
|
return l.innerDescWithNianHao(false, false)
|
|
}
|
|
|
|
// LunarDescWithEmperor 含君主信息的农历描述 / lunar descriptions with emperor.
|
|
//
|
|
// 获取含有君主信息的农历描述,如唐玄宗 开元二年正月初一,若无年号,则返回年份描述,如二零二五年正月初一。
|
|
// 君主信息仅供参考,多个皇帝用同一个年号的场景,此处不准
|
|
// Returns the lunar-date descriptions with emperor names when available. Emperor names are for reference only and may be ambiguous when multiple emperors used the same era name.
|
|
func (l LunarTime) LunarDescWithEmperor() []string {
|
|
return l.innerDescWithNianHao(true, false)
|
|
}
|
|
|
|
// LunarDescWithDynasty 含朝代信息的农历描述 / lunar descriptions with dynasty.
|
|
//
|
|
// 获取含有朝代信息的农历描述,如唐 开元二年正月初一,若无年号,则返回年份描述,如二零二五年正月初一。
|
|
// Returns the lunar-date descriptions with dynasty names when available. If no era name is available, the year is described directly.
|
|
func (l LunarTime) LunarDescWithDynasty() []string {
|
|
return l.innerDescWithNianHao(false, true)
|
|
}
|
|
|
|
// LunarDescWithDynastyAndEmperor 含朝代和君主信息的农历描述 / lunar descriptions with dynasty and emperor.
|
|
//
|
|
// 获取含有朝代和君主信息的农历描述,如唐 唐玄宗 开元二年正月初一,若无年号,则返回年份描述,如二零二五年正月初一。
|
|
// 君主信息仅供参考,多个皇帝用同一个年号的场景,此处不准
|
|
// Returns the lunar-date descriptions with both dynasty and emperor names when available. Emperor names are for reference only and may be ambiguous when multiple emperors used the same era name.
|
|
func (l LunarTime) LunarDescWithDynastyAndEmperor() []string {
|
|
return l.innerDescWithNianHao(true, true)
|
|
}
|
|
|
|
func (l LunarTime) innerDescWithNianHao(withEmperor bool, withDynasty bool) []string {
|
|
var res []string
|
|
if len(l.eras) > 0 {
|
|
for _, v := range l.eras {
|
|
tmp := v.String() + l.desc
|
|
if withEmperor {
|
|
tmp = v.Emperor + " " + tmp
|
|
}
|
|
if withDynasty {
|
|
tmp = v.Dynasty + " " + tmp
|
|
}
|
|
res = append(res, tmp)
|
|
}
|
|
} else {
|
|
res = append(res, lunarYearDesc(l.year)+"年"+l.desc)
|
|
}
|
|
return res
|
|
}
|
|
|
|
// LunarInfo 农历结构化信息 / structured lunar information.
|
|
//
|
|
// 返回该农历结果对应的结构化农历信息切片;若存在多个并行年号,则会有多条记录。
|
|
// Returns the structured lunar-calendar information for this result. Multiple records are returned when parallel era-name interpretations exist.
|
|
func (l LunarTime) LunarInfo() []LunarInfo {
|
|
var res []LunarInfo
|
|
for _, v := range l.eras {
|
|
li := LunarInfo{
|
|
SolarDate: l.solarDate,
|
|
LunarYear: l.year,
|
|
LunarYearChn: lunarYearDesc(l.year),
|
|
LunarMonth: l.month,
|
|
LunarDay: l.day,
|
|
IsLeap: l.leap,
|
|
LunarMonthDayDesc: l.desc,
|
|
GanzhiYear: GanZhiOfYear(l.year),
|
|
GanzhiMonth: l.ganzhiMonth,
|
|
GanzhiDay: l.GanZhiDay(),
|
|
CalendarSystem: l.calendarSystem,
|
|
CalendarName: l.calendarName,
|
|
Dynasty: v.Dynasty,
|
|
Emperor: v.Emperor,
|
|
Nianhao: v.Nianhao,
|
|
YearOfNianhao: v.YearOfNianHao,
|
|
EraDesc: v.String(),
|
|
LunarWithEraDesc: v.String() + l.desc,
|
|
ChineseZodiac: l.ShengXiao(),
|
|
JD: l.JD(),
|
|
JulianOnly: l.julianOnly,
|
|
}
|
|
res = append(res, li)
|
|
}
|
|
if len(l.eras) == 0 {
|
|
li := LunarInfo{
|
|
SolarDate: l.solarDate,
|
|
LunarYear: l.year,
|
|
LunarYearChn: lunarYearDesc(l.year),
|
|
LunarMonth: l.month,
|
|
LunarDay: l.day,
|
|
IsLeap: l.leap,
|
|
LunarMonthDayDesc: l.desc,
|
|
GanzhiYear: GanZhiOfYear(l.year),
|
|
GanzhiMonth: l.ganzhiMonth,
|
|
GanzhiDay: l.GanZhiDay(),
|
|
CalendarSystem: l.calendarSystem,
|
|
CalendarName: l.calendarName,
|
|
Dynasty: "",
|
|
Emperor: "",
|
|
Nianhao: "",
|
|
YearOfNianhao: 0,
|
|
EraDesc: lunarYearDesc(l.year) + "年",
|
|
LunarWithEraDesc: lunarYearDesc(l.year) + "年" + l.desc,
|
|
ChineseZodiac: l.ShengXiao(),
|
|
JD: l.JD(),
|
|
JulianOnly: l.julianOnly,
|
|
}
|
|
res = append(res, li)
|
|
}
|
|
return res
|
|
}
|
|
|
|
func lunarYearDesc(year int) string {
|
|
if year <= 0 {
|
|
return "前" + number2Chinese(1-year, true)
|
|
}
|
|
return number2Chinese(year, true)
|
|
}
|