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
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: dict[str, Any] = {
|
|
"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
|