年始ということでおおまかな目標をカレンダーに記しておきたいという人も多いのではないだろうか。おろしたての新手帳やカレンダーにペンで目標計画を入れるには最適な時期だ。デジタルでも同様の所作を手軽にできないだろうか?そう思った。Googleカレンダーをはじめ、デジタルカレンダー機能を持つサービスはクラウドで連動するので、一度PCで書き込めば、それがスマホやタブレットでも確認できるようになる。

PCからのカレンダー入力をさらに手早く行えれば、こまめなスケジューリング登録も億劫(おっくう)にならない。正月の期間、飽くなき利便性にチャレンジしてみようとクリップボードからカレンダーを入力できないかを考えた。

テキストエディターなどのコピー&ペーストで頻繁に使用するクリップボードには直前にコピーした文字列が格納されている。これを抜き出して、カレンダーサービスに文字列を渡せれば、わざわざブラウザを立ち上げなくてもカレンダー入力できる利便性がある。

GoogleカレンダーAPIをPythonから制御する

Googleカレンダーを取り扱うには、こちらの連載(ゼロからはじめるPython Googleカレンダーの予定を読み込んでカウントダウンしよう(上)カレンダー編 )を参考にPythonを使う。現在ではGoogle APIの仕様が異なっているが、import文にfrom google.oauth2 import service_accountを加えて、credentials = service_account.Credentials.from_service_account_file( CREDENTIALS_PATH, scopes=SCOPES)に変更すると読み込める。

import datetime
from googleapiclient.discovery import build
from google.oauth2 import service_account

# クレデンシャルファイルのパス
CREDENTIALS_PATH = "credentials.json"
# カレンダーID
CALENDAR_ID = "●●●●ad8807a52@group.calendar.google.com"
# スコープ
SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"]

# クレデンシャルの読み込み
credentials = service_account.Credentials.from_service_account_file(
    CREDENTIALS_PATH, scopes=SCOPES
)

# APIクライアント作成
service = build("calendar", "v3", credentials=credentials)

# 期間指定
now = datetime.datetime.utcnow()
start_date = now.isoformat() + "Z"
end_date = (now + datetime.timedelta(days=30)).isoformat() + "Z"

# イベント取得
events_result = service.events().list(
    calendarId=CALENDAR_ID,
    timeMin=start_date,
    timeMax=end_date,
    singleEvents=True,
    maxResults=30,
    orderBy="startTime",
).execute()
events = events_result.get("items", [])

# 表示
if not events:
    print("イベントは見つかりませんでした。")
else:
    print("イベント一覧:")
    for event in events:
        target_date = event["start"].get("dateTime", event["start"].get("date"))
        if "T" in target_date:
            target_date = target_date[:10]
        today = datetime.datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
        target_td = datetime.datetime.strptime(target_date, "%Y-%m-%d")
        days = (target_td - today).days
        print(f"{days:2}日 - {event['summary']}")
  • PowerShellに読み込まれるカレンダー日付からの逆算日時表示。なお、now = datetime.datetime.utcnow()が仕様変更により非推奨となるメッセージが出ているが、now = datetime.now(UTC)に変えて+ "Z"の2カ所を削除すると表示されなくなる。

    PowerShellに読み込まれるカレンダー日付からの逆算日時表示。なお、now = datetime.datetime.utcnow()が仕様変更により非推奨となるメッセージが出ているが、now = datetime.now(UTC)に変えて+ "Z"の2カ所を削除すると表示されなくなる。

実用度が高いと思ったスクリプトはエイリアスで呼び出しやすくしておこう

カレンダーをコンソールから読み込めるのも非常に便利だ。実用度が高いと思ったスクリプトはあとから忘れないようショートカットやコマンド化してしまうのが良い。ここではPowerShellのエイリアスとして登録してコマンドのように扱えるようにしてしまおう。PowerShellで

notepad $PROFILE

と入力すると開くプロフィールファイルに、自身が設置している場所を追記して上書き保存する。

function goocal {
    python "C:\Users\user\OneDrive\ドキュメント\script\n111.py"
}

これでコンソールからエイリアス名"goocal"と入力するけでPythonスクリプトが読み込まれる。ただしPythonファイルのCREDENTIALS_PATH = "credentials.json"の部分を相対パスから絶対パスへと変えておかないとcredentials.jsonの読み込みエラーが出る場合がある。

"goocal"と入力するだけでカレンダー逆算表示。独自のコマンドのように振る舞うので、頻繁に使うスクリプトに命名しておけばコマンド活用も進む。

