← 목록

사주 앱마다 결과가 다른 이유 — 사주·자미두수·별자리 계산 엔진을 Go로 다시 쓰며

2024년 2월 4일 17시 20분에 서울에서 태어난 사람의 띠는 기준에 따라 다릅니다.

  • 1월 1일 기준: 2024년 갑진년, 용띠
  • 설날(2024년 2월 10일) 기준: 아직 계묘년, 토끼띠
  • 사주1 기준: 토끼띠. 사주의 해는 입춘에 바뀌고, 2024년 입춘은 2월 4일 17시 27분이었습니다.

10분 늦은 17시 30분 출생과 사주 여덟 글자를 비교하면 다음과 같습니다.

출생 시각년주월주일주시주
17:20癸卯乙丑戊戌庚申
17:30甲辰丙寅戊戌辛酉

10분 사이에 여덟 글자 중 여섯 글자가 다릅니다. 입춘을 지나며 년주와 월주2가, 시진3 경계를 지나며 시주가 바뀌었습니다. 입춘 시각을 몇 분만 다르게 계산해도 이 근처에 태어난 사람의 사주는 달라집니다. 같은 생년월일시를 넣어도 사주 앱마다 결과가 다른 이유 중 하나입니다.

사주·자미두수4·별자리5를 함께 보는 사주사이트(saju.site)를 혼자 만들고 있습니다. 9월 25일에 계산을 맡던 Node 라이브러리를 Go로 직접 쓴 엔진으로 바꿨습니다. 바꾸기 전에 두 엔진에 같은 출생 정보 12,120건을 넣고, 결과가 다른 곳의 원인을 빠짐없이 분류했습니다. 이 글은 그 원인과, 수천 건의 차이를 사람이 일일이 보지 않고 분류한 방법을 정리한 것입니다.

요약

  • 두 엔진의 차이는 대부분 버그가 아니라 규칙과 계산 방식의 선택에서 나왔습니다. 사주 앱마다 결과가 다른 이유도 대개 여기에 있습니다. 확인한 원인은 여섯 가지입니다. 절입 시각을 어떻게 구하는지, 출생 시각을 어떻게 보정하는지, 밤 11시대 출생의 날짜를 언제 넘기는지, 한국 음력인지 중국 음력인지, 대운 나이를 어떻게 세는지, 자미두수 별 밝기 표가 어느 유파인지입니다.
  • 새 엔진에 예전 규칙을 다시 켤 수 있는 스위치를 달아, 두 엔진의 차이를 "규칙 차이"와 "버그"로 자동으로 나눴습니다. 설명되지 않는 차이가 0건이 될 때까지 새 엔진을 고쳤습니다.
  • 교체 뒤 한국 출생의 사주 네 기둥은 0.1%만 바뀌었습니다. 대운 나이는 넷 중 한 명꼴로 한 살 달라졌고, 자미두수는 30% 가까이 바뀌었습니다. 계산 시간은 100ms 이상에서 10~15ms로 줄었습니다.

사주사이트가 계산 엔진을 직접 만든 이유

서버는 Go인데 차트 계산만 Node 라이브러리 @orrery/core에 맡기고 있었습니다. 요청마다 Node 프로세스를 띄워 결과 JSON을 받아 오는 구조라 한 번에 100ms가 넘게 걸렸고, 운영 이미지에 Node 런타임을 따로 넣어야 했습니다. 사주·자미두수·출생 차트를 한 번에 계산해 주는 라이브러리 덕분에 사이트를 빨리 열 수 있었습니다. 다만 AGPL6이라 서비스 코드와 어떻게 엮을지는 계속 신경이 쓰였습니다.

더 큰 이유는 규칙을 직접 정하고 설명하기 위해서였습니다. 사주는 여덟 글자 중 하나만 달라도 풀이 전체가 달라집니다. 그런데 "이 사람 월주가 왜 이렇게 나왔나", "이 밝기 표는 어느 계열인가"라는 질문에 저는 답할 수 없었습니다. 다른 앱과 결과가 다를 때 어떤 규칙 차이 때문인지 설명하려면 계산을 직접 해야 했습니다.

목표는 세 가지였습니다.

  • 서버 안에서 Go로 계산한다. 외부 프로세스는 없다.
  • 해석 코드가 읽는 결과 JSON 모양은 그대로 둔다.
  • 예전 엔진과 결과가 다른 곳은 하나도 빠짐없이 어떤 규칙 때문인지 설명할 수 있어야 한다.

사주·자미두수·별자리는 모두 해와 달의 위치에서 나옵니다

세 체계는 모두 태어난 순간과 장소를 입력으로 받고, 태양이나 달의 위치로 값을 정합니다.

  • 사주: 년과 월은 태양이 황도7 위 어디에 있는지(절기8)로 바뀌고, 날과 시는 태어난 곳의 해 시각으로 정해집니다.
  • 별자리: 태어난 순간 태양이 황도를 30°씩 나눈 열두 칸 중 어디에 있었는지입니다. 출생 차트는 달과 행성의 위치까지 그린 것입니다.
  • 자미두수: 음력 생월·생일과 시진으로 열두 궁의 판을 짭니다.

