Pythonを使ってCSVの予定をGoogleカレンダーに載っけてみた

目標：Googleカレンダー⇔CSVファイル⇔別アプリのようにカレンダーと他アプリを連携すること。

その第一段階として、CSVのファイルに記述した予定をGoogleカレンダー上に登録するプログラムを作りました。

#GoogleカレンダーAPIの利用方法
こちらのサイトを参考にさせていただきました。
> 【Python】Google Calendar APIを使ってGoogle Calendarの予定を取得・追加する | 無次元日記
https://non-dimension.com/python-googlecalendarapi/

>Google Calendar APIのReference > Events: insert
https://developers.google.com/calendar/v3/reference/events/insert

##新規イベントを登録するソースコード
上記サイトを参考にしてカレンダーに*"変数eventの予定"*を追加するコードは以下のようになりました。
CID は目的のカレンダーIDを入れてください。

```CalendarAPI.py
from __future__ import print_function
import datetime
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request

# If modifying these scopes, delete the file token.pickle.
SCOPES = ['https://www.googleapis.com/auth/calendar']
CID = 'Your Calendar ID'

def insert(event):
    creds = None
    # The file token.pickle stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    if os.path.exists('token.pickle'):
        with open('token.pickle', 'rb') as token:
            creds = pickle.load(token)
    # If there are no (valid) credentials available, let the user log in.
    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)
        # Save the credentials for the next run
        with open('token.pickle', 'wb') as token:
            pickle.dump(creds, token)

    service = build('calendar', 'v3', credentials=creds)

    # Call the Calendar API
    create_event = service.events().insert(calendarId=CID, body=event).execute()
    return create_event['id']
```
##eventの規格
変数eventの中身はGoogle Calendar APIにわたす中身になっています。上記Googleサイトを確認すると、start(開始時刻),end(終了時刻)が必須でその他様々なオプションがあります。
今回はSummary(イベントタイトル)、location(場所)、description(説明)を一緒に載っけます。

#CSVからeventの生成
ソースコードは以下となります。

```csv2calendar.py
import pandas as pd
import pprint
import datetime
import CalendarAPI
TIMEZONE = 'Japan'

def TransTime(t):
    if len(t) > 11:
        strtime = datetime.datetime.strptime(t, '%Y/%m/%d %H:%M').isoformat()
        dictime = {
            'dateTime': strtime,
            'timeZone': TIMEZONE
        }
    else :
        strtime = datetime.datetime.strptime(t, '%Y/%m/%d').isoformat()
        dictime = {
            'date': strtime[0:10],
            'timeZone': TIMEZONE
        }
    return dictime


csv = pd.read_csv('test.csv')
d = csv.to_dict(orient='index')

for h, num in zip(d.values(), d.keys()):
    h['start'] = TransTime(h['start'])
    h['end']   = TransTime(h['end'])
    del h['id']
    try:
        event_id = insert_calendar.insert(h)
    except:
        print("Error: Can't put id={} on your calendar".format(csv.loc[num, 'id']))
    else:
        print("Put id={} on your calendar as new_id={}".format(csv.loc[num, 'id'], event_id))
        csv.loc[num, 'id'] = event_id

csv.to_csv('test.csv',index=False)
```
CSVは以下のように記述しました。

```test.csv
id,summary,location,description,start,end
1,会議1,会議室1,memo,2020/7/22 20:40,2020/7/23 0:30
2,会議2,会議室2,メモ,2020/7/30 12:00,2020/7/30 13:00
3,終日イベント,家,,2020/7/31,2020/07/31
4,終日イベント2,家,,2020/8/2,2020/8/3 12:00
```

##説明
プログラムの挙動はCSVを読み込みPandas DataFrameを介して辞書型に変換します。
この辞書型それぞれに対し、eventのフォーマットに変換しAPIを通します。

ここで、時刻については少々特殊な書式となっているので、後述する関数を用意しました。

また、CSVにはidの列を用意しています。Googleカレンダーに予定を追加した場合に固有のidが帰ってくるので、それで上書きしています。
そうすることで、本プログラムを今後CSV内の予定変更に対応させたときイベントの識別ができるようにしました。
###関数TransTimeについて
*TransTime*は開始時刻、終了時刻の「年/月/日 時:分」を変換する関数です
送信するeventは以下の書式をとります。

```
{'summary': '会議1',
 'location': '会議室1',
 'description': 'memo',
 'start':  {'dateTime': '2020-07-22T20:40:00', 'timeZone': 'Japan'},
 'end': {'dateTime': '2020-07-23T00:30:00', 'timeZone': 'Japan'},      
},
```
時刻をISOフォーマットに変換します。このときタイムゾーンを設定する必要があります。
また、終日予定の場合は時刻を書かずに以下のように記します。

```
{'summary': '終日イベント',
 'location': '家',
 'description': nan,
 'start': {'date': '2020-07-31', 'timeZone': 'Japan'},
 'end': {'date': '2020-07-31', 'timeZone': 'Japan'}
 }
```
条件分岐はCSV内の時刻が記されているセルの文字数で判別しています。
ちなみに、CSVの４行目の予定はAPIを叩いたときエラーを吐きます（start, endの書式は一致させなければならない）。
#次の目標
CSVの変更に対しGoogleカレンダーを更新する。