Skip to content

Commit 3b74044

Browse files
committed
fix gangzhi again
1 parent 6884bd0 commit 3b74044

11 files changed

Lines changed: 272 additions & 226 deletions

File tree

astro/beiji/calculator.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,11 @@ def get_year_ganzhi(year: int) -> tuple[str, str]:
114114
"""
115115
try:
116116
from sxtwl import fromSolar
117-
# sxtwl 以農曆年干支為準;以1月1日對應年份取得大致干支
118-
day = fromSolar(year, 6, 1) # 取6月1日避免年初歲末邊界問題
119-
stem = TIANGAN[day.getLunarYear(False).tg]
120-
branch = DIZHI[day.getLunarYear(False).dz]
117+
# 使用 getYearGZ(False) 以立春為準(與 bazi 等一致)
118+
d = fromSolar(year, 6, 1)
119+
ygz = d.getYearGZ(False)
120+
stem = TIANGAN[ygz.tg]
121+
branch = DIZHI[ygz.dz]
121122
return stem, branch
122123
except Exception:
123124
pass

astro/chinese/taixuan/taixuan_calculator.py

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717
from datetime import datetime, date
1818
from typing import Optional, Dict, List, Tuple
1919

20+
try:
21+
from sxtwl import fromSolar as _fromSolar
22+
_HAS_SXTWL = True
23+
except Exception:
24+
_HAS_SXTWL = False
25+
2026
# ── 載入太玄字典 ────────────────────────────────────────────
2127
try:
2228
from taixuanshifa.taixuanshifa import qigua as _taixuan_qigua, qigua_number as _taixuan_qigua_number
@@ -151,30 +157,48 @@ def _hour_to_sishi(hour: int) -> str:
151157
return "夜中"
152158

153159

154-
def _ganzhi_year(year: int) -> Tuple[str, str]:
155-
"""計算某年的天干地支"""
160+
def _ganzhi_year(year: int, month: int = 7, day: int = 1) -> Tuple[str, str]:
161+
"""使用 sxtwl 計算年干支(立春分界)。"""
162+
if _HAS_SXTWL:
163+
try:
164+
c = _fromSolar(year, month, day)
165+
y = c.getYearGZ(False)
166+
return TIANGAN[y.tg], DIZHI[y.dz]
167+
except Exception:
168+
pass
156169
tg_idx = (year - 4) % 10
157170
dz_idx = (year - 4) % 12
158171
return TIANGAN[tg_idx], DIZHI[dz_idx]
159172

160173

161-
def _ganzhi_month(year: int, month: int) -> Tuple[str, str]:
162-
"""計算農曆月的天干地支(簡化節氣法)"""
163-
# 地支:寅月對應 calendar month 2,申月對應 calendar month 8,以此類推
164-
dz_idx = month % 12
165-
# 月干取決於年干;五虎遁年起月法:甲/己年起丙寅,乙/庚年起戊寅,以此類推
174+
def _ganzhi_month(year: int, month: int, day: int = 15) -> Tuple[str, str]:
175+
"""使用 sxtwl 計算月干支(節氣)。"""
176+
if _HAS_SXTWL:
177+
try:
178+
c = _fromSolar(year, month, day)
179+
m = c.getMonthGZ()
180+
return TIANGAN[m.tg], DIZHI[m.dz]
181+
except Exception:
182+
pass
183+
# fallback approx
184+
dz_idx = (month + 1) % 12 # rough
166185
year_tg_idx = (year - 4) % 10
167-
tg_base = ((year_tg_idx % 5) * 2 + 2) % 10 # 寅月干支起始天干
168-
month_idx = (month - 2) % 12 # 距寅月的月數(0-based)
169-
tg_idx = (tg_base + month_idx) % 10
186+
tg_base = ((year_tg_idx % 5) * 2 + 2) % 10
187+
tg_idx = (tg_base + (month - 2) % 12) % 10
170188
return TIANGAN[tg_idx], DIZHI[dz_idx]
171189

172190

173191
def _ganzhi_day(year: int, month: int, day: int) -> Tuple[str, str]:
174-
"""計算日柱天干地支"""
192+
"""使用 sxtwl 計算日干支。"""
193+
if _HAS_SXTWL:
194+
try:
195+
c = _fromSolar(year, month, day)
196+
d = c.getDayGZ()
197+
return TIANGAN[d.tg], DIZHI[d.dz]
198+
except Exception:
199+
pass
200+
# old jd fallback
175201
jd = _julian_day(year, month, day)
176-
# _julian_day 回傳的儒略日以正午為起點,午夜值帶 .5 小數;
177-
# 加 0.5 後取整可得整數儒略日(以午夜為基準)。
178202
idx = int(jd + 0.5) + 49
179203
tg_idx = idx % 10
180204
dz_idx = idx % 12
@@ -353,8 +377,8 @@ def _calc_natal(self) -> TaiXuanResult:
353377
# 7. 取首資料(含星宿度數)
354378
shou = self._build_shou(serial, sishi, zhan_idx, days_precise)
355379
# 8. 干支四柱
356-
year_tg, year_dz = _ganzhi_year(self.year)
357-
month_tg, month_dz = _ganzhi_month(self.year, self.month)
380+
year_tg, year_dz = _ganzhi_year(self.year, self.month, self.day)
381+
month_tg, month_dz = _ganzhi_month(self.year, self.month, self.day)
358382
day_tg, day_dz = _ganzhi_day(self.year, self.month, self.day)
359383
hour_tg, hour_dz = _ganzhi_hour(self.hour, day_tg)
360384
# 9. 行年大限(未來 81 年,每 4.5 年一首)
@@ -503,7 +527,7 @@ def _calc_annual_shous(self, natal_serial: int, birth_year: int) -> List[Dict]:
503527
if key and key in _TAIXUAN_DICT:
504528
gua_dict = _TAIXUAN_DICT[key].get("卦", {})
505529
gua_title = next(iter(gua_dict.keys()), "——")
506-
year_tg, year_dz = _ganzhi_year(year)
530+
year_tg, year_dz = _ganzhi_year(year, month, day)
507531
result.append({
508532
"年份": year,
509533
"年齡": offset,

astro/chinstar/chinstar.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -293,9 +293,14 @@ def hour_to_branch_idx(hour: int) -> int:
293293
"""
294294
return ((hour + 1) // 2) % 12
295295

296-
def get_branch_idx(self, year: int) -> int:
297-
"""年支索引(子=0)"""
298-
return (year - 4) % 12
296+
def get_branch_idx(self, year: int, month: int = 7, day: int = 1) -> int:
297+
"""年支索引(子=0),優先 sxtwl。"""
298+
try:
299+
from sxtwl import fromSolar
300+
c = fromSolar(year, month, day)
301+
return c.getYearGZ(False).dz
302+
except Exception:
303+
return (year - 4) % 12
299304

300305
def get_host_idx(self, qin: str) -> int:
301306
"""取禽對應的宿序號(0-27)"""
@@ -346,7 +351,7 @@ def calc_tai_gong_idx(self, year: int, month: int, day: int, hour: int) -> int:
346351
Returns:
347352
胎宮地支索引 0=子 … 11=亥
348353
"""
349-
branch_idx = self.get_branch_idx(year)
354+
branch_idx = self.get_branch_idx(year, month, day)
350355
pos = branch_idx
351356
pos = (pos + month - 1) % 12
352357
pos = (pos + day - 1) % 12

astro/nine_star_ki.py

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@
2626
from datetime import date, datetime
2727
from typing import Optional
2828

29+
try:
30+
from sxtwl import fromSolar as _sxtwl_fromSolar
31+
_HAS_SXTWL = True
32+
except Exception:
33+
_HAS_SXTWL = False
34+
2935
# ============================================================
3036
# 九星定義 (Nine Star Definitions)
3137
# ============================================================
@@ -191,22 +197,19 @@ def _fly_star_ccw(start: int, steps: int) -> int:
191197
# ============================================================
192198

193199
def _li_chun_date(year: int) -> date:
194-
"""Approximate Li Chun (立春) date for *year*.
195-
196-
Li Chun falls on Feb 3, 4, or 5 each year. The exact date shifts
197-
slightly due to the tropical year. This approximation uses a simple
198-
formula that is accurate within ±1 day for 1901–2100.
199-
"""
200-
# Standard approximation: Feb 4 or 5 depending on the year's position
201-
# in the 4-year cycle (leap year offsets)
202-
# More precise: JDE ≈ 2451623.80984 + 365.242189623 * (Y - 2000 + 0/12)
203-
# For Li Chun (315° ecliptic lon), offset from Spring equinox ~ -44.7 days
204-
# Simple empirical formula (accurate to ±1 day 1901-2100):
205-
# day = int(4.6295 + 0.2422 * (year - 1900) - int((year - 1900) / 4))
206-
# But we clamp to [3, 5] and use Feb 4 as default fallback.
200+
"""Li Chun (立春) date. Uses sxtwl for precision when available."""
201+
if _HAS_SXTWL:
202+
try:
203+
for dtry in range(3, 6):
204+
dd = _sxtwl_fromSolar(year, 2, dtry)
205+
if dd.hasJieQi() and dd.getJieQi() == 3: # 立春
206+
return date(year, 2, dtry)
207+
# If not exact match in scan, fall to sxtwl-based effective year logic later
208+
except Exception:
209+
pass
210+
# Fallback approx (good enough)
207211
offset = 4.6295 + 0.2422 * (year - 1900) - (year - 1900) // 4
208-
day = int(offset)
209-
day = max(3, min(5, day))
212+
day = max(3, min(5, int(offset)))
210213
return date(year, 2, day)
211214

212215

@@ -266,28 +269,35 @@ def compute_year_star(birth_date: date) -> int:
266269

267270

268271
def _solar_month_index(d: date) -> int:
269-
"""Return the solar month index (0–11) for date *d*.
272+
"""Return the solar month index (0–11) for date *d* using sxtwl when available.
270273
271-
Index 0 = 寅月 (starts ~Feb 4, Li Chun).
272-
Index 11 = 丑月 (starts ~Jan 6).
274+
Index 0 = 寅月 (立春), matches getMonthGZ().dz convention in project.
273275
"""
274-
# Try matching from month 11 (January) down to find the current 節
275-
# We need to find which solar month the date falls in.
276-
# Build a list of (date, index) for the current and surrounding years
276+
if _HAS_SXTWL:
277+
try:
278+
c = _sxtwl_fromSolar(d.year, d.month, d.day)
279+
mgz = c.getMonthGZ()
280+
# dz: 0=子 ... but project solar month often 寅=2 mapped.
281+
# For nine star, the _YEAR... expects index aligned to their list.
282+
# Their list _SOLAR_TERM_STARTS[0] = 寅 (Feb)
283+
# sxtwl month branch: 寅 usually dz=2
284+
# Map: common in bazi month_gz.dz 寅=2 -> our solar 0
285+
dz = mgz.dz
286+
# 寅=2 -> 0, 卯=3->1, ..., 丑=1 ->11
287+
return (dz - 2) % 12
288+
except Exception:
289+
pass
290+
# Fallback to old approx logic
277291
year = d.year
278292
boundaries = []
279293
for idx, (m, day) in enumerate(_SOLAR_TERM_STARTS):
280294
if m == 1:
281-
# 丑月 starts in January — belongs to the current year's cycle
282295
boundaries.append((date(year, 1, day), idx))
283296
else:
284297
boundaries.append((date(year, m, day), idx))
285-
# Also add previous year's December (子月) boundary
286-
# Add next year January too
287298
boundaries.append((date(year + 1, 1, _SOLAR_TERM_STARTS[11][1]), 11))
288-
# Sort and find the latest boundary ≤ d
289299
boundaries.sort()
290-
result = 11 # default 丑月
300+
result = 11
291301
for bd, idx in boundaries:
292302
if d >= bd:
293303
result = idx

astro/qizheng/qizheng_dasha.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,15 @@ def compute_dasha(
147147
current_period_idx = idx
148148
break
149149

150-
# 計算流年 (flow year):
151-
# 流年地支 = 流年太歲地支 = (year - 4) % 12
152-
flow_year_branch = (current_year - 4) % 12 if current_year else -1
150+
# 計算流年 (flow year):使用 sxtwl 精確年支
151+
flow_year_branch = -1
152+
if current_year:
153+
try:
154+
from sxtwl import fromSolar
155+
c = fromSolar(current_year, 7, 1)
156+
flow_year_branch = c.getYearGZ(False).dz
157+
except Exception:
158+
flow_year_branch = (current_year - 4) % 12
153159
flow_year_palace = ""
154160
if flow_year_branch >= 0:
155161
fh = branch_to_house.get(flow_year_branch)

astro/shaozi/calculator.py

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
from datetime import datetime
1919
from typing import Dict, Optional, Tuple
2020

21+
try:
22+
from sxtwl import fromSolar as _fromSolar
23+
_HAS_SXTWL = True
24+
except Exception:
25+
_HAS_SXTWL = False
26+
2127
from astro.shaozi.constants import (
2228
HEAVENLY_STEMS,
2329
EARTHLY_BRANCHES,
@@ -162,44 +168,55 @@ def count(self) -> int:
162168
# 干支計算工具
163169
# ============================================================================
164170

165-
def _year_ganzhi(year: int) -> str:
166-
"""從西元年份計算年柱干支(以甲子 1984 為基準)"""
167-
base = 1984 # 甲子年
171+
def _year_ganzhi(year: int, month: int = 7, day: int = 1) -> str:
172+
"""sxtwl 精確年柱(立春)。"""
173+
if _HAS_SXTWL:
174+
try:
175+
c = _fromSolar(year, month, day)
176+
y = c.getYearGZ(False)
177+
return HEAVENLY_STEMS[y.tg] + EARTHLY_BRANCHES[y.dz]
178+
except Exception:
179+
pass
180+
base = 1984
168181
stem_idx = (year - base) % 10
169182
branch_idx = (year - base) % 12
170183
return HEAVENLY_STEMS[stem_idx] + EARTHLY_BRANCHES[branch_idx]
171184

172185

173-
def _month_ganzhi(year: int, month: int) -> str:
174-
"""
175-
計算月柱干支(簡化版,以節氣月份為準)
176-
月支:寅(2月) 卯(3月) … 丑(1月) index = month % 12
177-
月干:由年干推算,甲己年從丙寅起
178-
"""
186+
def _month_ganzhi(year: int, month: int, day: int = 15) -> str:
187+
"""sxtwl 精確月柱。"""
188+
if _HAS_SXTWL:
189+
try:
190+
c = _fromSolar(year, month, day)
191+
m = c.getMonthGZ()
192+
return HEAVENLY_STEMS[m.tg] + EARTHLY_BRANCHES[m.dz]
193+
except Exception:
194+
pass
179195
year_stem_idx = (year - 1984) % 10
180-
# 月干循環:甲己年→丙寅起 (stem_base=2);每年差2
181196
stem_base = ((year_stem_idx % 5) * 2 + 2) % 10
182-
# 月支:二月(2) → 寅(2), 三月(3) → 卯(3), …, 八月(8) → 申(8), …, 一月(1) → 丑(1)
183197
month_branch_idx = month % 12
184198
month_stem_idx = (stem_base + month - 2) % 10
185199
return HEAVENLY_STEMS[month_stem_idx] + EARTHLY_BRANCHES[month_branch_idx]
186200

187201

188202
def _day_ganzhi(year: int, month: int, day: int) -> str:
189-
"""
190-
計算日柱干支(Julian Day Number 法)
191-
參考點:2000-01-01 (JD=2451545) = 戊午日
192-
"""
203+
"""sxtwl 精確日柱。"""
204+
if _HAS_SXTWL:
205+
try:
206+
c = _fromSolar(year, month, day)
207+
d = c.getDayGZ()
208+
return HEAVENLY_STEMS[d.tg] + EARTHLY_BRANCHES[d.dz]
209+
except Exception:
210+
pass
193211
if month <= 2:
194212
year -= 1
195213
month += 12
196214
a = year // 100
197215
b = 2 - a + a // 4
198216
jd = int(365.25 * (year + 4716)) + int(30.6001 * (month + 1)) + day + b - 1524
199-
# 2451545 = 2000-01-01 = 戊午:stem 戊(4), branch 午(6)
200217
ref_jd = 2451545
201-
ref_stem = 4 # 戊
202-
ref_branch = 6 # 午
218+
ref_stem = 4
219+
ref_branch = 6
203220
delta = jd - ref_jd
204221
stem_idx = (ref_stem + delta) % 10
205222
branch_idx = (ref_branch + delta) % 12
@@ -220,8 +237,8 @@ def _hour_ganzhi(day_stem: str, hour: int) -> str:
220237

221238
def calculate_ganzhi_from_datetime(birth_dt: datetime) -> Dict[str, str]:
222239
"""從西曆日期時間計算四柱干支"""
223-
year_gz = _year_ganzhi(birth_dt.year)
224-
month_gz = _month_ganzhi(birth_dt.year, birth_dt.month)
240+
year_gz = _year_ganzhi(birth_dt.year, birth_dt.month, birth_dt.day)
241+
month_gz = _month_ganzhi(birth_dt.year, birth_dt.month, birth_dt.day)
225242
day_gz = _day_ganzhi(birth_dt.year, birth_dt.month, birth_dt.day)
226243
hour_gz = _hour_ganzhi(day_gz[0], birth_dt.hour)
227244
return {

0 commit comments

Comments
 (0)