Coverage for src/keel/window.py: 100%
20 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-16 18:07 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-16 18:07 +0000
1"""Merge-window logic — the night no-merge invariant, as a pure function.
3A project's ``merge_window`` (e.g. ``07:00-01:30``) is the **open** window; its
4complement is the night no-merge window. The window may wrap midnight. This module
5answers "is the merge window open at time T in the project's timezone?" with no I/O,
6so it is fully unit-testable with a fixed clock.
7"""
9from __future__ import annotations
11from datetime import datetime, time
12from zoneinfo import ZoneInfo
15def parse_window(window: str) -> tuple[time, time]:
16 """Parse ``HH:MM-HH:MM`` into ``(open, close)`` times."""
17 open_s, _, close_s = window.partition("-")
18 return _parse_time(open_s), _parse_time(close_s)
21def _parse_time(s: str) -> time:
22 hh, _, mm = s.strip().partition(":")
23 return time(int(hh), int(mm))
26def is_merge_open(timezone: str, merge_window: str, *, now: datetime | None = None) -> bool:
27 """True if the merge window is open at ``now`` (default: real time) in ``timezone``."""
28 tz = ZoneInfo(timezone)
29 if now is None:
30 now = datetime.now(tz)
31 elif now.tzinfo is None:
32 now = now.replace(tzinfo=tz)
33 current = now.astimezone(tz).time()
35 opens, closes = parse_window(merge_window)
36 if opens <= closes:
37 return opens <= current < closes
38 # window wraps midnight (e.g. 07:00-01:30): open late or early
39 return current >= opens or current < closes