사주와 별자리는 같은 기준을 씁니다. 둘 다 춘분점(0°)에서 시작해 황도를 나누는데, 24절기는 15°마다, 별자리는 30°마다 바뀝니다. 그래서 절기의 절반은 별자리 경계와 일치합니다. 춘분(0°)에 양자리가, 곡우(30°)에 황소자리가, 대한(300°)에 물병자리가 시작됩니다. 별자리 경계와 겹치는 이 열두 절기를 중기라고 합니다.

나머지 열두 절기, 입춘(315°)·경칩(345°)·청명(15°) 같은 절은 별자리 칸의 가운데에 있고, 사주의 달은 이 절에서 바뀝니다. 그래서 사주의 달과 별자리는 반 칸(15°) 어긋나 있습니다. 입춘은 물병자리(300°~330°)의 가운데입니다.

사주의 달과 별자리는 반 칸(15°) 어긋나 있습니다. 바깥 고리는 별자리 12칸, 안쪽 고리는 사주의 달 12칸입니다. 별자리 경계는 춘분·곡우 같은 중기이고, 사주의 달 경계는 입춘·경칩 같은 절입니다. 입춘은 물병자리의 가운데입니다

중기는 윤달도 정합니다. 음력 한 달(평균 29.5일)은 태양이 30°를 지나는 시간(평균 30.4일)보다 조금 짧아서, 태양이 다음 별자리로 넘어가지 않는 음력 달이 가끔 생깁니다. 그런 달이 윤달9 후보가 됩니다. 새 엔진의 윤달 판정 코드도 한 달 사이에 태양이 다음 30° 구간으로 넘어갔는지를 봅니다.

// sector 는 그날 0시의 태양 황경이 몇 번째 30° 구간(= 별자리)인지입니다.
func (c *Calendar) sector(jdn int) int {
	return int(math.Floor(astro.SunApparentLongitude(c.midnightJDE(jdn)) / 30))
}

// 동지에서 다음 동지까지 달이 13개인 해에는, 중기가 들지 않은 첫 달이 윤달입니다.
hasZhongqi := c.sector(s) != c.sector(next) // s: 이 달 초하루, next: 다음 달 초하루
if leapYear && !leapUsed && !hasZhongqi {
	m.leap = true
}

엔진의 기반은 태양과 달의 위치를 계산하는 astro 패키지이고, 사주·자미두수·별자리는 그 위에 얹은 규칙입니다. 앱마다 결과가 다르다면 천체 위치를 다르게 계산했거나, 같은 위치를 다른 규칙으로 읽은 것입니다. 사용자 화면에서 보이는 차이는 대부분 후자였습니다.

수천 건의 차이를 "규칙"과 "버그"로 나누기

새 엔진을 다 쓴 뒤 바로 교체하지 않았습니다. 두 엔진에 같은 출생 정보를 넣으면 결과가 수천 건씩 달랐고, 여기에는 두 종류가 섞여 있었습니다.

  • 일부러 바꾼 차이. 한국 음력이나 30분 보정 방식처럼 규칙을 바꿔서 생긴 차이입니다.
  • 실수로 생긴 차이. 새 엔진의 버그 때문에 생긴 차이입니다.

결과만 보고는 둘을 구분할 수 없고, 수천 건을 사람이 하나씩 볼 수도 없었습니다.

구분하려면 예전 규칙으로 다시 계산해 보면 됩니다. 규칙을 예전과 똑같이 맞췄는데도 결과가 다르면, 남은 차이는 계산 쪽 문제입니다.

그래서 새 엔진에 예전 규칙 스위치(WithLegacy)를 달았습니다. 스위치를 켜면 계산은 새 엔진이 하고, 규칙만 예전 엔진을 따릅니다. 중국 음력, 표준시 그대로 가르는 자미두수 시진, 예전 밝기 표, 해외 출생의 30분 차감 같은 것들입니다. 채점 테스트(TestGrade)는 이 스위치로 차이를 분류했습니다. 예전 엔진의 코드는 보지 않고, 결과만 받아 비교했습니다.

  1. 두 엔진 결과가 같으면 통과입니다.
  2. 다르면 스위치를 켜고 새 엔진으로 다시 계산합니다. 이번에 예전 엔진과 똑같이 나오면, 차이는 전부 규칙 때문입니다.
  3. 스위치를 켜도 다르면 규칙만으로는 설명되지 않는 차이입니다. 그중 세 경우만 따로 확인해 분류했고, 그래도 남는 것은 "설명 안 됨"으로 세어 0이 될 때까지 새 엔진을 고쳤습니다.

따로 확인한 세 경우는 스위치로 재현하지 않은 차이입니다. 예전 엔진의 근사식과 시간대 자료까지 똑같이 옮기지는 않았기 때문에, 대신 차이의 모양을 봤습니다.

  • 절입 시각 차이. 다른 곳이 년주·월주와 대운뿐이고, 절입 시각 차이로 설명될 때입니다. 절입 시각은 일주와 시주를 바꾸지 못하니, 그 둘까지 다르면 절입 탓이 아닙니다. 년주·월주가 다르면 태어난 순간이 절입에서 2시간 반 안쪽이어야 하고, 대운만 다르면 시작일 차이를 "사흘이 1년" 비율로 되돌려 절입 시각 차이가 2시간 반 안쪽인지 봤습니다. 절입이 조금만 달라도 거의 모든 사람의 대운 시작일이 며칠씩 움직여서, 아래 표에서 사주의 '절기' 칸이 큽니다.
  • 균시차 차이(해외 출생). 시진 경계 90초 안쪽이라 시진이 갈리거나, 자미두수 명반에 적힌 진태양시만 1분 안쪽으로 다른 경우입니다.
  • 시간대 자료 차이. 1970년 이전 일부 해외 시간대에서 예전 엔진(Node·ICU)과 서버의 tzdata 기록이 다른 경우입니다.

