|
26 | 26 | from datetime import date, datetime |
27 | 27 | from typing import Optional |
28 | 28 |
|
| 29 | +try: |
| 30 | + from sxtwl import fromSolar as _sxtwl_fromSolar |
| 31 | + _HAS_SXTWL = True |
| 32 | +except Exception: |
| 33 | + _HAS_SXTWL = False |
| 34 | + |
29 | 35 | # ============================================================ |
30 | 36 | # 九星定義 (Nine Star Definitions) |
31 | 37 | # ============================================================ |
@@ -191,22 +197,19 @@ def _fly_star_ccw(start: int, steps: int) -> int: |
191 | 197 | # ============================================================ |
192 | 198 |
|
193 | 199 | 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) |
207 | 211 | 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))) |
210 | 213 | return date(year, 2, day) |
211 | 214 |
|
212 | 215 |
|
@@ -266,28 +269,35 @@ def compute_year_star(birth_date: date) -> int: |
266 | 269 |
|
267 | 270 |
|
268 | 271 | 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. |
270 | 273 |
|
271 | | - Index 0 = 寅月 (starts ~Feb 4, Li Chun). |
272 | | - Index 11 = 丑月 (starts ~Jan 6). |
| 274 | + Index 0 = 寅月 (立春), matches getMonthGZ().dz convention in project. |
273 | 275 | """ |
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 |
277 | 291 | year = d.year |
278 | 292 | boundaries = [] |
279 | 293 | for idx, (m, day) in enumerate(_SOLAR_TERM_STARTS): |
280 | 294 | if m == 1: |
281 | | - # 丑月 starts in January — belongs to the current year's cycle |
282 | 295 | boundaries.append((date(year, 1, day), idx)) |
283 | 296 | else: |
284 | 297 | boundaries.append((date(year, m, day), idx)) |
285 | | - # Also add previous year's December (子月) boundary |
286 | | - # Add next year January too |
287 | 298 | boundaries.append((date(year + 1, 1, _SOLAR_TERM_STARTS[11][1]), 11)) |
288 | | - # Sort and find the latest boundary ≤ d |
289 | 299 | boundaries.sort() |
290 | | - result = 11 # default 丑月 |
| 300 | + result = 11 |
291 | 301 | for bd, idx in boundaries: |
292 | 302 | if d >= bd: |
293 | 303 | result = idx |
|
0 commit comments