131 lines
4.3 KiB
Python
131 lines
4.3 KiB
Python
"""24절기 + sxtwl 기반 月支 계산.
|
|
|
|
sxtwl(寿星天文历) 2.x의 JieQi index 순서:
|
|
0=冬至, 1=小寒, 2=大寒, 3=立春, 4=雨水, 5=驚蟄,
|
|
6=春分, 7=清明, 8=穀雨, 9=立夏, 10=小滿, 11=芒種,
|
|
12=夏至, 13=小暑, 14=大暑, 15=立秋, 16=處暑, 17=白露,
|
|
18=秋分, 19=寒露, 20=霜降, 21=立冬, 22=小雪, 23=大雪
|
|
|
|
(冬至부터 15° 간격으로 황경 증가 — sxtwl.cpp 의 `xn % 24` 계산식 기준)
|
|
|
|
saju에서 月支는 12개의 '절'(節, 홀수 황경 인덱스에 해당)을 기준으로 12구간으로 나눈다:
|
|
立春(3) → 寅(2)
|
|
驚蟄(5) → 卯(3)
|
|
清明(7) → 辰(4)
|
|
立夏(9) → 巳(5)
|
|
芒種(11) → 午(6)
|
|
小暑(13) → 未(7)
|
|
立秋(15) → 申(8)
|
|
白露(17) → 酉(9)
|
|
寒露(19) → 戌(10)
|
|
立冬(21) → 亥(11)
|
|
大雪(23) → 子(0)
|
|
小寒(1) → 丑(1)
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import date as _date
|
|
from functools import lru_cache
|
|
|
|
import sxtwl
|
|
|
|
|
|
# sxtwl JieQi index → 月支 인덱스 (子=0, 丑=1, 寅=2, ..., 亥=11)
|
|
# 12 '節' 만 매핑 (홀수 인덱스), '氣' (짝수 인덱스)는 月支 경계가 아님 → 직전 절의 月支 유지
|
|
JIEQI_TO_BRANCH: dict[int, int] = {
|
|
3: 2, # 立春 → 寅
|
|
5: 3, # 驚蟄 → 卯
|
|
7: 4, # 清明 → 辰
|
|
9: 5, # 立夏 → 巳
|
|
11: 6, # 芒種 → 午
|
|
13: 7, # 小暑 → 未
|
|
15: 8, # 立秋 → 申
|
|
17: 9, # 白露 → 酉
|
|
19: 10, # 寒露 → 戌
|
|
21: 11, # 立冬 → 亥
|
|
23: 0, # 大雪 → 子
|
|
1: 1, # 小寒 → 丑
|
|
}
|
|
|
|
|
|
@lru_cache(maxsize=64)
|
|
def _all_jieqi_dates(year: int) -> tuple[tuple[_date, int], ...]:
|
|
"""주어진 연도의 24절기 (date, jieqi_index) 튜플 리스트.
|
|
|
|
sxtwl.getJieQiByYear(year) → list[JieQiInfo(jd, jqIndex)] 사용.
|
|
sxtwl.JD2DD(jd) → Time(Y, M, D, ...)로 양력 변환.
|
|
|
|
@lru_cache로 같은 연도 반복 호출 시 sxtwl 재호출 방지.
|
|
"""
|
|
result: list[tuple[_date, int]] = []
|
|
jq_list = sxtwl.getJieQiByYear(year)
|
|
for info in jq_list:
|
|
t = sxtwl.JD2DD(info.jd)
|
|
try:
|
|
d = _date(int(t.Y), int(t.M), int(t.D))
|
|
except (ValueError, OverflowError):
|
|
continue
|
|
result.append((d, int(info.jqIndex)))
|
|
return tuple(result)
|
|
|
|
|
|
def get_current_solar_term(year: int, month: int, day: int) -> int:
|
|
"""현재 날짜에 적용되는 절기 인덱스 (0~23, sxtwl 기준) 반환.
|
|
|
|
입력 날짜 이전(또는 같은) 가장 가까운 절기의 인덱스를 반환.
|
|
절기가 아직 들어오지 않은 경우(연초 등) ±1년치를 함께 탐색.
|
|
"""
|
|
target = _date(year, month, day)
|
|
candidates: list[tuple[_date, int]] = []
|
|
for y in (year - 1, year, year + 1):
|
|
candidates.extend(_all_jieqi_dates(y))
|
|
candidates.sort()
|
|
|
|
last_idx = 0 # 폴백
|
|
for d, idx in candidates:
|
|
if d <= target:
|
|
last_idx = idx
|
|
else:
|
|
break
|
|
return last_idx
|
|
|
|
|
|
def get_solar_term_month_branch(year: int, month: int, day: int) -> int:
|
|
"""현재 날짜의 月支 인덱스 (0~11, 子=0...亥=11). 절(節) 기준 12구간.
|
|
|
|
12개 '절'만 추출해, 입력 날짜 이전 가장 가까운 절의 月支를 반환.
|
|
立春 이전이면 직전 해 小寒(=丑) 또는 大雪(=子) 기준.
|
|
"""
|
|
target = _date(year, month, day)
|
|
jie_dates: list[tuple[_date, int]] = []
|
|
for y in (year - 1, year, year + 1):
|
|
for d, qi_idx in _all_jieqi_dates(y):
|
|
if qi_idx in JIEQI_TO_BRANCH:
|
|
jie_dates.append((d, JIEQI_TO_BRANCH[qi_idx]))
|
|
jie_dates.sort()
|
|
|
|
last_branch = 1 # 立春 이전 폴백 — 丑(축)
|
|
for d, branch_idx in jie_dates:
|
|
if d <= target:
|
|
last_branch = branch_idx
|
|
else:
|
|
break
|
|
return last_branch
|
|
|
|
|
|
def get_days_to_next_solar_term(year: int, month: int, day: int) -> int:
|
|
"""다음 절기까지 남은 일수 (대운 계산용).
|
|
|
|
24절기 모두 대상. 입력 날짜 이후 가장 가까운 절기까지의 일 수 반환.
|
|
"""
|
|
target = _date(year, month, day)
|
|
all_qi: list[tuple[_date, int]] = []
|
|
for y in (year, year + 1):
|
|
all_qi.extend(_all_jieqi_dates(y))
|
|
all_qi.sort()
|
|
|
|
for d, _idx in all_qi:
|
|
if d > target:
|
|
return (d - target).days
|
|
return 30 # 폴백 (이론상 도달 불가)
|