예를 들어 서울 오전 1시 10분 출생의 자미두수 시진은 예전 엔진이 丑시, 새 엔진이 子시였습니다. 스위치를 켜서 시진을 표준시 그대로 가르면 새 엔진도 丑시가 나오므로, 이 차이는 자미두수 시계 규칙을 바꾼 결과입니다. 스위치를 켜도 丑시가 나오지 않았다면 새 엔진의 버그이고, 그런 경우를 찾아 고쳤습니다.

분류 코드를 줄이면 다음과 같습니다.

// 사주 결과가 예전 엔진과 다를 때, 그 차이가 어디서 왔는지 가립니다(요약).
// 시간대 자료가 갈린 경우는 이보다 먼저 따로 셉니다.
legacy, _ := saju.Calculate(in.WithLegacy()) // 계산은 새 엔진, 규칙만 예전 것
diffs := compare(old, legacy)
switch {
case len(diffs) == 0:
	return "convention" // 규칙만 맞추면 같아짐 → 일부러 바꾼 규칙 때문
case termExplainedAt(at, diffs):
	return "term" // 년주·월주·대운만 다르고, 절입 시각 차이(2시간 반 안)로 설명됨
case abroad && nearHourBoundary(in, 90*time.Second):
	return "eot" // 해외 출생, 시진 경계 90초 안 → 균시차 차이
}
return "other" // 설명 안 됨 → 새 엔진을 고친다

사주·자미두수 두 엔진의 결과가 다르면 예전 규칙 스위치를 켜고 다시 계산합니다. 같아지면 규칙 차이, 그래도 다르면 설명 안 됨으로 나누고, 설명 안 됨은 최종 0건이 될 때까지 새 엔진을 고쳤습니다

입력은 무작위 출생 5,368명과 가장자리 출생(엣지 케이스) 6,752명입니다. 가장자리는 무작위로는 거의 뽑히지 않는 입력입니다. 서머타임 전환과 건너뛴 시각, 한국 표준시가 바뀐 순간, 절입 앞뒤 1~3분, 고위도, 30·45분 시간대, 날짜변경선, 계산 범위의 양 끝을 넣었습니다. 표의 각 칸은 차이가 어느 원인으로 설명됐는지를 센 것입니다.

묶음사주자미두수출생 차트설명 안 됨
무작위 5,368명절기 4,297 · 해외 1,034 · 균시차 1바꾼 규칙 4,428 · 균시차 413위치 보정 5,327 · 시간대 자료 50
가장자리 6,752명절기 1,578 · 해외 5,126 · 균시차 32 · 시간대 자료 11바꾼 규칙 5,095 · 균시차 1,641 · 시간대 자료 11위치 보정 6,711 · 시간대 자료 360

설명 안 되는 차이가 0건이 된 뒤 엔진을 바꿨고, 채점 코드와 예전 엔진은 저장소에서 지웠습니다. 지금은 180명의 결과 전체를 고정해 둔 스냅숏 테스트10(TestSnapshot)로 결과가 뜻하지 않게 바뀌지 않았는지 확인합니다.

새 코드와 예전 코드를 나란히 돌려 결과를 비교하는 방법(차등 테스트, differential testing) 자체는 흔합니다. 다만 차이를 자동으로 분류하려면 예전 규칙을 새 코드 안에서 다시 켤 수 있어야 했습니다.

이유 ① 절입 시각: 사주의 해는 1월 1일이나 설날이 아니라 입춘에 바뀝니다

사주의 년주와 월주는 1월 1일이나 음력 초하루가 아니라 절기로 바뀝니다. 입춘 순간에 해가 바뀌고, 달마다 절입 순간에 월주가 바뀝니다. 앞의 예처럼 절입 근처에 태어난 사람은 절입 시각이 몇 분만 달라도 월주가, 입춘이면 년주까지 바뀝니다.

예전 엔진은 절입을 근사식으로 구했고, 실제 절입과 평균 26분, 최대 2시간 가까이 차이가 났습니다. 근사식은 가볍고 빠르지만, 절입 근처에 태어난 사람에게는 이 차이가 그대로 기둥 차이가 됩니다.

새 엔진은 태양의 겉보기 황경이 입춘 315°, 경칩 345°, 청명 15°처럼 30°마다 있는 절을 지나는 순간을 직접 찾습니다. 태양 위치는 VSOP87D11 계수로, 지구 자전이 고르지 않아 생기는 시간 차이(ΔT12)는 Espenak·Meeus 식으로 계산하고, Meeus 『Astronomical Algorithms』의 예제로 검증했습니다. 기준점은 한국천문연구원이 발표한 2024년 입춘, 2월 4일 17시 27분입니다.

// 2024년 입춘: 2월 4일 17:27 (한국천문연구원). 1분 앞뒤로 년주와 월주가 함께 바뀝니다.
before := pillarsOf(t, seoul(2024, 2, 4, 17, 26)) // 癸卯년 乙丑월
after := pillarsOf(t, seoul(2024, 2, 4, 17, 28))  // 甲辰년 丙寅월

