만세력이라는 거대한 벽 — 양력도 음력도 아닌 절기력

 


달력이 세 개 필요한 프로젝트

사주 앱을 만든다고 하면 대부분 "생년월일 입력받아서 계산하면 되는 거 아닌가"라고 생각한다. 나도 처음에는 그랬다. 그런데 실제로 구현에 들어가자 첫 번째 벽이 나타났다. 사주는 양력도 음력도 아닌, 절기력(節氣曆)이라는 전혀 다른 달력 체계를 기준으로 한다.

양력은 태양의 공전을 기준으로 한 달력이고, 음력은 달의 위상을 기준으로 한 달력이다. 절기력은 태양의 황경(黃經)을 기준으로 한 달력이다. 셋 다 "태양"과 관련이 있지만, 날짜를 나누는 기준이 전부 다르다. 사주 앱을 만든다는 것은, 양력 날짜를 입력받아서 음력으로 변환하고, 다시 절기력으로 변환해야 한다는 뜻이다.

이 "세 겹의 달력 변환"이 만세력(萬歲曆) 모듈의 본질이다. 그리고 이것이 사주 앱 개발에서 가장 까다로운 부분이라는 것을, 구현을 시작하고 나서야 제대로 실감했다.

연주(年柱): 1월 1일이 아니라 입춘에 바뀐다

우리가 일상에서 쓰는 "몇 년생"은 양력 1월 1일에 바뀐다. 음력으로는 설날에 바뀐다. 그런데 사주에서 연주(年柱)는 입춘(立春)에 바뀐다. 입춘은 보통 양력 2월 3일에서 5일 사이인데, 매년 날짜가 다를 뿐 아니라 시각까지 달라진다.

여기서 실제 구현의 어려움이 드러난다. 2024년 입춘은 2월 4일 16시 27분이다. 이 시각을 기준으로, 같은 날 16시 26분에 태어난 사람과 16시 28분에 태어난 사람의 연주가 다르다. 1분 차이로 갑진년(甲辰年)과 계묘년(癸卯年)이 갈린다. 그 사람의 띠가 용인지 토끼인지가 1분으로 결정되는 것이다.

이것은 단순히 "2월 4일이면 새해"라고 하드코딩할 수 없다는 뜻이다. 매년 입춘의 정확한 날짜와 시각을 알아야 하고, 입력된 출생 시각과 분 단위로 비교해야 한다. "대략 2월 초"가 아니라 "2024년 2월 4일 16시 27분"이라는 정밀한 데이터가 필요하다.

이 요구사항 하나만으로도, 사주 앱이 일반적인 날짜 계산과 차원이 다른 정밀도를 요구한다는 것을 알 수 있다. 그리고 이 정밀도는 연주에서 끝나지 않는다.

월주(月柱): 24절기 중 절(節) 12개가 경계

월주의 구현은 연주보다 더 까다롭다. 일반적으로 "몇 월"이라고 하면 양력이든 음력이든 1일을 기준으로 달이 바뀐다. 사주에서는 아니다. 24절기 중 "절(節)"에 해당하는 12개의 절기가 월의 경계를 결정한다.

구체적으로 보면 이렇다. 입춘(2월 초)부터 경칩 전까지가 사주의 1월(인월, 寅月)이다. 경칩부터 청명 전까지가 2월(묘월, 卯月). 이런 식으로 12개의 절기가 12개월의 시작점이 된다. 입춘, 경칩, 청명, 입하, 망종, 소서, 입추, 백로, 한로, 입동, 대설, 소한. 이 12개가 사주력의 월 경계다.

같은 날짜를 세 가지 달력으로 보면 완전히 다른 결과가 나온다. 예를 들어 2024년 3월 3일은 양력으로 3월, 음력으로 1월 23일, 사주력으로는 2월(묘월)이다. 양력 월, 음력 월, 사주 월이 전부 다른 것이다. 이 세 가지 체계를 혼동하면 월주가 통째로 틀리고, 월주가 틀리면 그 위에 쌓인 모든 분석이 무너진다.

