- 晚子时(真太阳时 23:00–24:00,约 4% 的盘)修正:换日改由 tyme4py provider 原生实现(cast 新增 sect 参数,1=子初 / 2=子正),排盘层不再自行预滚日。3.6.1 及此前会滚两次,日柱多进一天、时柱随之错干——锚点 1973-08-23 23:32 男:旧 癸巳日壬子时 → 正 壬辰日庚子时。已排过的晚子时盘建议重排核对 - --day-boundary 子正 现真正生效(走 sect=2,晚子时日柱算当天),此前只改审计文案不改实际排盘 - data/cities.json 港澳 28 个条目由 Asia/Shanghai 改为 Asia/Hong_Kong(19)、Asia/Macau(9):大陆 1986–1991 行夏令时而港澳 1979 年后无,沿用上海区会把大陆夏令时错加到港澳命主身上 - 修副本环境 `pytest tests/` 收集期中断(test_sync_tool 模块级加载外层 tools/,skipif 挡不住导入),现按设计整模块 skip - .gitignore 新增 data/.license.json、data/.update_check.json,本机授权与更新缓存不再可能误入仓 - 测试 178 项全绿(副本内 sync 工具 4 项按设计 skip);manifest 1278 文件,ed25519 签名,公钥指纹 3089bc1cc9a453e5…
107 lines
4.3 KiB
Python
107 lines
4.3 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""tyme4py 唯一接触面:输入真太阳时时刻,返回四柱及派生信息(纯计算,无命理判断)。"""
|
||
from datetime import datetime
|
||
|
||
import tyme4py
|
||
from tyme4py.solar import SolarTime, SolarTerm
|
||
|
||
LIBRARY_VERSION = tyme4py.__version__
|
||
|
||
_PILLARS = ("year", "month", "day", "hour")
|
||
|
||
|
||
def _solar_time_to_dt(t: SolarTime) -> datetime:
|
||
return datetime(t.get_year(), t.get_month(), t.get_day(),
|
||
t.get_hour(), t.get_minute(), t.get_second())
|
||
|
||
|
||
def _jie_bounds(cast_dt: datetime) -> tuple:
|
||
"""出生时刻前后最近的两个节令(is_jie()),以出生年为中心 ±1 年遍历。"""
|
||
prev_jie = next_jie = None
|
||
for y in (cast_dt.year - 1, cast_dt.year, cast_dt.year + 1):
|
||
for i in range(24):
|
||
term = SolarTerm.from_index(y, i)
|
||
if not term.is_jie():
|
||
continue
|
||
t = _solar_time_to_dt(term.get_julian_day().get_solar_time())
|
||
if t < cast_dt and (prev_jie is None or t > prev_jie[1]):
|
||
prev_jie = (term, t)
|
||
elif t > cast_dt and (next_jie is None or t < next_jie[1]):
|
||
next_jie = (term, t)
|
||
return prev_jie, next_jie
|
||
|
||
|
||
def _hidden(me, earth_branch) -> list:
|
||
items = []
|
||
for getter in ("get_hide_heaven_stem_main",
|
||
"get_hide_heaven_stem_middle",
|
||
"get_hide_heaven_stem_residual"):
|
||
hs = getattr(earth_branch, getter)()
|
||
if hs is not None:
|
||
items.append([hs.get_name(), me.get_ten_star(hs).get_name()])
|
||
return items
|
||
|
||
|
||
def _shen_sha(ganzhi: dict, gender: str) -> dict:
|
||
"""子平通行神煞(查法见 chart/shensha.py 与 rules/94_神煞细节.md)。"""
|
||
from chart.shensha import evaluate_natal, STAR_ORDER
|
||
|
||
raw = evaluate_natal(ganzhi, gender)
|
||
order = {name: i for i, name in enumerate(STAR_ORDER)}
|
||
return {
|
||
key: [f"{star}({note})"
|
||
for star, note in sorted(entries, key=lambda e: order[e[0]])]
|
||
for key, entries in raw.items()
|
||
}
|
||
|
||
|
||
def cast(cast_dt: datetime, gender: str, sect: int = 1) -> dict:
|
||
"""cast_dt:真太阳时(naive,北京时间语义)。
|
||
|
||
sect:日界流派。1=子初换日(tyme4py 默认 provider,晚子时 23:xx 日柱归次日);
|
||
2=子正换日(LunarSect2,晚子时日柱算当天)。晚子时的换日由 provider
|
||
原生实现,调用方不得再对 cast_dt 预先滚日(否则双重换日)。
|
||
"""
|
||
from tyme4py.eightchar.provider.impl import (
|
||
DefaultEightCharProvider, LunarSect2EightCharProvider,
|
||
)
|
||
from tyme4py.lunar import LunarHour
|
||
|
||
prev_provider = LunarHour.provider
|
||
LunarHour.provider = (LunarSect2EightCharProvider() if sect == 2
|
||
else DefaultEightCharProvider())
|
||
try:
|
||
solar = SolarTime.from_ymd_hms(cast_dt.year, cast_dt.month, cast_dt.day,
|
||
cast_dt.hour, cast_dt.minute, cast_dt.second)
|
||
ec = solar.get_lunar_hour().get_eight_char()
|
||
finally:
|
||
LunarHour.provider = prev_provider
|
||
pillars = {k: getattr(ec, f"get_{k}")() for k in _PILLARS}
|
||
me = ec.get_day().get_heaven_stem()
|
||
|
||
prev_jie, next_jie = _jie_bounds(cast_dt)
|
||
|
||
base = {k: {"gan": p.get_heaven_stem().get_name(),
|
||
"zhi": p.get_earth_branch().get_name()} for k, p in pillars.items()}
|
||
ganzhi = {k: v["gan"] + v["zhi"] for k, v in base.items()}
|
||
|
||
return {
|
||
**base,
|
||
"day_master": me.get_name(),
|
||
"ten_god": {k: me.get_ten_star(pillars[k].get_heaven_stem()).get_name()
|
||
for k in ("year", "month", "hour")},
|
||
"hidden": {k: _hidden(me, pillars[k].get_earth_branch()) for k in _PILLARS},
|
||
"terrain": {k: me.get_terrain(pillars[k].get_earth_branch()).get_name()
|
||
for k in _PILLARS},
|
||
"na_yin": {k: pillars[k].get_sound().get_name() for k in _PILLARS},
|
||
"void": {k: "".join(b.get_name() for b in pillars[k].get_extra_earth_branches())
|
||
for k in ("day", "hour")},
|
||
"shen_sha": _shen_sha(ganzhi, gender),
|
||
"prev_jie": {"name": prev_jie[0].get_name(), "time": prev_jie[1]},
|
||
"next_jie": {"name": next_jie[0].get_name(), "time": next_jie[1]},
|
||
"tai_yuan": ec.get_fetal_origin().get_name(),
|
||
"ming_gong": ec.get_own_sign().get_name(),
|
||
"library_version": LIBRARY_VERSION,
|
||
"raw": ec,
|
||
}
|