사주 입춘 경계: 2024년 입춘 17시 27분 앞뒤로 17:26 출생은 癸卯년 乙丑월, 17:28 출생은 甲辰년 丙寅월입니다. 예전 엔진의 절입 오차는 평균 ±26분, 최대 ±2시간 가까이, 새 엔진은 lunar-javascript와 최대 27초 차이

대량 대조는 lunar-javascript(MIT)와 했습니다. 절기 4,824개를 맞춰 보니 최대 27초 차이였습니다. 두 계산이 서로 맞는다는 뜻이고, 정답의 기준은 앞의 천문연 발표 시각입니다.

절입 시각은 대운13에도 영향을 줍니다. 대운은 태어난 때부터 절까지의 날수를 3으로 나눠 정하는데(사흘이 한 해), 이 비율 때문에 절입이 1분 어긋나면 대운 시작일은 2시간쯤 어긋납니다(365.2422 ÷ 3 ≈ 122배). 예전 근사식의 평균 오차 26분이면 이틀쯤입니다. 실제로 대운 시작일이 평균 2일, 최대 10일쯤 달라졌습니다.

대운 나이를 세는 방식도 바꿨습니다. 예전 엔진은 "대운이 시작되는 해 − 태어난 해"로 셌고, 새 엔진은 대운수(날수 ÷ 3을 반올림한 값)를 그대로 씁니다. 이 차이로 한국 출생의 넷 중 한 명꼴(24.6%)이 대운 나이가 한 살 달라졌고, 풀이에 나오는 "지금 대운"이 바뀐 사람은 12.4%였습니다.

해외 출생은 절입과 비교하는 기준 시각도 바뀌었습니다. 예전 엔진은 태어난 곳의 진태양시14를 한국 표준시 숫자로 읽어 절입과 비교했고, 뉴욕 출생이면 실제 순간과 14시간쯤 차이가 났습니다. 새 엔진은 년주·월주·대운을 정할 때 태어난 실제 순간을 절입 순간과 비교합니다.

이유 ② 시간 보정: 서울의 해는 시계보다 30분쯤 늦습니다

시주는 두 시간 단위의 시진으로 바뀝니다(23~01시 子, 01~03시 丑 …). 기준은 시계가 아니라 해의 위치입니다. 그런데 한국 표준시는 일본과 같은 동경 135°15 기준이고 서울은 약 127°라, 서울에서 해가 가장 높이 뜨는 때는 평균 낮 12시 반쯤입니다. 그래서 사주에서는 흔히 한국 출생의 시계에서 30분을 빼고 시주를 가립니다. 앞의 17시 30분 출생도 30분을 빼면 17시, 酉시가 시작되는 시각이라 시주가 바뀌었습니다.

빼는 양도 방식마다 다릅니다. 서울의 실제 경도로 계산하면 32분, 부산은 24분쯤입니다. 도시마다 경도대로 빼는 방식도 있고, 동경 127.5°를 기준으로 일괄 30분을 빼는 방식도 있습니다. 사주사이트는 한국 출생이면 도시와 상관없이 30분을 뺍니다.

30분을 빼는 구현도 두 가지가 있습니다.

  1. 출생 기록에 적힌 시각에서 30분을 뺀다.
  2. 태어난 순간을 UTC+8:30 시계로 읽는다.

대부분은 결과가 같지만, 한국 표준시가 지금과 달랐던 시기에는 갈립니다. 1954~1961년에는 표준시 자체가 UTC+8:30이었고, 1987·88년 여름에는 서머타임(UTC+10)을 했습니다.

출생(서울, 기록된 시각)그때 시계① 30분 빼기② UTC+8:30으로 읽기
1960년 3월 1일 07:10UTC+8:3006:40 卯시07:10 辰시
1988년 7월 1일 08:00UTC+10(서머타임)07:30 辰시06:30 卯시

1960년의 시계는 이미 동경 127.5° 기준이라 뺄 것이 없고, 1988년 여름의 시계는 한 시간 앞당겨져 있었으니 한 시간 반을 빼야 맞습니다. 새 엔진은 두 번째 방법을 씁니다. 기록된 시각을 IANA 시간대 자료16에 따라 실제 순간(UTC)으로 바꾼 뒤 고정 오프셋(+8:30)을 더하면, 이런 시기는 시간대 자료가 처리합니다.

// RefClock 은 시주·일주(그리고 자미두수 시진)를 가르는 시계입니다.
//   - 한국: UTC+8:30 고정(한국 표준시 − 30분).
//   - 해외: 태어난 곳의 진태양시(경도 + 균시차). 경도로 이미 보정하므로 30분을 따로 빼지 않습니다.
func RefClock(birth time.Time, tz string, longitude float64) time.Time {
	if IsKorea(tz) {
		return birth.UTC().Add(8*time.Hour + 30*time.Minute)
	}
	if loc, err := time.LoadLocation(tz); err == nil {
		birth = birth.In(loc) // 날짜변경선 접기는 그 순간의 표준시 차이를 씁니다.
	}
	return birth.UTC().Add(time.Duration(SolarOffsetMinutes(birth, longitude) * float64(time.Minute)))
}

