集中した作業時間
中級
これはPersonal Productivity分野の自動化ワークフローで、11個のノードを含みます。主にIf, Code, ItemLists, GoogleCalendar, ScheduleTriggerなどのノードを使用。 忙しいスケジュールの中で自動のにGoogleカレンダーにフォーカスタイムをブロック
前提条件
- •特別な前提条件なし、インポートしてすぐに使用可能
カテゴリー
ワークフロープレビュー
ノード接続関係を可視化、ズームとパンをサポート
ワークフローをエクスポート
以下のJSON設定をn8nにインポートして、このワークフローを使用できます
{
"id": "H1q8R1MfhEk8F0ni",
"meta": {
"instanceId": "bdc54da2c96840612a04bf3fd3a4cd97a7a7bd7c1152bbe41d5615f09311c097"
},
"name": "FocusTime",
"tags": [
{
"id": "SauVYJKjA9yiw2uI",
"name": "calendar-automation",
"createdAt": "2025-07-20T22:06:21.623Z",
"updatedAt": "2025-07-20T22:06:21.623Z"
}
],
"nodes": [
{
"id": "965166ee-bf7e-48e6-b559-624c4a5bb72f",
"name": "スケジュールトリガー",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-880,
96
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8 * * *"
}
]
}
},
"typeVersion": 1.1
},
{
"id": "af8970e0-6d2c-48d4-907b-5e022ab8d5ac",
"name": "週間イベント取得",
"type": "n8n-nodes-base.googleCalendar",
"position": [
-656,
96
],
"parameters": {
"options": {},
"calendar": {
"__rl": true,
"mode": "list",
"value": "primary",
"cachedResultName": "Primary"
},
"operation": "getAll"
},
"credentials": {
"googleCalendarOAuth2Api": {
"id": "Isqbn5j7Czj35HLS",
"name": "Google Calendar account"
}
},
"typeVersion": 1
},
{
"id": "76c503e8-926f-4254-be1b-f2c5b55938e8",
"name": "週間フォーカスタイム枠計算",
"type": "n8n-nodes-base.code",
"position": [
-448,
96
],
"parameters": {
"jsCode": "const events = $input.all();\nconst today = new Date();\n\n// Get Sunday of current week (start of week)\nconst sunday = new Date(today);\nsunday.setDate(today.getDate() - today.getDay());\nsunday.setHours(0, 0, 0, 0);\n\n// Create array of all 7 days (Sunday-Saturday)\nconst allDays = [];\nfor (let i = 0; i < 7; i++) {\n const day = new Date(sunday);\n day.setDate(sunday.getDate() + i);\n allDays.push(day);\n}\n\nconst dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];\nconsole.log('Analyzing full week:', allDays.map((d, i) => `${dayNames[i]} ${d.toDateString()}`));\n\nconst allFocusSlots = [];\n\n// Process each day of the week\nfor (let dayIndex = 0; dayIndex < allDays.length; dayIndex++) {\n const currentDay = allDays[dayIndex];\n const dayName = dayNames[dayIndex];\n \n const workStart = new Date(currentDay);\n workStart.setHours(9, 0, 0, 0);\n const workEnd = new Date(currentDay);\n workEnd.setHours(17, 0, 0, 0);\n \n // Filter events for this specific day\n const dayEvents = events\n .filter(event => {\n const eventStart = new Date(event.json.start.dateTime || event.json.start.date);\n return eventStart.toDateString() === currentDay.toDateString();\n })\n .sort((a, b) => {\n const aStart = new Date(a.json.start.dateTime || a.json.start.date);\n const bStart = new Date(b.json.start.dateTime || b.json.start.date);\n return aStart - bStart;\n });\n\n // Calculate total booked time for this day\n let totalBookedMinutes = 0;\n for (const event of dayEvents) {\n const startTime = new Date(event.json.start.dateTime || event.json.start.date);\n const endTime = new Date(event.json.end.dateTime || event.json.end.date);\n \n const effectiveStart = new Date(Math.max(startTime.getTime(), workStart.getTime()));\n const effectiveEnd = new Date(Math.min(endTime.getTime(), workEnd.getTime()));\n \n if (effectiveStart < effectiveEnd) {\n totalBookedMinutes += (effectiveEnd - effectiveStart) / (1000 * 60);\n }\n }\n\n const totalBookedHours = totalBookedMinutes / 60;\n console.log(`${dayName} ${currentDay.toDateString()}: ${totalBookedHours.toFixed(1)} hours booked`);\n\n // Only process days with 6+ hours booked\n if (totalBookedHours >= 6) {\n console.log(`Creating focus time for ${dayName} (${totalBookedHours.toFixed(1)} hours booked)`);\n \n const freeSlots = [];\n let currentTime = new Date(workStart);\n\n // Check time before first event\n if (dayEvents.length > 0) {\n const firstEventStart = new Date(dayEvents[0].json.start.dateTime || dayEvents[0].json.start.date);\n if (currentTime < firstEventStart) {\n const slotEnd = new Date(Math.min(firstEventStart.getTime(), workEnd.getTime()));\n if (currentTime < slotEnd) {\n freeSlots.push({\n start: new Date(currentTime),\n end: slotEnd\n });\n }\n }\n }\n\n // Check time between events\n for (let i = 0; i < dayEvents.length - 1; i++) {\n const currentEventEnd = new Date(dayEvents[i].json.end.dateTime || dayEvents[i].json.end.date);\n const nextEventStart = new Date(dayEvents[i + 1].json.start.dateTime || dayEvents[i + 1].json.start.date);\n \n const slotStart = new Date(Math.max(currentEventEnd.getTime(), workStart.getTime()));\n const slotEnd = new Date(Math.min(nextEventStart.getTime(), workEnd.getTime()));\n \n if (slotStart < slotEnd) {\n freeSlots.push({\n start: slotStart,\n end: slotEnd\n });\n }\n }\n\n // Check time after last event\n if (dayEvents.length > 0) {\n const lastEventEnd = new Date(dayEvents[dayEvents.length - 1].json.end.dateTime || dayEvents[dayEvents.length - 1].json.end.date);\n const slotStart = new Date(Math.max(lastEventEnd.getTime(), workStart.getTime()));\n \n if (slotStart < workEnd) {\n freeSlots.push({\n start: slotStart,\n end: new Date(workEnd)\n });\n }\n } else {\n // No events this day, but 6+ hours requirement met somehow (shouldn't happen, but handle it)\n console.log(`${dayName}: No events found but met 6+ hour criteria`);\n }\n\n // Filter slots that are at least 15 minutes long and add to all slots\n const validSlots = freeSlots.filter(slot => \n (slot.end - slot.start) >= 15 * 60 * 1000\n );\n\n validSlots.forEach(slot => {\n allFocusSlots.push({\n start: slot.start.toISOString(),\n end: slot.end.toISOString(),\n duration: Math.round((slot.end - slot.start) / (1000 * 60)),\n summary: \"Focus Time\",\n day: currentDay.toDateString(),\n dayName: dayName,\n bookedHours: totalBookedHours.toFixed(1)\n });\n });\n }\n}\n\nconsole.log(`Found ${allFocusSlots.length} focus time slots across the full week`);\n\nif (allFocusSlots.length === 0) {\n return [{\n json: {\n shouldCreateFocusTime: false,\n message: 'No days this week have 6+ hours booked. Focus time not needed.',\n weekAnalyzed: allDays.map((d, i) => `${dayNames[i]} ${d.toDateString()}`)\n }\n }];\n}\n\nreturn [{\n json: {\n shouldCreateFocusTime: true,\n totalSlotsFound: allFocusSlots.length,\n freeSlots: allFocusSlots,\n weekAnalyzed: allDays.map((d, i) => `${dayNames[i]} ${d.toDateString()}`)\n }\n}];"
},
"typeVersion": 2
},
{
"id": "a6d17dd6-3aea-4860-b3c1-cd95d8e6a78c",
"name": "フォーカスタイム作成要否判定",
"type": "n8n-nodes-base.if",
"position": [
-224,
96
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "condition-001",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.shouldCreateFocusTime }}",
"rightValue": true
}
]
}
},
"typeVersion": 2
},
{
"id": "49a4675b-2a0f-4219-9500-aa1803fd794b",
"name": "空き時間分割",
"type": "n8n-nodes-base.itemLists",
"position": [
0,
0
],
"parameters": {
"options": {},
"fieldToSplitOut": "freeSlots"
},
"typeVersion": 3
},
{
"id": "a7191014-8661-41c2-b121-7f7f924c895e",
"name": "フォーカスタイムイベント作成",
"type": "n8n-nodes-base.googleCalendar",
"position": [
224,
0
],
"parameters": {
"end": "={{ $json.end }}",
"start": "={{ $json.start }}",
"calendar": {
"__rl": true,
"mode": "list",
"value": "primary",
"cachedResultName": "Primary"
},
"additionalFields": {}
},
"credentials": {
"googleCalendarOAuth2Api": {
"id": "Isqbn5j7Czj35HLS",
"name": "Google Calendar account"
}
},
"typeVersion": 1
},
{
"id": "019babfa-6ac8-4028-951c-0cb776ef24cd",
"name": "結果ログ出力",
"type": "n8n-nodes-base.code",
"position": [
448,
0
],
"parameters": {
"jsCode": "const items = $input.all();\nconst summary = {\n message: 'Focus time blocks created successfully for the week',\n totalSlots: items.length,\n slots: items.map(item => ({\n start: item.json.start.dateTime,\n end: item.json.end.dateTime,\n summary: item.json.summary\n }))\n};\n\nconsole.log('Weekly Focus Time Creation Summary:', JSON.stringify(summary, null, 2));\n\nreturn [{\n json: summary\n}];"
},
"typeVersion": 2
},
{
"id": "7ecbdb56-9195-4d13-891b-e307d10417f5",
"name": "フォーカスタイム不要ログ",
"type": "n8n-nodes-base.code",
"position": [
0,
208
],
"parameters": {
"jsCode": "const data = $input.first().json;\nconsole.log('Weekly Focus Time Analysis:', data.message);\n\nreturn [{\n json: {\n message: data.message,\n weekAnalyzed: data.weekAnalyzed,\n action: 'no_focus_time_needed'\n }\n}];"
},
"typeVersion": 2
},
{
"id": "5a76f16a-5931-45d8-8319-440b39491a65",
"name": "付箋",
"type": "n8n-nodes-base.stickyNote",
"position": [
-944,
-80
],
"parameters": {
"height": 400,
"content": "Set the schedule time the workflow would run (recommend to set at a time before user's work day starts). "
},
"typeVersion": 1
},
{
"id": "f9e74c3f-ab4e-4e70-bddc-08f4903dc6f5",
"name": "付箋1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-688,
-80
],
"parameters": {
"width": 592,
"height": 400,
"content": "Workflow checks user's calendar from Sunday to Saturday of current week. Goal of this part is to determine if a day has 6 or more hours booked already\n\nNote: Update the credentials used in the \"Get Full Weeks Events\" node to use your Google credentials. "
},
"typeVersion": 1
},
{
"id": "8ac0657c-c682-414e-b56e-884f517bcda2",
"name": "付箋2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-80,
-176
],
"parameters": {
"width": 704,
"height": 544,
"content": "For days with 6 or more hours already booked, the workflow automatically blocks out the remaining hours for dedicated focus time. The workflows assumes an 8 hour work week. For example, if Monday has 6.5 booked already (for meetings, tasks etc.), the workflow will block the remaining 1.5 hours. The results are logged to help with any troubleshooting\n\nNote: Update the credentials used in the \"Create Focus Time Event\" node to use your Google credentials. "
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "9c49a780-08ab-4198-9a16-8cdb241b5425",
"connections": {
"965166ee-bf7e-48e6-b559-624c4a5bb72f": {
"main": [
[
{
"node": "af8970e0-6d2c-48d4-907b-5e022ab8d5ac",
"type": "main",
"index": 0
}
]
]
},
"49a4675b-2a0f-4219-9500-aa1803fd794b": {
"main": [
[
{
"node": "a7191014-8661-41c2-b121-7f7f924c895e",
"type": "main",
"index": 0
}
]
]
},
"af8970e0-6d2c-48d4-907b-5e022ab8d5ac": {
"main": [
[
{
"node": "76c503e8-926f-4254-be1b-f2c5b55938e8",
"type": "main",
"index": 0
}
]
]
},
"a7191014-8661-41c2-b121-7f7f924c895e": {
"main": [
[
{
"node": "019babfa-6ac8-4028-951c-0cb776ef24cd",
"type": "main",
"index": 0
}
]
]
},
"a6d17dd6-3aea-4860-b3c1-cd95d8e6a78c": {
"main": [
[
{
"node": "49a4675b-2a0f-4219-9500-aa1803fd794b",
"type": "main",
"index": 0
}
],
[
{
"node": "7ecbdb56-9195-4d13-891b-e307d10417f5",
"type": "main",
"index": 0
}
]
]
},
"76c503e8-926f-4254-be1b-f2c5b55938e8": {
"main": [
[
{
"node": "a6d17dd6-3aea-4860-b3c1-cd95d8e6a78c",
"type": "main",
"index": 0
}
]
]
}
}
}よくある質問
このワークフローの使い方は?
上記のJSON設定コードをコピーし、n8nインスタンスで新しいワークフローを作成して「JSONからインポート」を選択、設定を貼り付けて認証情報を必要に応じて変更してください。
このワークフローはどんな場面に適していますか?
中級 - 個人の生産性
有料ですか?
このワークフローは完全無料です。ただし、ワークフローで使用するサードパーティサービス(OpenAI APIなど)は別途料金が発生する場合があります。
関連ワークフロー
Telegramカレンダーボット
Gmail経由でTelegramへ毎日のカレンダー要約通知を送信
If
Code
Telegram
+
If
Code
Telegram
11 ノードYassin Zehar
個人の生産性
クラウドハリデー衝突の検出と会議の再スケジュール
Google CalendarとSlackを使って祝日の冲突を検出し、会議の再スケジュールを提案する
If
Set
Code
+
If
Set
Code
23 ノードTakuya Ojima
個人の生産性
フリーグッズ:毎日のリマインダーテンプレート
Google Calendar、Twilio、Claude AI を使って毎日のカレンダー要約 SMS を取得
If
Set
Code
+
If
Set
Code
13 ノードAnne Uy Gothong
個人の生産性
Python と AI を使ったカスタム天気メール
OpenWeatherMap、Python、GPT-4.1-miniを使用して個別の天気レポートを生成
Code
Gmail
Form Trigger
+
Code
Gmail
Form Trigger
11 ノードMoe Ahad
個人の生産性
ワークデイログ記録
AI作業時間表ジェネレーター - Gmail、カレンダー、GitHubをGoogleスプレッドシートに統合
If
Set
Code
+
If
Set
Code
31 ノードLuka Zivkovic
個人の生産性
学生・教員向けの自動作業リマインダーと納期追跡ツール
学生・教員に課題締切リマインダーを提供するためのNotionとメール統合
If
Notion
Email Send
+
If
Notion
Email Send
7 ノードOneclick AI Squad
個人の生産性