はじめに
コーディングをしていると、スケジュールのことが気になってしまうことが多いため、スケジュールの通知機能が必要になった。
はまりどころ
CalDAVを使用してカレンダーから予定を取得する際には、タイムゾーンに注意が必要である。
なぜなら、CalDAVから送られてくる日時は、サービスごとにタイムゾーンが異なると思われるためである。
例えば、DTSTART
やDTSTAMP
の末尾にZ
が付いている場合はUTCであることを示す。
工夫
-
カレンダーの取得回数の制限:
CalDAVサーバに負担をかけないように配慮するため、カレンダーの取得頻度を制限することが重要である。
具体的には、以下のような工夫を行った。-
12時間ごとの取得:
カレンダーの予定を12時間に一度取得するように設定した。
これにより、サーバへのリクエスト回数を減らし、サーバの負荷を軽減することができる。
-
12時間ごとの取得:
-
リマインダー機能の実装:
約15分前、約5分前、約1分前で通知を行うリマインダー機能を実装した。
これにより、予定を見逃すことを防ぎ、事前に準備をする時間を確保できるようにしている。
環境
- Python 3.12.8
- Windows 10 22H2
必要なライブラリ
以下のコマンドで必要なライブラリをインストールする。
py -m pip install caldav==1.4.0
py -m pip install plyer==2.1.0
サンプルコード
以下に、カレンダーから予定を取得し、トースト通知を行うサンプルコードを示す。
import time
import datetime
from caldav import DAVClient, Principal, Calendar, Sequence, CalendarObjectResource
from plyer import notification
URL: str = 'https://example.com/'
USERNAME: str = 'Hoge'
PASSWORD: str = 'Fuga'
def fetch_schedules(schedules : dict[str, datetime.datetime]) -> None:
"""
CalDAVサーバから予定を取得する。
Args:
schedules (dict[str, datetime.datetime]): 予定を格納する辞書。キーは予定のタイトル、値は開始時刻。
"""
with DAVClient(url=URL, username=USERNAME, password=PASSWORD) as client:
principal: Principal = client.principal()
calendars: list[Calendar] = principal.calendars()
now_utc: datetime = datetime.datetime.now(datetime.timezone.utc)
end_time: datetime = now_utc + datetime.timedelta(days=1)
for calendar in calendars:
try:
events: Sequence[CalendarObjectResource] = calendar.date_search(start=now_utc, end=end_time, expand=True)
for event in events:
d : dict[str, str] = {}
for event_data in event.data.splitlines():
pair: list[str] = event_data.split(":")
if len(pair) >= 2:
d[pair[0]] = pair[1]
if 'SUMMARY' in d and 'DTSTART' in d:
key: str = d['SUMMARY']
start_time_str: str = d['DTSTART']
start_time: datetime = datetime.datetime.strptime(start_time_str, '%Y%m%dT%H%M%SZ')
start_time: datetime = start_time.replace(tzinfo=datetime.timezone.utc)
schedules[key] = start_time
except Exception as e:
notification.notify(title="カレンダー取得 失敗", message=f"カレンダーの取得に失敗しました。: {str(e)}", timeout=10)
exit(1)
def check_notifications(schedules : dict[str, datetime.datetime]) -> None:
"""
取得したスケジュールを確認し、通知を行う。
Args:
schedules (dict[str, datetime.datetime]): 予定を格納する辞書。キーは予定のタイトル、値は開始時刻。
"""
now_utc: datetime = datetime.datetime.now(datetime.timezone.utc)
for k, start_time in schedules.items():
event_time: datetime = start_time
# 各通知タイミングを計算
fifteen_minutes_before: datetime = event_time - datetime.timedelta(minutes=15)
fourteen_minutes_before: datetime = event_time - datetime.timedelta(minutes=14)
five_minutes_before: datetime = event_time - datetime.timedelta(minutes=5)
six_minutes_before: datetime = event_time - datetime.timedelta(minutes=6)
one_minute_before: datetime = event_time - datetime.timedelta(minutes=1)
# トーストを表示する条件
is_fifteen_minutes_before: bool = fifteen_minutes_before >= now_utc >= fourteen_minutes_before
is_five_minutes_before: bool = five_minutes_before >= now_utc >= six_minutes_before
is_one_minute_before: bool = event_time >= now_utc >= one_minute_before
if is_fifteen_minutes_before or is_five_minutes_before or is_one_minute_before:
notification.notify(title=k, message=f"予定「{k}」", timeout=10)
def main() -> None:
schedules : dict[str, datetime.datetime] = {}
while True:
fetch_schedules(schedules)
for _ in range(12 * 60):
check_notifications(schedules)
time.sleep(60)
if __name__ == "__main__":
main()
さいごに
CalDAVを使用してカレンダーから予定を取得し、指定した時間の前に通知を行う機能を実装した。
この機能により、安心してコーディングを行えるようになった。
参考文献