해외 출생은 30분 대신 진태양시를 씁니다. 태어난 곳의 경도와 균시차17로 그 자리의 해 시각을 직접 구하는 방식입니다. 시주가 가장 크게 바뀐 곳이 여기입니다. 예전 엔진은 경도로 진태양시를 구한 뒤 한국 출생처럼 30분을 한 번 더 뺐습니다. 진태양시는 이미 경도로 보정한 시각이라, 새 엔진은 30분을 더 빼지 않습니다.

뉴욕 2000년 6월 1일 10시(EDT) 출생이면 진태양시로 약 9시 6분, 巳시입니다. 30분을 더 빼면 8시 36분, 辰시가 됩니다(TestAbroadHourUsesSolarTimeWithoutKoreanOffset). 해외 출생은 네 명 중 한 명꼴(25.8%)로 이 차이 때문에 시주가 바뀌었습니다.

해외 출생 사주의 시간 보정: 뉴욕 2000년 6월 1일 10:00 EDT 출생은 진태양시 09:06으로 巳시(새 엔진)입니다. 경도로 보정한 시각에서 30분을 또 빼면 08:36 辰시(예전 엔진)가 됩니다

밤 11시생의 일주: 야자시와 조자시

子시는 밤 11시에 시작해 새벽 1시에 끝나서 자정을 가로지릅니다. 그래서 子시가 시작될 때 날을 바꿀지, 자정에 바꿀지가 학파마다 다릅니다. 밤 11시~자정을 야자시18, 자정~새벽 1시를 조자시로 나눠 보는 관점이 대표적입니다. 만세력 앱마다 밤 11시대 출생의 일주가 다르게 나오는 흔한 이유입니다.

사주사이트의 사주는 子시가 시작되는 순간, 즉 30분 보정한 시계로 23시(한국 시각 23시 30분)에 날을 바꿉니다. 2000년 1월 1일 23:29는 戊午일 亥시, 23:30은 己未일 子시입니다(TestKoreaDayChangesAt2330KST).

그 밖에 시각을 다루며 정한 규칙입니다.

  • 애매한 시각은 거절하지 않습니다. 서머타임이 끝나며 두 번 오는 시각은 앞의 것으로, 시작되며 건너뛴(없는) 시각은 바뀌기 전 시계로 읽습니다. 예전 엔진에서는 건너뛴 시각을 계산할 수 없었습니다.
  • 날짜변경선 근처는 출생 기록의 날짜를 지킵니다. 키리바시 라인 제도(키리티마티)는 UTC+14인데 서경 157°라, 경도만으로 진태양시를 구하면 하루가 밀립니다. 경도 차이를 표준시 기준 ±12시간 안으로 접어 이를 막습니다(TestDateLineZonesKeepTheCivilDate).
  • 균시차도 근사식 대신 천문 계산으로 구합니다. 예전 근사식은 참값과 평균 0.49분, 최대 1.4분 차이가 났습니다.

이유 ③ 음력: 2027년 설날, 한국은 2월 7일이고 중국은 2월 6일입니다

음력 초하루는 삭(달과 해가 같은 방향에 겹치는 순간)이 든 날입니다. 그런데 그 "날"을 어느 자오선19의 시계로 가르느냐에 따라 초하루가 하루 갈릴 수 있습니다. 중국은 동경 120°(UTC+8), 한국은 1912년부터 동경 135°(UTC+9) 기준으로 음력을 만듭니다. 삭이 한국 시각 0~1시에 들면 한국에서는 이미 그날이지만 중국에서는 아직 전날이라, 초하루가 하루 갈립니다.

2027년 설날이 그렇습니다. 정월을 여는 삭이 한국 시각으로 2월 7일 0시 56분, 중국 시각으로는 2월 6일 23시 56분에 듭니다. 그래서 한국 설날은 2월 7일, 중국 춘절은 2월 6일입니다. 2028년도 하루 갈립니다(한국 1월 27일, 중국 1월 26일).

한국 음력과 중국 음력: 같은 삭이 한국 시각 0시 30분이면 한국(동경 135°)에서는 그날, 중국(동경 120°)에서는 23시 30분이라 전날이 초하루가 됩니다

새 엔진의 lunar 패키지는 한국천문연구원 역서와 같은 자오선을 씁니다(근거: 계승혁, 「보름달과 음력」).

  • 1911년까지: 동경 120°. 1908년에 표준시를 바꿨지만 음력은 중국 것을 그대로 썼습니다.
  • 1912년부터: 동경 135°.
  • 1954~61년(표준시 127.5°): 127.5°로 날을 갈라도 135°로 가른 것과 초하루가 모두 같아 따로 다루지 않습니다. 이것도 테스트로 고정해 두었습니다(TestKorean1954To1961MeridianDoesNotMatter).

두 달력이 다른 경우는 다음과 같습니다.

  • 설날(1900~2050년): 1916·1944·1954·1958·1966·1988·1997·2027·2028년
  • 윤달(1900~2050년): 2012년(한국 윤3월, 중국 윤4월), 2017년(한국 윤5월, 중국 윤6월). 초하루가 하루 갈리면 어느 달에 중기가 드는지도 달라져, 윤달 자리까지 바뀝니다. 2012년 5월 1일은 한국 음력으로 윤3월 11일, 중국 음력으로 4월 11일입니다.
  • 1950~2050년 날짜의 3.7%가 하루 다릅니다.

