서비스 : https://korea-holidays-api.greenteapie2023.workers.dev
GitHub : https://github.com/GreenteaPIE/korea-holidays-api
npm / PyPI :korea-holidays
시작하며
배송 예정일, 정산일, 예약 가능일처럼 “영업일 기준 며칠 뒤”를 계산해야 하는 일은 생각보다 흔하다. 그런데 막상 만들려고 하면 매번 같은 곳에서 막힌다. 공휴일 데이터다.
설날과 추석은 음력이라 해마다 날짜가 바뀌고, 대체공휴일이 붙고, 선거일과 임시공휴일은 그때그때 생긴다. 결국 다들 연초에 공휴일 목록을 코드에 하드코딩하고, 임시공휴일이 발표되면 허둥지둥 고친다.
그래서 API 키 없이 바로 쓸 수 있는 공휴일·영업일 API 를 만들었다. 처음엔 “날짜 목록 JSON 하나면 끝나겠지” 싶었는데, 만들수록 생각보다 고민할 게 많았다. 그 과정을 정리해 본다.
1. 대체공휴일 규칙은 공휴일마다, 해마다 다르다
대체공휴일은 한 번에 생긴 제도가 아니다. 대상이 조금씩 늘어 왔고, 공휴일마다 대체 조건도 다르다.
| 시기 | 대상 | 언제 대체되나 |
|---|---|---|
| 2014 ~ | 설날·추석 | 일요일이나 다른 공휴일과 겹칠 때 (토요일은 해당 없음) |
| 2014 ~ | 어린이날 | 토·일요일이나 다른 공휴일과 겹칠 때 |
| 2021.8 ~ | 삼일절·광복절·개천절·한글날 | 토·일요일과 겹칠 때 |
| 2023.5 ~ | 부처님오신날·기독탄신일 | 토·일요일과 겹칠 때 |
| 2026 ~ | 노동절·제헌절 (새로 공휴일이 됨) | 토·일요일과 겹칠 때 |
그래서 같은 “토요일에 걸린 추석”이라도 대체공휴일이 없다. 2026년 추석(9/24~26, 목~토)이 그렇다. 반대로 2027년에는 노동절·제헌절·광복절·개천절·한글날·성탄절이 줄줄이 주말과 겹쳐서 대체공휴일이 7번이나 생긴다.
처음엔 이 규칙을 코드로 계산할까 했지만 그만뒀다. 이유는 두 가지다.
- 예외가 너무 많다. 2017년엔 추석 연휴 첫날이 개천절과 겹쳐 대체공휴일이 10/6 로 밀렸고, 2025년엔 어린이날과 부처님오신날이 같은 날이라 대체공휴일이 하루만 생겼다.
- 임시공휴일은 규칙이 없다. 국무회의에서 정하면 그만이다.
그래서 공휴일은 연도별 데이터로 관리하고, 대신 데이터가 틀리지 않았는지 자동으로 검증하는 쪽을 택했다.
2. 데이터를 믿을 수 있게: 공식 데이터와 매주 자동 대조
공휴일 API 는 데이터가 한 번만 틀려도 신뢰를 잃는다. 그래서 GitHub Actions 로 매주 월요일 공공데이터포털(한국천문연구원 특일 정보)과 내 데이터를 비교하게 했다.
- 다른 날이 있으면 GitHub 이슈를 자동으로 만들고, 다시 일치하면 이슈를 자동으로 닫는다.
- 데이터 파일을 고쳐서 푸시하면 그때도 지원하는 모든 연도를 바로 대조한다.
2015 ~ 2023 년 과거 데이터를 넣고 처음 돌렸을 때, 2016 ~ 2027 년은 모두 일치했는데 2015년에 1건이 걸렸다.
2015년: 공식 16건, 누락 0건, 초과 1건
- 2015-08-14 임시공휴일(광복 70주년)
내 데이터가 틀린 줄 알았는데 찾아보니 반대였다. 2015년 8월 14일은 광복 70주년 임시공휴일로 실제로 쉬었던 날인데, 공식 특일 데이터에 빠져 있었다. 이런 날은 근거와 함께 “알려진 예외” 목록에 남기도록 했다. 자동 검증이 있어도 결국 판단은 사람이 해야 한다는 걸 배웠다.
3. 연차 꿀조합: “연차 N일로 최장 며칠 쉴 수 있을까”
매년 연말이면 “내년 연차 꿀조합” 글이 돈다. 이걸 계산해 주는 기능을 넣었다.
GET /vacation-tips?year=2027&leaveDays=2
→ 2027-09-11 ~ 09-19, 9일 (연차: 9/13 월, 9/17 금)
2027년 추석(9/14~16, 화~목) 앞뒤 월·금에 연차를 쓰면 9일을 쉰다. 계산은 이렇게 했다.
- 그 해의 모든 날을 영업일 / 주말 / 공휴일 로 미리 구분해 배열에 담는다.
- 각 날짜에서 시작해 연차를
leaveDays개까지 쓰면서 앞으로 늘려 간다. 이때 앞날이 쉬는 날이면 시작점으로 삼지 않고, 다음 날이 영업일인 곳에서만 끊는다. 그래야 쉬는 날을 버리지 않는 “최대 구간”만 남는다. - 공휴일이 하루도 없는 구간은 그냥 주말이라 빼는데, 이건 공휴일 개수의 누적합으로 O(1) 에 확인한다.
- 길이가 긴 순(같으면 연차를 덜 쓰는 순)으로 정렬하고, 겹치지 않게 골라 상위 몇 개를 추천한다.
토요일에 일하는 업종(weekend=sun)이나 회사 창립기념일(include=…)도 옵션으로 반영된다.
일반 사용자용으로 연차 꿀조합 계산기 페이지도 따로 만들었다.
4. Cloudflare Workers 무료 플랜의 CPU 10ms
서버는 Cloudflare Workers 에 올렸다. 무료이고 전 세계에 빠르게 배포되지만, 무료 플랜은 요청 하나에 CPU 10ms 라는 제한이 있다.
캘린더 구독용 iCal(.ics) 파일을 만들었더니 20ms 가 나왔다. 원인은 iCal 규격의 “한 줄 75바이트” 규칙이었다.
한글이 중간에 잘리지 않게 글자마다 TextEncoder 로 UTF-8 바이트 수를 구했는데, 이게 글자 수만큼 반복되니 느렸다.
// 전: 글자마다 인코더 호출
const size = encoder.encode(char).length
// 후: 코드 포인트로 바이트 수를 바로 계산
const size = cp < 0x80 ? 1 : cp < 0x800 ? 2 : cp < 0x10000 ? 3 : 4
여기에 “18자 이하 줄은 75바이트를 넘을 수 없으니 건너뛴다”는 조건을 더해 1.3ms 로 줄였다. 연차 추천도 처음엔 반복문 안에서 날짜 구분을 매번 계산했는데, 1번처럼 한 번만 계산해 두는 방식으로 바꿔 1ms 안팎이 됐다.
5. 하나의 데이터로 npm · PyPI · 구글 시트 · AI 비서까지
API 서버가 없어도 쓸 수 있게 같은 계산을 여러 형태로 제공한다.
- npm / PyPI 패키지
korea-holidays(둘 다 의존성 0개) - 구글 시트 함수
=KR_ADDBUSINESSDAYS(A2, 3) - MCP 서버 — Claude 같은 AI 비서가 도구로 호출한다. AI 는 한국 대체공휴일을 자주 틀리는데, 연결해 두면 추측하지 않고 계산한다.
여러 곳에 같은 데이터가 있으면 언젠가 어긋난다. 그래서 원칙을 하나 정했다. 원본은 TypeScript 데이터 파일 하나다.
- Python 패키지의 데이터는 TypeScript 데이터에서 자동으로 생성하고, 둘이 다르면 테스트가 실패한다.
- npm 과 PyPI 의 버전이 다르면 그것도 테스트가 실패한다.
- 계산 로직은 TypeScript 와 Python 에 따로 있어서, 연도·옵션·날짜 조합 2,300 가지가 넘는 경우를 두 언어로 돌려 결과가 모두 같은지 확인했다.
MCP 서버는 공식 MCP SDK 클라이언트로 테스트에서 직접 붙여 보고, 도구 목록 조회와 호출이 되는지 확인했다.
마치며
“공휴일 목록 JSON” 하나로 시작했는데, 만들다 보니 데이터의 정확성을 어떻게 지킬지가 가장 큰 고민이었다. 코드보다 데이터를 믿을 수 있게 만드는 장치(자동 대조, 생성된 데이터의 일치 테스트, 언어 간 결과 비교)에 시간을 더 썼고, 그게 맞는 선택이었다고 생각한다.
공휴일이 빠졌거나 틀린 걸 발견하면 GitHub 이슈로 알려 주시면 바로 반영하겠습니다.
GreenteaPIE