월주에서도 연주와 같은 문제가 반복된다. 절기의 정확한 시각이 필요하다. 경칩이 3월 5일 10시 23분이라면, 같은 날 10시 22분 출생과 10시 24분 출생의 월주가 다르다. 12개의 절기 각각에 대해 매년 정밀 시각을 확보해야 한다는 뜻이다.

일주(日柱): 60갑자의 끊임없는 순환

연주와 월주가 절기라는 "변동하는 경계"에 의존하는 것과 달리, 일주(日柱)는 비교적 단순한 규칙을 따른다. 60갑자(甲子, 乙丑, 丙寅... 癸亥)가 끊임없이 순환한다. 갑자일 다음은 을축일, 그 다음은 병인일. 60일 주기로 다시 갑자일이 돌아온다.

문제는 이 순환의 "기준점"을 어떻게 잡느냐다. 역사적으로 60갑자 일진은 한 번도 끊긴 적이 없다고 알려져 있다. 중국 은나라 시대부터 지금까지 단 하루도 빠짐없이 이어져왔다는 것이다. 따라서 특정 날짜의 일주를 알면, 거기서부터 날수를 세어 다른 날짜의 일주를 계산할 수 있다.

하지만 직접 구현하려면 몇 가지 함정이 있다. 윤년 처리, 양력-음력 변환 시의 날짜 불일치, 그리고 야자시(23:00 이후)에 의한 일주 변경 문제다. 특히 야자시는 같은 날짜인데 일주가 바뀌는 상황을 만드는데, 이것은 8편에서 자세히 다룬다.

실질적인 구현에서는 만세력 라이브러리가 제공하는 일진 데이터를 활용하되, 반드시 교차 검증을 거쳤다. 기준 날짜 여러 개를 잡아서, 전문 만세력 사이트와 결과가 일치하는지 확인하는 것이다. 한 번이라도 어긋나면 그 이후의 모든 일주가 틀려지므로, 이 검증은 타협할 수 없는 과정이었다.

JS 생태계에서 만세력을 구현한다는 것

설계 단계에서 가장 오래 고민한 부분이 만세력 라이브러리 선택이었다. JavaScript/TypeScript 생태계에서 "사주용 만세력"을 완벽하게 지원하는 단일 라이브러리는 존재하지 않았다. 양력-음력 변환, 절기 정밀 시각, 60갑자 일진, 진태양시 보정 — 이 네 가지를 모두 커버하는 패키지가 없었다는 뜻이다.

Claude에게 NPM에 등록된 한국 음력 관련 라이브러리들을 비교 분석해달라고 요청했다. AI가 각 라이브러리의 지원 범위, 최종 업데이트 시점, 절기 지원 여부, 정밀도, 사용자 수를 표로 정리해줬다. 이 비교표 하나가 의사결정 시간을 극적으로 줄였다.

최종적으로 "조합 전략"을 세웠다. 하나의 라이브러리에 의존하는 대신, 각 영역에 가장 적합한 도구를 조합하는 방식이다. 양력-음력 변환은 korean-lunar-calendar 라이브러리가 담당한다. 이 라이브러리는 1890년부터 2050년까지의 음력 데이터를 내장하고 있어 충분한 범위를 커버한다. 절기 정밀 시각은 한국천문연구원(KASI) 데이터를 별도로 확보하여 사용한다. 진태양시 보정과 서머타임 보정은 자체 구현한다. 그리고 사주 기둥 계산과 분석 엔진은 설계 문서를 기반으로 완전히 자체 구현한다.

이 조합 전략의 핵심 원칙은 "외부 라이브러리는 데이터 소스로만 사용하고, 사주 로직은 직접 통제한다"는 것이었다. 외부 라이브러리의 버그나 업데이트 중단이 사주 계산의 정확도에 영향을 미치는 것을 최소화하기 위해서다.

라이브러리 하나로 안 되는 이유

korean-lunar-calendar는 양력-음력 변환에는 신뢰할 수 있지만, 절기 시각을 분 단위로 제공하지는 않는다. 그런데 사주에서는 앞서 봤듯이 분 단위 정밀도가 필수다. 절기의 "날짜"만 알아서는 입춘 당일 출생자의 연주를 판정할 수 없다.

