IFTTT と GAS で残業時間を google カレンダーに記録する

# 残業が多い
残業時間のログを残すことで~~自分の人生のどれだけが仕事に奪われているのか~~仕事効率化の余地を可視化しようと思います。

# 事前準備
## アプレットの作成
IFTTT で `Android Device` と `Google Sheets` を連携させて、オフィスの WiFi から切断された時刻を google スプレッドシートに記録するようにします。
実際に使用するのはログ中の `OccurredAt` 列のみなので2列目以降は適当です。
![20191062224227.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/501098/f8594a14-8ecd-bbee-b19b-c657613c17da.png)

## カレンダー ID の取得
記録を残すための google カレンダーの ID を取得しておきます。

# GAS 側での処理

## IFTTT の日付フォーマットを整形
IFTTT で日付を記録すると `October 01, 2019 at 12:00PM` のような形式でタイムスタンプ記録されます。利用しやすさのため、これを `yyyy/mm/dd HH:mm` にフォーマットするために下記の関数を作っておきます。

```javascript
// IFTTT のタイムスタンプを yyyy/mm/dd HH:mm 形式に整形する関数
function formatIFTTT(timestamp) {
  var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
  var m = timestamp.match(/([A-Z][a-z]+) (\d{1,2})\, (\d{4}) at (\d{1,2}):(\d{1,2})([AP]M)/);
  var year = m[3];
  var month = Number(months.indexOf(m[1])) + 1;
  month = ("0" + month).slice(-2);
  var date = m[2];
  date = ("0" + date).slice(-2);
  var hour = Number(m[4]);
  if (m[6] == "PM") {
    if (hour != 12) {
      hour = hour + 12;
    }
  }
  else {
    if (hour == 12) {
      hour = 0;
    }
  }
  hour = ("0" + hour).slice(-2);
  var min = Number(m[5]);
  min = ("0" + min).slice(-2);
  return (year + "/" + month + "/" + date + " " + hour + ":" + min)
}
```

## メイン処理
+ ライブラリは [Moment](https://momentjs.com) を使用
+ 定時は18時として計算
+ 時間トリガーで毎日25時に処理実行（それ以降の退社なんて想像したくもない）  
    → 「前日の定時」から「実行時点でのログ最終行」までの時間差分を google カレンダーにイベントとして登録

```javascript
var CALENDAR_ID = PropertiesService.getScriptProperties().getProperty("CALENDAR_ID");
var SHEET_ID = PropertiesService.getScriptProperties().getProperty("SHEET_ID");
var sht = SpreadsheetApp.openById(SHEET_ID).getSheets();

// ログの最終行から残業時間を計算してカレンダーに登録する関数
function getOverWorkTime() {
  // 退社時刻
  var lastLog = sht[0].getRange(sht[0].getLastRow(), 1).getValue();
  var exitTime = Moment.moment(formatIFTTT(lastLog));

  // 現在時刻
  var now = Moment.moment();
  
  // 定時
  var closeTime = now.clone().add(-1, "days").hour(18).minutes(0).second(0);
  
  // 最終退社時刻から定時までの時間
  var overWorkSec = exitTime.diff(closeTime, "seconds");
  
  if (overWorkSec <= 0) {
    // 負の値＝前日の定時以前の退社＝残業なし
    return;
  }

  var seconds = Math.floor(overWorkSec);
  var h = Math.floor(seconds / 3600); // 時間
  var m = Math.floor((seconds % 3600) / 60) + 1; // 分
  var event = "残業（" + h + "時間" + m + "分）";
  
  // カレンダーに登録
  CalendarApp.getCalendarById(CALENDAR_ID).createEvent(event, closeTime.toDate(), exitTime.toDate());
}
```

# 今回の学び
GAS 用の Moment では `.add()` や `.subtract()` を使用すると元のオブジェクト自体が更新されてしまうようです。回避のためには `.clone()` を挟めば良いようです。

# コード全体

<details><summary>全文表示</summary><div>

```javascript
///////////////////////////////////////////////////////
// グローバル変数
///////////////////////////////////////////////////////

var CALENDAR_ID = PropertiesService.getScriptProperties().getProperty("CALENDAR_ID");
var SHEET_ID = PropertiesService.getScriptProperties().getProperty("SHEET_ID");
var sht = SpreadsheetApp.openById(SHEET_ID).getSheets();

///////////////////////////////////////////////////////
// 事前処理
///////////////////////////////////////////////////////

// IFTTTのタイムスタンプをyyyy/mm/dd HH:mm形式に整形する関数
function formatIFTTT(timestamp) {
  var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
  var m = timestamp.match(/([A-Z][a-z]+) (\d{1,2})\, (\d{4}) at (\d{1,2}):(\d{1,2})([AP]M)/);
  var year = m[3];
  var month = Number(months.indexOf(m[1])) + 1;
  month = ("0" + month).slice(-2);
  var date = m[2];
  date = ("0" + date).slice(-2);
  var hour = Number(m[4]);
  if (m[6] == "PM") {
    if (hour != 12) {
      hour = hour + 12;
    }
  }
  else {
    if (hour == 12) {
      hour = 0;
    }
  }
  hour = ("0" + hour).slice(-2);
  var min = Number(m[5]);
  min = ("0" + min).slice(-2);
  return (year + "/" + month + "/" + date + " " + hour + ":" + min)
}

///////////////////////////////////////////////////////
// メイン処理
///////////////////////////////////////////////////////

// ログの最終行から残業時間を計算してカレンダーに登録する関数
function getOverWorkTime() {
  // 退社時刻
  var lastLog = sht[0].getRange(sht[0].getLastRow(), 1).getValue();
  var exitTime = Moment.moment(formatIFTTT(lastLog));

  // 現在時刻
  var now = Moment.moment();
  
  // 定時
  var closeTime = now.clone().add(-1, "days").hour(18).minutes(0).second(0);
  
  // 最終退社時刻から定時までの時間
  var overWorkSec = exitTime.diff(closeTime, "seconds");
  
  if (overWorkSec <= 0) {
    // 負の値＝前日の定時以前の退社＝残業なし
    return;
  }
  
  var seconds = Math.floor(overWorkSec);
  var h = Math.floor(seconds / 3600); // 時間
  var m = Math.floor((seconds % 3600) / 60) + 1; // 分
  var event = "残業（" + h + "時間" + m + "分）";
  
  // カレンダーに登録
  CalendarApp.getCalendarById(CALENDAR_ID).createEvent(event, closeTime.toDate(), exitTime.toDate());
}
```
</div></details>