From 9b50cfff3f25a1ae3a4909856683c55dd7bd93a9 Mon Sep 17 00:00:00 2001 From: starainrt Date: Wed, 5 Jan 2022 17:20:55 +0800 Subject: [PATCH] bug fix && moon phase date calc --- basic/calendar.go | 39 ++++++++++++-- basic/moon.go | 68 +++++++++++++---------- calendar/chinese.go | 6 +-- calendar/chinese_test.go | 49 +++++++++++++++-- moon/moon.go | 114 +++++++++++++++++++++++++++++++++++++++ moon/moon_test.go | 47 +++++++++++++++- 6 files changed, 282 insertions(+), 41 deletions(-) diff --git a/basic/calendar.go b/basic/calendar.go index 5c48b6b..f38b20b 100644 --- a/basic/calendar.go +++ b/basic/calendar.go @@ -207,13 +207,13 @@ func JDE2DateByZone(JD float64, tz *time.Location, byZone bool) time.Time { return time.Unix(dates.Unix()+int64(tms), int64((tms-math.Floor(tms))*1000000000)) } -func GetLunar(year, month, day int) (lmonth, lday int, leap bool, result string) { +func GetLunar(year, month, day int, tz float64) (lmonth, lday int, leap bool, result string) { jde := JDECalc(year, month, float64(day)) //计算当前JDE时间 if month == 11 || month == 12 { //判断当前日期属于前一年周期还是后一年周期 //判断方法:当前日期与冬至日所在朔望月的关系 - winterday := GetJQTime(year, 270) + 8.0/24.0 //冬至日日期(世界时,北京时间) - Fday := TD2UT(CalcMoonS(float64(year)+11.0/12.0+5.0/30.0/12.0, 0), true) + 8.0/24.0 //朔月(世界时,北京时间) - Yday := TD2UT(CalcMoonS(float64(year)+1.0, 0), true) + 8.0/24.0 //下一朔月(世界时,北京时间) + winterday := GetJQTime(year, 270) + tz //冬至日日期(世界时,北京时间) + Fday := TD2UT(CalcMoonS(float64(year)+11.0/12.0+5.0/30.0/12.0, 0), true) + tz //朔月(世界时,北京时间) + Yday := TD2UT(CalcMoonS(float64(year)+1.0, 0), true) + tz //下一朔月(世界时,北京时间) if Fday-math.Floor(Fday) > 0.5 { Fday = math.Floor(Fday) + 0.5 } else { @@ -238,6 +238,9 @@ func GetLunar(year, month, day int) (lmonth, lday int, leap bool, result string) winter1 := jieqi[1] //第一年冬至日 winter2 := jieqi[25] //第二年冬至日 for k, v := range moon { + if tz != 8.0/24 { + v = v - 8.0/24 + tz + } if v-math.Floor(v) > 0.5 { moon[k] = math.Floor(v) + 0.5 } else { @@ -245,6 +248,9 @@ func GetLunar(year, month, day int) (lmonth, lday int, leap bool, result string) } } //置闰月为0点 for k, v := range jieqi { + if tz != 8.0/24 { + v = v - 8.0/24 + tz + } if v-math.Floor(v) > 0.5 { jieqi[k] = math.Floor(v) + 0.5 } else { @@ -298,6 +304,9 @@ func GetLunar(year, month, day int) (lmonth, lday int, leap bool, result string) if sleap { lmonth-- } + if lmonth <= 0 { + lmonth += 12 + } lday = int(jde-moon[i-1]) + 1 strmonth := []string{"十", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "冬", "腊"} strday := []string{"初", "十", "廿", "三"} @@ -319,7 +328,7 @@ func GetLunar(year, month, day int) (lmonth, lday int, leap bool, result string) return } -func GetSolar(year, month, day int, leap bool) float64 { +func GetSolar(year, month, day int, leap bool, tz float64) float64 { if month < 11 { year-- } @@ -328,6 +337,9 @@ func GetSolar(year, month, day int, leap bool) float64 { winter1 := jieqi[1] //第一年冬至日 winter2 := jieqi[25] //第二年冬至日 for k, v := range moon { + if tz != 8.0/24 { + v = v - 8.0/24 + tz + } if v-math.Floor(v) > 0.5 { moon[k] = math.Floor(v) + 0.5 } else { @@ -335,6 +347,9 @@ func GetSolar(year, month, day int, leap bool) float64 { } } //置闰月为0点 for k, v := range jieqi { + if tz != 8.0/24 { + v = v - 8.0/24 + tz + } if v-math.Floor(v) > 0.5 { jieqi[k] = math.Floor(v) + 0.5 } else { @@ -354,6 +369,17 @@ func GetSolar(year, month, day int, leap bool) float64 { mooncount++ } } + leapmonth := 20 + if mooncount == 13 { //存在闰月 + j, i := 3, 1 + for i = min; i <= max; i++ { + if !(moon[i] <= jieqi[j] && moon[i+1] > jieqi[j]) { + break + } + j += 2 + } + leapmonth = i - min + 1 + } if leap { month++ } @@ -362,6 +388,9 @@ func GetSolar(year, month, day int, leap bool) float64 { } else { month++ } + if month >= leapmonth && !leap { + month++ + } jde := moon[min-1+month] + float64(day) - 1 return jde } diff --git a/basic/moon.go b/basic/moon.go index 4841934..7fd6a2e 100644 --- a/basic/moon.go +++ b/basic/moon.go @@ -1106,36 +1106,40 @@ func MoonLight(JD float64) float64 { return k } -func SunMoonSeek(JDE float64) float64 { - p := HMoonSeeLo(JDE) - (HSunSeeLo(JDE)) - if p > 240 { - p -= 360 - } - if p < -240 { +func SunMoonSeek(JDE float64, degree float64) float64 { + p := HMoonSeeLo(JDE) - (HSunSeeLo(JDE)) - degree + for p < -180 { p += 360 } + for p > 180 { + p -= 360 + } return p } -func CalcMoonSH(Year float64, C int) float64 { - JDE := CalcMoonS(Year, C) +func CalcMoonSHByJDE(JDE float64, C int) float64 { C = C * 180 - i := 0 + JD1 := JDE for { - JDE -= 0.005 - i++ - if i > 1000 { - break - } - if SunMoonSeek(JDE) <= float64(C) { + JD0 := JD1 + stDegree := SunMoonSeek(JD0, float64(C)) + stDegreep := (SunMoonSeek(JD0+0.000005, float64(C)) - SunMoonSeek(JD0-0.000005, float64(C))) / 0.00001 + JD1 = JD0 - stDegree/stDegreep + if math.Abs(JD1-JD0) <= 0.00001 { break } } + return JD1 +} + +func CalcMoonSH(Year float64, C int) float64 { + JDE := CalcMoonS(Year, C) + C = C * 180 JD1 := JDE for { JD0 := JD1 - stDegree := SunMoonSeek(JD0) - float64(C) - stDegreep := (SunMoonSeek(JD0+0.000005) - SunMoonSeek(JD0-0.000005)) / 0.00001 + stDegree := SunMoonSeek(JD0, float64(C)) + stDegreep := (SunMoonSeek(JD0+0.000005, float64(C)) - SunMoonSeek(JD0-0.000005, float64(C))) / 0.00001 JD1 = JD0 - stDegree/stDegreep if math.Abs(JD1-JD0) <= 0.00001 { break @@ -1196,29 +1200,37 @@ func CalcMoonS(Year float64, C int) float64 { return JDE } -func CalcMoonXH(Year float64, C int) float64 { - JDE := CalcMoonX(Year, C) +func CalcMoonXHByJDE(JDE float64, C int) float64 { if C == 0 { C = 90 } else { C = -90 } - i := 0 + JD1 := JDE for { - JDE -= 0.005 - i++ - if i > 1000 { - break - } - if SunMoonSeek(JDE) <= float64(C) { + JD0 := JD1 + stDegree := SunMoonSeek(JD0, float64(C)) + stDegreep := (SunMoonSeek(JD0+0.000005, float64(C)) - SunMoonSeek(JD0-0.000005, float64(C))) / 0.00001 + JD1 = JD0 - stDegree/stDegreep + if math.Abs(JD1-JD0) <= 0.00001 { break } } + return JD1 +} + +func CalcMoonXH(Year float64, C int) float64 { + JDE := CalcMoonX(Year, C) + if C == 0 { + C = 90 + } else { + C = -90 + } JD1 := JDE for { JD0 := JD1 - stDegree := SunMoonSeek(JD0) - float64(C) - stDegreep := (SunMoonSeek(JD0+0.000005) - SunMoonSeek(JD0-0.000005)) / 0.00001 + stDegree := SunMoonSeek(JD0, float64(C)) + stDegreep := (SunMoonSeek(JD0+0.000005, float64(C)) - SunMoonSeek(JD0-0.000005, float64(C))) / 0.00001 JD1 = JD0 - stDegree/stDegreep if math.Abs(JD1-JD0) <= 0.00001 { break diff --git a/calendar/chinese.go b/calendar/chinese.go index f326a3e..07f4429 100644 --- a/calendar/chinese.go +++ b/calendar/chinese.go @@ -37,7 +37,7 @@ const ( // 传入 公历年月日 // 返回 农历月,日,是否闰月以及文字描述 func Lunar(year, month, day int) (int, int, bool, string) { - return basic.GetLunar(year, month, day) + return basic.GetLunar(year, month, day, 8.0/24.0) } // ChineseLunar 公历转农历 @@ -45,7 +45,7 @@ func Lunar(year, month, day int) (int, int, bool, string) { // 返回 农历月,日,是否闰月以及文字描述 // 忽略时区,日期一律按北京时间计算 func ChineseLunar(date time.Time) (int, int, bool, string) { - return basic.GetLunar(date.Year(), int(date.Month()), date.Day()) + return basic.GetLunar(date.Year(), int(date.Month()), date.Day(), 8.0/24.0) } // Solar 农历转公历 @@ -55,7 +55,7 @@ func ChineseLunar(date time.Time) (int, int, bool, string) { // 例:计算己亥猪年腊月三十日对应的公历(即2020年1月24日) // 由于农历还未到鼠年,故应当传入Solar(2019,12,30,false) func Solar(year, month, day int, leap bool) time.Time { - jde := basic.GetSolar(year, month, day, leap) + jde := basic.GetSolar(year, month, day, leap, 8.0/24.0) zone := time.FixedZone("CST", 8*3600) return basic.JDE2DateByZone(jde, zone, true) } diff --git a/calendar/chinese_test.go b/calendar/chinese_test.go index 8c3101d..10e6be4 100644 --- a/calendar/chinese_test.go +++ b/calendar/chinese_test.go @@ -5,7 +5,50 @@ import ( "testing" ) -func Test_Solar(t *testing.T) { - fmt.Println(Solar(2021, 1, 1, false)) - fmt.Println(Solar(2020, 1, 1, false)) +type LunarSolar struct { + Lyear int + Lmonth int + Lday int + Leap bool + Year int + Month int + Day int +} + +func Test_ChineseCalendar(t *testing.T) { + var testData = []LunarSolar{ + {Lyear: 2034, Lmonth: 1, Lday: 1, Leap: false, Year: 2034, Month: 2, Day: 19}, + {Lyear: 2033, Lmonth: 12, Lday: 30, Leap: false, Year: 2034, Month: 2, Day: 18}, + {Lyear: 2033, Lmonth: 11, Lday: 27, Leap: true, Year: 2034, Month: 1, Day: 17}, + {Lyear: 2033, Lmonth: 11, Lday: 1, Leap: true, Year: 2033, Month: 12, Day: 22}, + {Lyear: 2033, Lmonth: 11, Lday: 30, Leap: false, Year: 2033, Month: 12, Day: 21}, + {Lyear: 2023, Lmonth: 2, Lday: 30, Leap: false, Year: 2023, Month: 3, Day: 21}, + {Lyear: 2023, Lmonth: 2, Lday: 1, Leap: true, Year: 2023, Month: 3, Day: 22}, + {Lyear: 2020, Lmonth: 1, Lday: 1, Leap: false, Year: 2020, Month: 1, Day: 25}, + {Lyear: 2015, Lmonth: 1, Lday: 1, Leap: false, Year: 2015, Month: 2, Day: 19}, + {Lyear: 2014, Lmonth: 12, Lday: 30, Leap: false, Year: 2015, Month: 2, Day: 18}, + {Lyear: 1996, Lmonth: 1, Lday: 1, Leap: false, Year: 1996, Month: 2, Day: 19}, + {Lyear: 1995, Lmonth: 12, Lday: 30, Leap: false, Year: 1996, Month: 2, Day: 18}, + {Lyear: 1996, Lmonth: 10, Lday: 30, Leap: false, Year: 1996, Month: 12, Day: 10}, + {Lyear: 2014, Lmonth: 9, Lday: 1, Leap: true, Year: 2014, Month: 10, Day: 24}, + {Lyear: 2014, Lmonth: 9, Lday: 30, Leap: false, Year: 2014, Month: 10, Day: 23}, + {Lyear: 2014, Lmonth: 10, Lday: 1, Leap: false, Year: 2014, Month: 11, Day: 22}, + {Lyear: 2021, Lmonth: 12, Lday: 29, Leap: false, Year: 2022, Month: 1, Day: 31}, + } + for _, v := range testData { + var lyear int = v.Year + lmonth, lday, leap, desp := Lunar(v.Year, v.Month, v.Day) + if lmonth > v.Month { + lyear-- + } + fmt.Println(lyear, desp, v.Year, v.Month, v.Day) + if lyear != v.Lyear || lmonth != v.Lmonth || lday != v.Lday || leap != v.Leap { + t.Fatal(v, lyear, lmonth, lday, leap, desp) + } + + date := Solar(v.Lyear, v.Lmonth, v.Lday, v.Leap) + if date.Year() != v.Year || int(date.Month()) != v.Month || date.Day() != v.Day { + t.Fatal(v, date) + } + } } diff --git a/moon/moon.go b/moon/moon.go index 0d72225..b262d00 100644 --- a/moon/moon.go +++ b/moon/moon.go @@ -2,6 +2,7 @@ package moon import ( "errors" + "math" "time" "b612.me/astro/basic" @@ -180,24 +181,137 @@ func ShuoYue(year float64) time.Time { return basic.JDE2DateByZone(jde, time.UTC, false) } +func NextShuoYue(date time.Time) time.Time { + return nextMoonPhase(date, 0) +} + +func LastShuoYue(date time.Time) time.Time { + return lastMoonPhase(date, 0) +} + +func ClosestShuoYue(date time.Time) time.Time { + return closestMoonPhase(date, 0) +} + +func closestMoonPhase(date time.Time, typed int) time.Time { + //0=shuo 1=wang 2=shangxian 3=xiaxian + jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) + if typed < 2 { + return basic.JDE2DateByZone(basic.TD2UT(basic.CalcMoonSHByJDE(jde, typed), false), date.Location(), false) + } + return basic.JDE2DateByZone(basic.TD2UT(basic.CalcMoonXHByJDE(jde, typed-2), false), date.Location(), false) +} + +func nextMoonPhase(date time.Time, typed int) time.Time { + //0=shuo 1=wang 2=shangxian 3=xiaxian + diffCode := 0.00 + switch typed { + case 1: + diffCode = 180 + case 2: + diffCode = 90 + case 3: + diffCode = 270 + } + jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) + cost := basic.HMoonSeeLo(jde) - basic.HSunSeeLo(jde) - float64(diffCode) + for cost < 0 { + cost += 360 + } + if cost < 0 && math.Floor(math.Abs(cost)*10000) == 0 { + cost = 0 + } + if cost < 240 { + jde += (240 - cost) / 11.19 + } + if typed < 2 { + return basic.JDE2DateByZone(basic.TD2UT(basic.CalcMoonSHByJDE(jde, typed), false), date.Location(), false) + } + return basic.JDE2DateByZone(basic.TD2UT(basic.CalcMoonXHByJDE(jde, typed-2), false), date.Location(), false) +} + +func lastMoonPhase(date time.Time, typed int) time.Time { + //0=shuo 1=wang 2=shangxian 3=xiaxian + diffCode := 0.00 + switch typed { + case 1: + diffCode = 180 + case 2: + diffCode = 90 + case 3: + diffCode = 270 + } + jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) + cost := basic.HMoonSeeLo(jde) - basic.HSunSeeLo(jde) - float64(diffCode) + for cost < 0 { + cost += 360 + } + if cost > 0 && math.Floor(math.Abs(cost)*10000) == 0 { + cost = 360 + } + if cost > 120 { + jde -= (cost - 120) / 11.19 + } + if typed < 2 { + return basic.JDE2DateByZone(basic.TD2UT(basic.CalcMoonSHByJDE(jde, typed), false), date.Location(), false) + } + return basic.JDE2DateByZone(basic.TD2UT(basic.CalcMoonXHByJDE(jde, typed-2), false), date.Location(), false) +} + // WangYue 望月 func WangYue(year float64) time.Time { jde := basic.TD2UT(basic.CalcMoonSH(year, 1), false) return basic.JDE2DateByZone(jde, time.UTC, false) } +func NextWangYue(date time.Time) time.Time { + return nextMoonPhase(date, 1) +} + +func LastWangYue(date time.Time) time.Time { + return lastMoonPhase(date, 1) +} + +func ClosestWangYue(date time.Time) time.Time { + return closestMoonPhase(date, 1) +} + // ShangXianYue 上弦月 func ShangXianYue(year float64) time.Time { jde := basic.TD2UT(basic.CalcMoonXH(year, 0), false) return basic.JDE2DateByZone(jde, time.UTC, false) } +func NextShangXianYue(date time.Time) time.Time { + return nextMoonPhase(date, 2) +} + +func LastShangXianYue(date time.Time) time.Time { + return lastMoonPhase(date, 2) +} + +func ClosestShangXianYue(date time.Time) time.Time { + return closestMoonPhase(date, 2) +} + // XiaXianYue 下弦月 func XiaXianYue(year float64) time.Time { jde := basic.TD2UT(basic.CalcMoonXH(year, 1), false) return basic.JDE2DateByZone(jde, time.UTC, false) } +func NextXiaXianYue(date time.Time) time.Time { + return nextMoonPhase(date, 3) +} + +func LastXiaXianYue(date time.Time) time.Time { + return lastMoonPhase(date, 3) +} + +func ClosestXiaXianYue(date time.Time) time.Time { + return closestMoonPhase(date, 3) +} + // EarthDistance 日地距离 // 返回date对应UTC世界时日地距离 func EarthDistance(date time.Time) float64 { diff --git a/moon/moon_test.go b/moon/moon_test.go index dbd8b6d..8f2c013 100644 --- a/moon/moon_test.go +++ b/moon/moon_test.go @@ -6,6 +6,49 @@ import ( "time" ) -func Test_Rise(t *testing.T) { - fmt.Println(RiseTime(time.Now(), 120, 40, 10, true)) +func Test_MoonPhaseDate(t *testing.T) { + //指定北京时间2022年1月20日 + tz := time.FixedZone("CST", 8*3600) + date := time.Date(2022, 01, 20, 00, 00, 00, 00, tz) + //指定日期后的下一个朔月 + moonPhase01 := NextShuoYue(date) + fmt.Println("下一朔月", moonPhase01) + if moonPhase01.Unix() != 1643694349 { + t.Fatal(moonPhase01) + } + //指定日期后的上一个朔月 + moonPhase01 = LastShuoYue(date) + fmt.Println("上一朔月", moonPhase01) + if moonPhase01.Unix() != 1641148399 { + t.Fatal(moonPhase01) + } + //离指定日期最近的朔月 + moonPhase01 = ClosestShuoYue(date) + fmt.Println("最近朔月", moonPhase01) + if moonPhase01.Unix() != 1643694349 { + t.Fatal(moonPhase01) + } + //离指定日期最近的望月时间 + moonPhase01 = ClosestWangYue(date) + fmt.Println("最近望月", moonPhase01) + if moonPhase01.Unix() != 1642463294 { + t.Fatal(moonPhase01) + } + //离指定日期最近的上弦月时间 + moonPhase01 = ClosestShangXianYue(date) + fmt.Println("最近上弦月", moonPhase01) + if moonPhase01.Unix() != 1641751864 { + t.Fatal(moonPhase01) + } + //离指定日期最近的下弦月时间 + moonPhase01 = ClosestXiaXianYue(date) + fmt.Println("最近下弦月", moonPhase01) + if moonPhase01.Unix() != 1643118043 { + t.Fatal(moonPhase01) + } + //------------------- + for i := 0; i < 26; i++ { + moonPhase01 = LastShuoYue(moonPhase01) + fmt.Println("上一朔月", moonPhase01) + } }