GASで周期を計算し次のhappeningを予測する話

# 先んじてコードを掲載
GASなどわかる方はコードを、それ以外の方は目次から導入にお飛びください。

```happening.js
function initialFullSync() {
/* イニシアルフルシンクをします */
  var items = Calendar.Events.list('カレンダーID');
  var nextSyncToken = items.nextSyncToken; //ネクストシンクトークンを取得
  var properties = PropertiesService.getScriptProperties();
    properties.setProperty("syncToken", nextSyncToken); //ネクストシンクトークンを保管
}

function initialCal() {
/* 使うものを準備します */
  var spreadSheet = SpreadsheetApp.openById('スプレッドシートID');
  var sheet = spreadSheet.getSheetByName('シート名');
  var lastRow = sheet.getLastRow();
  var calendar = CalendarApp.getCalendarById('カレンダーID');
/* 半年分のデータを取得します */
  var endTime = new Date();
  var startTime = new Date();
    startTime.setMonth(endTime.getMonth()-6);
  var event = calendar.getEvents(startTime,endTime);
/* 取得したデータをセットします */
  for(var i=1; i<event.length+1; i++){
    sheet.getRange('a'+i).setValue(event[i-1].getTitle());
    sheet.getRange('b'+i).setValue(event[i-1].getStartTime());
  }
/* 計算します */
  for(var i=1; i<=lastRow-1; i++){
    start = sheet.getRange(i, 2).getValue();
    end = sheet.getRange(i+1, 2).getValue();
    dur = Math.floor((end-start)/86400000); //差を算出
    sheet.getRange(i+1, 3).setValue(dur);
  }
  sheet.getRange(lastRow, 4).setValue("=AVERAGEA(C2:C"+lastRow+")"); //平均を算出
/* 推定日を登録します */
  var est = new Date(sheet.getRange(lastRow, 2).getValue());
    est.setDate(est.getDate()+sheet.getRange(lastRow, 4).getValue()+1);
    calendar.createAllDayEvent("incidentDay", est);
}

function onCalendarEdit() {
/* 推定日を削除します */
  var calendar = CalendarApp.getCalendarById('カレンダーID');
  var spreadSheet = SpreadsheetApp.openById('スプレッドシートID');
  var sheet = spreadSheet.getSheetByName('シート名');
  var lastRow = sheet.getLastRow();
  var ested = new Date(sheet.getRange(lastRow, 2).getValue());
    ested.setDate(ested.getDate()+sheet.getRange(lastRow, 4).getValue()+1);
    calendar.getEventsForDay(ested);
    calendar.getEventsForDay(ested)[0].deleteEvent();
/* 更新された分を取得します */
  var properties = PropertiesService.getScriptProperties();
  var nextSyncToken = properties.getProperty("syncToken"); //保管されているネクストシンクトークンを取得
  var optArg = {
    syncToken : nextSyncToken
  }; //オプショナルアーギュメント、任意的実引数
/* 更新分をセットします */
  var events = Calendar.Events.list('カレンダーID', optArg);
  var title = events.items[0].summary;
    sheet.getRange(lastRow+1, 1).setValue(title);
  var date = events.items[0].start.date;
    sheet.getRange(lastRow+1, 2).setValue(date);
/* 計算し推定日を登録します */
  sheet.getRange(lastRow+1, 3).setValue((sheet.getRange(lastRow+1, 2).getValue()-sheet.getRange(lastRow, 2).getValue())/86400000); //差を算出
  sheet.getRange(lastRow+1, 4).setValue("=AVERAGEA(C2:C"+(lastRow+1)+")"); //平均を算出
  var est = new Date(sheet.getRange(lastRow+1, 2).getValue());
    est.setDate(est.getDate()+sheet.getRange(lastRow+1, 4).getValue()+1);
    calendar.createAllDayEvent("incidentDay", est); //推定日を登録
/* 次回への布石 */
  var nextSyncToken = events["nextSyncToken"];
    properties.setProperty("syncToken", nextSyncToken); //ネクストシンクトークンを更新
} 
```
# 導入
　GASとはGoogle Apps Scriptの略称である。主にGoogleサービスの利便性を向上させるために使われている。今回はGASとGCalendarとGSpreadSheetを用いる。
## 動機
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">人類　床に紙束を置いてはならぬ　それを踏んでもならぬ　背中を強打したくなければこれらだけは守りなさい</p>&mdash; MM (@mnmtmym) <a href="https://twitter.com/mnmtmym/status/1368454210954661891?ref_src=twsrc%5Etfw">March 7, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
　背中を強打したことでひらめいたから。その数日前に
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">危機管理というかモノの扱いが幼児並み……？　先週は木の板を割るというか折るというかしましたし。この間は腕時計を机の角にぶつけて割りました()()</p>&mdash; MM (@mnmtmym) <a href="https://twitter.com/mnmtmym/status/1367732684017991681?ref_src=twsrc%5Etfw">March 5, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

