from datetime import datetime, time, timedelta from typing import List def get_day_night_splits( session_start: datetime, session_end: datetime, day_start_boundary: time, day_end_boundary: time, ) -> List[dict]: """ Splits a sleep session into day and night segments based on boundaries. A segment is defined by [segment_start, segment_end] and its type (day/night). """ segments = [] points = [session_start, session_end] for offset in [0, 1]: base_date = session_start.date() + timedelta(days=offset) try: b1 = datetime.combine(base_date, day_start_boundary).replace( tzinfo=session_start.tzinfo ) b2 = datetime.combine(base_date, day_end_boundary).replace( tzinfo=session_start.tzinfo ) if session_start < b1 < session_end: points.append(b1) if session_start < b2 < session_end: points.append(b2) except (ValueError, AttributeError): continue sorted_points = sorted(list(set(points))) for i in range(len(sorted_points) - 1): seg_start = sorted_points[i] seg_end = sorted_points[i + 1] midpoint = seg_start + (seg_end - seg_start) / 2 is_day = day_start_pattern( midpoint.time(), day_start_boundary, day_end_boundary ) segments.append( {"start": seg_start, "end": seg_end, "type": "day" if is_day else "night"} ) return segments def day_start_pattern(current_time: time, start: time, end: time) -> bool: if start <= end: return start <= current_time < end else: # Spans midnight return current_time >= start or current_time < end from datetime import datetime, time, timedelta def minutes_after(dt: datetime, reference: time) -> float: """Returns minutes after a reference time.""" ref_delta = timedelta(hours=reference.hour, minutes=reference.minute) dt_delta = timedelta(hours=dt.hour, minutes=dt.minute) return (dt_delta - ref_delta).total_seconds() / 60.0