You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

69 lines
2.1 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from typing import Any
@dataclass(frozen=True)
class AvailabilityResult:
start: str
end: str
available: bool
busy_slots: list[dict[str, str]]
checked_calendars: list[str]
class CalendarAvailabilityAgent:
def __init__(self, calendar_service: Any) -> None:
self.calendar_service = calendar_service
def get_availability(
self, start: str, end: str, calendar_ids: list[str] | None = None
) -> AvailabilityResult:
start_dt = _parse_iso_datetime(start)
end_dt = _parse_iso_datetime(end)
if end_dt <= start_dt:
raise ValueError("end must be after start.")
calendars = calendar_ids or ["primary"]
query_body = {
"timeMin": start_dt.isoformat(),
"timeMax": end_dt.isoformat(),
"items": [{"id": calendar_id} for calendar_id in calendars],
}
freebusy = self.calendar_service.freebusy().query(body=query_body).execute()
calendars_payload = freebusy.get("calendars", {})
busy_slots: list[dict[str, str]] = []
for calendar_id, data in calendars_payload.items():
for busy_slot in data.get("busy", []):
busy_slots.append(
{
"calendar_id": calendar_id,
"start": busy_slot["start"],
"end": busy_slot["end"],
}
)
return AvailabilityResult(
start=start_dt.isoformat(),
end=end_dt.isoformat(),
available=len(busy_slots) == 0,
busy_slots=busy_slots,
checked_calendars=calendars,
)
def _parse_iso_datetime(value: str) -> datetime:
normalized = value.strip()
if normalized.endswith("Z"):
normalized = normalized[:-1] + "+00:00"
parsed = datetime.fromisoformat(normalized)
if parsed.tzinfo is None:
raise ValueError("datetime must include a timezone offset, for example +01:00.")
return parsed