2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PythonでNextCloud(CalDAV)のカレンダーの予定を取得する

Last updated at Posted at 2022-08-22

概要

初めての記事投稿になります。

この記事ではPythonでNextCloudのカレンダーの予定を取得する方法を載せようと思います。
自分が試そうと思ったときに日本語の記事が見つからなかったため、ここに残しておこうと思います。
動機としては研究室のカレンダーがNextCloudを使用しており、それを手軽に見れるようにしたいなといった感じです。
ThunderBirdやIOSのカレンダーにCalDAVをインポートすれば表示されるのですが、据え置きのディスプレイを見るだけで今日の予定が知ることができればもっといいなと思いました。

使用するモジュール

今回使用するPythonモジュールはcaldavとdatetimeです。

caldavをインストールしていない方はまずインストールをしましょう。

pip install caldav

caldavのドキュメントはこちらを参照。

次にPythonでインポートしましょう。

import caldav
import datetime

NextCloudからカレンダーの情報を取得する

まずクライアント側からカレンダーから色々情報を取得するコードを書きます。
まずCalDAVのURLを取得しましょう。
NextCloudの場合、設定とインポートから取得できると思います。
今回はPythonから取得したいので、通常のCalDAVアドレスをコピーを選択。
image.png

取得したURLを使ってPythonクライアントからカレンダーの情報を取得します。
urlには先ほどのcalDAVアドレスを入れてください。usernameとpasswordは認証がある場合は入力してください。
自分の研究室のNextCloudでは、認証があったためそちらの値を使用しました。
認証がない場合は省略だと思います。

client = caldav.DAVClient(url=url, username=username, password=password)
# プリンシパル オブジェクトの設定
principal = client.principal()

# カレンダーの取得
calendars = principal.calendars()

これでカレンダーの情報を入手できたので、ここから予定を抽出していきます。

まずcalendarsですがここにはNextCloudにあるすべてのカレンダーがリストとして入っています。
リスト内のカレンダーをそのままprintすると個別のカレンダーのURLが表示されます。またcalendar.nameprintするとカレンダーの名前が出てきます。

for calendar in calendars:
    # カレンダーのURLを表示
    print(calendar)

    # カレンダーの名前を表示
    print(calendar.name)

他にもcalendar.addでカレンダーに予定を追加したりすることができますが、今回は予定の抽出のみを行います。

カレンダーから予定を取得する

次にカレンダーから予定を取得していきます。
カレンダーの取得には日付を指定した方が良いです。日付を指定しないとすべての予定が取得されてしまいます。

日付は予定の始まりと終わりを指定し、datetime型で入力します。
今回は今日の予定を取得します。

今日の予定は以下のようになっています。
カレンダーは2種類ありtest1、test2は同一のカレンダー、test3はもう1つのカレンダーの予定となっております。
test1とtest2では終日かそうでないかで違っています。ここの違いが後にデータの形の差として現れてきます。
image.png

そしてPython今日の予定を取得したい場合は以下のように書きます。

# 今日の日付を取得
today = datetime.datetime.now()


for calendar in calendars:
    try:
        events = calendar.date_search(start=today, end=today+datetime.timedelta(days=1), expand=True)
    except:
        continue

todayには今日の日付を入れています。today+datetime.timedelta(days=1)には明日の日付(今日の日付+1日)が入っています。
エラー処理を入れないとエラーを吐くので入れています。(おそらく指定した期間で予定がない場合にエラーを吐くのかも...)
エラー処理の書き方が良くないのは見逃してください。

eventsには拡張子が.icsのファイルのURLが入ったEvent型のデータが入っています。.icsについては各自で調べてください。

まだここでは予定が見れていない状態なので、このeventsから予定を確認できる形までもっていきます。

for event in events:
    print(event.data)

このevent.dataにはstr型の文字列が入っています。ここにはカレンダーに入力されている予定の詳細が1つの文字列として入っています。データの区切りは\r\nで表現されています。

出力結果 (UIDとPRODIDは隠しています)

'BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nPRODID:*//EN\r\nBEGIN:VEVENT\r\nUID:*\r\nDTSTART;VALUE=DATE:20220823\r\nDTEND;VALUE=DATE:20220824\r\nCREATED:20220822T223116Z\r\nDTSTAMP:20220822T223125Z\r\nLAST-MODIFIED:20220822T223125Z\r\nSEQUENCE:2\r\nSTATUS:CONFIRMED\r\nSUMMARY:test\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n'

もちろんこのままでは見にくいので見やすい形に持っていきましょう。
やり方はなんでもいいですが、今回はsplitlines()を使用しました。

event.splitlines()

# 出力
['BEGIN:VCALENDAR',
 'VERSION:2.0',
 'CALSCALE:GREGORIAN',
 'PRODID:*',
 'BEGIN:VEVENT',
 'UID:*',
 'DTSTART;VALUE=DATE:20220823',
 'DTEND;VALUE=DATE:20220824',
 'CREATED:20220822T223116Z',
 'DTSTAMP:20220822T223125Z',
 'LAST-MODIFIED:20220822T223125Z',
 'SEQUENCE:3',
 'STATUS:CONFIRMED',
 'SUMMARY:test2',
 'END:VEVENT',
 'END:VCALENDAR']

見やすいですね。また辞書に入れたほうが扱いやすそうだったのでこれを辞書に変換しました。

d = {}
for event_data in event.data.splitlines():
    pair = event_data.split(":")
    d[pair[0]] = pair[1]
