[개발][HomeLab] n8n과 Python으로 음력 생일 Google Calendar에 평생 자동 박제하기 (시행착오 포함)
매년 돌아오는 부모님의 음력 생일, 매번 달력을 찾아보고 캘린더에 수동으로 등록하는 것이 번거로웠다. 이번 기회에 n8n과 Python(FastAPI) 을 활용해, 매년 1월 1일에 그 해의 음력 생일을 자동으로 계산해서 Google Calendar에 등록하는 시스템을 구축했다.
이 글은 그 과정에서의 아키텍처 설계와, 꽤나 고생했던 시행착오들을 정리한 기록이다.
1. 아키텍처 구성
- 서버: Intel N100 미니 PC (Ubuntu Server + Docker)
- API 서버: Python (FastAPI +
korean_lunar_calendar라이브러리) - 자동화 도구: n8n (Docker)
- 목표:
- n8n이 매년 1월 1일에 트리거 발생
- Python 서버에 "올해 이 음력 날짜가 양력으로 언제야?" 요청
- 반환받은 날짜를 Google Calendar에 등록 (중복 방지 처리 포함)
2. Python 음력 변환 API 서버 구축 (Docker)
n8n 자체적으로는 한국의 복잡한 음력(윤달, 평달, 작은 달 등)을 계산하기 어렵다. 따라서 Python의 korean_lunar_calendar 라이브러리를 사용하는 마이크로서비스를 N100 서버에 띄우기로 했다.
1) Dockerfile & docker-compose
안정성을 위해 Python 3.11-slim 이미지를 사용했다.
시행착오 1: 호스트 바인딩 (0.0.0.0) 처음에 uvicorn main:app으로만 실행했더니, n8n(외부 컨테이너)에서 접속이 불가능했다. 컨테이너 내부의 localhost는 외부와 격리되어 있기 때문이다. --host 0.0.0.0 옵션을 추가하여 외부 접속을 허용해야 한다.
YAML
# docker-compose.yml
version: '3.8'
services:
lunar-api:
build: .
container_name: lunar-api
ports:
- "8000:8000"
restart: unless-stopped
2) 핵심 로직 (main.py)과 '작은 달' 문제 해결
가장 큰 난관은 "음력 30일이 없는 달(작은 달)" 처리였다. 음력 9월 30일이 생일인 경우, 어떤 해에는 9월이 29일까지밖에 없어 에러가 발생했다.
해결책:
- 윤달 계산이 실패하면 평달로 자동 재시도.
- 30일이 없는 달(29일까지만 있는 달)인 경우, 자동으로 29일로 보정해서 계산하도록 로직 추가.
Python
# main.py (최종 수정본)
@app.post("/convert")
def convert_lunar_to_solar(date: LunarDateRequest):
try:
# 1. 기본 변환 시도
is_valid = calendar.setLunarDate(date.year, date.month, date.day, date.intercalation)
# 2. [예외처리] 30일이 없는 작은 달의 경우 -> 29일로 변경해서 재시도
if not is_valid and date.day == 30:
is_valid = calendar.setLunarDate(date.year, date.month, 29, date.intercalation)
# 3. [예외처리] 윤달 요청했으나 윤달이 없는 해 -> 평달로 변경
if not is_valid and date.intercalation:
is_valid = calendar.setLunarDate(date.year, date.month, date.day, False)
# 평달로 바꿨는데 또 작은 달인 경우
if not is_valid and date.day == 30:
is_valid = calendar.setLunarDate(date.year, date.month, 29, False)
if not is_valid:
raise ValueError("변환 실패")
# ... (이하 리턴 로직)
3. n8n 워크플로우 구성 및 에러 해결
1) Schedule Trigger (Cron)
매년 1월 1일 자정에 실행되도록 Cron Expression을 설정했다.
- Expression:
0 0 0 1 1 *(초 분 시 일 월 요일)
2) Google Calendar ID 포맷 에러 (Critical)
중복 등록을 막기 위해 Google Calendar 노드의 ID 옵션을 사용했다. 처음에는 가독성을 위해 언더바(_)를 사용했다.
- 시도한 ID:
lunar_{{year}}_{{month}}_{{day}} - 결과:
400 Bad Request (Invalid resource id value)
원인 및 해결: Google Calendar API 정책상, ID에는 언더바(_)를 사용할 수 없다. (숫자 0-9와 소문자 a-v만 허용). 따라서 언더바를 제거하고 다 붙여 쓰는 방식으로 수정하여 해결했다.
- 수정한 ID:
lunar{{year}}{{month}}{{day}}(예: `lunar2026101)
4. 최종 결과
이제 시스템은 다음과 같이 동작한다.
- 등록된 가족들의 음력 생일 리스트를 Python 서버로 보낸다.
- Python 서버는 윤달 여부와 작은 달(29일/30일)을 판단해 정확한 양력 날짜를 돌려준다.
- n8n은 Google Calendar에 일정을 등록한다. (이미 등록된 경우
ID덕분에 중복 생성되지 않음)
매년 1월 1일 00:00에 n8n이 자동으로 음력 생일인 일정을 내 캘린더에 등록한다.
이제 평생 음력 생일을 챙기기 위해 달력을 뒤적거릴 필요가 없어졌다.

끝.