- 新增显式古历 API,支持先秦古历与秦汉颛顼历选择 - 将默认公农历转换范围扩展至 -721..3000 - 支持后九月解析、负年份干支日和古历法相符节气 - 补充秦汉、先秦、交接边界和节气回归测试
682 lines
21 KiB
Go
682 lines
21 KiB
Go
package calendar
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"time"
|
|
|
|
"b612.me/astro/basic"
|
|
)
|
|
|
|
// AncientCalendarSystem 古六历系统 / ancient calendar system.
|
|
//
|
|
// 用于显式选择先秦古历或秦汉颛顼历。
|
|
// It identifies an explicitly selected pre-Qin or Qin/Early-Han calendar.
|
|
type AncientCalendarSystem string
|
|
|
|
const (
|
|
AncientCalendarDefault AncientCalendarSystem = ""
|
|
AncientCalendarChunqiu AncientCalendarSystem = "chunqiu"
|
|
AncientCalendarZhou AncientCalendarSystem = "zhou"
|
|
AncientCalendarLu AncientCalendarSystem = "lu"
|
|
AncientCalendarHuangdi AncientCalendarSystem = "huangdi"
|
|
AncientCalendarYin AncientCalendarSystem = "yin"
|
|
AncientCalendarXia1 AncientCalendarSystem = "xia1"
|
|
AncientCalendarXia2 AncientCalendarSystem = "xia2"
|
|
AncientCalendarZhuanxu AncientCalendarSystem = "zhuanxu"
|
|
AncientCalendarQinHan AncientCalendarSystem = "qin_han"
|
|
)
|
|
|
|
const (
|
|
ancientMinYear = -721
|
|
ancientMaxYear = -221
|
|
ancientBoundaryMinYear = ancientMinYear - 1
|
|
ancientBoundaryMaxYear = qinHanMinYear
|
|
ancientLunarMonth = 29.0 + 499.0/940.0
|
|
ancientSolarYear = 365.25
|
|
chunqiuLunarMonth = 30328.0 / 1027.0
|
|
chunqiuYearEpoch = -721
|
|
chunqiuJDEpoch = 1457727.761054236
|
|
chunqiuLeapYearCount = 244
|
|
ancientDateEpsilon = 1e-9
|
|
)
|
|
|
|
type ancientMonth struct {
|
|
lunarYear int
|
|
month int
|
|
day int
|
|
leap bool
|
|
startJDN int
|
|
endJDN int
|
|
system AncientCalendarSystem
|
|
name string
|
|
}
|
|
|
|
type ancientSixParameters struct {
|
|
yEpoch int
|
|
jdEpoch float64
|
|
jdEpochMoon float64
|
|
ziOffset int
|
|
name string
|
|
}
|
|
|
|
var chunqiuLeapYearBitmap = []byte{
|
|
82, 73, 82, 164, 8, 155, 72, 201, 160, 138, 162, 144, 37, 73, 162, 73,
|
|
145, 164, 81, 146, 34, 19, 163, 148, 168, 34, 67, 69, 37, 37, 1,
|
|
}
|
|
|
|
// SolarToLunarWithCalendar 公历转农历(显式古历) / solar to lunar calendar with an explicit ancient calendar.
|
|
//
|
|
// 传入公历日期和古历系统,返回该古历系统下的农历结果。
|
|
// Input is a civil date and an ancient calendar system. The result uses that explicit calendar.
|
|
func SolarToLunarWithCalendar(date time.Time, system AncientCalendarSystem) (Time, error) {
|
|
if system == AncientCalendarDefault {
|
|
return SolarToLunar(date)
|
|
}
|
|
date = date.In(getCst())
|
|
return innerSolarToLunarByYMDWithCalendar(date.Year(), int(date.Month()), date.Day(), date, system)
|
|
}
|
|
|
|
// SolarToLunarByYMDWithCalendar 公历转农历(按年月日,显式古历) / solar to lunar calendar by YMD with an explicit ancient calendar.
|
|
//
|
|
// 传入公历年月日和古历系统,返回该古历系统下的农历结果。
|
|
// Inputs are civil year, month, day, and an ancient calendar system.
|
|
func SolarToLunarByYMDWithCalendar(year, month, day int, system AncientCalendarSystem) (Time, error) {
|
|
if system == AncientCalendarDefault {
|
|
return SolarToLunarByYMD(year, month, day)
|
|
}
|
|
return innerSolarToLunarByYMDWithCalendar(year, month, day, time.Time{}, system)
|
|
}
|
|
|
|
// LunarToSolarWithCalendar 农历描述转公历(显式古历) / lunar description to solar date with an explicit ancient calendar.
|
|
//
|
|
// 传入农历日期描述和古历系统,返回该古历系统下匹配的公历日期。
|
|
// Input is a lunar-date description and an ancient calendar system.
|
|
func LunarToSolarWithCalendar(desc string, system AncientCalendarSystem) ([]Time, error) {
|
|
if system == AncientCalendarDefault {
|
|
return LunarToSolar(desc)
|
|
}
|
|
date, err := parseChineseDate(desc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if date.year == 0 || date.comment != "" {
|
|
return nil, fmt.Errorf("显式古历暂不支持年号日期")
|
|
}
|
|
if date.houMonth && system != AncientCalendarQinHan && system != AncientCalendarZhuanxu {
|
|
return nil, fmt.Errorf("未找到对应日期")
|
|
}
|
|
result, err := LunarToSolarByYMDWithCalendar(date.year, date.month, date.day, date.leap, system)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return []Time{result}, nil
|
|
}
|
|
|
|
// LunarToSolarByYMDWithCalendar 农历转公历(按年月日,显式古历) / lunar to solar calendar by YMD with an explicit ancient calendar.
|
|
//
|
|
// 传入农历年月日、闰月标记和古历系统,返回该古历系统下匹配的公历日期。
|
|
// Inputs are lunar year, month, day, leap-month flag, and an ancient calendar system.
|
|
func LunarToSolarByYMDWithCalendar(year, month, day int, leap bool, system AncientCalendarSystem) (Time, error) {
|
|
if system == AncientCalendarDefault {
|
|
return LunarToSolarByYMD(year, month, day, leap)
|
|
}
|
|
if system == AncientCalendarQinHan {
|
|
if result, ok := lunarToSolarQinHan(year, month, day, leap); ok {
|
|
return tagCalendar(result, AncientCalendarQinHan, ancientCalendarName(AncientCalendarQinHan)), nil
|
|
}
|
|
return Time{}, fmt.Errorf("未找到对应日期")
|
|
}
|
|
lmonth, ok := ancientMonthByLunar(year, month, leap, system)
|
|
if !ok {
|
|
return Time{}, fmt.Errorf("未找到对应日期")
|
|
}
|
|
if day < 1 || day > lmonth.endJDN-lmonth.startJDN {
|
|
return Time{}, fmt.Errorf("日期超出范围")
|
|
}
|
|
lmonth.day = day
|
|
date := ancientJDNToDate(lmonth.startJDN + day - 1)
|
|
if !ancientSolarYearInRange(date.Year()) {
|
|
return Time{}, fmt.Errorf("未找到对应日期")
|
|
}
|
|
return ancientTime(date, lmonth), nil
|
|
}
|
|
|
|
func calendricalJieQiWithCalendar(year, term int, system AncientCalendarSystem) (time.Time, error) {
|
|
if _, err := calendricalJieQiTermIndex(term); err != nil {
|
|
return time.Time{}, err
|
|
}
|
|
if system == AncientCalendarDefault {
|
|
return defaultCalendricalJieQi(year, term)
|
|
}
|
|
return calendricalJieQiBySystem(year, term, system)
|
|
}
|
|
|
|
func defaultCalendricalJieQi(year, term int) (time.Time, error) {
|
|
if year < ancientMinYear || year > hanQingJieQiMaxYear {
|
|
return time.Time{}, fmt.Errorf("该年份暂不支持历法节气")
|
|
}
|
|
if year < -479 {
|
|
return time.Time{}, fmt.Errorf("历法 %s 暂不支持历法节气", AncientCalendarChunqiu)
|
|
}
|
|
if year < qinHanMinSolarYear {
|
|
return calendricalJieQiBySystem(year, term, AncientCalendarZhou)
|
|
}
|
|
if year == qinHanMinSolarYear {
|
|
qinHanDate, qinHanErr := calendricalJieQiBySystem(year, term, AncientCalendarQinHan)
|
|
if qinHanErr == nil {
|
|
return qinHanDate, nil
|
|
}
|
|
return calendricalJieQiBySystem(year, term, AncientCalendarZhou)
|
|
}
|
|
if year < qinHanMaxYear {
|
|
return calendricalJieQiBySystem(year, term, AncientCalendarQinHan)
|
|
}
|
|
if year == qinHanMaxYear {
|
|
qinHanDate, qinHanErr := calendricalJieQiBySystem(year, term, AncientCalendarQinHan)
|
|
if qinHanErr == nil {
|
|
return qinHanDate, nil
|
|
}
|
|
return hanQingCalendricalJieQiDate(year, term)
|
|
}
|
|
return hanQingCalendricalJieQiDate(year, term)
|
|
}
|
|
|
|
func calendricalJieQiBySystem(year, term int, system AncientCalendarSystem) (time.Time, error) {
|
|
switch system {
|
|
case AncientCalendarQinHan:
|
|
if year < qinHanMinSolarYear || year > qinHanMaxYear {
|
|
return time.Time{}, fmt.Errorf("历法 %s 不支持该年份", system)
|
|
}
|
|
date, err := ancientSixCalendricalJieQiDate(year, term, AncientCalendarZhuanxu)
|
|
if err != nil {
|
|
return time.Time{}, err
|
|
}
|
|
if !qinHanCalendricalDateSupported(date) {
|
|
return time.Time{}, fmt.Errorf("历法 %s 不支持该年份", system)
|
|
}
|
|
return date, nil
|
|
case AncientCalendarChunqiu:
|
|
return time.Time{}, fmt.Errorf("历法 %s 暂不支持历法节气", system)
|
|
case AncientCalendarZhou, AncientCalendarLu, AncientCalendarHuangdi, AncientCalendarYin, AncientCalendarXia1, AncientCalendarXia2, AncientCalendarZhuanxu:
|
|
if !ancientSolarYearInRange(year) {
|
|
return time.Time{}, fmt.Errorf("历法 %s 不支持该年份", system)
|
|
}
|
|
return ancientSixCalendricalJieQiDate(year, term, system)
|
|
default:
|
|
return time.Time{}, fmt.Errorf("不支持的古历系统: %s", system)
|
|
}
|
|
}
|
|
|
|
func calendricalJieQiTermIndex(term int) (int, error) {
|
|
if term < 0 || term >= 360 || term%15 != 0 {
|
|
return 0, fmt.Errorf("节气参数超出范围")
|
|
}
|
|
return ((term - JQ_冬至 + 360) % 360) / 15, nil
|
|
}
|
|
|
|
func ancientSixCalendricalJieQiDate(year, term int, system AncientCalendarSystem) (time.Time, error) {
|
|
termIndex, err := calendricalJieQiTermIndex(term)
|
|
if err != nil {
|
|
return time.Time{}, err
|
|
}
|
|
param, ok := ancientSixCalendarParameters(system)
|
|
if !ok {
|
|
return time.Time{}, fmt.Errorf("不支持的古历系统: %s", system)
|
|
}
|
|
dy := year - param.yEpoch - 1
|
|
winterSolstice := param.jdEpoch + float64(dy)*ancientSolarYear
|
|
if termIndex == 0 {
|
|
winterSolstice += ancientSolarYear
|
|
}
|
|
jd := winterSolstice + float64(termIndex)*ancientSolarYear/24
|
|
return calendricalJieQiDateFromJD(jd), nil
|
|
}
|
|
|
|
func calendricalJieQiDateFromJD(jd float64) time.Time {
|
|
jdn := int(math.Floor(jd + 0.5 + ancientDateEpsilon))
|
|
date := ancientJDNToDate(jdn)
|
|
return time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, getCst())
|
|
}
|
|
|
|
func qinHanStartDate() time.Time {
|
|
return qinHanJDNToDate(qinHanMonthStartJDNs(qinHanMinYear)[0])
|
|
}
|
|
|
|
func qinHanEndDate() time.Time {
|
|
months := qinHanMonthsForYear(qinHanMaxYear)
|
|
if len(months) == 0 {
|
|
return time.Time{}
|
|
}
|
|
return qinHanJDNToDate(months[len(months)-1].endJDN)
|
|
}
|
|
|
|
func qinHanCalendricalDateSupported(date time.Time) bool {
|
|
if date.Before(qinHanStartDate()) {
|
|
return false
|
|
}
|
|
end := qinHanEndDate()
|
|
if end.IsZero() {
|
|
return false
|
|
}
|
|
return date.Before(end)
|
|
}
|
|
|
|
func innerSolarToLunarAncientByYMD(year, month, day int, hmi time.Time) (Time, bool) {
|
|
system, ok := defaultAncientCalendarSystemForYear(year)
|
|
if !ok {
|
|
return Time{}, false
|
|
}
|
|
result, err := innerSolarToLunarByYMDWithCalendar(year, month, day, hmi, system)
|
|
if err != nil {
|
|
return Time{}, false
|
|
}
|
|
return result, true
|
|
}
|
|
|
|
func lunarToSolarAncientDefault(year, month, day int, leap bool) (Time, bool) {
|
|
system, ok := defaultAncientCalendarSystemForLunarYear(year)
|
|
if !ok {
|
|
return Time{}, false
|
|
}
|
|
result, err := LunarToSolarByYMDWithCalendar(year, month, day, leap, system)
|
|
if err != nil {
|
|
return Time{}, false
|
|
}
|
|
if !ancientSolarYearInRange(result.Solar().In(getCst()).Year()) {
|
|
return Time{}, false
|
|
}
|
|
return result, true
|
|
}
|
|
|
|
func innerSolarToLunarByYMDWithCalendar(year, month, day int, hmi time.Time, system AncientCalendarSystem) (Time, error) {
|
|
if system == AncientCalendarQinHan {
|
|
if err := validateQinHanCalendarSolarInput(year, month, day); err != nil {
|
|
return Time{}, err
|
|
}
|
|
if year > qinHanMaxYear {
|
|
return Time{}, fmt.Errorf("历法 %s 不支持该年份", system)
|
|
}
|
|
if result, ok := innerSolarToLunarQinHanByYMD(year, month, day); ok {
|
|
if !hmi.IsZero() {
|
|
result.solarTime = hmi
|
|
for i := range result.lunars {
|
|
result.lunars[i].solarDate = hmi
|
|
}
|
|
}
|
|
return tagCalendar(result, AncientCalendarQinHan, ancientCalendarName(AncientCalendarQinHan)), nil
|
|
}
|
|
return Time{}, fmt.Errorf("无法获取农历信息")
|
|
}
|
|
if !isPreQinSystem(system) {
|
|
return Time{}, fmt.Errorf("不支持的古历系统: %s", system)
|
|
}
|
|
if err := validatePreQinCalendarSolarInput(year, month, day, system); err != nil {
|
|
return Time{}, err
|
|
}
|
|
targetJDN := ancientDateJDN(year, month, day)
|
|
for lunarYear := year - 1; lunarYear <= year+1; lunarYear++ {
|
|
months, ok := ancientMonthsForYear(lunarYear, system)
|
|
if !ok {
|
|
continue
|
|
}
|
|
for _, m := range months {
|
|
if targetJDN >= m.startJDN && targetJDN < m.endJDN {
|
|
m.day = targetJDN - m.startJDN + 1
|
|
date := hmi
|
|
if date.IsZero() {
|
|
date = time.Date(year, time.Month(month), day, 0, 0, 0, 0, getCst())
|
|
}
|
|
return ancientTime(date, m), nil
|
|
}
|
|
}
|
|
}
|
|
return Time{}, fmt.Errorf("无法获取农历信息")
|
|
}
|
|
|
|
func validatePreQinCalendarSolarInput(year, month, day int, system AncientCalendarSystem) error {
|
|
if !ancientSolarYearInRange(year) {
|
|
return fmt.Errorf("历法 %s 不支持该年份", system)
|
|
}
|
|
return validateAncientCivilDate(year, month, day)
|
|
}
|
|
|
|
func ancientSolarYearInRange(year int) bool {
|
|
return year >= ancientMinYear && year <= qinHanMinSolarYear
|
|
}
|
|
|
|
func validateQinHanCalendarSolarInput(year, month, day int) error {
|
|
if year < qinHanMinSolarYear || year > qinHanMaxYear {
|
|
return fmt.Errorf("历法 %s 不支持该年份", AncientCalendarQinHan)
|
|
}
|
|
return validateAncientCivilDate(year, month, day)
|
|
}
|
|
|
|
func validateAncientCivilDate(year, month, day int) error {
|
|
if month < 1 || month > 12 {
|
|
return fmt.Errorf("月份超出范围")
|
|
}
|
|
if day < 1 || day > 31 {
|
|
return fmt.Errorf("日期超出范围")
|
|
}
|
|
if err := basic.ValidateCivilDate(year, month, float64(day)); err != nil {
|
|
return fmt.Errorf("公历日期不存在")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func defaultAncientCalendarSystemForYear(year int) (AncientCalendarSystem, bool) {
|
|
if year < ancientMinYear || year > qinHanMinSolarYear {
|
|
return AncientCalendarDefault, false
|
|
}
|
|
if year < -479 {
|
|
return AncientCalendarChunqiu, true
|
|
}
|
|
return AncientCalendarZhou, true
|
|
}
|
|
|
|
func defaultAncientCalendarSystemForLunarYear(year int) (AncientCalendarSystem, bool) {
|
|
if year < ancientBoundaryMinYear || year > ancientMaxYear {
|
|
return AncientCalendarDefault, false
|
|
}
|
|
if year < -479 {
|
|
return AncientCalendarChunqiu, true
|
|
}
|
|
return AncientCalendarZhou, true
|
|
}
|
|
|
|
func ancientMonthsForYear(year int, system AncientCalendarSystem) ([]ancientMonth, bool) {
|
|
if !ancientSystemSupportsTableYear(year, system) {
|
|
return nil, false
|
|
}
|
|
switch system {
|
|
case AncientCalendarChunqiu:
|
|
return chunqiuMonthsForYear(year)
|
|
case AncientCalendarZhou, AncientCalendarLu, AncientCalendarHuangdi, AncientCalendarYin, AncientCalendarXia1, AncientCalendarXia2, AncientCalendarZhuanxu:
|
|
return ancientSixMonthsForYear(year, system)
|
|
default:
|
|
return nil, false
|
|
}
|
|
}
|
|
|
|
func ancientSystemSupportsTableYear(year int, system AncientCalendarSystem) bool {
|
|
if year < ancientBoundaryMinYear {
|
|
return false
|
|
}
|
|
if system == AncientCalendarChunqiu {
|
|
return year <= -479
|
|
}
|
|
if !isPreQinSystem(system) {
|
|
return false
|
|
}
|
|
return year <= ancientBoundaryMaxYear
|
|
}
|
|
|
|
func isPreQinSystem(system AncientCalendarSystem) bool {
|
|
switch system {
|
|
case AncientCalendarChunqiu, AncientCalendarZhou, AncientCalendarLu, AncientCalendarHuangdi, AncientCalendarYin, AncientCalendarXia1, AncientCalendarXia2, AncientCalendarZhuanxu:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func chunqiuLeapYear(index int) int {
|
|
if index < 0 || index >= chunqiuLeapYearCount {
|
|
return 0
|
|
}
|
|
if chunqiuLeapYearBitmap[index/8]&(1<<uint(index%8)) != 0 {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func chunqiuAccLeapsBefore(index int) int {
|
|
if index <= 0 {
|
|
return 0
|
|
}
|
|
if index > chunqiuLeapYearCount {
|
|
index = chunqiuLeapYearCount
|
|
}
|
|
count := 0
|
|
fullBytes := index / 8
|
|
for i := 0; i < fullBytes; i++ {
|
|
count += bitCount(chunqiuLeapYearBitmap[i])
|
|
}
|
|
for i := fullBytes * 8; i < index; i++ {
|
|
count += chunqiuLeapYear(i)
|
|
}
|
|
return count
|
|
}
|
|
|
|
func bitCount(v byte) int {
|
|
count := 0
|
|
for v != 0 {
|
|
v &= v - 1
|
|
count++
|
|
}
|
|
return count
|
|
}
|
|
|
|
func chunqiuMonthsForYear(year int) ([]ancientMonth, bool) {
|
|
i := year - chunqiuYearEpoch
|
|
if i < -1 || i >= chunqiuLeapYearCount {
|
|
return nil, false
|
|
}
|
|
leap := 0
|
|
accLeaps := 0
|
|
if i >= 0 {
|
|
leap = chunqiuLeapYear(i)
|
|
accLeaps = chunqiuAccLeapsBefore(i)
|
|
}
|
|
accMonths := 12*i + accLeaps
|
|
monthCount := 12 + leap
|
|
m0 := chunqiuJDEpoch + float64(accMonths)*chunqiuLunarMonth
|
|
jd0 := ancientJDAtLocalMidnight(year-1, 12, 31)
|
|
jdn0 := int(math.Floor(jd0 + 0.6))
|
|
starts := make([]int, monthCount+1)
|
|
for idx := 0; idx <= monthCount; idx++ {
|
|
starts[idx] = jdn0 + int(math.Floor(m0+float64(idx)*chunqiuLunarMonth-jd0+ancientDateEpsilon))
|
|
}
|
|
months := make([]ancientMonth, 0, monthCount)
|
|
for idx := 0; idx < monthCount; idx++ {
|
|
month := idx + 1
|
|
isLeap := false
|
|
if monthCount == 13 && idx == 12 {
|
|
month = 12
|
|
isLeap = true
|
|
}
|
|
months = append(months, ancientMonth{
|
|
lunarYear: year,
|
|
month: month,
|
|
leap: isLeap,
|
|
startJDN: starts[idx],
|
|
endJDN: starts[idx+1],
|
|
system: AncientCalendarChunqiu,
|
|
name: ancientCalendarName(AncientCalendarChunqiu),
|
|
})
|
|
}
|
|
return months, true
|
|
}
|
|
|
|
func ancientSixMonthsForYear(year int, system AncientCalendarSystem) ([]ancientMonth, bool) {
|
|
param, ok := ancientSixCalendarParameters(system)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
dy := year - param.yEpoch - 1
|
|
w0 := param.jdEpoch + float64(dy)*ancientSolarYear
|
|
w1 := w0 + ancientSolarYear
|
|
i := math.Floor((math.Floor(w0+1.5) - 0.5 - param.jdEpochMoon) / ancientLunarMonth)
|
|
m0 := param.jdEpochMoon + i*ancientLunarMonth
|
|
m1 := m0 + 13*ancientLunarMonth
|
|
monthCount := 12
|
|
if math.Floor(m1+0.5) < math.Floor(w1+0.5)+0.1 {
|
|
monthCount = 13
|
|
}
|
|
monthOffset := param.ziOffset
|
|
if param.ziOffset > 0 {
|
|
if monthCount == 13 {
|
|
monthOffset++
|
|
}
|
|
m1 = m0 + float64(monthCount+13)*ancientLunarMonth
|
|
w2 := w1 + ancientSolarYear
|
|
monthCount = 12
|
|
if math.Floor(m1+0.5) < math.Floor(w2+0.5)+0.1 {
|
|
monthCount = 13
|
|
}
|
|
}
|
|
m0 += float64(monthOffset) * ancientLunarMonth
|
|
jd0 := ancientJDAtLocalMidnight(year-1, 12, 31)
|
|
jdn0 := int(math.Floor(jd0 + 0.6))
|
|
months := make([]ancientMonth, 0, monthCount)
|
|
for idx := 0; idx < monthCount; idx++ {
|
|
m := m0 + float64(idx)*ancientLunarMonth
|
|
startOffset := int(math.Floor(m - jd0 + ancientDateEpsilon))
|
|
endOffset := int(math.Floor(m + ancientLunarMonth - jd0 + ancientDateEpsilon))
|
|
start := jdn0 + startOffset
|
|
end := jdn0 + endOffset
|
|
month, isLeap := ancientSixMonthNumber(system, idx, monthCount)
|
|
months = append(months, ancientMonth{
|
|
lunarYear: year,
|
|
month: month,
|
|
leap: isLeap,
|
|
startJDN: start,
|
|
endJDN: end,
|
|
system: system,
|
|
name: param.name,
|
|
})
|
|
}
|
|
return months, true
|
|
}
|
|
|
|
func ancientSixMonthNumber(system AncientCalendarSystem, index, monthCount int) (int, bool) {
|
|
if monthCount == 13 && index == 12 {
|
|
if system == AncientCalendarZhuanxu {
|
|
return 9, true
|
|
}
|
|
return 12, true
|
|
}
|
|
if system == AncientCalendarZhuanxu {
|
|
return 1 + ((index + 9) % 12), false
|
|
}
|
|
return index + 1, false
|
|
}
|
|
|
|
func ancientSixCalendarParameters(system AncientCalendarSystem) (ancientSixParameters, bool) {
|
|
switch system {
|
|
case AncientCalendarZhou:
|
|
return ancientSixParameters{-104, 1683430.5001, 1683430.5001, 0, ancientCalendarName(system)}, true
|
|
case AncientCalendarHuangdi:
|
|
return ancientSixParameters{170, 1783510.5001, 1783510.5001, 0, ancientCalendarName(system)}, true
|
|
case AncientCalendarYin:
|
|
return ancientSixParameters{-47, 1704250.5001, 1704250.5001, 1, ancientCalendarName(system)}, true
|
|
case AncientCalendarLu:
|
|
jdEpoch := 1545730.5001
|
|
return ancientSixParameters{-481, jdEpoch, jdEpoch - ancientLunarMonth/19.0, 0, ancientCalendarName(system)}, true
|
|
case AncientCalendarZhuanxu:
|
|
jdEpochMoon := 1726575.5001
|
|
return ancientSixParameters{14, jdEpochMoon - ancientSolarYear/8.0, jdEpochMoon, -1, ancientCalendarName(system)}, true
|
|
case AncientCalendarXia1:
|
|
return ancientSixParameters{444, 1883590.5001, 1883590.5001, 2, ancientCalendarName(system)}, true
|
|
case AncientCalendarXia2:
|
|
jdEpochMoon := 1883650.5001
|
|
return ancientSixParameters{444, jdEpochMoon - ancientSolarYear/6.0, jdEpochMoon, 2, ancientCalendarName(system)}, true
|
|
default:
|
|
return ancientSixParameters{}, false
|
|
}
|
|
}
|
|
|
|
func ancientMonthByLunar(year, month int, leap bool, system AncientCalendarSystem) (ancientMonth, bool) {
|
|
if !ancientSystemSupportsTableYear(year, system) {
|
|
return ancientMonth{}, false
|
|
}
|
|
months, ok := ancientMonthsForYear(year, system)
|
|
if !ok {
|
|
return ancientMonth{}, false
|
|
}
|
|
for _, m := range months {
|
|
if m.month == month && m.leap == leap {
|
|
return m, true
|
|
}
|
|
}
|
|
return ancientMonth{}, false
|
|
}
|
|
|
|
func ancientTime(date time.Time, month ancientMonth) Time {
|
|
return Time{
|
|
solarTime: date,
|
|
lunars: []LunarTime{
|
|
{
|
|
solarDate: date,
|
|
year: month.lunarYear,
|
|
month: month.month,
|
|
day: month.day,
|
|
leap: month.leap,
|
|
desc: formatAncientLunarDateString(month.month, month.day, month.leap, month.system),
|
|
calendarSystem: month.system,
|
|
calendarName: month.name,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func tagCalendar(date Time, system AncientCalendarSystem, name string) Time {
|
|
for i := range date.lunars {
|
|
date.lunars[i].calendarSystem = system
|
|
date.lunars[i].calendarName = name
|
|
}
|
|
return date
|
|
}
|
|
|
|
func formatAncientLunarDateString(month, day int, leap bool, system AncientCalendarSystem) string {
|
|
if leap {
|
|
if system == AncientCalendarZhuanxu {
|
|
return "后九月" + formatLunarDayString(day)
|
|
}
|
|
return "闰" + formatAncientMonthName(month) + "月" + formatLunarDayString(day)
|
|
}
|
|
return formatAncientMonthName(month) + "月" + formatLunarDayString(day)
|
|
}
|
|
|
|
func formatAncientMonthName(month int) string {
|
|
return ancientMonthNames[month]
|
|
}
|
|
|
|
func ancientCalendarName(system AncientCalendarSystem) string {
|
|
switch system {
|
|
case AncientCalendarChunqiu:
|
|
return "春秋历"
|
|
case AncientCalendarZhou:
|
|
return "周历"
|
|
case AncientCalendarLu:
|
|
return "鲁历"
|
|
case AncientCalendarHuangdi:
|
|
return "黄帝历"
|
|
case AncientCalendarYin:
|
|
return "殷历"
|
|
case AncientCalendarXia1:
|
|
return "夏历(冬至版)"
|
|
case AncientCalendarXia2:
|
|
return "夏历(雨水版)"
|
|
case AncientCalendarZhuanxu:
|
|
return "颛顼历"
|
|
case AncientCalendarQinHan:
|
|
return "秦汉颛顼历"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func ancientDateJDN(year, month, day int) int {
|
|
return int(math.Floor(basic.JDECalc(year, month, float64(day)) + 0.5))
|
|
}
|
|
|
|
func ancientJDAtLocalMidnight(year, month, day int) float64 {
|
|
return basic.JDECalc(year, month, float64(day))
|
|
}
|
|
|
|
func ancientJDNToDate(jdn int) time.Time {
|
|
return basic.JDE2DateByZone(float64(jdn)-0.5, getCst(), true)
|
|
}
|