# -*- coding: utf-8 -*- """上海时区与每日时间计算测试: uv run python tests/test_timeutil.py""" import os import sys from datetime import datetime sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.stdout.reconfigure(encoding="utf-8") from reaction_board.timeutil import ( # noqa: E402 SHANGHAI_TZ, day_bounds, in_snapshot_quiet_window, interval_next_run, is_date, next_after_snapshot_quiet_window, next_daily_run, parse_hhmm, shanghai_date, shanghai_datetime, ) def ts(text): return datetime.strptime(text, "%Y-%m-%d %H:%M:%S").replace(tzinfo=SHANGHAI_TZ).timestamp() def test_shanghai_date_boundary(): # UTC 2026-10-31 15:59:59 = 上海 2026-10-31 23:59:59 assert shanghai_date(ts("2026-10-31 23:59:59")) == "2026-10-31" # UTC 2026-10-31 16:00:00 = 上海 2026-11-01 00:00:00 assert shanghai_date(ts("2026-11-01 00:00:00")) == "2026-11-01" assert shanghai_datetime().tzinfo is not None def test_parse_hhmm(): assert parse_hhmm("00:01") == (0, 1) assert parse_hhmm("23:59") == (23, 59) for bad in ("24:00", "1:01", "00:60", "", "abc"): try: parse_hhmm(bad) except ValueError: pass else: raise AssertionError(f"{bad!r} 应失败") def test_next_daily_run(): # 当天 00:01 之前 -> 当天 00:01 now = ts("2026-11-01 00:00:10") nxt = next_daily_run(now, "00:01") assert nxt == ts("2026-11-01 00:01:00") # 正好 00:01:00 -> 下一天(严格大于) assert next_daily_run(ts("2026-11-01 00:01:00"), "00:01") == ts("2026-11-02 00:01:00") # 当天 00:01 之后 -> 次日 assert next_daily_run(ts("2026-11-01 23:59:59"), "00:01") == ts("2026-11-02 00:01:00") # 跨月 assert next_daily_run(ts("2026-10-31 12:00:00"), "00:01") == ts("2026-11-01 00:01:00") def test_day_bounds_and_date_validation(): start, end = day_bounds("2026-11-01") assert start == ts("2026-11-01 00:00:00") assert end == ts("2026-11-02 00:00:00") assert end - start == 86400 assert is_date("2026-11-01") for bad in ("2026-13-01", "2026-02-30", "2026/11/01", "20261101", ""): assert not is_date(bad), bad try: day_bounds("bad") except ValueError: pass else: raise AssertionError("非法日期应失败") def test_snapshot_quiet_window(): assert in_snapshot_quiet_window(ts("2026-11-02 23:00:00")) assert in_snapshot_quiet_window(ts("2026-11-02 23:59:59")) assert in_snapshot_quiet_window(ts("2026-11-03 00:00:00")) assert in_snapshot_quiet_window(ts("2026-11-03 00:59:59")) assert not in_snapshot_quiet_window(ts("2026-11-03 01:00:00")) assert not in_snapshot_quiet_window(ts("2026-11-02 22:59:59")) assert not in_snapshot_quiet_window(ts("2026-11-02 12:00:00")) assert next_after_snapshot_quiet_window(ts("2026-11-02 22:00:00")) == ts( "2026-11-02 22:00:00" ) assert next_after_snapshot_quiet_window(ts("2026-11-02 23:15:00")) == ts( "2026-11-03 01:00:00" ) assert next_after_snapshot_quiet_window(ts("2026-11-03 00:30:00")) == ts( "2026-11-03 01:00:00" ) # 22:50 + 60min = 23:50 → 推到次日 01:00 assert interval_next_run(ts("2026-11-02 22:50:00"), 3600) == ts( "2026-11-03 01:00:00" ) assert interval_next_run(ts("2026-11-02 12:00:00"), 3600) == ts( "2026-11-02 13:00:00" ) def main(): test_shanghai_date_boundary() test_parse_hhmm() test_next_daily_run() test_day_bounds_and_date_validation() test_snapshot_quiet_window() print("OK: 上海时区与每日时间计算测试全部通过") if __name__ == "__main__": main()