1900~1960년 윤달 23개와 설날 60개(61년 가운데 1947년은 대조 자료를 옮겨 적다 틀려 제외)를 공개 자료와 대조해 모두 같았고, 계승혁 글에 나오는 설날이 갈리는 해도 모두 재현했습니다. 중국 음력 달 시작 2,486개는 lunar-javascript와 모두 같았습니다.

사주는 절기로 년주와 월주를 가르기 때문에, 양력 생일로 넣으면 음력 달력이 바뀌어도 사주는 그대로입니다. 하지만 생일을 음력으로 입력하면 양력으로 바꾸는 단계에서 달력이 쓰입니다. 1960~2008년 음력 날짜의 2.9%가 양력으로 하루 다른 날이 됐고, 이런 사람은 일주(와 시주의 천간)도 바뀝니다. 한국 음력으로 바로잡힌 것입니다. 음력 날짜를 직접 쓰는 자미두수는 영향이 훨씬 큽니다.

이유 ④ 자미두수: 같은 시계, 한국 음력, 다른 밝기 표

자미두수 명반20은 음력 생월과 생시로 명궁 자리를 정하고, 그 위에 별을 배치합니다. 사주와 같은 출생 정보를 쓰지만, 예전 엔진과는 세 가지 규칙이 다릅니다.

  1. 시계. 예전 엔진은 사주 시주를 가를 때는 한국 출생의 30분을 빼고, 자미두수 시진은 한국 표준시 그대로 갈랐습니다. 표준시 그대로 가르는 것도 자미두수에서 쓰는 방식이지만, 사주사이트의 리딩은 두 체계를 한 글 안에서 함께 읽기 때문에 같은 시각을 봐야 했습니다. 서울 01:10 출생이면 사주는 00:40으로 子시인데, 예전 자미두수는 丑시였습니다.
  2. 달력. 예전 엔진은 중국 음력 기준이었습니다. 한국 서비스라 한국 음력이 필요했고, 두 달력이 갈리는 날에는 생월이나 생일이 달라집니다.
  3. 밝기 표. 14주성이 어느 궁에서 얼마나 밝은지21(廟旺得利平不陷)를 정한 표는 유파마다 다릅니다. 예전 엔진의 표가 어느 계열인지 확인하지 못해, 근거를 댈 수 있는 표로 바꿨습니다. 두 표는 168칸 중 101칸이 다릅니다.

자미두수 시진과 사주 시주를 같은 시계로 가릅니다. 서울 01:10 출생은 사주 시주가 30분을 빼 00:40 子시, 예전 자미두수는 표준시 그대로 01:10 丑시, 지금 자미두수는 사주와 같은 子시입니다

새 엔진에서는 이렇게 정했습니다.

  • 시진을 사주와 같은 RefClock으로 가릅니다. 무작위 600명(서울·부산·뉴욕·런던·도쿄·키리바시)으로 사주 시주와 자미두수 시진의 지지가 늘 같은지 봅니다(TestHourBranchMatchesSaju).
  • 한국 음력을 씁니다(TestUsesKoreanLunarCalendar).
  • 밤 11시~자정 출생은 날을 넘기지 않고 그날로 봅니다(보정한 시계 기준). 23시에 날을 바꾸는 사주와는 다른 규칙입니다.
  • 밝기는 『자미두수전서』 계열 7단계 표로 바꿨습니다. 요즘 명반 프로그램들이 널리 쓰는 표와 같고, 고전 격국22 이름이 가리키는 칸과도 맞는지 테스트합니다(TestBrightnessTable).
격국별과 자리밝기
極向離明格紫微 午廟
英星入廟格破軍 子·午廟
巨機同臨格天機 卯·酉 / 巨門 卯·酉旺 / 廟
梁同巳亥格天梁 巳·亥陷

그 결과 한국 출생의 25.1%는 시진이, 29.4%는 별 위치가 하나 이상 바뀌었습니다. 밝기 표기를 빼면 이번 교체에서 가장 많이 바뀐 부분입니다. 25.1%는 간단히 어림할 수 있습니다. 시진은 두 시간(120분)짜리 칸인데 시계를 30분 옮겼으니, 칸마다 경계 바로 뒤 30분에 태어난 사람, 곧 넷 중 한 명이 옆 칸으로 넘어갑니다.

별자리: 날짜표가 아니라 태양 위치로 정합니다

별자리 운세 표에는 흔히 물고기자리가 2월 19일부터라고 적혀 있습니다. 하지만 태양이 물고기자리(황경 330°)에 들어가는 순간은 해마다 조금씩 움직입니다. 2025년에는 2월 18일 19시 6분(한국 시각)이었습니다. 이날 저녁 8시에 태어난 사람은 표로 보면 물병자리, 태양 위치로 보면 물고기자리입니다. 별자리 앱마다 경계일 생일의 별자리가 다른 이유입니다. 사주사이트는 절기를 구하는 것과 같은 태양 계산으로 별자리를 정합니다. 황경 330°는 절기로 치면 우수, 곧 물고기자리가 시작되는 중기입니다.

출생 차트는 이번 교체에서 바뀐 게 가장 적습니다. 예전 엔진은 해·달·행성 위치를 빛이 오는 데 걸리는 시간(광행시)과 광행차, ΔT 없이 계산했고, 새 엔진은 이것들을 모두 넣은 겉보기 위치를 씁니다. 차이는 해 20″, 달 35~100″, 행성 3~30″입니다. 1″는 1°의 3,600분의 1이라, 행성의 별자리가 바뀐 사람은 0.1%였습니다. 하우스는 플라시두스 방식으로, 극지방에서는 포르피리 방식으로 나눕니다.

