From 616cd54222156428e7c478c581fde08e618f7b74 Mon Sep 17 00:00:00 2001
From: starainrt <i@b612.me>
Date: Sat, 26 Oct 2024 21:28:26 +0800
Subject: [PATCH 1/2] =?UTF-8?q?fix:=E6=97=B6=E5=8C=BA=E8=BD=AC=E6=8D=A2?=
 =?UTF-8?q?=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 basic/calendar.go       | 16 ++++++++++------
 mercury/mercury_test.go | 20 ++++++++++++++++----
 moon/moon.go            |  3 +++
 moon/moon_test.go       | 31 ++++++++++++-------------------
 neptune/neptune_test.go | 20 ++++++++++++++++----
 sun/sun.go              | 16 +++++++---------
 sun/sun_test.go         | 19 +++++++++++++++++--
 7 files changed, 81 insertions(+), 44 deletions(-)

diff --git a/basic/calendar.go b/basic/calendar.go
index c3647e5..40917b6 100644
--- a/basic/calendar.go
+++ b/basic/calendar.go
@@ -197,10 +197,14 @@ func JDE2Date(JD float64) time.Time {
 	Days = math.Floor(Days)
 	tz, _ := time.LoadLocation("Local")
 	dates := time.Date(int(Years), time.Month(int(Months)), int(Days), 0, 0, 0, 0, tz)
-	dates = time.Unix(dates.Unix()+int64(tms), int64((tms-math.Floor(tms))*1000000000))
-	return dates
+	return time.Unix(dates.Unix()+int64(tms), int64((tms-math.Floor(tms))*1000000000))
 }
 
