Toggleの記録をgoogleカレンダーに自動で記録する。

toggleはGoogleカレンダーに登録したスケジュールを確認する機能はありますが、toggle上で記録したタイムエントリをそのままGoogleカレンダーに登録する機能はありません。  
そこで、Google Apps Script(GAS)を活用し、定期的にtoggleのタイムエントリ情報をGoogleカレンダーへ自動登録するスクリプトを紹介します。  

この記事では、トリガーを設定することで一定時間ごとに自動で書き込むようにし、Googleカレンダーから時間管理を振り返りやすくする流れをまとめました。


## 1. 目的
- **Google Apps Script**を使って、toggleの記録を自動的にGoogleカレンダーへ反映させる。
- toggleの「記録」とGoogleカレンダーを同期させることで、管理の手間を削減する。

## 2. 必要な情報の取得
このスクリプトを動かすためには、以下の3つを事前に取得しておく必要があります。

1. **toggle API**  
2. **google calendar ID**  
3. **toggle workspace id**  

それぞれの確認方法を下記に示します。

### 2-1. toggle API
1. toggleを開き、上部メニューの **[Profile]** → **[Profile settings]** をクリック。  
2. 画面下部にある **API Token** を確認し、控えておきます。  

![スクリーンショット 2025-01-07 21.04.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627571/e976be3d-6da7-488b-00c8-ba2777e33480.png)  

