はじめに
日々の業務をToggl Trackで記録しているけれど、
日報を書くのが面倒・集計が手間 と感じていませんか?
本記事では、Google Apps Scriptを使って
「自動でToggleからスプレッドシートに日報を出力する方法」 を紹介します。
業務時間のチェックや日報作成の時短に、ぜひ活用してください!
目次
-
- スプレッドシートにボタンを作成してスクリプト割り当て
- TogglのAPIトークンを取得
- GASにAPIキーを設定
-
各処理の解説
- 1. 定数/変数の定義
- 2. メイン処理:outputTogglTasks()
- 3. API取得処理:getTogglApiResponse()
- 4. 工数集計処理:calculateTotalHours()
- 5. 警告表示:judgmentWarning()
- 6. 出力:outputDailyReport()
- 7. 出力補助:setCellValue()
- 8. 午前・終日の分岐:isNowNoonTime()
設計概要
- Toggl APIを使って当日の作業時間を取得
- 合計時間とタスク一覧を自動計算
- 午前・終日のフォーマットを自動切り替え
- 所定時間外なら合計時間を赤字に
- スプレッドシートに日報として出力
<完成図>
事前準備
スプレッドシートにボタンを作成してスクリプト割り当て
TogglのAPIトークンを取得
Toggl APIを使うために、TogglのAPIトークンを取得します。
- Toggl Trackのプロフィールページ(https://track.toggl.com/profile)にアクセスします。
- 「API token」をコピー(Click to reveal部分)します。
GASにAPIキーを設定
Google Apps Script (GAS) にAPIキーを設定します。
- Google Apps Script の左にあるメニューから
「歯車マーク > プロジェクトの設定」 を開き、ページ内のスクリプトプロパティに移動します。
-
togglApiKey
というキー名で、先ほど取得したトークンを登録します。
この設定を行うことで、スクリプトがToggl APIを利用できるようになります。
完成コード全文
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const togglApiKey = PropertiesService.getScriptProperties().getProperty('togglApiKey');
const today = new Date().toISOString().split('T')[0]; // 今日の日付を取得
const togglTodayData = `https://api.track.toggl.com/api/v9/me/time_entries?start_date=${today}T00:00:00%2B09:00&end_date=${today}T23:59:59%2B09:00`; // 今日の作業データを取得するためのAPIリクエスト
const taskMap = {}; // タスク名ごとの作業時間
let totalHours = 0; // 今日1日全体の作業時間の合計
let outputRow = 2; // 何行目から書くか
let togglTodayTasks = []; // Togglから取得した本日の作業データ
// Togglデータを日報形式で出力する
function outputTogglTasks() {
togglTodayTasks = getTogglApiResponse(); // TogglのAPIレスポンスを取得
sheet.clear(); // 既存の内容をクリア
// 記録が空だったら注意文を表示
if (!togglTodayTasks.length) {
return setCellValue(sheet, 1, '⚠️ 本日のTogglデータはありません。');
}
// ストップウォッチが止まっていない場合処理を終了
for (let i = 0; i < togglTodayTasks.length; i++) {
if (!togglTodayTasks[i].stop) {
return setCellValue(sheet, 1, `⚠️ 作業「${togglTodayTasks[i].description || '(no description)'}」が終了していません。Togglでストップウォッチを止めてください。`);
}
}
// 工数計算
calculateTotalHours();
outputRow = setCellValue(sheet, outputRow, `合計${Math.round(totalHours * 100) / 100}時間です`);
// 警告判定
judgmentWarning();
// 本文出力
outputDailyReport();
console.log(togglTodayTasks);
}
// TogglのAPIレスポンスを取得
function getTogglApiResponse() {
// togglApiKeyが空だったらスプシの右下にポップアップ表示
if (!togglApiKey) {
SpreadsheetApp.getActiveSpreadsheet().toast('Toggl APIキーが未設定です。gasのスクリプトプロパティで設定してください。');
return [];
}
// TogglのAPIにGETリクエストを送信
const response = UrlFetchApp.fetch(togglTodayData, {
method: 'get',
headers: {
'Content-Type': 'application/json', // JSON形式で
'Authorization': 'Basic ' + Utilities.base64Encode(togglApiKey + ':api_token'), // Basic認証
},
});
// 返ってきた作業記録をjsに変換
return JSON.parse(response.getContentText());
}
// 合計工数とタスクごとの工数を計算
function calculateTotalHours() {
togglTodayTasks.forEach(entry => {
const name = entry.description || '(no description)'; // タスク名
const durationHours = (new Date(entry.stop) - new Date(entry.start)) / (1000 * 60 * 60); // 工数(h)
taskMap[name] = (taskMap[name] || 0) + durationHours; // 作業ごとの合計時間
totalHours += durationHours; // 合計時間
});
}
// 工数に応じた警告判定
function judgmentWarning() {
// 合計時間が基準範囲に収まっているか判定し、条件に合わない場合は赤字にする
if (isNowNoonTime()) {
// 午前日報は1〜4時間以外は赤字
if (totalHours < 1 || totalHours > 4) {
sheet.getRange(outputRow - 1, 3).setFontColor('red');
}
} else {
// 通常の日報は7〜12時間以外は赤字
if (totalHours < 7 || totalHours > 12) {
sheet.getRange(outputRow - 1, 3).setFontColor('red');
}
}
}
// 本文の出力
function outputDailyReport() {
// 出力
if (isNowNoonTime()) {
[
'',
'お疲れ様です。',
'本日在宅のため、午前中の日報をお送りします。 ',
'',
'■ 午前Todo',
...Object.keys(taskMap).map(task => `・${task}(${Math.round(taskMap[task] * 100) / 100}h)`),
'',
'■ 午後Todo',
'・'
].forEach(line => {
outputRow = setCellValue(sheet, outputRow, line);
});
} else {
[
'',
'お疲れ様です。',
'本日の日報お送りいたします。',
'',
'■業務',
...Object.keys(taskMap).map(task => `・${task}(${Math.round(taskMap[task] * 100) / 100}h)`),
'',
'■翌営業日やること',
'・',
'',
'■所感'
].forEach(line => {
outputRow = setCellValue(sheet, outputRow, line);
});
}
}
// 指定列に値を設定するための関数
function setCellValue(sheet, row, value) {
sheet.getRange(row, 3).setValue(value);
return row + 1; // 次の行に進む
}
// 12:00〜14:30の時間帯かどうかを判定(午前日報用)
function isNowNoonTime() {
const now = new Date();//現在の時刻を取得
const currentTime = now.getHours() + now.getMinutes() / 60;
return currentTime >= 12 && currentTime < 14.5;
}
各処理の解説
1. 定数/変数の定義
まずはスクリプト全体で使用する定数や変数を定義しています。Toggl APIで取得した作業記録を一時的に格納する配列や、スプレッドシートの出力開始行、作業時間の合計をカウントするための変数など、後続の処理で必要になる情報を初期化しています。
※今回は私のタスクを管理する個人用のプロジェクトで
外部からの干渉がないのでグローバルで定義しています。
グループや他人と共有する際は、関数内スコープやモジュールを検討しましょう。
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); // スプレッドシートの指定
const togglApiKey = PropertiesService.getScriptProperties().getProperty('togglApiKey'); // APIキーの取得
const today = new Date().toISOString().split('T')[0]; // 今日の日付を取得
const togglTodayData = `https://api.track.toggl.com/api/v9/me/time_entries?start_date=${today}T00:00:00%2B09:00&end_date=${today}T23:59:59%2B09:00`; // 今日の作業データを取得するためのAPIリクエスト
const taskMap = {}; // タスク名ごとの作業時間
let totalHours = 0; // 今日1日全体の作業時間の合計
let outputRow = 2; // 何行目から書くか
let togglTodayTasks = []; // Togglから取得した本日の作業データ
2. メイン処理:outputTogglTasks()
この関数が日報出力のメイン処理を担っています。Togglから作業データを取得し、エラーチェックを行った後、複数の関数(データの整形・工数の集計・スプレッドシートへの出力)を呼び出しながら、一括して処理します。
// Togglデータを日報形式で出力する
function outputTogglTasks() {
togglTodayTasks = getTogglApiResponse(); // TogglのAPIレスポンスを取得
sheet.clear(); // 既存の内容をクリア
// 記録が空だったら注意文を表示
if (!togglTodayTasks.length) {
return setCellValue(sheet, 1, '⚠️ 本日のTogglデータはありません。');
}
// ストップウォッチが止まっていない場合処理を終了
for (let i = 0; i < togglTodayTasks.length; i++) {
if (!togglTodayTasks[i].stop) {
return setCellValue(sheet, 1, `⚠️ 作業「${togglTodayTasks[i].description || '(no description)'}」が終了していません。Togglでストップウォッチを止めてください。`);
}
}
// 工数計算
calculateTotalHours();
outputRow = setCellValue(sheet, outputRow, `合計${Math.round(totalHours * 100) / 100}時間です`);
// 警告判定
judgmentWarning();
// 本文出力
outputDailyReport();
}
3. API取得処理:getTogglApiResponse()
スクリプト内でtogglApiKey
が設定されているか確認します。設定されていない場合は、スプレッドシートにエラーメッセージを表示します。
次にToggl APIを利用して、当日の作業ログを取得します。ここでは、UrlFetchApp.fetch()
を使ってAPIからデータを取得し、JSON形式でレスポンスを受け取ります。
// TogglのAPIレスポンスを取得
function getTogglApiResponse() {
// togglApiKeyが空だったらスプシの右下にポップアップ表示
if (!togglApiKey) {
SpreadsheetApp.getActiveSpreadsheet().toast('Toggl APIキーが未設定です。gasのスクリプトプロパティで設定してください。');
return [];
}
// TogglのAPIにGETリクエストを送信
const response = UrlFetchApp.fetch(togglTodayData, {
method: 'get',
headers: {
'Content-Type': 'application/json', // JSON形式で
'Authorization': 'Basic ' + Utilities.base64Encode(togglApiKey + ':api_token'), // Basic認証
},
});
// 返ってきた作業記録をjsに変換
return JSON.parse(response.getContentText());
}
Toggl APIを使って作業データを取得すると、以下のようなJSONレスポンスが返されます。実際に取得されるデータの例は次の通りです。
{
"data": [
{ id: 3936030404,
workspace_id: 9072306,
project_id: null,
task_id: null,
billable: false,
start: '2025-05-16T08:01:52+00:00',
stop: '2025-05-16T08:56:55+00:00',
duration: 3303,
description: '勉強会',
tags: [ '会議' ],
tag_ids: [ 17382408 ],
duronly: true,
at: '2025-05-16T08:56:58.524968Z',
server_deleted_at: null,
user_id: 11640125,
uid: 11640125,
wid: 9072306 }
]
}
Toggl APIから取得したデータで今回必要な項目
-
description
: タスクの詳細名(例:「会議」「コーディング」など) -
start
: タスクの開始時刻 -
stop
: タスクの終了時刻
これらの情報を元に、作業時間やタスク名を集計して日報を作成します。
4. 工数集計処理:calculateTotalHours()
各タスクの作業時間を集計し、合計時間を算出します。
// 合計工数とタスクごとの工数を計算
function calculateTotalHours() {
togglTodayTasks.forEach(entry => {
const name = entry.description || '(no description)'; // タスク名
const durationHours = (new Date(entry.stop) - new Date(entry.start)) / (1000 * 60 * 60); // 工数(h)
taskMap[name] = (taskMap[name] || 0) + durationHours; // 作業ごとの合計時間
totalHours += durationHours; // 合計時間
});
}
5. 警告表示:judgmentWarning()
Toggleの手動修正した場合のミスをそのまま送るのを防ぐため、午前なら1〜4h以外、午後なら7〜12h以外は合計時間を赤字にします。
// 工数に応じた警告判定
function judgmentWarning() {
// 合計時間が基準範囲に収まっているか判定し、条件に合わない場合は赤字にする
if (isNowNoonTime()) {
// 午前日報は1〜4時間以外は赤字
if (totalHours < 1 || totalHours > 4) {
sheet.getRange(outputRow - 1, 3).setFontColor('red');
}
} else {
// 終日の日報は7〜12時間以外は赤字
if (totalHours < 7 || totalHours > 12) {
sheet.getRange(outputRow - 1, 3).setFontColor('red');
}
}
}
6. 出力:outputDailyReport()
午前中または終日で日報形式を切り替え、スプレッドシートに出力します。
// 本文の出力
function outputDailyReport() {
// 出力
if (isNowNoonTime()) {
[
'',
'お疲れ様です。',
'本日在宅のため、午前中の日報をお送りします。 ',
'',
'■ 午前Todo',
...Object.keys(taskMap).map(task => `・${task}(${Math.round(taskMap[task] * 100) / 100}h)`),
'',
'■ 午後Todo',
'・'
].forEach(line => {
outputRow = setCellValue(sheet, outputRow, line);
});
} else {
[
'',
'お疲れ様です。',
'本日の日報お送りいたします。',
'',
'■業務',
...Object.keys(taskMap).map(task => `・${task}(${Math.round(taskMap[task] * 100) / 100}h)`),
'',
'■翌営業日やること',
'・',
'',
'■所感'
].forEach(line => {
outputRow = setCellValue(sheet, outputRow, line);
});
}
}
7. 出力補助:setCellValue()
指定した行と列に値を設定し、次に進むための行番号を返します。
// 指定列に値を設定するための関数
function setCellValue(sheet, row, value) {
sheet.getRange(row, 3).setValue(value);
return row + 1; // 次の行に進む
}
8. 午前・終日日報の分岐:isNowNoonTime()
時間帯が12:00〜14:30かで午前日報か・終日日報かを判定します。
// 12:00〜14:30の時間帯かどうかを判定(午前日報用)
function isNowNoonTime() {
const now = new Date();//現在の時刻を取得
const currentTime = now.getHours() + now.getMinutes() / 60;
return currentTime >= 12 && currentTime < 14.5;
}
さいごに
毎日のルーティンである、ちょっと面倒くさい日報作成。
ボタンひとつでまとめてくれるのは、地味にありがたいです!
Toggl × GAS の連携を通じて、APIの扱いやGASの便利さも改めて実感できて、
個人的にもいい勉強になりました。
出力フォーマットやチェック条件は、業務スタイルに合わせて自由にカスタマイズできるので、
ぜひ使ってみてください!