62
67

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 3 years have passed since last update.

Pythonで明日ランニングするかどうかを自動で判断し、GoogleCalendarに追加するシステムを作った。

Last updated at Posted at 2020-02-29

はじめに

平日は基本的に、毎日ランニングをしているのですが、当然ながら雨の日や、休日に走る気はありません。
そこで、次の日走るかどうかの判断を自動化し、GoogleCalendarに予定を追加するプログラムをPythonで書いてみました。

環境

  • macOS, Linux
  • python 3.8.1

定期実行するのに crontab を使っているため、macOSかLinuxを想定していますが、WindowsではAWSなどを使えば、同じようなことができるかもしれません。

Pythonの設定

まず、Pythonを設定します。個人的には、環境を汚すのが好きではないので、プロジェクト毎に virtualenv を作っています。本題とはずれるので、詳しくはこちらを参考にしてください。

$ pyenv virtualenv 3.8.1 develop_3.8.1
$ pyenv activate develop_3.8.1
$ pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib

明日の天気を判定する

天気予報の取得には、OpenWeatherMapを使用しました。

使い方は至ってシンプルで、アカウントを作成したあとの「API keys」というタブからAPIキーを取得し、OPENWEATHER_API_KEY にセットします。
ちなみに、今回は場所の指定に郵便番号を使いましたが、都市名や、緯度経度でも指定することができるようです

import requests

BASE_URL = 'http://api.openweathermap.org/data/2.5/forecast'
OPENWEATHER_API_KEY = 'Your OpenWeather Api key'
ZIP_CODE = 'Zip code of your area'

url = '{}?units=metric&zip={zip},JP&APPID={key}'.format(BASE_URL, zip=ZIP_CODE, key=OPENWEATHER_API_KEY)

res = requests.get(url).json()

すると、このような結果が返ってきます。