とも呟いていた。これらから、タイトルの通りGASでhappeningの周期を計算し、次のhappeningがいつ起こるのかを予測することを考え付いた。
## 概要
　Calendarからhappeningを取得 -> GAS -> SpreadSheetにhappeningを書き込む -> GASも用いて周期を計算する -> 計算結果から推定日を予定としてCalendarに書き込む -> Calendarの変更をtriggerにし、推定日を更新
## 手順
　今回はGASだけでなくGCPやAPIも用いるため、念入りな準備が必要である。覚書も兼ねて画像多めで説明する。
### GCP

https://console.cloud.google.com/home/

上記リンクを踏むとリダイレクトされて、GCPを使ったことのない人であれば![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/641abf02-6ada-702a-e76f-290c905ea662.png)といった画面が表示されるはずだ。規約等読んで、良ければ同意して続行する。![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/231205b5-09ed-f373-bb19-eaaa309485a8.png)矢印のどちらかからプロジェクトを作成する。![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/3706e8a0-83d7-8a00-04ae-88a931b7b6fb.png)プロジェクト名はお好きにどうぞ。今回は"happening"とした。場所は個人であれば「組織なし」のままでよい。そのまま作成する。ダッシュボードに遷移するだろう。![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/4f51fb6c-ff88-2ba4-e93d-56a04ba2b0a3.png)左上、ナビゲーションメニュー🍔 > APIとサービス > ライブラリで![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/0a01f1ba-9aa9-21ad-9dc2-17ec7c193f89.png)並んだAPIのなかからGoogle Calendar APIを選択する。![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/5765ea4f-e514-596e-6df8-3bbe65b3949e.png)詳細を読んで、良ければ有効にする。![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/78c8674e-cf7b-f90e-2c5d-4f78cd8dd9cd.png)APIとサービス > OAuth同意画面で個人ならば外部を選択し作成する。![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/d2dda3b0-8f7a-618c-9240-cfb282931de5.png)アプリ情報はお好きにどうぞ。アプリ名とメールアドレスは必須。![9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/16d72b52-5244-f044-ce18-ca25619393a1.png)基本的には現在ログインしているGoogleアカウントのGMailアドレスを想定しているため、それ以外を記入したことによって何が起こるかはわからない。![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/e6b1200b-cfd8-da49-b01a-ebfceb549a84.png)スコープを追加する。![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/d88adb31-a4aa-ef1d-732a-a0e7422cff80.png)右に表示される一覧からGoogle Calendar API、そのなかでも.../auth/calendar.eventsの「すべてのカレンダーの予定の表示と編集」を選択する。![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/b559d86d-9c4f-e6c5-e80e-c16c90e49dd8.png)機密性の高いスコープとして表示されていれば良い。保存して次へ。![13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/8129d612-af82-9a41-a1d2-815d28597850.png)テストユーザは自分を追加しておく。追加しないのであれば後で本番環境に設定しておく。![15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/b8f8c1b9-7316-f9ef-3b15-04da35d10b71.png)ダッシュボードに戻る。![16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/80d0232f-9400-f0d2-65ee-aa1870c18ffa.png)
プロジェクト情報あるいはプロジェクト設定から「プロジェクト番号」をメモしておく。
### GAS

https://drive.google.com/drive/