반대로 KASI 데이터는 절기 시각의 정밀도는 완벽하지만, 양력-음력 변환이나 60갑자 일진 계산 기능은 없다. 각 도구가 자기만의 강점과 한계를 갖고 있는 것이다.

이 상황에서 AI의 역할이 빛났다. "이 라이브러리는 이것에 강하고 저것에 약하다"는 분석을 개별적으로 하는 것은 개발자도 할 수 있다. 하지만 여러 라이브러리의 강점을 조합해서 "이렇게 쓰면 전체를 커버할 수 있다"는 조합 전략을 빠르게 도출하는 것은 AI와의 대화가 훨씬 효율적이었다.

실제로 라이브러리를 하나씩 설치해서 테스트하는 시간보다, AI와 30분 동안 대화하면서 후보를 좁히고 조합 방안을 설계하는 것이 결과적으로 더 빨랐다. 물론 최종 검증은 직접 코드를 돌려봐야 하지만, "어디서부터 시작할 것인가"의 방향 설정에서 시간을 크게 절약했다.

세 겹의 변환이 만드는 복잡도

만세력 모듈의 실제 동작을 정리하면 이렇다. 사용자가 양력 생년월일시를 입력한다. 먼저 양력을 음력으로 변환한다(korean-lunar-calendar). 동시에 해당 연도의 절기 정밀 시각을 조회한다(KASI 데이터). 입력된 시각과 절기 시각을 비교하여 연주를 결정한다. 같은 방식으로 월주를 결정한다. 일주는 60갑자 순환으로 계산한다. 시주는 일간과 출생 시각으로 결정하되, 진태양시 보정을 적용한다.

이 파이프라인의 각 단계가 이전 단계에 의존하며, 어느 한 단계에서 오류가 발생하면 최종 결과가 통째로 틀려진다. 특히 연주가 틀리면 월주 계산의 기반이 흔들리고, 일주가 틀리면 시주와 십신 분석이 전부 무너진다. 만세력의 정확도가 곧 앱 전체의 신뢰도인 이유다.

설계 문서에 이 파이프라인을 다이어그램으로 그려둔 것이 구현 단계에서 큰 도움이 됐다. 각 단계를 독립적인 함수로 구현하고, 단계별로 단위 테스트를 작성할 수 있었기 때문이다. "전체를 한꺼번에 테스트"하는 것이 아니라 "변환 단계별로 정확한지 검증"하는 전략이 가능해진 것이다.

이 과정에서 배운 것

첫째, 사주 앱 개발의 핵심 난이도는 "분석 알고리즘"이 아니라 "달력 변환"에 있다. 오행 비율 계산이나 십신 판정은 규칙이 명확하다. 하지만 양력을 절기력으로 변환하는 것은 천문학적 데이터에 의존하는, 근본적으로 다른 종류의 문제다.

둘째, 단일 라이브러리에 의존하지 않는 조합 전략이 이 도메인에서는 현실적인 선택이다. 완벽한 라이브러리를 기다리는 것보다, 각 영역의 최선을 조합하는 것이 더 빠르고 더 정확하다.

셋째, AI와의 라이브러리 비교 분석은 "직접 써보기 전 단계"에서 가장 효율적이다. 설치하고 테스트하는 시간을 줄여주는 것이 아니라, "무엇을 설치하고 테스트할 것인가"를 빠르게 결정하게 해준다.

넷째, 만세력 파이프라인의 단계별 분리가 테스트 가능성의 핵심이다. 복잡한 시스템을 한 덩어리로 만들면 "어디서 틀렸는지"를 찾는 것이 불가능에 가깝다.

다음 편 예고

만세력의 구조를 잡았으니, 이제 절기 데이터의 "정밀도"라는 더 구체적인 전쟁이 기다린다. 한국천문연구원(KASI) 데이터를 어떻게 확보했는지, 왜 빌드 타임에 130년분을 JSON으로 미리 수집하는 전략을 택했는지, 그리고 분 단위 정밀도가 실제로 사주를 바꾸는 사례를 7편에서 다룬다.

댓글

이 블로그의 인기 게시물

사랑을 직접 올리지 않는 설계

감정을 변수로 옮기다 — 3계층 감정 모델

시작의 충동 — "타로 웹앱을 만들어볼까?"