Files
ZiPingEightChar/tests/test_day_boundary.py
T
moxiaonian b20fd5c3c9 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…
2026-10-12 19:05:09 +08:00

107 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""子初换日(设计文档 §4.5 / §6.1 B 组)。
- 纯函数边界:真太阳时 22:59→当日 False,23:00→次日 True,
23:59→次日 True,00:00→当日 False
- 黄金样本:golden/time_boundary.yaml 中带 expect.is_late_zi 的条目,
走全链条 L0-a ~ L0-e(日界判定必须用真太阳时,不能用钟表时间)
"""
import sys
import unittest
from datetime import datetime
from pathlib import Path
import yaml
SKILL_ROOT = Path(__file__).resolve().parents[1]
SCRIPTS_DIR = SKILL_ROOT / "scripts"
GOLDEN_DIR = Path(__file__).resolve().parent / "golden"
if str(SCRIPTS_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPTS_DIR))
_VENDOR = SKILL_ROOT / "vendor"
if str(_VENDOR) not in sys.path:
sys.path.insert(0, str(_VENDOR))
from astro.day_boundary import resolve_calendar_day # noqa: E402
from astro.solar_time import to_true_solar_time # noqa: E402
from astro.timezone_resolve import to_utc # noqa: E402
from geo.cities import resolve_location # noqa: E402
# (真太阳时, 期望日历日, 期望 is_late_zi)
BOUNDARY_CASES = [
("1990-06-15 22:59", "1990-06-15", False),
("1990-06-15 23:00", "1990-06-16", True),
("1990-06-15 23:59", "1990-06-16", True),
("1990-06-16 00:00", "1990-06-16", False),
]
class DayBoundaryUnitTest(unittest.TestCase):
def test_boundary_cases(self):
for tst_str, day_str, expect_late in BOUNDARY_CASES:
with self.subTest(tst=tst_str):
tst = datetime.strptime(tst_str, "%Y-%m-%d %H:%M")
day, flag = resolve_calendar_day(tst)
self.assertEqual(day, datetime.strptime(day_str, "%Y-%m-%d"))
self.assertIs(flag["is_late_zi"], expect_late)
def test_day_is_midnight(self):
# 返回的日历日必须是 00:00:00
day, _flag = resolve_calendar_day(datetime(1990, 6, 15, 23, 30))
self.assertEqual((day.hour, day.minute, day.second), (0, 0, 0))
class LateZiGoldenTest(unittest.TestCase):
def test_late_zi_samples(self):
with open(GOLDEN_DIR / "time_boundary.yaml", encoding="utf-8") as f:
samples = [
s for s in yaml.safe_load(f)
if "is_late_zi" in s.get("expect", {})
]
self.assertGreaterEqual(len(samples), 2, "time_boundary.yaml B 组应有 2 条样本")
for s in samples:
with self.subTest(sample=s["name"], place=s["place"]):
loc = resolve_location(s["place"])
clock = datetime.strptime(s["birth"], "%Y-%m-%d %H:%M")
utc, _tz_audit = to_utc(clock, loc)
tst, _st_audit = to_true_solar_time(utc, loc.lon)
_day, flag = resolve_calendar_day(tst)
self.assertIs(flag["is_late_zi"], s["expect"]["is_late_zi"])
class LateZiSectPillarsTest(unittest.TestCase):
"""晚子时双滚回归:换日由 tyme4py provider 口径实现(sect 参数),
调用方不再预滚日。锚点:1973-08-23 23:32 真太阳时。
子初 → 壬辰日庚子时(滚一日);子正 → 辛卯日庚子时(不滚,晚子时时柱仍次日起)。
"""
def test_sect1_rolls_once(self):
from chart.tyme_adapter import cast
r = cast(datetime(1973, 8, 23, 23, 32), "男", sect=1)
self.assertEqual(r["day"]["gan"] + r["day"]["zhi"], "壬辰")
self.assertEqual(r["hour"]["gan"] + r["hour"]["zhi"], "庚子")
def test_sect2_no_roll(self):
from chart.tyme_adapter import cast
r = cast(datetime(1973, 8, 23, 23, 32), "男", sect=2)
self.assertEqual(r["day"]["gan"] + r["day"]["zhi"], "辛卯")
self.assertEqual(r["hour"]["gan"] + r["hour"]["zhi"], "庚子")
def test_provider_restored(self):
from chart.tyme_adapter import cast
from tyme4py.eightchar.provider.impl import DefaultEightCharProvider
from tyme4py.lunar import LunarHour
cast(datetime(1973, 8, 23, 23, 32), "男", sect=2)
self.assertIsInstance(LunarHour.provider, DefaultEightCharProvider)
def test_non_late_zi_unaffected(self):
from chart.tyme_adapter import cast
for sect in (1, 2):
with self.subTest(sect=sect):
r = cast(datetime(1973, 8, 23, 12, 0), "男", sect=sect)
self.assertEqual(r["day"]["gan"] + r["day"]["zhi"], "辛卯")
self.assertEqual(r["hour"]["gan"] + r["hour"]["zhi"], "甲午")
if __name__ == "__main__":
unittest.main()