{
    "cod": "200",
    "message": 0,
    "cnt": 40,
    "list": [
        {
            "dt": 150000000,
            "main": {
                "temp": 13.03,
                "feels_like": 9.94,
                "temp_min": 12.61,
                "temp_max": 13.03,
                "pressure": 1016,
                "sea_level": 1016,
                "grnd_level": 1010,
                "humidity": 40,
                "temp_kf": 0.42
            },
            "weather": [
                {
                    "id": 803,
                    "main": "Clouds",
                    "description": "broken clouds",
                    "icon": "04d"
                }
            ],
            "clouds": {
                "all": 60
            },
            "wind": {
                "speed": 1.52,
                "deg": 150
            },
            "sys": {
                "pod": "d"
            },
            "dt_txt": "2020-02-29 06:00:00"
        },
        ...

結果は、5日後までの3時間毎の結果が返ってきています。
list の配列には、 dt_txt の日時の天気予報が入っており、

  • temp: 温度
  • feels_like: 体感温度
  • temp_min: 最低気温
  • temp_max: 最高気温
  • pressure: 大気圧
  • sea_level: 海面の大気圧
  • grnd_level: 地上の大気圧
  • humidity: 湿度

などが取れるようです。情報量が多く、素晴らしいですね。しかし、今回は雨かどうか知りたいだけなので、 weathermain に着目したいと思います。

import datetime

tommorow = datetime.datetime.now() + datetime.timedelta(days=1)
tommorow_str = tommorow.strftime('%Y-%m-%d')

def is_rain(tommorow_str):
    tommorow_morning_dt = [tommorow_str + ' 06:00:00', tommorow_str + ' 09:00:00']
    tommorow_morning_weather = []

    weather_preds = res['list']

    for pred in weather_preds:
        if pred['dt_txt'] in tommorow_morning_dt:
            for weather in pred['weather']:
                tommorow_morning_weather.append(weather['main'])

    return 'Rain' in tommorow_morning_weather

まず、datetime を用いて、明日の日付を計算します。
そして、ランニングするのは早朝なので、明日の午前6時ごろと、午前9時ごろの日付を文字列として tommorow_morning_dt に入れておきます。そして先ほどの結果と比較し、 dt_txt が目的の日付であれば、 tommorow_morning_weather に追加しています。

これで明日のランニングをする時間帯の天気を取得することができました。しかし、この実装は正直微妙だと思っていて、日付を文字列で比較するのはナンセンスな気がするので、いい方法があったら教えてください。

休日かどうかを判断する

基本的に、平日以外は走る気がないので、休日は除外したいです。単に休日を除外するだけであれば話は簡単なのですが、祝日についても考慮しなければならないので、こちらを参考に、 jpholiday というパッケージを使いました。先ほど作った雨かどうかを判定する is_rain() も使用することで、ランニングをするときの条件を表すことができます。
ちなみに、 datetimeweekday() という関数は、月〜金が 0~4 に対応しており、土日は 5,6 に対応しています。

import jpholiday

if tommorow.weekday() < 5 and not jpholiday.is_holiday(tommorow) and not is_rain(tommorow_str):
    # Google Calendarにイベントを追加
    add_event(tommorow_str)

Google Calendarにイベントを追加する

最後に、Google Calendarにイベントを追加します。Google Calendar APIを有効化する方法は、こちらにわかりやすくまとまっているので、参考にしてください。詳細はリンク先に任せるとして、簡潔にAPIを使うまでの手順を説明します。

  1. Python Quickstart の「Enable the Google Calendar API」というボタンを押す。
  2. ポップアップが表示されたあと、「DOWNLOAD CLIENT CONFIGURATION」というボタンを押し、 credentials.json をダウンロードする。
  3. credentials.json をプロジェクトと同じディレクトリに移動する。

とりあえずはこれで、APIを使う準備が整いました。

次に、追加したいカレンダーのIDを確認します。IDの確認方法も、先ほどのリンクで詳しく説明されているので、簡潔にフローを示します。

  1. Google Calendarを開く。
  2. 左のサイドバーの「マイカレンダー」のオーバーフローメニューから、追加したいカレンダーの「設定と共有」ボタンを押す。
  3. 「カレンダーの結合」の一番上の項目に「カレンダーID」が表示されているので、それをメモしておく。

確認したカレンダーIDを CALENDAR_ID にセットすることで、ようやくイベントの追加を行うことができます。

import os.path
import pickle

from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request

CALENDAR_ID = 'Your Google Calendar ID'
DIR = os.path.dirname(os.path.abspath(__file__))

def add_event(tommorow):
    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.
    token_pickle_path = os.path.join(DIR, 'token.pickle')
    credentials_path = os.path.join(DIR, 'credentials.json')

    if os.path.exists(token_pickle_path):
        with open(token_pickle_path, '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_path, SCOPES)
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run
        with open(token_pickle_path, 'wb') as token:
            pickle.dump(creds, token)

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

    # Call the Calndar API
    event = {
        'summary': 'ランニング',
        'location': 'Your running location',
        'start': {
            'dateTime': '{}T07:30:00'.format(tommorow),
            'timeZone': 'Japan',
        },
        'end': {
            'dateTime': '{}T09:00:00'.format(tommorow),
            'timeZone': 'Japan',
        },
    }

    service.events().insert(calendarId=CALENDAR_ID, body=event).execute()

前半のコードに関しては、公式のサンプル からとってきたものなので、特に手を加える必要はなさそうです。あとは、 eventsummary に追加したいイベントのタイトルを、 startenddateTime にそれぞれ開始時間、終了時間を入れれば完成です。

完成コード

add_calendar.py
import datetime
import os.path
import pickle
import requests

import jpholiday
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']

# 任意の値を設定してください。
CALENDAR_ID = 'Your Google Calendar ID'
OPENWEATHER_API_KEY = 'You Openweather API key'
ZIP_CODE = 'Zip code of your area'
DIR = os.path.dirname(os.path.abspath(__file__))


def add_event(tommorow):
    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.
    token_pickle_path = os.path.join(DIR, 'token.pickle')
    credentials_path = os.path.join(DIR, 'credentials.json')

    if os.path.exists(token_pickle_path):
        with open(token_pickle_path, '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_path, SCOPES)
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run
        with open(token_pickle_path, 'wb') as token:
            pickle.dump(creds, token)

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

    event = {
        'summary': 'ランニング',
        'location': 'Your running location',
        'start': {
            'dateTime': '{}T07:30:00'.format(tommorow),
            'timeZone': 'Japan',
        },
        'end': {
            'dateTime': '{}T09:00:00'.format(tommorow),
            'timeZone': 'Japan',
        },
    }

    service.events().insert(calendarId=CALENDAR_ID, body=event).execute()


def is_rain(tommorow):
    BASE_URL = 'http://api.openweathermap.org/data/2.5/forecast'
    url = '{}?units=metric&zip={zip},JP&APPID={key}'.format(BASE_URL, zip=ZIP_CODE, key=OPENWEATHER_API_KEY)

    res = requests.get(url).json()
    weather_preds = res['list']

    tommow_morning_dt = [tommorow + ' 06:00:00', tommorow + ' 09:00:00']
    tommow_morning_weather = []

    for pred in weather_preds:
        if pred['dt_txt'] in tommow_morning_dt:
            for weather in pred['weather']:
                tommow_morning_weather.append(weather['main'])

    return 'Rain' in tommow_morning_weather


def main():
    tommorow = datetime.datetime.now() + datetime.timedelta(days=1)
    tommorow_str = tommorow.strftime('%Y-%m-%d')

    # weekday: 0 ~ 4
    if tommorow.weekday() < 5 and not jpholiday.is_holiday(tommorow) and not is_rain(tommorow_str):
        add_event(tommorow_str)


if __name__ == '__main__':
    main()

なお、このコードを

$ python add_calendar.py

で実行した際に、1度目に警告画面が表示されますが、問題ないので、

  1. アカウントを選択
  2. 「詳細」リンクをクリック
  3. 「Quickstart(安全ではないページ)に移動」リンクをクリック
  4. 「許可」ボタンをクリック
  5. 「許可」ボタンをクリック

の手順で進めてください。2度目以降はこの操作を行う必要はありません。

ここまでで、明日の天気が雨でなく、平日であることを確認した上で、ランニングの予定を追加するスクリプトが完成したので、これを自動化したいと思います。

イベント追加の定期実行

定期実行については、macOSとLinuxにデフォルトで搭載されている crontab を使えば簡単に行うことができます。詳しく知りたい方は、こちら を参考にしてください。まず、

$ crontab -e

crontab を立ち上げます。そして、このように書きます。

00 18 * * 0-4 [pythonのパス] [プロジェクトのディレクトリ]/add_calendar.py

pythonのパスは

$ which python

で確認することができます。

00 18 * * 0-4 というのは、日曜日〜木曜日の18時に実行するという意味です。左から順に、「分」、「時」、「日」、「月」、「曜日」を表しています。プログラム中で平日かどうかを判断しているため、曜日指定は必要ない気もしますが、少しでも無駄な処理を減らすために入れておきました。

まとめ

今回はランニングを追加する目的でスクリプトを書きましたが、条件を判断してカレンダーに予定を追加したいということは、割と頻繁にあると思うので、是非活用してみてください。

参考

pyenv と pyenv-virtualenv で環境構築

datetimeとjpholidayを組み合わせて、平日か土日祝日かを判定するスクリプト

pythonを使ってOpenWeatherMapから天気情報を取得

無料天気予報APIのOpenWeatherMapを使ってみる

PythonでGoogleカレンダーに予定を追加する

Google Calendar APIを使ってGoogle Calendarの予定を取得・追加する

crontabの書き方

62
67
2

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
62
67

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?