CI - Security, Lint & Tests / validate (push) Failing after 1m3s
ajout de compteur d'heure disponible ajout du calendrier de dispo dans les profils publics
97 lines
3.7 KiB
Python
97 lines
3.7 KiB
Python
"""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}'
|
|
|
|
|
|
def slot_duration_minutes(start_time, end_time):
|
|
"""Minutes a slot spans, wrapping past midnight like slot_end does.
|
|
|
|
Works on SQLite where ``Time`` columns are stored as text — the
|
|
arithmetic stays in pure Python so it is portable across databases.
|
|
|
|
Args:
|
|
start_time: A ``datetime.time``.
|
|
end_time: A ``datetime.time`` (may be earlier if the slot wraps
|
|
past midnight).
|
|
|
|
Returns:
|
|
int: Duration in whole minutes.
|
|
"""
|
|
anchor = date_cls(2000, 1, 1)
|
|
start_dt = datetime.combine(anchor, start_time)
|
|
end_dt = datetime.combine(anchor, end_time)
|
|
if end_dt <= start_dt:
|
|
end_dt += timedelta(days=1)
|
|
return int((end_dt - start_dt).total_seconds() // 60)
|
|
|
|
|
|
def total_hours(slots):
|
|
"""Aggregate slot durations into a single float, rounded to one decimal.
|
|
|
|
Args:
|
|
slots: Iterable of objects with ``start_time`` and ``end_time``
|
|
attributes (``PlayerDisponibility`` or ``CoachAvailability``).
|
|
|
|
Returns:
|
|
float: Total hours across all slots, e.g. ``8.5``.
|
|
"""
|
|
total_minutes = sum(slot_duration_minutes(s.start_time, s.end_time) for s in slots)
|
|
return round(total_minutes / 60, 1)
|