Spaces:
Paused
Paused
# ========================= | |
# WORKING HOURS CONFIG | |
# ========================= | |
# Working hours: 9:00-18:00 (20 slots) = 20 slots per working day | |
# Each slot is 30 minutes, starting at 9:00 AM | |
SLOTS_PER_WORKING_DAY = 20 # 9:00-18:00 (9 hours * 2 slots/hour) | |
MORNING_SLOTS = 8 # 9:00-13:00 (4 hours * 2 slots/hour) | |
AFTERNOON_SLOTS = 10 # 14:00-18:00 (4 hours * 2 slots/hour) | |
LUNCH_BREAK_START_SLOT = 8 # 13:00-14:00 | |
LUNCH_BREAK_END_SLOT = 10 # 14:00 | |
from datetime import datetime, date, time, timezone, timedelta | |
def slot_to_datetime(slot: int, base_date: date = None, base_timezone=None) -> datetime: | |
""" | |
Convert a slot index to a naive datetime in local time, accounting for working days. | |
Args: | |
slot: The slot index (each slot = 30 minutes within working hours) | |
base_date: Base date for slot 0 (defaults to today) | |
base_timezone: Ignored (kept for API compatibility) | |
Returns: | |
datetime: The corresponding naive datetime in local time | |
""" | |
if base_date is None: | |
base_date = date.today() | |
# Calculate which working day and slot within that day | |
working_day = get_working_day_from_slot(slot) | |
slot_within_day = get_slot_within_day(slot) | |
# Get the actual calendar date for this working day | |
target_date = base_date + timedelta(days=working_day) | |
# Calculate time within the working day (9:00 AM + slot_within_day * 30 minutes) | |
minutes_from_9am = slot_within_day * 30 | |
target_time = datetime.combine( | |
target_date, datetime.min.time().replace(hour=9) | |
) + timedelta(minutes=minutes_from_9am) | |
return target_time | |
def get_working_day_from_slot(slot: int) -> int: | |
"""Get the working day index (0=first working day) from a slot. | |
Args: | |
slot (int): The slot index. | |
Returns: | |
int: The working day index (0-based). | |
""" | |
return slot // SLOTS_PER_WORKING_DAY | |
def get_slot_within_day(slot: int) -> int: | |
"""Get the slot position within a working day (0-19). | |
Args: | |
slot (int): The slot index. | |
Returns: | |
int: The slot position within the day (0-19). | |
""" | |
return slot % SLOTS_PER_WORKING_DAY | |
def task_spans_lunch_break(task) -> bool: | |
"""Check if a task spans across the lunch break period (13:00-14:00). | |
Args: | |
task: The task to check. | |
Returns: | |
bool: True if the task spans across lunch break. | |
""" | |
start_slot_in_day = get_slot_within_day(task.start_slot) | |
end_slot_in_day = start_slot_in_day + task.duration_slots - 1 | |
# Check if task overlaps with lunch break slots (8-9, which is 13:00-14:00) | |
return ( | |
start_slot_in_day <= LUNCH_BREAK_END_SLOT - 1 | |
and end_slot_in_day >= LUNCH_BREAK_START_SLOT | |
) | |
def is_weekend_slot(slot: int) -> bool: | |
"""Check if a slot falls on a weekend. | |
Args: | |
slot: The slot index | |
Returns: | |
bool: True if the slot is on a weekend | |
""" | |
working_day = get_working_day_from_slot(slot) | |
# For simplicity, assume every 7th day starting from day 5 and 6 are weekends | |
# This is a simplification - in practice you'd want to use actual calendar logic | |
day_of_week = working_day % 7 | |
return day_of_week >= 5 # Saturday (5) and Sunday (6) | |
def get_slot_date(slot: int, base_date: date = None) -> date: | |
"""Get the date for a given slot. | |
Args: | |
slot: The slot index | |
base_date: Base date for slot 0 (defaults to today) | |
Returns: | |
date: The date for this slot | |
""" | |
if base_date is None: | |
base_date = date.today() | |
working_days = get_working_day_from_slot(slot) | |
return base_date + timedelta(days=working_days) | |