"""The half-hour slot, in one place (MNT-12). Availability, coach bookings and matches are all built from the same rule: *a slot given a start and no end lasts thirty minutes*. It was written five times, in four modules, and the copies had drifted in the way that matters — not in the arithmetic, but in what each did when the input was wrong: matches.py flashed an error and redirected matches.py, later set start_time to None and reported success team_matches.py `except ValueError: pass`, silently availability.py `continue`, dropping the slot without a word Waves E and G closed the match side by putting a marshmallow schema at the form boundary. This module is the other half: the constant and the two functions the remaining callers need, so that the availability routes can use the same schema treatment without each one re-deciding what a slot is. `DEFAULT_SLOT_MINUTES` is deliberately not configurable. It is a product decision written into the UI — the availability grid draws half-hour cells — and a setting would let the two disagree. """ from datetime import date as date_cls from datetime import datetime, timedelta #: How long a slot lasts when only its start is given. DEFAULT_SLOT_MINUTES = 30 #: Monday-first, matching `datetime.weekday()` and the availability grid. DAY_NAMES = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday') def slot_end(start_time, minutes=DEFAULT_SLOT_MINUTES, on=None): """The end of a slot starting at `start_time`. Args: start_time: A `datetime.time`. minutes: Slot length; defaults to DEFAULT_SLOT_MINUTES. on: The date the slot falls on. Only matters for a slot that would cross midnight, where the arbitrary date the old helpers used (`datetime.today()`) made the result depend on when the code ran. Returns: datetime.time: The end of the slot, wrapping past midnight like the previous implementations did. """ anchor = on or date_cls(2000, 1, 1) return (datetime.combine(anchor, start_time) + timedelta(minutes=minutes)).time() def day_name(day_of_week): """Name of a weekday index, or a readable fallback. Every caller indexed a module-level list directly, so an out-of-range day — which nothing prevented before the schemas — was an IndexError inside a JSON route, i.e. a 500 with an HTML body. """ if 0 <= day_of_week < len(DAY_NAMES): return DAY_NAMES[day_of_week] return f'Day {day_of_week}'