diff --git a/libs/agno/agno/tools/google/calendar.py b/libs/agno/agno/tools/google/calendar.py index d86d49929c..91ba623346 100644 --- a/libs/agno/agno/tools/google/calendar.py +++ b/libs/agno/agno/tools/google/calendar.py @@ -610,7 +610,8 @@ class GoogleCalendarTools(GoogleToolkit): is_available = False break - if is_available and slot_end.hour <= working_hours_end: + working_day_end = current.replace(hour=working_hours_end, minute=0, second=0, microsecond=0) + if is_available and slot_end <= working_day_end: available_slots.append({"start": current.isoformat(), "end": slot_end.isoformat()}) current += datetime.timedelta(minutes=30) diff --git a/libs/agno/tests/unit/tools/test_google_calendar.py b/libs/agno/tests/unit/tools/test_google_calendar.py index f81127976c..b71ebb2576 100644 --- a/libs/agno/tests/unit/tools/test_google_calendar.py +++ b/libs/agno/tests/unit/tools/test_google_calendar.py @@ -3,6 +3,7 @@ import json import os import tempfile +from datetime import datetime from unittest.mock import MagicMock, Mock, patch import pytest @@ -464,6 +465,48 @@ class TestFetchAllEvents: class TestFindAvailableSlots: + @pytest.mark.parametrize( + ("duration_minutes", "end_hour", "expected_last_end", "expected_count"), + [ + (30, 17, "17:00:00", 16), + (45, 17, "16:45:00", 15), + (60, 17, "17:00:00", 15), + (480, 17, "17:00:00", 1), + (481, 17, None, 0), + (1500, 17, None, 0), + (45, 18, "17:45:00", 17), + ], + ) + @patch.object(GoogleCalendarTools, "fetch_all_events") + @patch.object(GoogleCalendarTools, "_get_working_hours") + def test_available_slots_end_within_working_hours( + self, + mock_working_hours, + mock_fetch, + calendar_tools, + duration_minutes, + end_hour, + expected_last_end, + expected_count, + ): + mock_working_hours.return_value = json.dumps( + {"start_hour": 9, "end_hour": end_hour, "timezone": "UTC", "locale": "en"} + ) + mock_fetch.return_value = json.dumps([]) + + result = json.loads( + calendar_tools.find_available_slots("2026-10-02", "2026-10-02", duration_minutes=duration_minutes) + ) + + slots = result["available_slots"] + assert len(slots) == expected_count + if expected_last_end is not None: + assert slots[-1]["end"] == f"2026-10-02T{expected_last_end}+00:00" + for slot in slots: + start = datetime.fromisoformat(slot["start"]) + end = datetime.fromisoformat(slot["end"]) + assert end <= start.replace(hour=end_hour, minute=0, second=0, microsecond=0) + @patch.object(GoogleCalendarTools, "fetch_all_events") @patch.object(GoogleCalendarTools, "_get_working_hours") def test_find_available_slots_success(self, mock_working_hours, mock_fetch, calendar_tools):