키론23은 라이선스 걱정 없이 서버에 넣을 수 있는 위치표를 찾지 못해 직접 만들었습니다. JPL 소천체 데이터베이스의 궤도 요소에서 출발해 태양과 여덟 행성의 중력을 넣은 적분기를 짰고, 1899~2101년 위치표(10일 간격)를 만드는 데 2분이 걸립니다. 키론이 별자리를 옮겨 가는 날짜를 공개 자료와 맞춰 확인했습니다.

결과: 한국 출생 사주 네 기둥은 0.1%, 자미두수는 30% 가까이 바뀌었습니다

1960~2008년생 한국 출생 6,000명과 해외 출생 2,000명을 두 엔진에 넣어, 사용자 화면에 보이는 값이 얼마나 바뀌는지 재 봤습니다(2026년 9월 25일에 한 번 실행).

예전 엔진과 결과가 달라진 사람의 비율. 한국 출생은 자미두수 별 자리 29.4%, 시진 25.1%, 대운수 24.6%, 사주 네 기둥 0.1%. 해외 출생은 대운수 27.3%, 사주 네 기둥 26.6%

한국 출생 사주 네 기둥이 바뀐 0.1%(양력 입력 기준)는 절입 근처의 월주이고, 해외 출생 26.6%는 대부분 시주(25.8%)입니다. 자미두수 음력 날짜가 바뀐 5.2%는 대부분 달력 차이(3.1%)와 자시 경계(2.0%)에서 나왔습니다. 자시 경계는 한국 시각 0시~0시 30분 출생이 보정한 시계로는 아직 전날 23시대라 음력 날짜가 하루 앞당겨진 경우입니다. 주성 밝기 표기는 모든 명반에서 바뀌었습니다(주성 14개 중 평균 9개).

한국 출생 사주의 네 기둥은 두 엔진이 거의 같았고, 차이는 대운 나이, 절입 근처, 해외 출생, 그리고 규칙을 바꾼 자미두수에 몰려 있습니다.

속도는 Node 프로세스 기동을 포함해 100ms 이상이던 것이 10~15ms가 됐습니다. 운영 이미지에서 Node를 뺐고, DB는 건드리지 않아 되돌리기는 이전 릴리스 재배포로 끝납니다.

이미 결제한 사용자도 챙겨야 했습니다. 풀이 캐시 버전을 올려, 같은 입력으로 새로 요청하면 새 차트로 새 리딩을 쓰게 했습니다. 예전 차트로 쓴 글은 "지난 이야기"에 그대로 두었고, 같은 입력으로 전체 리딩을 이미 받은 사람은 잠긴 맛보기를 열 때 다시 결제하지 않습니다.

남은 것과 배운 것

아직 못 한 것도 있습니다.

  • 행성 위치를 JPL Horizons24와 대조하지 못했습니다. 지금은 Meeus 예제와 공개된 1961년 출생 차트까지만 맞춰 봤습니다.
  • 2050년 뒤, 삭이 자정 1분 안에 드는 날은 아직 확인하지 못했습니다. 1912~2050년에는 2005년 12월 2일(한국 시각 00:00:54) 하나로, 발표된 역과 같습니다. 그 뒤로 2051·2074·2097년에 더 있는데, 달 이론과 ΔT의 오차 범위 안이라 발표된 역이 나오면 확인해야 합니다.
  • 명왕성 식은 1885~2099년용입니다. 계산 범위를 2100년 이후로 넓히려면 바꿔야 합니다.

배운 것은 세 가지입니다.

  1. "예전과 같다"는 정답의 기준이 아닙니다. 맞는지는 바깥 기준으로만 말할 수 있었습니다. 천문연 발표 시각, 발표된 역, Meeus 예제, 고전 격국 이름 같은 것들입니다.
  2. 목표는 차이를 0으로 만드는 게 아니라, 모든 차이를 설명할 수 있게 만드는 것이었습니다. 예전 규칙 스위치 덕분에 12,120건을 비교하며 나온 차이를 사람이 하나씩 보지 않고도 원인별로 나눌 수 있었습니다.
  3. 가장자리 입력은 일부러 만들어야 합니다. 무작위 입력으로는 서머타임에 건너뛴 시각이나 날짜변경선 출생이 거의 나오지 않습니다. 지금은 이런 입력 300개 이상을 일반 테스트(TestEdgeCasesCalculate)에 넣어 두었습니다.

같은 생년월일시인데 사주 앱(어플)마다 결과가 다르다면, 대개 이 글에서 다룬 여섯 가지 중 하나가 다른 것입니다. 절입 시각, 시간 보정, 밤 11시대의 날짜, 음력 기준, 대운 나이 세는 법, 자미두수의 밝기 표입니다. 앞의 예(2024년 2월 4일 17시 20분과 17시 30분)는 사주사이트의 무료 만세력에서 확인할 수 있고, 사주·자미두수·별자리를 함께 읽는 리딩은 사주사이트에 있습니다. 틀린 곳이 있으면 알려 주세요.