*参考: [https://note.com/umu11/n/ncf5ab7d644e1]*

### 2-2. google calendar ID
1. Googleカレンダーを開き、登録先にしたいカレンダーの **[設定と共有]** をクリック。  
2. **[カレンダーの統合]** という項目の **カレンダーID** を確認します。  

> （個人的には「行動記録」用など、新しいカレンダーを作成しておくほうが後々管理しやすいです）

![スクリーンショット 2025-01-07 21.07.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627571/b12321fe-fbdd-977d-792b-52135ac85a44.png)

### 2-3. toggle workspace id
1. toggleで上部メニューの **[Analysis]** タブを開きます。  
2. [Analysis] 画面のURLを確認すると、下記のようになっているはずです。 
```
https://track.toggl.com/analytics/XXXXXX/...
```
この `XXXXXX` の部分（7桁の数字）が **toggle workspace id** になります。


## 3. Google Apps Scriptの用意
Google Apps Scriptを起動して以下のコードをコピペします。  
先ほど取得した **toggle API**、**google calendar ID**、**toggle workspace id** を、それぞれ適切な値に置き換えてください。

```javascript
var CACHE_KEY = 'toggl_exporter:lastmodify_datetime';
var TIME_OFFSET = 9 * 60 * 60; // JST。必要に応じて使います
var TOGGL_API_HOSTNAME = 'https://api.track.toggl.com/api/v9';
var TOGGL_API_TOKEN = 'XXXX(toggle API):api_token';
var GOOGLE_CALENDAR_ID = 'XXXX(google calender ID)';
var WORKSPACE_ID = 'XXXX(toggle workspace id)'; 
var TIME_SPAN_HOURS = 12; 
var TOGGL_API_HOSTNAME = 'https://api.track.toggl.com/api/v9';

// 認証情報のヘッダ (Basic認証)
var HEADERS = {
  "Authorization": "Basic " + Utilities.base64Encode(TOGGL_API_TOKEN),
  "Content-Type": "application/json"
};

/**
 * 過去 TIME_SPAN_HOURS 時間分のエントリーを取得し、Google カレンダーへ書き込むメイン処理
 * 時間ベーストリガーを「12時間おき」に設定すると、定期的に実行されます。
 */
function watch() {
  try {
    Logger.log("Starting watch function...");

    // 現在時刻と、そのTIME_SPAN_HOURS時間前の時刻をISO8601形式で取得
    var endDate = new Date();
    var endISO  = endDate.toISOString();
    var startDate = new Date(endDate.getTime() - TIME_SPAN_HOURS * 60 * 60 * 1000);
    var startISO  = startDate.toISOString();

    Logger.log("Fetching Toggl entries from " + startISO + " to " + endISO);

    // toggleから指定時間範囲のタイムエントリを取得
    var timeEntries = getTimeEntriesWithinRange(startISO, endISO);
    if (!timeEntries || timeEntries.length === 0) {
      Logger.log("No Toggl entries found in the last " + TIME_SPAN_HOURS + " hours.");
      return;
    }

    // Googleカレンダーを取得
    var calendar = CalendarApp.getCalendarById(GOOGLE_CALENDAR_ID);
    if (!calendar) {
      throw new Error("Calendar not found with ID: " + GOOGLE_CALENDAR_ID);
    }
    // カレンダーのタイムゾーンを適宜調整
    calendar.setTimeZone("Asia/Tokyo");

    // 取得したタイムエントリを、Googleカレンダーのイベントとして登録
    for (var i = 0; i < timeEntries.length; i++) {
      var entry = timeEntries[i];

      // stop が存在しない（まだタイマーが動いている等）の場合はスキップ
      if (!entry.stop) {
        Logger.log("Skipping entry with no stop time: " + JSON.stringify(entry));
        continue;
      }

      // イベントタイトル
      var title = entry.description ? entry.description : "No description";

      // 開始時刻、終了時刻
      var startTime = new Date(entry.start);
      var endTime   = new Date(entry.stop);

      // プロジェクトIDやタグの情報
      var projectId = entry.pid ? ("Project ID: " + entry.pid) : "No Project ID";
      var tags      = (entry.tags && entry.tags.length > 0) ? entry.tags.join(", ") : "No tags";

      // カレンダーイベントのdescriptionフィールド
      var options = {
        description: projectId + "\nTags: " + tags
      };

      // カレンダーイベントを作成
      var newEvent = calendar.createEvent(title, startTime, endTime, options);
      Logger.log("Created event: " + newEvent.getTitle()
                 + " (" + startTime.toString() + " - " + endTime.toString() + ")");
    }

    Logger.log("Watch function completed successfully.");
  } catch (e) {
    Logger.log("Error in watch function: " + e.toString());
  }
}

/**
 * startISO～endISO (ISO8601形式) の範囲でTogglのタイムエントリーを取得し、配列で返す
 */
function getTimeEntriesWithinRange(startISO, endISO) {
  var uri = TOGGL_API_HOSTNAME
          + "/me/time_entries"
          + "?start_date=" + encodeURIComponent(start)
          + "&end_date="   + encodeURIComponent(endISO);

  Logger.log("Request URI: " + uri);

  var response = UrlFetchApp.fetch(uri, {
    method: 'GET',
    headers: HEADERS,
    muteHttpExceptions: true
  });

  var code = response.getResponseCode();
  Logger.log("Time Entries Response Code: " + code);
  if (code !== 200) {
    throw new Error("Failed to fetch Toggl entries. Code=" + code);
  }

  var content = response.getContentText();
  Logger.log(
    "Time Entries Response (first 500 chars): " 
    + content.substring(0, Math.min(content.length, 500))
  );

  // JSONパースして結果（配列）を返す
  return JSON.parse(content);
}
```

## 4.トリガーの設定  
コードを保存したのち、[トリガー] 画面を開きます。  
- 関数: **watch**  
- イベントの種類: **時間主導型**  
- 時間ベースのタイマー: **12時間ごと** などを選択  

こうすることで、定期的にこのスクリプトが実行され、toggle上の記録が自動でGoogleカレンダーへ書き込まれるようになります。  
（実行間隔は用途に応じて調整してください）
> （個人的にはそこまで頻繁に読み込む必要はないと判断しているので12時間おきです。朝と夜に一回ずつタスクの確認をするイメージ。）


## 5.実行・確認  
すべての設定が完了したら、まずは手動でスクリプトを一度実行し、エラーが出ないか確認します。  
Googleカレンダーにイベントが作成されることを確認したら完了です。  
トリガーを設定している場合は、定期的に自動で登録されるようになります。  
toggleに記録を入れたら、自動的にGoogleカレンダーの対象カレンダーにイベントが追加されていれば成功です。  
これで、toggleでの時間管理とGoogleカレンダーでのスケジュール管理の両方をスムーズに連携させることができます。


以上の手順で、toggleとGoogleカレンダーの連携を自動化できます。
「記録」はtoggleで、全体の可視化はGoogleカレンダーで、といった使い分けが可能になるので、時間管理の効率がぐっと向上するはずです。ぜひ活用してみてください。

## 参考
https://note.com/umu11/n/ncf5ab7d644e1

ありがとうChatGPTo1