+// JDE2DateByZone JDE(儒略日)转日期
+// JD: 儒略日
+// tz: 目标时区
+// byZone: (true: 传入的儒略日视为目标时区当地时间的儒略日,false: 传入的儒略日视为UTC时间的儒略日)
+// 回参:转换后的日期,时区始终为目标时区
 func JDE2DateByZone(JD float64, tz *time.Location, byZone bool) time.Time {
 	JD = JD + 0.5
 	Z := float64(int(JD))
@@ -231,12 +235,12 @@ func JDE2DateByZone(JD float64, tz *time.Location, byZone bool) time.Time {
 	}
 	tms := (Days - math.Floor(Days)) * 24 * 3600
 	Days = math.Floor(Days)
+	var transTz = tz
 	if !byZone {
-		dates := time.Date(int(Years), time.Month(int(Months)), int(Days), 0, 0, 0, 0, time.UTC)
-		return time.Unix(dates.Unix()+int64(tms), int64((tms-math.Floor(tms))*1000000000)).In(tz)
+		transTz = time.UTC
 	}
-	dates := time.Date(int(Years), time.Month(int(Months)), int(Days), 0, 0, 0, 0, tz)
-	return time.Unix(dates.Unix()+int64(tms), int64((tms-math.Floor(tms))*1000000000))
+	return time.Date(int(Years), time.Month(int(Months)), int(Days), 0, 0, 0, 0, transTz).
+		Add(time.Duration(int64(1000000000 * tms))).In(tz)
 }
 
 func GetLunar(year, month, day int, tz float64) (lmonth, lday int, leap bool, result string) {
diff --git a/mercury/mercury_test.go b/mercury/mercury_test.go
index 44bdbfb..be6a641 100644
--- a/mercury/mercury_test.go
+++ b/mercury/mercury_test.go
@@ -7,8 +7,20 @@ import (
 )
 
 func TestMercury(t *testing.T) {
-	date := time.Now().Add(time.Hour * -24)
-	fmt.Println(CulminationTime(date, 115))
-	fmt.Println(RiseTime(date, 115, 23, 0, false))
-	fmt.Println(DownTime(date, 115, 23, 0, false))
+	tz := time.FixedZone("CST", 8*3600)
+	date := time.Date(2022, 01, 20, 00, 00, 00, 00, tz)
+	if NextConjunction(date).Unix() != 1642933683 {
+		t.Fatal(NextConjunction(date).Unix())
+	}
+	if CulminationTime(date, 115).Unix() != 1642654651 {
+		t.Fatal(CulminationTime(date, 115).Unix())
+	}
+	date, err := (RiseTime(date, 115, 40, 0, false))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if date.Unix() != 1642636481 {
+		t.Fatal(date.Unix())
+	}
+	fmt.Println(DownTime(date, 115, 40, 0, false))
 }
diff --git a/moon/moon.go b/moon/moon.go
index 5c96c5c..b810112 100644
--- a/moon/moon.go
+++ b/moon/moon.go
@@ -209,11 +209,14 @@ func Phase(date time.Time) float64 {
 }
 
 // ShuoYue 朔月
+// 返回Date对应UTC世界时的月相大小
 func ShuoYue(year float64) time.Time {
 	jde := basic.TD2UT(basic.CalcMoonSH(year, 0), false)
 	return basic.JDE2DateByZone(jde, time.UTC, false)
 }
 
+// NextShuoYue 下次朔月时间
+// 返回date之后的下一个朔月时间(UTC时间)
 func NextShuoYue(date time.Time) time.Time {
 	return nextMoonPhase(date, 0)
 }
diff --git a/moon/moon_test.go b/moon/moon_test.go
index 7b1a30e..a6e2c88 100644
--- a/moon/moon_test.go
+++ b/moon/moon_test.go
@@ -13,38 +13,38 @@ func Test_MoonPhaseDate(t *testing.T) {
 	//指定日期后的下一个朔月
 	moonPhase01 := NextShuoYue(date)
 	fmt.Println("下一朔月", moonPhase01)
-	if moonPhase01.Unix() != 1643694349 {
-		t.Fatal(moonPhase01)
+	if moonPhase01.Unix() != 1643694356 {
+		t.Fatal(moonPhase01.Unix())
 	}
 	//指定日期后的上一个朔月
 	moonPhase01 = LastShuoYue(date)
 	fmt.Println("上一朔月", moonPhase01)
-	if moonPhase01.Unix() != 1641148399 {
-		t.Fatal(moonPhase01)
+	if moonPhase01.Unix() != 1641148406 {
+		t.Fatal(moonPhase01.Unix())
 	}
 	//离指定日期最近的朔月
 	moonPhase01 = ClosestShuoYue(date)
 	fmt.Println("最近朔月", moonPhase01)
-	if moonPhase01.Unix() != 1643694349 {
-		t.Fatal(moonPhase01)
+	if moonPhase01.Unix() != 1643694356 {
+		t.Fatal(moonPhase01.Unix())
 	}
 	//离指定日期最近的望月时间
 	moonPhase01 = ClosestWangYue(date)
 	fmt.Println("最近望月", moonPhase01)
-	if moonPhase01.Unix() != 1642463294 {
-		t.Fatal(moonPhase01)
+	if moonPhase01.Unix() != 1642463301 {
+		t.Fatal(moonPhase01.Unix())
 	}
 	//离指定日期最近的上弦月时间
 	moonPhase01 = ClosestShangXianYue(date)
 	fmt.Println("最近上弦月", moonPhase01)
-	if moonPhase01.Unix() != 1641751864 {
-		t.Fatal(moonPhase01)
+	if moonPhase01.Unix() != 1641751871 {
+		t.Fatal(moonPhase01.Unix())
 	}
 	//离指定日期最近的下弦月时间
 	moonPhase01 = ClosestXiaXianYue(date)
 	fmt.Println("最近下弦月", moonPhase01)
-	if moonPhase01.Unix() != 1643118043 {
-		t.Fatal(moonPhase01)
+	if moonPhase01.Unix() != 1643118050 {
+		t.Fatal(moonPhase01.Unix())
 	}
 	//-------------------
 	for i := 0; i < 26; i++ {
@@ -52,10 +52,3 @@ func Test_MoonPhaseDate(t *testing.T) {
 		fmt.Println("上一朔月", moonPhase01)
 	}
 }
-
-func TestMoon(t *testing.T) {
-	now := time.Now()
-	fmt.Println(RiseTime(now, 115, 40, 0, true))
-	fmt.Println(CulminationTime(now, 115, 40))
-	fmt.Println(DownTime(now, 115, 40, 0, true))
-}
diff --git a/neptune/neptune_test.go b/neptune/neptune_test.go
index 0039bd6..506b33c 100644
--- a/neptune/neptune_test.go
+++ b/neptune/neptune_test.go
@@ -7,8 +7,20 @@ import (
 )
 
 func TestNeptune(t *testing.T) {
-	date := time.Now().Add(time.Hour * -24)
-	fmt.Println(CulminationTime(date, 115))
-	fmt.Println(RiseTime(date, 115, 23, 0, false))
-	fmt.Println(DownTime(date, 115, 23, 0, false))
+	tz := time.FixedZone("CST", 8*3600)
+	date := time.Date(2022, 01, 20, 00, 00, 00, 00, tz)
+	if NextConjunction(date).Unix() != 1647171796 {
+		t.Fatal(NextConjunction(date).Unix())
+	}
+	if CulminationTime(date, 115).Unix() != 1642665021 {
+		t.Fatal(CulminationTime(date, 115).Unix())
+	}
+	date, err := (RiseTime(date, 115, 40, 0, false))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if date.Unix() != 1642644398 {
+		t.Fatal(date.Unix())
+	}
+	fmt.Println(DownTime(date, 115, 40, 0, false))
 }
diff --git a/sun/sun.go b/sun/sun.go
index 222f02e..d3f95dd 100644
--- a/sun/sun.go
+++ b/sun/sun.go
@@ -2,7 +2,6 @@ package sun
 
 import (
 	"errors"
-	"math"
 	"time"
 
 	"b612.me/astro/basic"
@@ -38,9 +37,11 @@ func RiseTime(date time.Time, lon, lat, height float64, aero bool) (time.Time, e
 	if date.Hour() > 12 {
 		date = date.Add(time.Hour * -12)
 	}
+	//忽略时区的字面量时间
 	jde := basic.Date2JDE(date)
 	_, loc := date.Zone()
 	timezone := float64(loc) / 3600.0
+	//risedate 时区修正后的时间,转换应当包括时区
 	riseJde := basic.GetSunRiseTime(jde, lon, lat, timezone, aeroFloat, height)
 	if riseJde == -2 {
 		err = ERR_SUN_NEVER_RISE
@@ -80,7 +81,7 @@ func DownTime(date time.Time, lon, lat, height float64, aero bool) (time.Time, e
 }
 
 // MorningTwilight 晨朦影
-// date,当地时区日期
+// date,当地时区日期,返回的时间时区与此参数中的时区一致
 // lon,经度,东正西负
 // lat,纬度,北正南负
 // angle,朦影角度:可选-6 -12 -18(民用、航海、天文)
@@ -99,11 +100,11 @@ func MorningTwilight(date time.Time, lon, lat, angle float64) (time.Time, error)
 	if calcJde == -1 {
 		err = ERR_TWILIGHT_NOT_EXISTS
 	}
-	return basic.JDE2Date(calcJde), err
+	return basic.JDE2DateByZone(calcJde, date.Location(), true), err
 }
 
 // EveningTwilight 昏朦影
-// date,当地时区日期
+// date,当地时区日期,返回的时间时区与此参数中的时区一致
 // lon,经度,东正西负
 // lat,纬度,北正南负
 // angle,朦影角度:可选-6 -12 -18(民用、航海、天文)
@@ -123,7 +124,7 @@ func EveningTwilight(date time.Time, lon, lat, angle float64) (time.Time, error)
 	if calcJde == -1 {
 		err = ERR_TWILIGHT_NOT_EXISTS
 	}
-	return basic.JDE2Date(calcJde), err
+	return basic.JDE2DateByZone(calcJde, date.Location(), true), err
 }
 
 // EclipticObliquity 黄赤交角
@@ -256,10 +257,7 @@ func Zenith(date time.Time, lon, lat float64) float64 {
 // CulminationTime 太阳中天时间
 // 返回给定经纬度、对应date时区date时刻的太阳中天日期
 func CulminationTime(date time.Time, lon float64) time.Time {
-	jde := basic.Date2JDE(date)
-	if jde-math.Floor(jde) > 0.5 {
-		jde++
-	}
+	jde := basic.Date2JDE(date.Add(time.Duration(-1*date.Hour())*time.Hour)) + 0.5
 	_, loc := date.Zone()
 	timezone := float64(loc) / 3600.0
 	calcJde := basic.GetSunTZTime(jde, lon, timezone) - timezone/24.00
diff --git a/sun/sun_test.go b/sun/sun_test.go
index 95b87e8..196e232 100644
--- a/sun/sun_test.go
+++ b/sun/sun_test.go
@@ -7,8 +7,23 @@ import (
 )
 
 func TestSun(t *testing.T) {
-	now := time.Now()
-	fmt.Println(RiseTime(now, 115, 40, 0, true))
+	ja, err := time.LoadLocation("Asia/Tokyo")
+	if err != nil {
+		t.Fatal(err)
+	}
+	now, err := time.ParseInLocation("2006-01-02 15:04:05", "2020-01-01 00:00:00", ja)
+	if err != nil {
+		t.Fatal(err)
+	}
+	d, err := RiseTime(now, 115, 40, 0, true)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if d.Format("2006-01-02 15:04:05") != "2020-01-01 08:41:45" {
+		t.Fatal(d.Format("2006-01-02 15:04:05"))
+	}
 	fmt.Println(CulminationTime(now, 115))
 	fmt.Println(DownTime(now, 115, 40, 0, true))
+	fmt.Println(MorningTwilight(now, 115, 40, -6))
+	fmt.Println(EveningTwilight(now, 115, 40, -6))
 }

From b0920d327c73b7f5e8a29a79aa5cee317c759642 Mon Sep 17 00:00:00 2001
From: starainrt <i@b612.me>
Date: Tue, 31 Dec 2024 13:52:59 +0800
Subject: [PATCH 2/2] =?UTF-8?q?bug=20fix:=E8=85=8A=E6=9C=88=E5=8D=81?=
 =?UTF-8?q?=E4=BA=8C=E6=98=BE=E7=A4=BA=E4=B8=BA=E6=AD=A3=E6=9C=88=E5=88=9D?=
 =?UTF-8?q?=E4=B8=80=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 calendar/chinese.go      |  2 +-
 calendar/chinese_test.go | 43 +++++++++++++++++++++++++++++-----------
 2 files changed, 32 insertions(+), 13 deletions(-)

diff --git a/calendar/chinese.go b/calendar/chinese.go
index d7fdb32..7e96913 100644
--- a/calendar/chinese.go
+++ b/calendar/chinese.go
@@ -143,7 +143,7 @@ recalc:
 	magic := int32(upper[idx])<<8 + int32(lower[idx])
 	springMonth := (magic&0x800000)>>23 + 1
 	springDay := (magic & 0x7FFFFF) >> 18
-	if springMonth == int32(month) && springDay == int32(day) {
+	if !useGoto && springMonth == int32(month) && springDay == int32(day) {
 		return 1, 1, false, "正月初一"
 	}
 	if !useGoto && (springMonth > int32(month) || (springMonth == int32(month) && springDay > int32(day))) {
diff --git a/calendar/chinese_test.go b/calendar/chinese_test.go
index 18f9ae4..f89ff90 100644
--- a/calendar/chinese_test.go
+++ b/calendar/chinese_test.go
@@ -18,6 +18,7 @@ type lunarSolar struct {
 
 func Test_ChineseCalendar(t *testing.T) {
 	var testData = []lunarSolar{
+		{Lyear: 1995, Lmonth: 12, Lday: 12, Leap: false, Year: 1996, Month: 1, Day: 31},
 		{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},
@@ -37,19 +38,37 @@ func Test_ChineseCalendar(t *testing.T) {
 		{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 := SolarToLunar(time.Date(v.Year, time.Month(v.Month), v.Day, 0, 0, 0, 0, time.Local))
-		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)
-		}
+		{
+			var lyear int = v.Year
+			lmonth, lday, leap, desp := SolarToLunar(time.Date(v.Year, time.Month(v.Month), v.Day, 0, 0, 0, 0, time.Local))
+			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 := LunarToSolar(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)
+			date := LunarToSolar(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)
+			}
+		}
+		{
+			var lyear int = v.Year
+			lmonth, lday, leap, desp := RapidSolarToLunar(time.Date(v.Year, time.Month(v.Month), v.Day, 0, 0, 0, 0, time.Local))
+			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 := RapidLunarToSolar(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)
+			}
 		}
 	}
 }