노션 캘린더 공휴일 및 국경일 추가 자동화툴 없이 무료로

작년 즈음 마음에 드는 자기 관리용 노션 탬플릿을 구매해서 할 일 관리부터 프로젝트, 일정 모두 노션에 기입하고 있다. 그러다보니 한 번 기입해 둔 일정은 모두 노션 캘린더에서 확인이 가능한데 노션 캘린더 솔직히 너무 불편하다…🫠
특히 구글 캘린더나 네이버 캘린더와 달리 한국 공휴일이 자동으로 표시되지 않는다는 게 제일 불편했다. 일정을 짤 때마다 다른 캘린더를 확인해야 다가오는 공휴일을 확인할 수 있었기 때문이다.
그래서 공휴일 정보를 노션 캘린더에 자동으로 연동시킬 방법을 찾다 보니 Make나 Zapier 같은 자동화 툴로 가능하다는 포스팅이 있었다. 문제는 유료 자동화 툴을 쓰지 않는다는 거였고…고민하다가 클로드에게 물어서 Google Apps Script를 활용한 방법을 찾았다. 이 방법은 완전히 무료고, 한 번 설정해두면 매년 자동으로 공휴일이 추가된다.
Table of Contents
이 방법이 왜 좋은가?
- 무료 – Google Apps Script는 구글 계정만 있으면 누구나 무료로 사용 가능하다
- 자동화 – 한 번만 설정하면 매년 1월 1일에 자동으로 그 해의 공휴일이 추가된다
- 정확한 데이터 – 공공데이터포털에서 제공하는 정부 공식 공휴일 정보를 사용한다
- 원하는 대로 수정 가능 – 코드를 조금만 수정하면 다른 속성도 자동으로 설정할 수 있다
준비할 것
- 노션 계정
- 구글 계정
- 공공데이터포털 회원 가입
1. 공공데이터포털 가입
먼저 공휴일 데이터를 가져올 수 있도록 공공데이터포털에 가입한 후 API 키를 받아야 한다. API라는 것은 “공공데이터에 접근할 수 있는 열쇠” 정도로 생각하면 된다.
1)공휴일 API 서비스 신청하기
- 상단 검색창에 한국천문연구원 특일 정보 검색
- “활용신청” 버튼을 클릭
- 신청 정보를 입력하고 제출하면 바로 승인된다. (활용목적은 “일정 관리” 정도로)
2)API 키 확인
- 마이페이지로 이동
- “API 신청현황” 메뉴 클릭
- 승인된 서비스에서 “일반 인증키(Decoding)” 탭을 클릭
- 나오는 긴 문자열을 복사
⚠️ 중요: “Decoding”된 키를 사용해야 한다! Encoding된 키를 쓰면 오류가 발생한다.
2. 노션 API 연결
외부 프로그램이 노션에 데이터를 추가할 수 있도록 작업한다.
1)Notion API 설정
- Notion Developers 사이트 접속
- 노션 계정으로 로그인
- “My integrations” → “New integration” 클릭
- 이름 입력 (예: “공휴일 자동화” 같이 알아보기 쉬운 이름)
- “Submit” 클릭
- 생성되면 나오는 “Internal Integration Secret” 복사 (노션 API 키)
2)공휴일을 저장할 데이터베이스 생성
공휴일이 추가될 노션 데이터베이스가 필요하다. 기존에 사용 중인 스케쥴 관리 데이터베이스에 연동하거나, 없다면 새로 만든다.
필수 속성:
- 이름 (Title 타입)
- 날짜 (Date 타입)
3)데이터베이스에 Integration 연결하기
- 데이터베이스 페이지 우상단의 “…” (점 세 개) 메뉴를 클릭
- “Add connections” 또는 “연결 추가” 선택
- 아까 만든 Integration(예: “공휴일 자동화”)을 선택
4)데이터베이스 ID 찾기

데이터베이스 페이지에서 [공유] → [링크 복사] 를 누른다.

