전 세계 휴일 충돌 검출 및 회의 재정렬
고급
이것은Personal Productivity분야의자동화 워크플로우로, 23개의 노드를 포함합니다.주로 If, Set, Code, Slack, HttpRequest 등의 노드를 사용하며. Google Calendar와 Slack을 사용하여 공휴일 충돌을 검출하고 회의 재정렬을 제안
사전 요구사항
- •Slack Bot Token 또는 Webhook URL
- •대상 API의 인증 정보가 필요할 수 있음
카테고리
워크플로우 미리보기
노드 연결 관계를 시각적으로 표시하며, 확대/축소 및 이동을 지원합니다
워크플로우 내보내기
다음 JSON 구성을 복사하여 n8n에 가져오면 이 워크플로우를 사용할 수 있습니다
{
"id": "K67zdPMDUtledQFV",
"meta": {
"instanceId": "189dde98270e9ce0f006f0e9deb96aa5e627396fc6279cac8902c9b06936984d"
},
"name": "Global Holiday Conflict Detector and Meeting Rescheduler",
"tags": [],
"nodes": [
{
"id": "b487208f-33b3-4527-8e18-bb3c2a8746d7",
"name": "일일 점검",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-752,
224
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 9
}
]
}
},
"typeVersion": 1.2
},
{
"id": "a8f9264a-5dba-4801-9c1e-e12a59237b8b",
"name": "워크플로우 설정",
"type": "n8n-nodes-base.set",
"position": [
-528,
224
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "id-1",
"name": "currentYear",
"type": "number",
"value": "={{ new Date().getFullYear() }}"
},
{
"id": "id-2",
"name": "nextWeekStart",
"type": "string",
"value": "={{ new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0] }}"
},
{
"id": "id-3",
"name": "nextWeekEnd",
"type": "string",
"value": "={{ new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toISOString().split('T')[0] }}"
},
{
"id": "id-4",
"name": "slackChannel",
"type": "string",
"value": "C09FB9QQQTX"
},
{
"id": "id-5",
"name": "calendarId",
"type": "string",
"value": "c_91f92ee12632d48cc78642add679d75aa8aecb09abea89eadbc97cb17d2de336@group.calendar.google.com"
},
{
"id": "b5660f95-e978-4a1a-9c33-225dfa4e9552",
"name": "countryCodes",
"type": "array",
"value": "[\"US\", \"GB\", \"DE\", \"IN\", \"CN\", \"KR\", \"HK\"]"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "6472f288-c7e2-49da-bd32-61b91d9528b3",
"name": "다음 주 공휴일 병합 및 필터링",
"type": "n8n-nodes-base.code",
"position": [
144,
224
],
"parameters": {
"jsCode": "// Get the next week date range from Workflow Configuration\nconst nextWeekStart = new Date($('Workflow Configuration').first().json.nextWeekStart);\nconst nextWeekEnd = new Date($('Workflow Configuration').first().json.nextWeekEnd);\n\n// Combine all holiday data from the loop\nconst allHolidays = [];\n\n// Process all input items (which are the results from each loop iteration)\nfor (const item of $input.all()) {\n // The HTTP Request node in the loop outputs the holiday array in item.json\n if (Array.isArray(item.json)) {\n for (const holiday of item.json) {\n allHolidays.push(holiday);\n }\n }\n}\n\n// Filter holidays that fall within next week range\nconst nextWeekHolidays = allHolidays.filter(holiday => {\n const holidayDate = new Date(holiday.date);\n return holidayDate >= nextWeekStart && holidayDate <= nextWeekEnd;\n});\n\n// Map to desired output format\nconst formattedHolidays = nextWeekHolidays.map(holiday => ({\n date: holiday.date,\n name: holiday.name || holiday.localName,\n country: holiday.countryCode === 'US' ? 'United States' : \n holiday.countryCode === 'GB' ? 'United Kingdom' : \n holiday.countryCode === 'DE' ? 'Germany' : \n holiday.countryCode === 'IN' ? 'India' : \n holiday.countryCode === 'CN' ? 'China' : \n holiday.countryCode === 'KR' ? 'South Korea' : \n holiday.countryCode === 'HK' ? 'Hong Kong' : holiday.countryCode,\n countryCode: holiday.countryCode\n}));\n\n// Return all filtered holidays as a single item\nreturn [{\n json: {\n holidays: formattedHolidays\n }\n}];"
},
"typeVersion": 2
},
{
"id": "524535d0-6d38-4da8-a5c9-e775f4ff4e50",
"name": "다음 주 캘린더 이벤트 가져오기",
"type": "n8n-nodes-base.googleCalendar",
"position": [
-304,
272
],
"parameters": {
"options": {},
"timeMax": "={{ $('Workflow Configuration').first().json.nextWeekEnd }}T23:59:59Z",
"timeMin": "={{ $('Workflow Configuration').first().json.nextWeekStart }}T00:00:00Z",
"calendar": {
"__rl": true,
"mode": "id",
"value": "={{ $('Workflow Configuration').first().json.calendarId }}"
},
"operation": "getAll",
"returnAll": true
},
"typeVersion": 1.3
},
{
"id": "5ed5e1c2-726a-43bd-8f49-ed40f3eac9fa",
"name": "공휴일 충돌 감지",
"type": "n8n-nodes-base.code",
"position": [
480,
224
],
"parameters": {
"jsCode": "// Get the single item containing the list of all holidays from the first input\nconst holidayList = $input.all(0)[0].json.holidays || [];\n\n// Get all items containing calendar events from the second input\nconst calendarEventItems = $input.all(1);\n\n// Create a map of holiday dates for quick lookup\nconst holidayMap = new Map();\nholidayList.forEach(holiday => {\n const date = holiday.date;\n if (!holidayMap.has(date)) {\n holidayMap.set(date, []);\n }\n holidayMap.get(date).push({\n name: holiday.name,\n country: holiday.countryCode || holiday.country\n });\n});\n\n// Detect conflicts by iterating through each calendar event item\nconst conflicts = [];\ncalendarEventItems.forEach(eventItem => {\n const event = eventItem.json;\n const eventStart = event.start?.dateTime || event.start?.date;\n if (!eventStart) return; // Skip if no start time\n \n const eventDate = eventStart.split('T')[0];\n \n // Check if this date has any holidays\n if (holidayMap.has(eventDate)) {\n const holidaysOnDate = holidayMap.get(eventDate);\n const eventTime = eventStart.includes('T') ? eventStart.split('T')[1].substring(0, 5) : 'All day';\n const attendees = event.attendees ? event.attendees.map(a => a.email) : [];\n \n conflicts.push({\n eventName: event.summary || 'Untitled Event',\n eventDate: eventDate,\n eventTime: eventTime,\n holidayName: holidaysOnDate.map(h => h.name).join(', '),\n affectedCountries: [...new Set(holidaysOnDate.map(h => h.country))].join(', '),\n attendees: attendees,\n eventId: event.id\n });\n }\n});\n\n// Return all found conflicts in a SINGLE item to ensure subsequent nodes run only once\nreturn [{\n json: {\n conflicts: conflicts,\n totalConflicts: conflicts.length,\n checkDate: new Date().toISOString()\n }\n}];"
},
"typeVersion": 2
},
{
"id": "32c956cf-781f-4548-96a9-9d758a7f22a3",
"name": "충돌 발견 여부 확인",
"type": "n8n-nodes-base.if",
"position": [
752,
224
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "id-1",
"operator": {
"type": "array",
"operation": "notEmpty"
},
"leftValue": "={{ $('Detect Holiday Conflicts').item.json.conflicts }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "fd84ad56-55c4-49c4-9e60-368977208c0c",
"name": "재조정 제안 생성",
"type": "n8n-nodes-base.code",
"position": [
1072,
224
],
"parameters": {
"jsCode": "const conflicts = $input.first().json.conflicts || [];\n\n// Get the holiday list from the 'Merge and Filter' node which ran before the conflict detection\nconst holidays = $('Merge and Filter Next Week Holidays').first().json.holidays.map(h => h.date);\n\nfunction isHoliday(dateStr) {\n return holidays.includes(dateStr);\n}\n\nfunction isWeekend(date) {\n const day = date.getDay();\n return day === 0 || day === 6; // Sunday or Saturday\n}\n\nfunction findNextAvailableDate(startDate) {\n let currentDate = new Date(startDate);\n currentDate.setDate(currentDate.getDate() + 1);\n \n for (let i = 0; i < 30; i++) {\n const dateStr = currentDate.toISOString().split('T')[0];\n if (!isWeekend(currentDate) && !isHoliday(dateStr)) {\n const originalTime = startDate.toTimeString().split(' ')[0].substring(0, 5);\n return { date: dateStr, time: originalTime };\n }\n currentDate.setDate(currentDate.getDate() + 1);\n }\n \n return {\n date: currentDate.toISOString().split('T')[0],\n time: startDate.toTimeString().split(' ')[0].substring(0, 5)\n };\n}\n\nconst enhancedConflicts = conflicts.map(conflict => {\n const eventDateTime = conflict.eventTime === 'All day' ? new Date(conflict.eventDate) : new Date(`${conflict.eventDate}T${conflict.eventTime}`);\n const suggestion = findNextAvailableDate(eventDateTime);\n \n return {\n ...conflict,\n suggestedDate: suggestion.date,\n suggestedTime: suggestion.time,\n originalDate: conflict.eventDate,\n originalTime: conflict.eventTime\n };\n});\n\nreturn [{\n json: {\n conflicts: enhancedConflicts,\n totalConflicts: enhancedConflicts.length\n }\n}];"
},
"typeVersion": 2
},
{
"id": "4bb37c98-7400-4e4c-9519-4cafa5555067",
"name": "Slack 다이제스트 포맷팅",
"type": "n8n-nodes-base.code",
"position": [
1376,
224
],
"parameters": {
"jsCode": "// Get the conflicts data from the previous node\nconst conflictsData = $input.first().json;\nconst conflicts = conflictsData.conflicts || [];\n\n// Use a single backslash \\n for newlines\nlet slackMessage = ':warning: *Holiday Conflict Alert* :warning:\\n\\n';\n\nif (conflicts.length === 0) {\n slackMessage += 'No conflicts detected for next week. All clear! :white_check_mark:';\n} else {\n slackMessage += `Found *${conflicts.length}* meeting(s) scheduled during public holidays next week:\\n\\n`;\n \n conflicts.forEach((conflict, index) => {\n slackMessage += `*${index + 1}. ${conflict.eventName}*\\n`;\n slackMessage += `:calendar: *Date:* ${conflict.originalDate}\\n`;\n slackMessage += `:clock3: *Time:* ${conflict.originalTime}\\n`;\n slackMessage += `:earth_americas: *Affected Countries:* ${conflict.affectedCountries}\\n`;\n slackMessage += `:pushpin: *Holiday:* ${conflict.holidayName}\\n`;\n \n // Add suggestion if it exists\n if (conflict.suggestedDate) {\n slackMessage += `:bulb: *Suggestion:* Reschedule to ${conflict.suggestedDate} at ${conflict.suggestedTime}\\n`;\n }\n \n slackMessage += '\\n---\\n\\n';\n });\n \n slackMessage += ':point_right: Please review and reschedule these meetings to accommodate team members in affected regions.';\n}\n\n// Return the formatted message\nreturn [{\n json: {\n slackMessage: slackMessage\n }\n}];"
},
"typeVersion": 2
},
{
"id": "d01a7818-dc28-4490-ad5b-e5963cd22cbd",
"name": "Slack 다이제스트 게시",
"type": "n8n-nodes-base.slack",
"position": [
1680,
224
],
"webhookId": "5337846c-6b31-45a2-80ac-6960f6350ab1",
"parameters": {
"text": "={{ $json.slackMessage }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Workflow Configuration').first().json.slackChannel }}"
},
"otherOptions": {},
"authentication": "oAuth2"
},
"typeVersion": 2.3
},
{
"id": "d04fe2ce-a6f3-49d5-a384-39a3a201fa37",
"name": "항목 반복 처리",
"type": "n8n-nodes-base.splitInBatches",
"position": [
-304,
80
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "74320878-4302-440c-99ce-5aefefcb1ca2",
"name": "공휴일 정보 가져오기",
"type": "n8n-nodes-base.httpRequest",
"position": [
-80,
32
],
"parameters": {
"url": "=https://date.nager.at/api/v3/PublicHolidays/{{ $('Workflow Configuration').first().json.currentYear }}/{{ $json.countryCodes }}",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "392bf796-1880-4a89-ad6b-7838a7a19351",
"name": "참고: 일일 점검",
"type": "n8n-nodes-base.stickyNote",
"position": [
-816,
64
],
"parameters": {
"color": "white",
"height": 144,
"content": "**Purpose:** Triggers the workflow once every weekday morning.\n**Key:** Runs at 09:00 server time (adjust in node).\n**Tip:** Change to weekly if you only need Monday runs."
},
"typeVersion": 1
},
{
"id": "0541544f-2e8e-41a7-b899-76de44829cca",
"name": "참고: 워크플로우 설정",
"type": "n8n-nodes-base.stickyNote",
"position": [
-544,
-208
],
"parameters": {
"color": "white",
"width": 256,
"height": 208,
"content": "**Purpose:** Central place to define variables.\n**Fields:** `currentYear`, `nextWeekStart`, `nextWeekEnd`, `countryCodes`, `slackChannel`, `calendarId`.\n**Tip:** Edit country list and calendar/channel IDs here only."
},
"typeVersion": 1
},
{
"id": "02931af5-a167-422c-ae32-53412ed0f4f4",
"name": "참고: 다음 주 공휴일 병합 및 필터링",
"type": "n8n-nodes-base.stickyNote",
"position": [
80,
48
],
"parameters": {
"color": "white",
"content": "**Purpose:** Merges API results and filters to next week only.\n**Output:** `holidays[]` with `date`, `name`, `countryCode`."
},
"typeVersion": 1
},
{
"id": "79ac359f-12a3-43ba-bf75-b987f37f482e",
"name": "참고: 다음 주 캘린더 이벤트 가져오기",
"type": "n8n-nodes-base.stickyNote",
"position": [
-368,
480
],
"parameters": {
"color": "white",
"content": "**Purpose:** Reads all events in next week’s window from Google Calendar.\n**Config:** Uses `calendarId` and `timeMin/Max` from the Set node.\n**Note:** Re-connect your own Google credential in n8n."
},
"typeVersion": 1
},
{
"id": "165237d2-da5e-408f-bb46-77be9bf4c38c",
"name": "참고: 공휴일 충돌 감지",
"type": "n8n-nodes-base.stickyNote",
"position": [
384,
48
],
"parameters": {
"color": "white",
"content": "**Purpose:** Compares event dates with holiday dates to find conflicts.\n**Output:** Single item `{ conflicts[], totalConflicts }` so downstream runs once."
},
"typeVersion": 1
},
{
"id": "3b55ac06-aa21-44de-a6e0-37051e02cfa1",
"name": "참고: 충돌 발견 여부 확인",
"type": "n8n-nodes-base.stickyNote",
"position": [
688,
48
],
"parameters": {
"color": "white",
"content": "**Purpose:** Guards the branch; continues only when conflicts exist.\n**Condition:** Array not empty."
},
"typeVersion": 1
},
{
"id": "fc01dc46-773b-4881-b646-2bcf21c7bc1f",
"name": "참고: 재조정 제안 생성",
"type": "n8n-nodes-base.stickyNote",
"position": [
992,
48
],
"parameters": {
"color": "white",
"content": "**Purpose:** Suggests next business day that is not a holiday/weekend.\n**Logic:** Looks ahead up to 30 days, preserving original start time."
},
"typeVersion": 1
},
{
"id": "cec6277f-583d-4ba9-8063-2e956395bff5",
"name": "참고: Slack 다이제스트 포맷팅",
"type": "n8n-nodes-base.stickyNote",
"position": [
1296,
48
],
"parameters": {
"color": "white",
"content": "**Purpose:** Creates a readable Slack message.\n**Includes:** Event, date/time, affected countries, holiday, suggestion."
},
"typeVersion": 1
},
{
"id": "6d766a0d-bea6-4bbb-a824-127b53b9dc45",
"name": "참고: Slack 다이제스트 게시",
"type": "n8n-nodes-base.stickyNote",
"position": [
1600,
48
],
"parameters": {
"color": "white",
"content": "**Purpose:** Posts the digest to Slack.\n**Config:** Uses `slackChannel` from the Set node.\n**Note:** Re-connect your own Slack OAuth in n8n (left unconfigured)."
},
"typeVersion": 1
},
{
"id": "8461d7cc-e39d-4c86-9c6f-96a7aa55ba4f",
"name": "참고: 항목 반복 처리",
"type": "n8n-nodes-base.stickyNote",
"position": [
-208,
-160
],
"parameters": {
"color": "white",
"content": "**Purpose:** Iterates through each country code.\n**Flow:** Splits the array to call the holiday API per country, then loops back."
},
"typeVersion": 1
},
{
"id": "f857f854-7ed9-43c1-af05-7e596d8369dc",
"name": "참고: 공휴일 정보 가져오기",
"type": "n8n-nodes-base.stickyNote",
"position": [
96,
-192
],
"parameters": {
"color": "white",
"width": 288,
"height": 192,
"content": "**Purpose:** Calls Nager.Date API for public holidays.\n**URL:** `https://date.nager.at/api/v3/PublicHolidays/{year}/{country}`.\n**Security:** No API key needed. Keep credentials empty."
},
"typeVersion": 1
},
{
"id": "48d8def6-1e2e-4e03-b963-a44f9e2c0de1",
"name": "템플릿 개요",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1632,
-448
],
"parameters": {
"color": "yellow",
"width": 688,
"height": 752,
"content": "## What this template does\nDetects global public-holiday conflicts in **next week’s** meetings and posts a Slack digest with suggested reschedule dates.\n\n## Who it’s for\nRemote and distributed teams that want to avoid scheduling meetings on regional holidays.\n\n## How it works\n1) Fetch public holidays for selected country codes. \n2) Pull next week’s events from Google Calendar. \n3) Detect date overlaps and generate a reschedule suggestion (next weekday that isn’t a holiday). \n4) Post a single Slack summary message.\n\n## How to set up\n- Configure **countryCodes**, **calendarId**, and **slackChannel** in the **Workflow Configuration** (Set) node. \n- Connect your own Google Calendar and Slack credentials in n8n (left as unauthenticated by design).\n\n## Requirements\n- n8n (self-hosted or Cloud) \n- Slack app with chat:write and channel access \n- Google Calendar with read access\n\n## Customize\n- Change the time window by editing `nextWeekStart/End` in **Workflow Configuration**. \n- Adjust the suggestion logic in **Generate Reschedule Suggestions**."
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "c01575e0-ea13-45a5-bb0a-c771b4107645",
"connections": {
"b487208f-33b3-4527-8e18-bb3c2a8746d7": {
"main": [
[
{
"node": "a8f9264a-5dba-4801-9c1e-e12a59237b8b",
"type": "main",
"index": 0
}
]
]
},
"d04fe2ce-a6f3-49d5-a384-39a3a201fa37": {
"main": [
[
{
"node": "6472f288-c7e2-49da-bd32-61b91d9528b3",
"type": "main",
"index": 0
}
],
[
{
"node": "74320878-4302-440c-99ce-5aefefcb1ca2",
"type": "main",
"index": 0
}
]
]
},
"4bb37c98-7400-4e4c-9519-4cafa5555067": {
"main": [
[
{
"node": "d01a7818-dc28-4490-ad5b-e5963cd22cbd",
"type": "main",
"index": 0
}
]
]
},
"74320878-4302-440c-99ce-5aefefcb1ca2": {
"main": [
[
{
"node": "d04fe2ce-a6f3-49d5-a384-39a3a201fa37",
"type": "main",
"index": 0
}
]
]
},
"a8f9264a-5dba-4801-9c1e-e12a59237b8b": {
"main": [
[
{
"node": "d04fe2ce-a6f3-49d5-a384-39a3a201fa37",
"type": "main",
"index": 0
},
{
"node": "524535d0-6d38-4da8-a5c9-e775f4ff4e50",
"type": "main",
"index": 0
}
]
]
},
"32c956cf-781f-4548-96a9-9d758a7f22a3": {
"main": [
[
{
"node": "fd84ad56-55c4-49c4-9e60-368977208c0c",
"type": "main",
"index": 0
}
]
]
},
"5ed5e1c2-726a-43bd-8f49-ed40f3eac9fa": {
"main": [
[
{
"node": "32c956cf-781f-4548-96a9-9d758a7f22a3",
"type": "main",
"index": 0
}
]
]
},
"fd84ad56-55c4-49c4-9e60-368977208c0c": {
"main": [
[
{
"node": "4bb37c98-7400-4e4c-9519-4cafa5555067",
"type": "main",
"index": 0
}
]
]
},
"6472f288-c7e2-49da-bd32-61b91d9528b3": {
"main": [
[
{
"node": "5ed5e1c2-726a-43bd-8f49-ed40f3eac9fa",
"type": "main",
"index": 0
}
]
]
}
}
}자주 묻는 질문
이 워크플로우를 어떻게 사용하나요?
위의 JSON 구성 코드를 복사하여 n8n 인스턴스에서 새 워크플로우를 생성하고 "JSON에서 가져오기"를 선택한 후, 구성을 붙여넣고 필요에 따라 인증 설정을 수정하세요.
이 워크플로우는 어떤 시나리오에 적합한가요?
고급 - 개인 생산성
유료인가요?
이 워크플로우는 완전히 무료이며 직접 가져와 사용할 수 있습니다. 다만, 워크플로우에서 사용하는 타사 서비스(예: OpenAI API)는 사용자 직접 비용을 지불해야 할 수 있습니다.
관련 워크플로우 추천
AI 자기소개서를 사용한 직업 검색 자동화
Google Jobs, RemoteOK 및 GPT-3.5를 사용한 AI 자기소개서 포함 채용 공고 검색 자동화
If
Set
Code
+
If
Set
Code
17 노드Shelly-Ann Davy
개인 생산성
LinkedIn 직업 검색
LinkedIn 직업 검색: 자동 이력서 매칭(GPT/Gemini) + 취업서생성기 + Telegram 알림
If
Set
Code
+
If
Set
Code
33 노드Hojjat Jashnniloofar
개인 생산성
AI 기반 회의 연구 및 일일 아젠다 (Google 캘린더, Attio CRM 및 Slack)
AI 기반 회의 연구 및 일일 아젠다: Google 캘린더, Attio CRM 및 Slack 활용
If
Set
Code
+
If
Set
Code
30 노드Harry Siggins
AI 요약
자동화된 회의 준비
GPT-5 및 Gemini 리서치를 사용한 캘린더에서 Slack까지 Attio CRM 통해 회의 자동 준비
If
Set
Code
+
If
Set
Code
39 노드Harry Siggins
AI 요약
경쟁 가격 모니터링 및 알림 (Bright Data, Sheets, Slack)
Bright Data, Sheets, Slack을 사용하여 경쟁 가격 모니터링 및 알림을 수행합니다.
If
Set
Code
+
If
Set
Code
29 노드Daniel Shashko
시장 조사
귀하의 워크플로우를 GitHub 저장소에 저장
매일 워크플로우 백업을 GitHub에 저장하고 Slack 알림 전송
If
N8n
Set
+
If
N8n
Set
18 노드Andrew
데브옵스