v3.6.2 同步发版:晚子时不再双重换日、港澳时区归位

- 晚子时(真太阳时 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…
This commit is contained in:
2026-10-12 19:05:09 +08:00
parent 415cb472b1
commit b20fd5c3c9
14 changed files with 175 additions and 72 deletions
+5 -1
View File
@@ -5,9 +5,13 @@ ZI_HOUR_START = 23 # 真太阳时 23:00 换日
def resolve_calendar_day(tst: datetime, rule: str = "子初") -> tuple[datetime, dict]:
"""
日界归算:rule="子初"(默认)真太阳时 23:00–24:00 归入次日;
日界归算与审计:rule="子初"(默认)真太阳时 23:00–24:00 归次日;
rule="子正" 以 0:00 为日界,晚子时不换日。
注意:换日的**实际排盘效果**由 chart.tyme_adapter.cast 的 sect 参数
(tyme4py provider)原生实现;本函数返回的日历日仅供审计与外部参考,
调用方不得把它再拼回时刻传给 cast(会双重换日)。
返回
(日历日 00:00 的 datetime, flag)
"""
+6 -4
View File
@@ -88,11 +88,13 @@ def main() -> int:
# L0-d UTC → 真太阳时(经度修正 + 均时差)
tst, st_audit = to_true_solar_time(utc, loc.lon)
# L0-e 子初换日:真太阳时 23:00–24:00 归入次日
day, bd_audit = resolve_calendar_day(tst)
# L0-e 日界审计:is_late_zi 警示与口径文案;换日本身由排盘层
# provider 口径实现(见 tyme_adapter.cast 的 sect 参数),此处不再滚日
_day, bd_audit = resolve_calendar_day(tst)
# 排盘时刻 = 换日后的日历日 + 真太阳时的时分秒
cast_dt = day.replace(hour=tst.hour, minute=tst.minute, second=tst.second)
# 排盘时刻 = 真太阳时原样(晚子时的日界归算交给 tyme4py provider,
# 预先滚日会造成双重换日)
cast_dt = tst
# L1-a 排盘(tyme4py 唯一接触面)
raw = cast(cast_dt, a.gender)
+21 -5
View File
@@ -55,11 +55,27 @@ def _shen_sha(ganzhi: dict, gender: str) -> dict:
}
def cast(cast_dt: datetime, gender: str) -> dict:
"""cast_dt:已完成子初换日调整的真太阳时(naive,北京时间语义)。"""
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()
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()
+6 -5
View File
@@ -85,13 +85,14 @@ def _cast_chart(name: str, gender: str, birth: str, place: str,
# L0-d UTC → 真太阳时(经度修正 + 均时差)
tst, st_audit = to_true_solar_time(utc, loc.lon)
# L0-e 日界归算:默认子初换日(真太阳时 23:00–24:00 归入次日)
day, bd_audit = resolve_calendar_day(tst, rule=day_boundary)
# L0-e 日界审计:is_late_zi 警示与口径文案;换日由排盘层 provider
# 口径实现(sect 参数),此处不再对时刻预先滚日(避免双重换日)
_day, bd_audit = resolve_calendar_day(tst, rule=day_boundary)
cast_dt = day.replace(hour=tst.hour, minute=tst.minute, second=tst.second)
cast_dt = tst
# L1-a 排盘(tyme4py 唯一接触面)
raw = cast(cast_dt, gender)
# L1-a 排盘(tyme4py 唯一接触面);子正口径走 sect=2(晚子时日柱算当天)
raw = cast(cast_dt, gender, sect=2 if day_boundary == "子正" else 1)
# L1-b 起运、大运、流年、流月、小运
forward = is_forward(gender, raw["year"]["gan"])