Google Calendar APIで複数カレンダーを統合管理する自動化システム
Google Calendar APIで複数カレンダーを統合管理する自動化システム
仕事用カレンダー、個人カレンダー、副業用カレンダーと複数のGoogleカレンダーを使っていると、「今週の空き時間はどこか」を確認するのが面倒になった。PythonとGoogle Calendar APIを使って、複数カレンダーを一元管理するシステムを作った。
Google Calendar APIの準備
Google Cloud Consoleでの設定
- Google Cloud Console でプロジェクトを作成
- 「APIとサービス」→「ライブラリ」でGoogle Calendar APIを有効化
- 「認証情報」→「OAuth 2.0クライアントID」を作成(デスクトップアプリ)
credentials.jsonをダウンロード
必要なライブラリのインストール
pip install google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client
認証フローの実装
import os
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']
def get_calendar_service():
creds = None
# トークンが存在する場合は読み込む
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
# トークンがないか期限切れの場合は再認証
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES
)
creds = flow.run_local_server(port=0)
# トークンを保存
with open('token.json', 'w') as token:
token.write(creds.to_json())
return build('calendar', 'v3', credentials=creds)
初回実行時にブラウザが開いてGoogleアカウントへの認可を求められる。2回目以降はtoken.jsonを使って自動認証される。
複数カレンダーの予定を一括取得
from datetime import datetime, timedelta
import pytz
def get_all_events(service, calendar_ids: list, days_ahead: int = 7):
"""
複数カレンダーから指定日数分の予定を取得する
Args:
service: Google Calendar APIのサービスオブジェクト
calendar_ids: カレンダーIDのリスト
days_ahead: 何日先まで取得するか
Returns:
日時でソートされた予定のリスト
"""
jst = pytz.timezone('Asia/Tokyo')
now = datetime.now(jst)
time_min = now.isoformat()
time_max = (now + timedelta(days=days_ahead)).isoformat()
all_events = []
for calendar_id in calendar_ids:
try:
events_result = service.events().list(
calendarId=calendar_id,
timeMin=time_min,
timeMax=time_max,
maxResults=100,
singleEvents=True,
orderBy='startTime'
).execute()
events = events_result.get('items', [])
for event in events:
# カレンダーIDを付加して区別できるようにする
event['_calendar_id'] = calendar_id
all_events.append(event)
except Exception as e:
print(f"カレンダー {calendar_id} の取得に失敗: {e}")
# 開始時刻でソート
all_events.sort(key=lambda e: e.get('start', {}).get('dateTime',
e.get('start', {}).get('date', '')))
return all_events
カレンダーIDの取得方法
def list_calendars(service):
"""ユーザーが持つカレンダーの一覧を表示する"""
calendars = service.calendarList().list().execute()
for calendar in calendars.get('items', []):
print(f"名前: {calendar['summary']}")
print(f"ID: {calendar['id']}")
print("---")
カレンダーIDはprimary(メインカレンダー)かxxxx@group.calendar.google.comの形式。
空き時間を計算する
複数カレンダーの予定を統合した上で、空き時間を計算する:
def find_free_slots(events: list, work_start_hour: int = 9,
work_end_hour: int = 18, min_duration_minutes: int = 60):
"""
予定の隙間にある空き時間を検出する
Args:
events: get_all_eventsで取得した予定リスト
work_start_hour: 作業開始時刻(時)
work_end_hour: 作業終了時刻(時)
min_duration_minutes: 空き時間の最小時間(分)
Returns:
空き時間のリスト [(開始datetime, 終了datetime), ...]
"""
jst = pytz.timezone('Asia/Tokyo')
today = datetime.now(jst).date()
free_slots = []
# 日付ごとに予定をグループ化
from collections import defaultdict
events_by_date = defaultdict(list)
for event in events:
start = event.get('start', {})
if 'dateTime' in start:
start_dt = datetime.fromisoformat(start['dateTime'])
events_by_date[start_dt.date()].append(event)
# 各日の空き時間を計算
for day_offset in range(7):
target_date = today + timedelta(days=day_offset)
day_events = events_by_date.get(target_date, [])
# 作業時間帯を設定
work_start = jst.localize(datetime.combine(target_date,
datetime.min.time().replace(hour=work_start_hour)))
work_end = jst.localize(datetime.combine(target_date,
datetime.min.time().replace(hour=work_end_hour)))
# 予定の開始・終了時刻を収集
busy_times = []
for event in day_events:
start_str = event.get('start', {}).get('dateTime')
end_str = event.get('end', {}).get('dateTime')
if start_str and end_str:
busy_times.append((
datetime.fromisoformat(start_str),
datetime.fromisoformat(end_str)
))
busy_times.sort()
# 隙間を計算
current = work_start
for busy_start, busy_end in busy_times:
if busy_start > current:
duration = (busy_start - current).total_seconds() / 60
if duration >= min_duration_minutes:
free_slots.append((current, busy_start))
current = max(current, busy_end)
# 最後の予定から作業終了まで
if current < work_end:
duration = (work_end - current).total_seconds() / 60
if duration >= min_duration_minutes:
free_slots.append((current, work_end))
return free_slots
使い方
def main():
service = get_calendar_service()
# カレンダーIDを設定(list_calendarsで確認したIDを使う)
CALENDAR_IDS = [
'primary', # メインカレンダー
'work@group.calendar.google.com', # 仕事用
'personal@group.calendar.google.com', # 個人用
]
# 1週間分の予定を取得
print("予定を取得中...")
events = get_all_events(service, CALENDAR_IDS, days_ahead=7)
print(f"\n今後7日間の予定: {len(events)}件")
for event in events:
start = event['start'].get('dateTime', event['start'].get('date'))
print(f" - {start}: {event.get('summary', '(タイトルなし)')}")
# 空き時間を計算
free_slots = find_free_slots(events, min_duration_minutes=60)
print(f"\n1時間以上の空き時間: {len(free_slots)}件")
for start, end in free_slots:
duration = int((end - start).total_seconds() / 60)
print(f" - {start.strftime('%m/%d %H:%M')}〜{end.strftime('%H:%M')} ({duration}分)")
if __name__ == "__main__":
main()
まとめ
Google Calendar APIで複数カレンダーを統合したことで:
- 「今週どこが空いているか」を1コマンドで確認できる
- 複数のカレンダーをまたいだ空き時間の計算ができる
- 日程調整の返信が速くなった
応用として、空き時間をSlackに通知したり、Googleスプレッドシートに書き出して共有したりもできる。OAuth認証さえ乗り越えれば、あとのAPIは直感的に使いやすい。