Calendar Scheduling Logic Skill

SkillProductivity

Find available time slots in a calendar and schedule meetings while respecting constraints

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Calendar Scheduling Logic Skill skill

What this skill tells your AI

The instructions your AI receives, as published by cxcscmu/skilllearnbench in skills/b1-one-shot-claude-haiku-4-5/schedule-planning/calendar-scheduling-logic/SKILL.md and read by ahel’s review.

Overview

This skill covers scheduling algorithms to find available time slots in a calendar, handle time conflicts, prioritize blue-colored flexible blocks, and match meeting requests with available time windows.

Key Concepts

Calendar Representation

# Event representation
event = {
    'name': 'Weekly Group Meeting',
    'start_hour': 10,
    'start_minute': 0,
    'end_hour': 11,
    'end_minute': 0,
    'color': 'red',           # 'red', 'blue', 'yellow', 'gray'
    'is_flexible': True/False # Only blue is True
}

# 15-minute interval representation
# 9:00am = index 0, 9:15am = index 1, etc.

Constraint Types

  1. User Availability Window: Person only available during specific hours
  2. Date Constraint: Meeting must be on specific date
  3. Duration Requirement: Meeting needs exact duration
  4. Block Overriding: Blue blocks can be overridden for higher priority meetings

Code Examples

Time Conversion to Minutes

def time_to_minutes(hour, minute):
    """Convert hour:minute to total minutes since midnight"""
    return hour * 60 + minute

def minutes_to_time(minutes):
    """Convert total minutes to (hour, minute) tuple"""
    hour = minutes // 60
    minute = minutes % 60
    return (hour, minute)

Convert Time Slots to Intervals