각주

  1. 사주(四柱). 태어난 해·달·날·시를 각각 두 글자(천간과 지지)로 적은 네 기둥입니다. 모두 여덟 글자라 '팔자'라고도 합니다. ↩

  2. 년주·월주·일주·시주는 사주의 네 기둥으로, 각각 태어난 해·달·날·시를 나타냅니다. 년주의 아래 글자(지지)가 띠입니다. 卯는 토끼, 辰은 용입니다. ↩

  3. 하루를 두 시간씩 열두 칸으로 나눈 옛 시간 단위입니다. 子시(23~01시)부터 亥시(21~23시)까지 십이지 이름을 붙입니다. ↩

  4. 자미두수(紫微斗數). 음력 생년월일시로 열두 궁의 판을 세우고, 자미성을 비롯한 별들을 배치해 읽는 동양 점성술입니다. ↩

  5. 여기서 별자리는 서양 점성술을 가리킵니다. 흔히 말하는 별자리(태어난 순간 태양이 있던 칸)와, 해·달·행성의 자리를 모두 그린 출생 차트를 함께 다룹니다. 이 별자리는 춘분점에서 황도를 30°씩 나눈 황도 12궁이라, 세차 운동 때문에 지금은 하늘의 실제 별자리와 한 칸 가까이 어긋나 있습니다. ↩

  6. 오픈소스 라이선스의 하나입니다. 이 라이선스의 코드를 고쳐 네트워크 서비스로 제공하면, 사용자에게 그 소스 코드를 공개해야 합니다. ↩

  7. 황도는 지구에서 볼 때 태양이 1년 동안 하늘에서 지나가는 길입니다. 그 길 위에서 태양이 몇 도 지점에 있는지를 황경이라 하고, 춘분점이 0°입니다. ↩

  8. 1년을 태양의 위치로 24등분한 것이 24절기입니다. 그중 입춘·경칩처럼 사주의 달이 시작되는 12개를 '절', 춘분·곡우처럼 그 사이에 있는 12개를 '중기'라 하고, 절에 들어서는 순간을 절입이라 합니다. ↩

  9. 음력 열두 달은 태양의 1년보다 11일쯤 짧습니다. 이 차이를 메우려고 2~3년에 한 번 넣는 달이 윤달입니다. ↩

  10. 지금 결과 전체를 파일로 저장해 두고, 코드를 바꿀 때마다 결과가 그대로인지 비교하는 테스트입니다. ↩

  11. 프랑스 경도국(Bureau des longitudes)이 만든 행성 위치 이론입니다. 태양과 행성의 위치를 아주 정밀하게 계산할 수 있는 계수 표로 공개되어 있습니다. ↩

  12. 지구 자전이 조금씩 불규칙해서 생기는, 천문 계산용 균일한 시간과 실제 시계 시간의 차이입니다. 2020년대에는 약 70초입니다. ↩

  13. 대운은 사주에서 10년 단위로 바뀌는 운의 흐름이고, 대운수는 첫 대운이 시작되는 나이입니다. 태어난 때부터 다음(또는 지난) 절까지의 날수를 3으로 나눠 정합니다. ↩

  14. 시계가 아니라 그 자리에서 해가 실제로 가장 높이 뜨는 때를 정오로 삼는 시간입니다. 경도와 균시차로 계산합니다. ↩

  15. 한국 표준시는 일본과 같은 동경 135° 자오선 기준(UTC+9)입니다. 경도 15°가 1시간이라, 동경 약 127°인 서울은 해의 시각이 30분 남짓 늦습니다. ↩

  16. 나라·지역별 표준시와 서머타임의 변경 이력을 모은 공용 데이터베이스(tzdata)입니다. 대부분의 운영체제와 프로그래밍 언어가 이 자료를 씁니다. ↩

  17. 지구 궤도가 타원이고 자전축이 기울어 있어서, 해의 실제 시각이 1년 동안 평균보다 최대 16분쯤 앞서고 14분쯤 늦는 차이입니다. ↩

  18. 야자시(夜子時)는 밤 11시~자정, 조자시(朝子時)는 자정~새벽 1시를 가리킵니다. 둘 다 子시지만 야자시 출생을 그날로 볼지 다음 날로 볼지가 학파마다 다릅니다. 자미두수에서는 이 구간을 晩子라고도 부릅니다. ↩

  19. 같은 경도를 잇는 선입니다. 어느 경도의 시각으로 날짜를 가르느냐에 따라 자정이 달라집니다. ↩

  20. 명반은 자미두수에서 열두 궁에 별을 배치한 판이고, 명궁은 그중 그 사람 자신을 나타내는 궁입니다. ↩

  21. 14주성은 자미성을 비롯해 명반의 중심이 되는 별 14개입니다. 밝기는 별이 놓인 궁에 따라 그 힘이 얼마나 잘 드러나는지를 7단계(廟·旺·得·利·平·不·陷)로 매긴 것입니다. ↩

  22. 별이나 글자가 특정하게 짜인 구성에 붙인 고전의 이름입니다. 예를 들어 極向離明格은 자미성이 午궁에 있는 구성입니다. ↩

  23. 토성과 천왕성 사이를 도는 소천체로, 서양 점성술 출생 차트에서 쓰입니다. 적분기는 궤도의 시작 상태에서 출발해, 중력에 따른 움직임을 짧은 시간 단위로 쪼개 이어서 계산하는 프로그램입니다. ↩

  24. 미국 NASA 제트추진연구소(JPL)가 제공하는 천체 위치 계산 서비스로, 행성 위치를 확인할 때 표준처럼 쓰입니다. ↩