print(d)

# 出力
{'BEGIN': 'VEVENT', 'VERSION': '2.0', 'CALSCALE': 'GREGORIAN', 'PRODID': '*', 'UID': '*', 'DTSTART;VALUE=DATE': '20220823', 'DTEND;VALUE=DATE': '20220824', 'CREATED': '20220822T223116Z', 'DTSTAMP': '20220822T224939Z', 'LAST-MODIFIED': '20220822T224939Z', 'SEQUENCE': '3', 'STATUS': 'CONFIRMED', 'SUMMARY': 'test2', 'END': 'VCALENDAR'}

ここで出力されている'SUMMARY'が予定の名前になります。
そして予定の始まりと終わりについてですが、場合によってkeyの部分の値が変わってきます。
今回の3つのテストの出力は以下のようになります。

{'BEGIN': 'VEVENT',
  'VERSION': '2.0',
  'CALSCALE': 'GREGORIAN',
  'PRODID': '*',
  'UID': '*',
  'DTSTART;VALUE=DATE': '20220823',
  'DTEND;VALUE=DATE': '20220824',
  'CREATED': '20220822T223100Z',
  'DTSTAMP': '20220822T224941Z',
  'LAST-MODIFIED': '20220822T224941Z',
  'SEQUENCE': '3',
  'STATUS': 'CONFIRMED',
  'SUMMARY': 'test3',
  'END': 'VCALENDAR'},
 {'BEGIN': 'VEVENT',
  'VERSION': '2.0',
  'CALSCALE': 'GREGORIAN',
  'PRODID': '*',
  'UID': '*',
  'DTSTART': '20220823T010000Z',
  'DTEND': '20220823T020000Z',
  'CREATED': '20220822T223132Z',
  'DTSTAMP': '20220822T224936Z',
  'LAST-MODIFIED': '20220822T224936Z',
  'SEQUENCE': '3',
  'STATUS': 'CONFIRMED',
  'SUMMARY': 'test1',
  'END': 'VCALENDAR'},
 {'BEGIN': 'VEVENT',
  'VERSION': '2.0',
  'CALSCALE': 'GREGORIAN',
  'PRODID': '*',
  'UID': '*',
  'DTSTART;VALUE=DATE': '20220823',
  'DTEND;VALUE=DATE': '20220824',
  'CREATED': '20220822T223116Z',
  'DTSTAMP': '20220822T224939Z',
  'LAST-MODIFIED': '20220822T224939Z',
  'SEQUENCE': '3',
  'STATUS': 'CONFIRMED',
  'SUMMARY': 'test2',
  'END': 'VCALENDAR'}

出力結果からわかる通り'DTSTART'だったり'DTSTART;VALUE=DATE'だったりします。先ほど少し触れましたがこれは終日が終日でないかの違いになります。終日でない(つまり時間指定)場合、予定の始まりは'DTSTART'、予定の終わりは'DTEND'となっています。
逆に終日の場合'DTSTART;VALUE=DATE'が予定の始まりの日、'DTEND;VALUE=DATE'が予定の終わりになります。
今回は記述していないので省略されていますが、予定の説明はDESCRIPTIONになります。他の値については調べるなり考えてみるなりお願いします。

そして最後にこれを元にデータを表示します。各自好きなデータを使いましょう。
一例として予定の名前とその始まりと終わりをまとめるコードを書きました。参考程度にしてください。

if 'SUMMARY' in schedule.keys():
    key = schedule['SUMMARY']
if 'DTSTART' in d.keys():
    start = d['DTSTART'][:8]
elif 'DTSTART;VALUE=DATE' in d.keys():
    start = a['DTSTART;VALUE=DATE'][:8]
    
if 'DTEND' in d.keys():
    end = d['DTSTART'][:8]
elif 'DTEND;VALUE=DATE' in d.keys():
    end = d['DTEND;VALUE=DATE'][:8]

print(key)
print((start,end))

# 出力
'test1'
('20220823', '20220823')

といった感じになります。calendar.nameもつけてあげるともっとわかりやすいかもしれませんね。

完成版コード

最後に今回のコードをまとめたものを書いておきます。上のやつと少し違う部分がありますがそこは読み解いてください。

import caldav
import datetime

url = ''
username = ''
password = ''

client = caldav.DAVClient(url=url, username=username, password=password)
principal = client.principal()
calendars = principal.calendars()
    
today = datetime.datetime.now()
schedules = []
for calendar in calendars:
        try:
            events = calendar.date_search(start=today, end=today+datetime.timedelta(days=1), expand=True)
            for event in events:
                d = {}
                for event_data in event.data.splitlines():
                    pair = event_data.split(":")
                    d[pair[0]] = pair[1]
                schedules.append(d)
        except:
            continue

d = {}
for schedule in schedules:
    if 'SUMMARY' in schedule.keys():
        key = schedule['SUMMARY']
    if 'DTSTART' in schedule.keys():
        value1 = schedule['DTSTART'][:8]
    elif 'DTSTART;VALUE=DATE' in schedule.keys():
        value1 = schedule['DTSTART;VALUE=DATE'][:8]
    
    if 'DTEND' in schedule.keys():
        value2 = schedule['DTSTART'][:8]
    elif 'DTEND;VALUE=DATE' in schedule.keys():
        value2 = schedule['DTEND;VALUE=DATE'][:8]
    d[key] = (value1,value2)
print(d)
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?