브라우저에 붙여넣었을 때 www.notion.so/여기에 표시되는 32자리문자가 ID?v=….
3.구글 앱스 스크립트로 자동화 코드 만들기
1)새 프로젝트 만들기
- Google Apps Script에 접속
- 구글 계정으로 로그인
- “새 프로젝트” 버튼을 클릭
- 프로젝트 이름을 “노션 캘린더 공휴일 추가” 등으로 알아보기 쉽게 작성한다.
2)코드 입력하기
기본으로 있는 function myFunction() {} 코드를 전부 지우고, 아래 코드를 통째로 복사해서 붙여넣는다.
이 코드가 하는 일:
- 공공데이터포털에서 올해 공휴일 정보 수집
- 가져온 정보를 노션 데이터베이스 형식에 맞게 정리
- 노션 데이터베이스에 하나씩 추가
javascript
// ================== 설정 부분 (여기만 수정하세요) ==================
const CONFIG = {
// 공공데이터포털 API 키 (Decoding된 키 사용)
PUBLIC_API_KEY: 'YOUR_PUBLIC_API_KEY_HERE',
// Notion API 키 (Integration Secret)
NOTION_API_KEY: 'YOUR_NOTION_API_KEY_HERE',
// Notion 데이터베이스 ID (32자리)
NOTION_DATABASE_ID: 'YOUR_NOTION_DATABASE_ID_HERE'
};
// ================== 메인 함수 (연 2회: 7월, 12월 실행) ==================
/**
* 공휴일을 노션에 자동으로 추가하는 메인 함수
* - 7월 1일: 다음 해 공휴일 추가 (API 업데이트 반영)
* - 12월 1일: 현재년/다음년 재확인 (누락 방지)
* - 중복 체크를 통해 이미 있는 공휴일은 건너뜀
*/
function addHolidaysToNotion() {
try {
const now = new Date();
const currentMonth = now.getMonth() + 1; // 1월=1, 12월=12
// 7월과 12월에만 실행
if (currentMonth !== 7 && currentMonth !== 12) {
console.log(`현재 ${currentMonth}월입니다. 7월과 12월에만 실행됩니다.`);
return;
}
console.log(`=== ${currentMonth}월 공휴일 자동 추가 시작 ===`);
const currentYear = now.getFullYear();
const years = [currentYear, currentYear + 1]; // 현재년 + 다음년
let totalAdded = 0; // 추가된 공휴일 수
let totalSkipped = 0; // 건너뛴 공휴일 수
// 현재년과 다음년 모두 처리
for (const year of years) {
console.log(`\n${year}년 공휴일 조회 중...`);
const holidays = getHolidaysFromAPI(year);
console.log(`${holidays.length}개의 공휴일 발견`);
// 각 공휴일 처리
for (const holiday of holidays) {
// 중복 체크: 이미 노션에 있는지 확인
if (isHolidayExists(holiday)) {
console.log(`⏭️ ${holiday.name} (${holiday.date}) - 이미 존재함, 건너뜀`);
totalSkipped++;
} else {
// 노션에 추가
if (addHolidayToNotion(holiday)) {
totalAdded++;
}
Utilities.sleep(1000); // API 호출 간격 조절 (1초 대기)
}
}
}
console.log(`\n완료! 추가: ${totalAdded}개, 건너뜀: ${totalSkipped}개`);
} catch (error) {
console.error('오류 발생:', error.toString());
}
}
// ================== 강제 실행 함수 (테스트용) ==================
/**
* 월 체크 없이 강제로 실행하는 함수
* 테스트나 수동 실행 시 사용
*/
function addHolidaysToNotionForce() {
try {
console.log('=== 공휴일 강제 추가 시작 ===');
const currentYear = new Date().getFullYear();
const years = [currentYear, currentYear + 1];
let totalAdded = 0;
let totalSkipped = 0;
for (const year of years) {
console.log(`\n${year}년 공휴일 조회 중...`);
const holidays = getHolidaysFromAPI(year);
console.log(`${holidays.length}개의 공휴일 발견`);
for (const holiday of holidays) {
if (isHolidayExists(holiday)) {
console.log(`⏭️ ${holiday.name} (${holiday.date}) - 이미 존재함`);
totalSkipped++;
} else {
if (addHolidayToNotion(holiday)) {
totalAdded++;
}
Utilities.sleep(1000);
}
}
}
console.log(`\n완료! 추가: ${totalAdded}개, 건너뜀: ${totalSkipped}개`);
} catch (error) {
console.error('오류 발생:', error.toString());
}
}
// ================== 중복 체크 함수 ==================
/**
* 노션 데이터베이스에 해당 공휴일이 이미 존재하는지 확인
* @param {Object} holiday - 공휴일 객체 {name, date}
* @return {boolean} 존재하면 true, 없으면 false
*/
function isHolidayExists(holiday) {
const url = `https://api.notion.com/v1/databases/${CONFIG.NOTION_DATABASE_ID}/query`;
// 이름과 날짜가 모두 일치하는 항목 검색
const payload = {
'filter': {
'and': [
{
'property': '이름',
'title': {
'equals': holiday.name
}
},
{
'property': '날짜',
'date': {
'equals': holiday.date
}
}
]
},
'page_size': 1 // 하나만 찾으면 됨
};
const options = {
'method': 'POST',
'headers': {
'Authorization': `Bearer ${CONFIG.NOTION_API_KEY}`,
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
'payload': JSON.stringify(payload)
};
try {
const response = UrlFetchApp.fetch(url, options);
const data = JSON.parse(response.getContentText());
return data.results.length > 0; // 결과가 있으면 이미 존재
} catch (error) {
console.error(`중복 체크 오류 (${holiday.name}):`, error);
return false; // 오류 발생 시 추가 시도
}
}
// ================== 공휴일 API 호출 함수 ==================
/**
* 공공데이터포털에서 특정 연도의 공휴일 정보를 가져옴
* @param {number} year - 조회할 연도 (예: 2025)
* @return {Array} 공휴일 객체 배열 [{name, date, originalDate}, ...]
*/
function getHolidaysFromAPI(year) {
// 두 가지 API 엔드포인트 (하나 실패 시 다른 것 시도)
const url1 = `https://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getRestDeInfo`;
const url2 = `https://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getHoliDeInfo`;
// API 요청 파라미터
// 중요: serviceKey도 encodeURIComponent로 인코딩 필요!
const params = {
'serviceKey': CONFIG.PUBLIC_API_KEY, // API 인증키
'solYear': year.toString(), // 조회 연도
'numOfRows': 50, // 최대 결과 수
'_type': 'xml' // 응답 형식
};
// URL 파라미터 문자열 생성 (모든 값 인코딩)
const paramString = Object.keys(params)
.map(key => `${key}=${encodeURIComponent(params[key])}`)
.join('&');
let fullUrl = `${url1}?${paramString}`;
try {
console.log('첫 번째 API 시도');
let response = UrlFetchApp.fetch(fullUrl);
let xmlText = response.getContentText();
// 에러 없고 데이터가 있으면 파싱
if (!xmlText.includes('SERVICE_ERROR') && !xmlText.includes('NOT_REGISTERED')) {
const holidays = parseHolidayXML(xmlText);
if (holidays.length > 0) {
console.log('첫 번째 API 성공');
return holidays;
}
}
// 첫 번째 API 실패 시 두 번째 시도
console.log('두 번째 API 시도');
fullUrl = `${url2}?${paramString}`;
response = UrlFetchApp.fetch(fullUrl);
xmlText = response.getContentText();
const holidays = parseHolidayXML(xmlText);
return holidays;
} catch (error) {
console.error('공휴일 API 호출 실패:', error);
return [];
}
}
// ================== XML 파싱 함수 ==================
/**
* API 응답 XML에서 공휴일 정보를 추출
* @param {string} xmlText - API 응답 XML 문자열
* @return {Array} 공휴일 객체 배열
*/
function parseHolidayXML(xmlText) {
const holidays = [];
try {
// XML에서 <item> 태그들 추출
const itemMatches = xmlText.match(/<item>[\s\S]*?<\/item>/g);
if (!itemMatches) {
console.log('XML에서 공휴일 데이터를 찾을 수 없습니다.');
return holidays;
}
// 각 item에서 공휴일 정보 추출
itemMatches.forEach(item => {
const dateNameMatch = item.match(/<dateName>([^<]+)<\/dateName>/); // 공휴일 이름
const locDateMatch = item.match(/<locdate>([^<]+)<\/locdate>/); // 날짜 (yyyyMMdd)
if (dateNameMatch && locDateMatch) {
const name = dateNameMatch[1];
const dateStr = locDateMatch[1];
// 날짜 형식 변환: 20250101 → 2025-01-01
const formattedDate = `${dateStr.slice(0,4)}-${dateStr.slice(4,6)}-${dateStr.slice(6,8)}`;
holidays.push({
name: name, // 공휴일 이름 (예: "설날")
date: formattedDate, // 노션용 날짜 (2025-01-01)
originalDate: dateStr // 원본 날짜 (20250101)
});
}
});
} catch (error) {
console.error('XML 파싱 오류:', error);
}
return holidays;
}
// ================== 노션 추가 함수 ==================
/**
* 공휴일을 노션 데이터베이스에 추가
* @param {Object} holiday - 공휴일 객체 {name, date}
* @return {boolean} 성공 시 true, 실패 시 false
*/
function addHolidayToNotion(holiday) {
const url = 'https://api.notion.com/v1/pages';
// 노션 페이지 데이터 구조
const payload = {
'parent': {
'database_id': CONFIG.NOTION_DATABASE_ID
},
'properties': {
// 제목 속성 (공휴일 이름)
'이름': {
'title': [
{
'text': {
'content': holiday.name
}
}
]
},
// 날짜 속성
'날짜': {
'date': {
'start': holiday.date
}
},
// 선택 속성 (명료화 → 일정)
// 필요 없으면 이 부분 전체 삭제 가능
'명료화': {
'select': {
'name': '일정'
}
}
}
};
const options = {
'method': 'POST',
'headers': {
'Authorization': `Bearer ${CONFIG.NOTION_API_KEY}`,
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
'payload': JSON.stringify(payload)
};
try {
const response = UrlFetchApp.fetch(url, options);
const responseCode = response.getResponseCode();
if (responseCode === 200) {
console.log(`✅ ${holiday.name} (${holiday.date}) 추가 완료`);
return true;
} else {
console.error(`❌ ${holiday.name} 추가 실패: ${response.getContentText()}`);
return false;
}
} catch (error) {
console.error(`❌ ${holiday.name} 추가 중 오류:`, error);
return false;
}
}
// ================== 테스트 함수 ==================
/**
* API 연결만 빠르게 테스트
*/
function testAPI() {
console.log('=== API 연결 테스트 시작 ===');
const year = new Date().getFullYear();
const holidays = getHolidaysFromAPI(year);
console.log('공휴일 API 테스트 결과:', holidays.length > 0 ? '✅ 성공' : '❌ 실패');
if (holidays.length > 0) {
console.log(`${holidays.length}개의 공휴일 발견`);
console.log('첫 번째 공휴일:', holidays[0]);
}
console.log('=== API 연결 테스트 완료 ===');
}
3)설정값 수정하기
코드 맨 위의 CONFIG 부분만 수정하면 된다. 아까모아둔 세 가지 정보가 필요하다.
javascript
const CONFIG = {
PUBLIC_API_KEY: '공공데이터포털_Decoding_키',
NOTION_API_KEY: '노션_Integration_Secret',
NOTION_DATABASE_ID: '노션_데이터베이스_32자리_ID'
};
따옴표 안에 복사한 키를 그대로 붙여넣으면 된다.
4)노션 속성 이름 맞추기
코드 아래쪽에 보면 addHolidayToNotion 함수가 있다. 여기서 노션에 추가될 때 어떤 속성에 어떤 값이 들어갈지 정해져 있다.
javascript
'properties': {
'이름': { // 데이터베이스의 제목 속성 이름
'title': [...]
},
'날짜': { // 데이터베이스의 날짜 속성 이름
'date': {...}
},
}
각자 준비한 데이터베이스에 맞게 수정하면 된다.
- 제목 속성 이름이 “할 일”이라면
'이름'→'할 일' - 날짜 속성 이름이 “Date”라면
'날짜'→'Date'
속성 이름은 대소문자까지 정확히 일치해야 한다!
4.테스트
코드가 제대로 작동하는지 확인해볼 차례다. 단계별로 천천히 테스트해보자.
1)구글에 권한 주기
처음 실행할 때는 구글 Apps Script가 외부(노션) API를 호출할 수 있도록 권한을 줘야 하다.
- 함수 선택 드롭다운(코드 위쪽)에서
testAPI선택 - “실행” 버튼(▶️) 클릭
- 권한 요청 팝업이 뜨면:
- “검토” 클릭
- “안전하지 않은 앱으로 이동” 클릭
- “허용” 클릭
2)API 연결 확인
Ctrl+Enter(맥은 Cmd+Enter)를 눌러 실행 로그를 확인해보자. 로그에서 이런 내용이 보여야 정상이다:
응답 코드: 200← 성공!찾은 공휴일 개수: 18(또는 다른 숫자)
3)실행 테스트
이제 실제로 노션에 공휴일이 추가되는지 테스트해보자.
- 함수 선택에서
addHolidaysToNotionForce선택 - “실행” 클릭
- 노션 데이터베이스에 공휴일이 추가되었는지 확인
로그에 이런 메시지가 나타나면 성공이다:
완료! 18/18개의 공휴일이 추가되었습니다.
5. 자동 실행설정하기
공공데이터포털의 공휴일 정보는 매년 1회 갱신된다. 처음에 매년 1월 1일에 그 해의 공휴일을 추가하도록 설정했는데, 실제 사용하다보니 다음 해 초반(특히 설 연휴)의 공휴일을 미리 알 수 없는 문제가 있었다.
따라서 자동 갱신 주기를 조정할 필요가 있었는데, 한국천문연구원은 매년 3월 다음 해의 달력 정보를 발표하고 이후에 공공데이터포털의 정보가 업데이트되는 것으로 확인되었다. 정확한 시점은 알 수 없지만 대략 3~6월 사이에 내년의 기념일 정보가 업데이트되는 것으로 보인다. 그러므로 자동실행 시점은 매년 7월 1일로 설정하고, 혹시라도 누락이 발생하는 경우를 놓치지 않기 위해 12월 1일에 다시 한 번 스크립트를 실행하게 설정하기로 했다.
1)트리거 추가
트리거는 “특정 시간에 자동으로 실행하라”는 예약 설정이다.
- 왼쪽 메뉴에서 “트리거” (시계 모양 아이콘 ⏰) 클릭
- 우하단의 “트리거 추가” 버튼 클릭
- 다음과 같이 설정:
실행할 함수: addHolidaysToNotion
배포에서 실행: Head
이벤트 소스: 시간 기반
시간 기반 트리거 유형: 월 기준 타이머
날짜: 1일
시간: 오전 9시~오전 10시
- “저장” 클릭
2) 작동 원리
설정한 트리거는 이렇게 작동하다:
- 매월 1일 오전 9-10시 사이에 스크립트가 자동 실행
- 코드에서 7월과 12월인지 확인
- 7월과 12월에만 실제로 공휴일 추가
- 나머지 달은 로그만 남기고 종료
트리거로 인해 스크립트는 매월 1일마다 실행되지만 실제로 공휴일을 추가하는 건 코드를 통해 7월, 12월로 한정하게 된다.
이렇게 유료 자동화 도구 없이도 앱스크립트를 통해 노션 데이터베이스에 공휴일 정보를 추가할 수 있다. 노션 캘린더에서 해당 데이터베이스의 일정을 볼 수 있도록 표시해 놓으면 노션 캘린더 어플에서도 확인할 수 있다! 그렇게 설정해서 1년동안 꾸역꾸역 쓰고 있었는데…결국 노션 캘린더 어플의 처참하기 짝이 없는 UI 불편함으로 인해 구글 캘린더로 넘어왔다!!!🤣🤣
그러나 개인 대소사와 일정을 전부 노션에 쑤셔넣는 삶에 익숙해졌기 때문에, 노션과 캘린더 어플이 연동되지 않으면 정말 난감한 상황이었다. 그래서 결국 노션 데이터베이스의 일정 데이터를 구글 캘린더에 연동하는 스크립트를 새로 만들었다. 그 방법은 또 따로 소개하겠다.