カレンダー入力用にカスタマイズする

次に、PythonスクリプトをGoogleカレンダー入力用にカスタマイズする。スコープの範囲をcalendar.readonlyからcalendarに変更し、書き込み可能に変えて、service.events().insert(calendarId=CALENDAR_ID, body=event).execute()を加えることで、読み取りではなく書き込みが可能になる。ポイントはクリップボードの扱いになるが、タイトル以外に日時を渡すには、どうしても数字が必要だ。あらかじめ、01100700ツーリング(1月10日7時ツーリング)のように、テキストエディタに入力してからこれをコピーする。前の8桁で月日時、残り文字列をタイトルに入れようというわけだ。

01100700ツーリング

Pythonからクリップボードのやりとりを行えるpyperclipライブラリをimport文(pip install pyperclip)に加えて、Pythonのスライスで日時とタイトルを切り分けてカレンダーの仕様に格納する。

import datetime
import pyperclip
from googleapiclient.discovery import build
from google.oauth2 import service_account

CREDENTIALS_PATH = "credentials.json"
CALENDAR_ID = "●●●●●@gmail.com"
SCOPES = ["https://www.googleapis.com/auth/calendar"]

credentials = service_account.Credentials.from_service_account_file(
    CREDENTIALS_PATH, scopes=SCOPES
)
service = build("calendar", "v3", credentials=credentials)

# クリップボードから文字列取得
clip_text = pyperclip.paste().strip()

# Pythonのスライス構文で先頭8文字を日時、残りをタイトルとする 例01100700ツーリング
date_str = clip_text[:8]
title = clip_text[8:].strip()

# MMDDHHMM を分解
month = int(date_str[0:2])
day   = int(date_str[2:4])
hour  = int(date_str[4:6])
minute= int(date_str[6:8])

year = datetime.date.today().year

start_dt = datetime.datetime(year, month, day, hour, minute)
end_dt   = start_dt + datetime.timedelta(hours=1)

event = {
    "summary": title,
    "start": {"dateTime": start_dt.isoformat(), "timeZone": "Asia/Tokyo"},
    "end":   {"dateTime": end_dt.isoformat(), "timeZone": "Asia/Tokyo"},
}

created_event = service.events().insert(calendarId=CALENDAR_ID, body=event).execute()
print(f"イベントを作成しました: {created_event.get('htmlLink')}")
  • テキストエディターでクリップボードにコピーして、Pythonコードを実行すると

    テキストエディターでクリップボードにコピーして、Pythonコードを実行すると

  • 無事にカレンダーに入力された

    無事にカレンダーに入力された

カレンダー入力スクリプトを秀丸エディターのマクロから呼び出す

クリップボードにデータがある状態でPythonスクリプトを実行すればいいのだが、コンソール(PowerShell)を立ち上げ、実行するのも面倒。ここでは、エディターのマクロと組み合わせる。秀丸マクロのcopy;は、選択状態にしておけば実行時にクリップボードに入れてくれるので、これもひと手間省ける。あとはpythonファイルをrunで実行する。さきほど同様にPythonの方もクレデンシャルファイルのパスCREDENTIALS_PATH = "credentials.json"を絶対パスに直しておく。

秀丸マクロ

copy;
run "powershell -NoExit -NoProfile -Command python C:\\Users\\user\\OneDrive\\デスクトップ\\Calendar\\orijinal.py";

実際に動作させてみよう。現在は、熊リスクでかなり危険であるアウトドアだが、1月終盤には奥多摩から八王子方面にフルフェイスヘルメット&ボディプロテクターに鉄製のスティックなど対熊装備にもなるアイテムを装着の上でツーリングで下見。渓流での釣りが解禁される2月末ごろには年間遊漁券購入の上、場所を厳選して敢行してみたいものだ。

エディターに8桁日付と予定文字列を即座に打ち込む。

01200800装備の上でツーリング
02280800漁業権購入、渓流釣り敢行

そして1行ずつ選択してはマクロを実行する。マクロにはショートカットを割り振っておくと便利だ。

  • 選択状態にしてマクロを実行すると

    選択状態にしてマクロを実行すると

  • カレンダーに登録される

    カレンダーに登録される

実際に試してみたところ、カレンダーに登録されると各端末のカレンダー表示やスマートウォッチにも登録されるため、入力時の手順の効率化の効果は大きい。不思議とスケジュールを入力する楽しみも増したような気がするのであった。