80 lines
2.3 KiB
Python
80 lines
2.3 KiB
Python
|
|
"""Working-hours helpers.
|
|||
|
|
|
|||
|
|
Working hours are stored per tenant as:
|
|||
|
|
{"mon": [["09:00", "13:00"], ["14:00", "18:00"]], ..., "sun": []}
|
|||
|
|
Multiple intervals per day model lunch breaks. Missing/empty day = closed.
|
|||
|
|
All computations happen in the tenant's timezone.
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
from datetime import date, datetime, time, timedelta
|
|||
|
|
from zoneinfo import ZoneInfo
|
|||
|
|
|
|||
|
|
WEEKDAY_KEYS = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
|
|||
|
|
|
|||
|
|
# Bosnian day names (Latin) for UI / agent context
|
|||
|
|
DAY_NAMES_BS = {
|
|||
|
|
"mon": "ponedjeljak",
|
|||
|
|
"tue": "utorak",
|
|||
|
|
"wed": "srijeda",
|
|||
|
|
"thu": "četvrtak",
|
|||
|
|
"fri": "petak",
|
|||
|
|
"sat": "subota",
|
|||
|
|
"sun": "nedjelja",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
DEFAULT_WORKING_HOURS: dict[str, list[list[str]]] = {
|
|||
|
|
"mon": [["09:00", "18:00"]],
|
|||
|
|
"tue": [["09:00", "18:00"]],
|
|||
|
|
"wed": [["09:00", "18:00"]],
|
|||
|
|
"thu": [["09:00", "18:00"]],
|
|||
|
|
"fri": [["09:00", "18:00"]],
|
|||
|
|
"sat": [["09:00", "14:00"]],
|
|||
|
|
"sun": [],
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _parse_hhmm(s: str) -> time:
|
|||
|
|
h, m = s.split(":")
|
|||
|
|
return time(int(h), int(m))
|
|||
|
|
|
|||
|
|
|
|||
|
|
def day_intervals(
|
|||
|
|
working_hours: dict, day: date, tz: ZoneInfo
|
|||
|
|
) -> list[tuple[datetime, datetime]]:
|
|||
|
|
"""Open intervals for a calendar day as tz-aware datetimes."""
|
|||
|
|
key = WEEKDAY_KEYS[day.weekday()]
|
|||
|
|
out = []
|
|||
|
|
for start_s, end_s in working_hours.get(key, []):
|
|||
|
|
start = datetime.combine(day, _parse_hhmm(start_s), tzinfo=tz)
|
|||
|
|
end = datetime.combine(day, _parse_hhmm(end_s), tzinfo=tz)
|
|||
|
|
if end > start:
|
|||
|
|
out.append((start, end))
|
|||
|
|
return out
|
|||
|
|
|
|||
|
|
|
|||
|
|
def is_open_at(working_hours: dict, at: datetime, tz: ZoneInfo) -> bool:
|
|||
|
|
at = at.astimezone(tz)
|
|||
|
|
return any(s <= at < e for s, e in day_intervals(working_hours, at.date(), tz))
|
|||
|
|
|
|||
|
|
|
|||
|
|
def hours_summary_bs(working_hours: dict) -> str:
|
|||
|
|
"""Human-readable Bosnian working-hours summary for prompts/emails."""
|
|||
|
|
parts = []
|
|||
|
|
for key in WEEKDAY_KEYS:
|
|||
|
|
intervals = working_hours.get(key, [])
|
|||
|
|
if not intervals:
|
|||
|
|
parts.append(f"{DAY_NAMES_BS[key]}: zatvoreno")
|
|||
|
|
else:
|
|||
|
|
spans = ", ".join(f"{a}–{b}" for a, b in intervals)
|
|||
|
|
parts.append(f"{DAY_NAMES_BS[key]}: {spans}")
|
|||
|
|
return "; ".join(parts)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def iter_days(start: date, end: date):
|
|||
|
|
d = start
|
|||
|
|
while d <= end:
|
|||
|
|
yield d
|
|||
|
|
d += timedelta(days=1)
|