def time_range_to_intervals(start_hour, start_min, end_hour, end_min, interval_minutes=15):
    """Convert a time range to 15-minute interval indices"""
    start_total_min = time_to_minutes(start_hour, start_min)
    end_total_min = time_to_minutes(end_hour, end_min)

    intervals = []
    current = start_total_min
    while current < end_total_min:
        intervals.append(current // interval_minutes)
        current += interval_minutes

    return intervals

Track Busy Intervals

def mark_busy_intervals(calendar_events, day_start_hour=0, day_end_hour=24):
    """Mark all busy intervals in a day"""
    total_intervals = (day_end_hour - day_start_hour) * 60 // 15
    busy = [False] * total_intervals

    for event in calendar_events:
        intervals = time_range_to_intervals(
            event['start_hour'], event['start_minute'],
            event['end_hour'], event['end_minute']
        )
        for i in intervals:
            if 0 <= i < total_intervals:
                busy[i] = True

    return busy

Find Available Slots (Basic)

def find_available_slots(busy_array, duration_intervals):
    """Find all available slots of at least duration_intervals length"""
    available_slots = []
    consecutive = 0
    start_idx = None

    for i, is_busy in enumerate(busy_array):
        if not is_busy:
            if consecutive == 0:
                start_idx = i
            consecutive += 1

            if consecutive >= duration_intervals:
                available_slots.append((start_idx, i + 1))
        else:
            consecutive = 0

    return available_slots

Filter by User Availability Window

def filter_by_availability_window(available_slots, user_start, user_end, interval_minutes=15):
    """Filter slots to only those within user's available window"""
    user_start_intervals = time_to_minutes(user_start[0], user_start[1]) // interval_minutes
    user_end_intervals = time_to_minutes(user_end[0], user_end[1]) // interval_minutes

    filtered = []
    for start_idx, end_idx in available_slots:
        # Check if slot overlaps with user's availability
        if end_idx > user_start_intervals and start_idx < user_end_intervals:
            # Constrain to user's window
            constrained_start = max(start_idx, user_start_intervals)
            constrained_end = min(end_idx, user_end_intervals)
            filtered.append((constrained_start, constrained_end))

    return filtered

Select Earliest Valid Slot

def select_earliest_slot(available_slots, duration_intervals):
    """Select the earliest slot that can fit the duration"""
    for start_idx, end_idx in available_slots:
        slot_duration = end_idx - start_idx
        if slot_duration >= duration_intervals:
            return (start_idx, start_idx + duration_intervals)

    return None

Handle Blue Block Override

def create_busy_with_overrides(calendar_events, allow_blue_override=True):
    """Mark busy intervals, but allow blue blocks to be overridden"""
    total_intervals = 24 * 60 // 15
    busy = [False] * total_intervals
    blue_blocks = []

    for event in calendar_events:
        intervals = time_range_to_intervals(
            event['start_hour'], event['start_minute'],
            event['end_hour'], event['end_minute']
        )

        if event.get('color') == 'blue' and allow_blue_override:
            blue_blocks.append((intervals[0], intervals[-1]))
        else:
            for i in intervals:
                if 0 <= i < total_intervals:
                    busy[i] = True

    return busy, blue_blocks

Complete Scheduling Algorithm

def schedule_meeting(calendar_events, request):
    """
    Schedule a meeting respecting all constraints

    request = {
        'duration_hours': 1.0,
        'available_times': {'start': (10, 0), 'end': (14, 0), 'timezone': None},
        'timezone': 'US/Eastern'
    }
    """
    duration_minutes = request['duration_hours'] * 60
    duration_intervals = duration_minutes // 15

    # Get busy times and blue overridable blocks
    busy, blue_blocks = create_busy_with_overrides(calendar_events)

    # Find all available slots
    available_slots = find_available_slots(busy, duration_intervals)

    # Filter by user's availability window
    if request['available_times']:
        available_slots = filter_by_availability_window(
            available_slots,
            request['available_times']['start'],
            request['available_times']['end']
        )

    # Select earliest slot
    selected = select_earliest_slot(available_slots, duration_intervals)

    if selected:
        start_intervals, end_intervals = selected
        start_hour, start_min = minutes_to_time(start_intervals * 15)
        end_hour, end_min = minutes_to_time(end_intervals * 15)
        return {
            'start': (start_hour, start_min),
            'end': (end_hour, end_min),
            'success': True
        }

    return {'success': False, 'reason': 'No available slots'}

Advanced: Priority-Based Scheduling

def schedule_multiple_meetings(calendar_events, requests, priority_fn=None):
    """
    Schedule multiple meetings, resolving conflicts with priority function

    priority_fn: function that returns numeric priority (higher = more important)
    """
    # Sort by priority
    if priority_fn:
        requests = sorted(requests, key=priority_fn, reverse=True)
    else:
        # Default: earlier constraint window = higher priority
        requests = sorted(
            requests,
            key=lambda r: time_to_minutes(r['available_times']['end'][0], r['available_times']['end'][1])
        )

    scheduled = []
    working_events = calendar_events.copy()

    for request in requests:
        slot = schedule_meeting(working_events, request)
        if slot['success']:
            # Add booked meeting to calendar
            working_events.append({
                'name': f"Meeting: {request['sender']}",
                'start_hour': slot['start'][0],
                'start_minute': slot['start'][1],
                'end_hour': slot['end'][0],
                'end_minute': slot['end'][1],
                'color': 'booked'
            })
            scheduled.append({'request': request, 'slot': slot})

    return scheduled

Best Practices

  1. Always work with intervals to avoid floating-point time issues
  2. Store both interval indices and actual times for clarity
  3. Handle timezone conversions before scheduling
  4. Validate that proposed times fall within business hours
  5. Log which blue blocks were overridden for transparency
  6. Ensure slot duration exactly matches requirement (no partial hours unless specified)

Signals

GitHub stars
83
Forks
5
Last commit
Jul 2026
Advanced
Catalog kind
skill
Gateway key
calendar-scheduling-logic
Source
github.com/cxcscmu/skilllearnbench