2025-09-15 20:55:38 +08:00
package calendar
import (
"time"
2026-05-01 22:38:44 +08:00
"b612.me/astro/basic"
2025-09-15 20:55:38 +08:00
)
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"`
2026-06-09 19:35:18 +08:00
// CalendarSystem 历法系统
CalendarSystem AncientCalendarSystem `json:"calendarSystem"`
// CalendarName 历法名称
CalendarName string `json:"calendarName"`
2026-09-23 18:55:12 +08:00
// JD 该农历日精确的儒略日 / exact Julian day.
JD float64 `json:"jd"`
2026-09-17 12:27:40 +08:00
// JulianOnly 是否只存在于儒略历 / Julian-calendar-only date.
JulianOnly bool `json:"julianOnly,omitempty"`
2025-09-15 20:55:38 +08:00
// 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
2026-09-17 12:27:40 +08:00
// solarCandidates 是该农历日的全部合法公历候选,首个元素恒等于 solarTime;单候选时为 nil。
solarCandidates [] time . Time
2025-09-15 20:55:38 +08:00
}
2026-05-01 22:38:44 +08:00
// Solar 公历时间 / solar time.
//
// 返回内部保存的公历 `time.Time`,不做时区或历法再计算。
2026-05-27 16:08:11 +08:00
// Returns the stored civil `time.Time` directly, without any further time-zone or calendar conversion.
2025-09-15 20:55:38 +08:00
func ( t Time ) Solar () time . Time {
return t . solarTime
}
2026-05-01 22:38:44 +08:00
// Time 公历时间 / solar time.
//
// 是 `Solar` 的同义接口,便于把 `calendar.Time` 当作普通时间对象使用。
2026-05-27 16:08:11 +08:00
// This is an alias of `Solar`, convenient when `calendar.Time` is used as a regular time object.
2025-09-15 20:55:38 +08:00
func ( t Time ) Time () time . Time {
return t . solarTime
}
2026-05-01 22:38:44 +08:00
// Lunars 农历候选结果 / lunar candidates.
//
// 返回全部候选农历结果。
2026-05-27 16:08:11 +08:00
// Returns all candidate lunar-calendar results.
2025-09-15 20:55:38 +08:00
func ( t Time ) Lunars () [] LunarTime {
return t . lunars
}
2026-05-01 22:38:44 +08:00
// LunarDesc 农历描述 / lunar descriptions.
//
// 返回全部候选结果的农历描述,如开元二年正月初一;若无年号,则返回年份描述,如二零二五年正月初一。
2026-05-27 16:08:11 +08:00
// Returns the lunar-date descriptions for all candidates. If no era name is available, the year is described directly.
2025-09-15 20:55:38 +08:00
func ( t Time ) LunarDesc () [] string {
var res [] string
for _ , v := range t . lunars {
res = append ( res , v . LunarDesc () ... )
}
return res
}
2026-05-01 22:38:44 +08:00
// LunarDescWithEmperor 含君主信息的农历描述 / lunar descriptions with emperor.
//
// 返回全部候选结果中含有君主信息的农历描述,如唐玄宗 开元二年正月初一;若无年号,则返回年份描述,如二零二五年正月初一。
2026-05-27 16:08:11 +08:00
// Returns the candidate descriptions with emperor names when available. If no era name is available, the year is described directly.
2025-09-15 20:55:38 +08:00
func ( t Time ) LunarDescWithEmperor () [] string {
var res [] string
for _ , v := range t . lunars {
res = append ( res , v . LunarDescWithEmperor () ... )
}
return res
}
2026-05-01 22:38:44 +08:00
// LunarDescWithDynasty 含朝代信息的农历描述 / lunar descriptions with dynasty.
//
// 返回全部候选结果中含有朝代信息的农历描述,如唐 开元二年正月初一;若无年号,则返回年份描述,如二零二五年正月初一。
2026-05-27 16:08:11 +08:00
// Returns the candidate descriptions with dynasty names when available. If no era name is available, the year is described directly.
2025-09-15 20:55:38 +08:00
func ( t Time ) LunarDescWithDynasty () [] string {
var res [] string
for _ , v := range t . lunars {
res = append ( res , v . LunarDescWithDynasty () ... )
}
return res
}
2026-05-01 22:38:44 +08:00
// LunarDescWithDynastyAndEmperor 含朝代与君主信息的农历描述 / lunar descriptions with dynasty and emperor.
//
// 返回全部候选结果中含有朝代和君主信息的农历描述,如唐 唐玄宗 开元二年正月初一;若无年号,则返回年份描述,如二零二五年正月初一。
2026-05-27 16:08:11 +08:00
// Returns the candidate descriptions with both dynasty and emperor names when available. If no era name is available, the year is described directly.
2025-09-15 20:55:38 +08:00
func ( t Time ) LunarDescWithDynastyAndEmperor () [] string {
var res [] string
for _ , v := range t . lunars {
res = append ( res , v . LunarDescWithDynastyAndEmperor () ... )
}
return res
}
2026-05-01 22:38:44 +08:00
// LunarInfo 农历结构化信息 / structured lunar information.
//
// 返回全部候选结果对应的结构化农历信息切片。
2026-05-27 16:08:11 +08:00
// Returns the structured lunar-calendar information for all candidates.
2025-09-15 20:55:38 +08:00
func ( t Time ) LunarInfo () [] LunarInfo {
var res [] LunarInfo
for _ , v := range t . lunars {
res = append ( res , v . LunarInfo () ... )
}
return res
}
2026-05-01 22:38:44 +08:00
// Eras 朝代、皇帝、年号信息 / era information.
//
// 返回全部候选结果对应的朝代、皇帝、年号信息。
2026-05-27 16:08:11 +08:00
// Returns the dynasty, emperor, and era-name records associated with all candidates.
2025-09-15 20:55:38 +08:00
func ( t Time ) Eras () [] EraDesc {
var res [] EraDesc
for _ , v := range t . lunars {
res = append ( res , v . eras ... )
}
return res
}
2026-05-01 22:38:44 +08:00
// Lunar 首个农历结果 / first lunar result.
//
// 若存在多个候选结果,只返回第一个;无结果时返回零值 `LunarTime`。
2026-05-27 16:08:11 +08:00
// Returns only the first candidate when multiple results exist. A zero-value `LunarTime` is returned when no result is available.
2025-09-15 20:55:38 +08:00
func ( t Time ) Lunar () LunarTime {
if len ( t . lunars ) > 0 {
return t . lunars [ 0 ]
}
return LunarTime {}
}
2026-05-01 22:38:44 +08:00
// Add 时间偏移 / add a duration.
//
2026-09-17 12:27:40 +08:00
// 按公历时间轴精确相加;区间跨过 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.
2025-09-15 20:55:38 +08:00
func ( t Time ) Add ( d time . Duration ) Time {
2026-09-17 12:27:40 +08:00
newT := t . solarTime . Add ( d )
if crossesGregorianReformGap ( t . solarTime , newT ) {
2026-09-23 18:55:12 +08:00
jd := Date2JD ( t . solarTime ) + d . Seconds () / 86400.0
newT = basic . JD2DateByZone ( jd , t . solarTime . Location (), true )
2025-09-15 20:55:38 +08:00
}
rT , _ := SolarToLunar ( newT )
return rT
}
2026-09-17 12:27:40 +08:00
// 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 )
}
2025-09-15 20:55:38 +08:00
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
2026-06-09 19:35:18 +08:00
//后九月
houMonth bool
//历法系统
calendarSystem AncientCalendarSystem
//历法名称
calendarName string
2026-09-23 18:55:12 +08:00
// julianOnly 该农历日是否只存在于儒略历(如 700-02-29);Solar 取后继日作为规范日。
2026-09-17 12:27:40 +08:00
julianOnly bool
2026-09-23 18:55:12 +08:00
// jd 该农历日精确的儒略日,仅在 julianOnly 时显式保存。
jd float64
2025-09-15 20:55:38 +08:00
eras [] EraDesc
}
2026-09-17 12:27:40 +08:00
// JulianOnly 该农历日是否只存在于儒略历 / whether the date exists only in the Julian calendar.
func ( l LunarTime ) JulianOnly () bool {
return l . julianOnly
}
2026-09-23 18:55:12 +08:00
// JD 该农历日精确的儒略日;儒略历独有闰日比 Solar() 早一天 / exact Julian day.
func ( l LunarTime ) JD () float64 {
if l . julianOnly && l . jd > 0 {
return l . jd
2026-09-17 12:27:40 +08:00
}
2026-09-23 18:55:12 +08:00
return Date2JD ( l . solarDate )
2026-09-17 12:27:40 +08:00
}
// 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 ()
}
2026-09-23 18:55:12 +08:00
// JD 主历法农历日精确的儒略日 / exact Julian day of the primary lunar date.
func ( t Time ) JD () float64 {
2026-09-17 12:27:40 +08:00
if len ( t . lunars ) == 0 {
2026-09-23 18:55:12 +08:00
return Date2JD ( t . solarTime )
2026-09-17 12:27:40 +08:00
}
2026-09-23 18:55:12 +08:00
return t . lunars [ 0 ]. JD ()
2026-09-17 12:27:40 +08:00
}
// 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
}
2026-05-01 22:38:44 +08:00
// ShengXiao 生肖 / Chinese zodiac.
2025-09-15 20:55:38 +08:00
func ( l LunarTime ) ShengXiao () string {
shengxiao := [] string { "猴" , "鸡" , "狗" , "猪" , "鼠" , "牛" , "虎" , "兔" , "龙" , "蛇" , "马" , "羊" }
diff := l . LunarYear () % 12
if diff < 0 {
diff += 12
}
return shengxiao [ diff ]
}
2026-05-01 22:38:44 +08:00
// Zodiac 生肖别名 / zodiac alias.
2025-09-15 20:55:38 +08:00
func ( l LunarTime ) Zodiac () string {
return l . ShengXiao ()
}
2026-05-01 22:38:44 +08:00
// GanZhiYear 年干支 / sexagenary year name.
2025-09-15 20:55:38 +08:00
func ( l LunarTime ) GanZhiYear () string {
return GanZhiOfYear ( l . year )
}
2026-05-01 22:38:44 +08:00
// GanZhiMonth 月干支 / sexagenary month name.
2025-09-15 20:55:38 +08:00
func ( l LunarTime ) GanZhiMonth () string {
return l . ganzhiMonth
}
2026-05-01 22:38:44 +08:00
// GanZhiDay 日干支 / sexagenary day name.
2025-09-15 20:55:38 +08:00
func ( l LunarTime ) GanZhiDay () string {
2026-09-23 18:55:12 +08:00
if l . julianOnly && l . jd > 0 {
return ganZhiOfJD ( l . jd )
2026-09-17 12:27:40 +08:00
}
2025-09-15 20:55:38 +08:00
return GanZhiOfDay ( l . solarDate )
}
2026-05-01 22:38:44 +08:00
// LunarYear 农历年 / lunar year.
2025-09-15 20:55:38 +08:00
func ( l LunarTime ) LunarYear () int {
return l . year
}
2026-05-01 22:38:44 +08:00
// LunarMonth 农历月 / lunar month.
2025-09-15 20:55:38 +08:00
func ( l LunarTime ) LunarMonth () int {
return l . month
}
2026-05-01 22:38:44 +08:00
// LunarDay 农历日 / lunar day.
2025-09-15 20:55:38 +08:00
func ( l LunarTime ) LunarDay () int {
return l . day
}
2026-05-01 22:38:44 +08:00
// IsLeap 是否闰月 / whether the month is leap.
2025-09-15 20:55:38 +08:00
func ( l LunarTime ) IsLeap () bool {
return l . leap
}
2026-06-09 19:35:18 +08:00
// CalendarSystem 历法系统 / calendar system.
func ( l LunarTime ) CalendarSystem () AncientCalendarSystem {
return l . calendarSystem
}
// CalendarName 历法名称 / calendar name.
func ( l LunarTime ) CalendarName () string {
return l . calendarName
}
2026-05-01 22:38:44 +08:00
// Eras 朝代、皇帝、年号信息 / era information.
//
// 返回该农历结果对应的朝代、皇帝、年号信息。
2026-05-27 16:08:11 +08:00
// Returns the dynasty, emperor, and era-name records associated with this lunar result.
2025-09-15 20:55:38 +08:00
func ( l LunarTime ) Eras () [] EraDesc {
return l . eras
}
2026-05-01 22:38:44 +08:00
// MonthDay 农历月日描述 / lunar month-day description.
//
// 获取农历月日描述,如正月初一。此处,十一月表示为冬月,十二月表示为腊月。
2026-05-27 16:08:11 +08:00
// Returns the lunar month-day description, such as `正月初一`. In this package, month 11 is written as `冬月` and month 12 as `腊月`.
2025-09-15 20:55:38 +08:00
func ( l LunarTime ) MonthDay () string {
return l . desc
}
2026-05-01 22:38:44 +08:00
// LunarDesc 农历描述 / lunar descriptions.
//
// 获取农历描述,如开元二年正月初一,若无年号,则返回年份描述,如二零二五年正月初一。
2026-05-27 16:08:11 +08:00
// Returns the lunar-date descriptions for this result. If no era name is available, the year is described directly.
2025-09-15 20:55:38 +08:00
func ( l LunarTime ) LunarDesc () [] string {
return l . innerDescWithNianHao ( false , false )
}
2026-05-01 22:38:44 +08:00
// LunarDescWithEmperor 含君主信息的农历描述 / lunar descriptions with emperor.
//
// 获取含有君主信息的农历描述,如唐玄宗 开元二年正月初一,若无年号,则返回年份描述,如二零二五年正月初一。
2025-09-15 20:55:38 +08:00
// 君主信息仅供参考,多个皇帝用同一个年号的场景,此处不准
2026-05-27 16:08:11 +08:00
// 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.
2025-09-15 20:55:38 +08:00
func ( l LunarTime ) LunarDescWithEmperor () [] string {
return l . innerDescWithNianHao ( true , false )
}
2026-05-01 22:38:44 +08:00
// LunarDescWithDynasty 含朝代信息的农历描述 / lunar descriptions with dynasty.
//
// 获取含有朝代信息的农历描述,如唐 开元二年正月初一,若无年号,则返回年份描述,如二零二五年正月初一。
2026-05-27 16:08:11 +08:00
// Returns the lunar-date descriptions with dynasty names when available. If no era name is available, the year is described directly.
2025-09-15 20:55:38 +08:00
func ( l LunarTime ) LunarDescWithDynasty () [] string {
return l . innerDescWithNianHao ( false , true )
}
2026-05-01 22:38:44 +08:00
// LunarDescWithDynastyAndEmperor 含朝代和君主信息的农历描述 / lunar descriptions with dynasty and emperor.
//
// 获取含有朝代和君主信息的农历描述,如唐 唐玄宗 开元二年正月初一,若无年号,则返回年份描述,如二零二五年正月初一。
2025-09-15 20:55:38 +08:00
// 君主信息仅供参考,多个皇帝用同一个年号的场景,此处不准
2026-05-27 16:08:11 +08:00
// 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.
2025-09-15 20:55:38 +08:00
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 {
2026-06-11 09:33:46 +08:00
res = append ( res , lunarYearDesc ( l . year ) + "年" + l . desc )
2025-09-15 20:55:38 +08:00
}
return res
}
2026-05-01 22:38:44 +08:00
// LunarInfo 农历结构化信息 / structured lunar information.
//
// 返回该农历结果对应的结构化农历信息切片;若存在多个并行年号,则会有多条记录。
2026-05-27 16:08:11 +08:00
// Returns the structured lunar-calendar information for this result. Multiple records are returned when parallel era-name interpretations exist.
2025-09-15 20:55:38 +08:00
func ( l LunarTime ) LunarInfo () [] LunarInfo {
var res [] LunarInfo
for _ , v := range l . eras {
li := LunarInfo {
SolarDate : l . solarDate ,
LunarYear : l . year ,
2026-06-11 09:33:46 +08:00
LunarYearChn : lunarYearDesc ( l . year ),
2025-09-15 20:55:38 +08:00
LunarMonth : l . month ,
LunarDay : l . day ,
IsLeap : l . leap ,
LunarMonthDayDesc : l . desc ,
GanzhiYear : GanZhiOfYear ( l . year ),
GanzhiMonth : l . ganzhiMonth ,
2026-09-17 12:27:40 +08:00
GanzhiDay : l . GanZhiDay (),
2026-06-09 19:35:18 +08:00
CalendarSystem : l . calendarSystem ,
CalendarName : l . calendarName ,
2025-09-15 20:55:38 +08:00
Dynasty : v . Dynasty ,
Emperor : v . Emperor ,
Nianhao : v . Nianhao ,
YearOfNianhao : v . YearOfNianHao ,
EraDesc : v . String (),
LunarWithEraDesc : v . String () + l . desc ,
ChineseZodiac : l . ShengXiao (),
2026-09-23 18:55:12 +08:00
JD : l . JD (),
2026-09-17 12:27:40 +08:00
JulianOnly : l . julianOnly ,
2025-09-15 20:55:38 +08:00
}
res = append ( res , li )
}
if len ( l . eras ) == 0 {
li := LunarInfo {
SolarDate : l . solarDate ,
LunarYear : l . year ,
2026-06-11 09:33:46 +08:00
LunarYearChn : lunarYearDesc ( l . year ),
2025-09-15 20:55:38 +08:00
LunarMonth : l . month ,
LunarDay : l . day ,
IsLeap : l . leap ,
LunarMonthDayDesc : l . desc ,
GanzhiYear : GanZhiOfYear ( l . year ),
GanzhiMonth : l . ganzhiMonth ,
2026-09-17 12:27:40 +08:00
GanzhiDay : l . GanZhiDay (),
2026-06-09 19:35:18 +08:00
CalendarSystem : l . calendarSystem ,
CalendarName : l . calendarName ,
2025-09-15 20:55:38 +08:00
Dynasty : "" ,
Emperor : "" ,
Nianhao : "" ,
YearOfNianhao : 0 ,
2026-06-11 09:33:46 +08:00
EraDesc : lunarYearDesc ( l . year ) + "年" ,
LunarWithEraDesc : lunarYearDesc ( l . year ) + "年" + l . desc ,
2025-09-15 20:55:38 +08:00
ChineseZodiac : l . ShengXiao (),
2026-09-23 18:55:12 +08:00
JD : l . JD (),
2026-09-17 12:27:40 +08:00
JulianOnly : l . julianOnly ,
2025-09-15 20:55:38 +08:00
}
res = append ( res , li )
}
return res
}
2026-06-11 09:33:46 +08:00
func lunarYearDesc ( year int ) string {
if year <= 0 {
return "前" + number2Chinese ( 1 - year , true )
}
return number2Chinese ( year , true )
}