Google Driveを開く。![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/ca6ad677-53a2-5fd2-e8af-b8770368691c.png)新規 > その他 > Google Apps Scriptで新規作成。![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/c551e759-21f0-aa90-d20d-1a3c0a98696d.png)このような画面が開けばそれで良い。![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/fc049246-558c-af2d-2aa5-2bc240a2f26c.png)必要であれば名前を変更する。今回は"happening"とした。左側からプロジェクトの設定を選択する。![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/25c5f7ce-c3ed-b13b-2051-362a841968a4.png)プロジェクトを変更する。![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/c8e73b91-428a-bf1c-e759-18ded9fc6bc0.png)ここに先程のプロジェクト番号をコピペしプロジェクトを設定する。![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/c797f6e4-886c-b40f-b68d-76e62bc8b122.png)標準の文字の下に番号があれば良い。![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/e1e251a6-a8ad-3285-9d27-a394bcba2fa6.png)左側からエディタに戻り、サービスを追加する。![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/3e58cd6c-7408-f861-e33a-267be4fe7639.png)一覧からGoogle Calendar APIを選択する。バージョンとIDは事情がなければこのままで良い。
### Calendar

https://calendar.google.com/calendar/u/0/r

Google Calendarを開く。![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/1a5cfca8-c344-3eea-73b8-708fc42443f6.png)右上の設定を開く。![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/7e937264-416b-b574-1510-2d408540c520.png)設定 > カレンダーを追加 > 新しいカレンダーを作成でカレンダーを新規作成する。名前と説明はお好きにどうぞ。今回は"happening"とした。カレンダーにhappenedを書き込んでおく。![13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/9e6e6ae9-a41d-4dbb-a09d-b869478a75ba.png)マイカレンダーの設定 > [新規作成したカレンダー] > カレンダーの設定から下にスクロールしてカレンダーIDを探す。アットマーク以降も全て含めてメモしておく。
### SpreadSheet

https://docs.google.com/spreadsheets/u/0/

Google SpreadSheetを開く。![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/714a0fd1-db61-3ebc-5f34-a3fce70d8c90.png)新しいスプレッドシートを作成する。事情がなければ「空白」を選択する。![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/ae0ebcc3-849d-f020-3873-6af588f252b4.png)必要であれば名前を変更する。今回は"happening"とした。スプレッドシートのURLからスプレッドシートIDを抜き出しメモしておく。.../spreadsheets/d/"ここがIDになります"/edit#gid=0
### コーディング
GASのエディタを開き、前掲のコードをコピペする。スプレッドシートID、シート名、カレンダーIDをメモしたものに書き換える。initialFullSyncを実行すると![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/4e1a5cef-8bc5-2694-b023-c939d55d0214.png)と表示されるため権限を確認し、良ければ承認する。ここで何かしらのエラーが発生した場合、GCPでプロジェクトを本番環境に設定するかカレンダーをオープンアクセスにすることで解決する可能性が高い。initialFullSyncが実行完了されたらinitialCalも実行する。完了し成功していれば![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/76386d91-68b7-f9ac-d992-f16541e9634d.png)![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/9d55690a-f159-1474-4f77-b7b4e5f2aca6.png)こうなるはずである。GASに戻り、左側からトリガーを選択する。![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/7a3ff70f-ec61-d517-9e23-fb45e8d3cb03.png)矢印のどちらかからトリガーを作成する。![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405407/3d8ae914-1f93-7383-75ef-d1a47c35a664.png)onCalendarEditを選択し、カレンダーからカレンダー更新済みを選択する。オーナーにカレンダーIDをコピペしトリガーを追加する。
# 結果
　次のhappeningは3月23日と推定された。気をつけて過ごそうと思う。
# 参考

- https://for-dummies.net/gas-noobs/how-to-use-oncalendar-change-triggers-for-gas/
- https://qiita.com/cazimayaa/items/5fdfbc060dff7a11ee15
- https://qiita.com/cazimayaa/items/6af9dd1fd1228c93cf89
- https://developers.google.com/calendar/v3/sync
- https://developers.google.com/apps-script/advanced/calendar
- https://developers.google.com/apps-script/reference