천문 계산

음력-양력 상호 변환 함수 모듈에 관한 간단한 설명 7

by 창환 posted Jan 18, 2013
?

단축키

Prev이전 문서

Next다음 문서

ESC닫기

크게 작게 위로 아래로 댓글로 가기 인쇄
음양력 변환 모듈에 대한 마지막 글입니다. 이번에는 양력을 음력으로 변환하는 방법에 대해 설명하겠습니다. 정확히는 율리우스적일로된 날짜를 음력으로 바꾸는 방법입니다. 

음력을 계산하는 함수는 단 하나입니다. 

LuniSolarCal: 율리우스 적일을 음력으로 바꾸는 함수


이 함수는 율리우스적일 형태의 날짜를 입력받아 음력날짜로 바꾸어 출력합니다. 음력으로 바꾸는 방법은 기본적으로 청나라에서 만들어진 시헌력법에서 정한 방법을 따릅니다만(천문연구원도 시헌력에 따라 음력을 산출함), 여기에서 쓴 방법과 세부 조건은 조금 다릅니다. 이 조건에 명시되어 있지는 않지만 한 해가 시작되는 시점인 세수는 인월(寅月, 동짓달로부부터 세 번째 오는 달)로 하며, 천정동지(天正冬至)는 음력으로 바로 전 해의 동지, 즉 그 해의 1월(세수) 바로 전의 동지를 뜻합니다.

우선 시헌력에서는 다음 원칙에 따라 음력을 계산합니다. 

1) 동지가 든 달을 11월로 한다. 
2) 중기가 포함되지 않은 달(무중월)을 윤달로 하되(무중치윤법), 1년에 윤달이 2회 이상 있으면 첫번째 달만 윤달로 한다. 
3) 춘분이 든 달은 2월, 하지가 든 달은 5월, 추분이 든 달은 8월로 한다. 

그런데, 1과 2의 조건이 3의 조건에 우선하며, 3번 조건은 무중월이 2번 이상 반복되어 나타나는 경우에, 지킬 수 없는 때가 있습니다. 따라서 여기에서는 1과 2의 조건만을 반영하여 음력을 계산하였습니다. 또한 음력의 몇 가지 특성을 반영하였는데요, 

1) 2년 연속으로 윤년이 오는 경우는 없다. 
2) 무중월은 2개월 이상 연속으로 이어지지 않는다.

이 두 가지 특성을 반영하면 자동화 과정이 훨씬 간단해지게 됩니다. 

음력 계산 순서는 우선 구하려는 해의 입기시각과 정삭시각을 계산하여 표로 만들고, 천정동지와 그 다음 동지 사이에 삭망월이 몇 개인지를 찾습니다. 11월(천정동지월)부터 11월(동지월)까지이므로 이 사이에 삭망월이 13개가 들어가면 윤달이 포함되어 있는 것이고, 12개가 들어가면 모두 평달임을 알 수 있습니다. 12개의 달이 모두 평달이면, 그 사이에 무중월이 있든 없든, 11월부터 다음달은 12월, 그 다음달은 1월, 이런 식으로 윤달 없이 월 번호를 매기면 됩니다. 

13개의 삭망월이 포함되는 경우라면, 윤달의 위치를 찾아주어야하는데, 정확한 판단을 위해서는 천정동지가 포함된 11월과 그 다음 이어지는 2개월의 윤달 여부를 파악해야합니다. 여기에서 쓰는 조건이 바로 앞에서 설명한 두 조건을 이용합니다. 윤달이 포함된 주기(천정동지~그해의 동지 사이의 기간)는 2번 이상 연속적으로 나타나지 않으므로, 이전 주기와 다음 주기에는 윤달이 없습니다. 따라서, 천정동지가 포함된 11월(천정동지월)이 있는  주기와 이번 동지의 다음 주기에는 모두 평달이 들어가게 됩니다.

이를 염두에 두고 따져보면, 천정동지월과 이어지는 2개월의 윤달 여부는 다음처럼 됩니다(천정동지월-다음월-다음월 순). 
1) 평-평-평 : 이 때는 이 3개월이 11월, 12월, 1월이 되며, 천정동지 다음의 1~10월 사이에 있는 첫번째 무중월이 윤달이 됩니다. 
2) 평-윤-평 : 이 때는 천정동지월 바로 다음달이 윤 11월이 되어 11월, 윤 11월, 12월이 됩니다. 그 1~10월은 모두 평달로 처리합니다.
3) 평-평-윤 : 이 때는 천정동지월을 11월, 다음 달이 12월, 그 다음달이 윤 12월이 되고, 1~10월은 모두 평월로 처리합니다. 그런데, 12월은 지구가 근일점 부근을 지나므로, 윤달이 끼는 경우가 매우 드뭅니다. 실제로 정기법을 쓰면 12월 윤은 나타나지 않습니다.

율리우스적일을 음력으로 바꾸는 LuniSolarCal 함수는 여기까지 설명한 논리에 따라 음력을 계산합니다. 처리과정은 소스코드에 주석문 형태로 달아놓겠습니다. 

이 함수에 쓰는 구조체에는 삭망월의 자료를 저장합니다. 

Type LunarDay
  StartDay As Double '삭망월의 시작일(=정삭일)
  MonLength As Integer '달의 길이(큰 달=30일, 작은 달=29일)
  Junggi As Boolean '무중월, 유중월 여부(무중월이면 거짓)
  MonName As Byte '월 이름(월 번호)
  LYear As Integer '삭망월이 포함된 연도
End Type

음력 계산에 사용하는 주 함수는 LuniSolarCal입니다. 율리우스적일(JD)을 입력으로 받아 음력 연도(LunarYear), 월(LunarMon), 일(LunarDay), 윤달 여부(IsLeap)를 출력합니다. 윤달 여부는 윤달이며 참, 아니면 거짓으로 출력합니다. 함수 작동에 대한 상세한 내용은 주석문을 참고하세요. 

    Private Sub LuniSolarCal(ByVal jd As Double, LunarYear As Integer, LunarMon As Byte, LunarDay As Byte, IsLeap As Boolean)
      Dim jd0 As Double, dYear As Double, yJD0 As Double, bf As Double, B As Integer, M As Integer
      Dim i As Integer, j As Integer, k As Integer, LD(25) As LunarDay, SD(25) As Double
      Dim PreWinter As Double, ThisWinter As Double, Count1 As Integer, idx1 As Integer, idx2 As Integer
      Dim LeapType As Byte, Leap13 As Boolean, fMON As Integer, A As Integer, LCount As Integer
     
      jd0 = GetJD0(jd) + 0.5 '입력일을 정오로 설정
      dYear = InvJDYear(jd0) '입력일의 연도 계산
    
    '입력일이 천정동지일 이전이면 계산 주기를 1년 앞으로, 입력일 연도이 동지 이후이면 1년 뒤로 바꿈.
      If jd0 < cJunggi(dYear, 270, -13) Then dYear = dYear - 1
      If jd0 > cJunggi(dYear, 270, 355) Then dYear = dYear + 1
    
    '입력일 연도의 1일의 율리우스 적일 계산
      yJD0 = JULIANDAY(dYear, 1, 1)
      Call Set24Julgi: CalcJulGi yJD0  '24절기 입기시각 계산
    
    '정삭시각을 계산하여 배열에 저장(해당연도 1월 1일을 기준으로 96일 이전부터 이후 427일 사이의 기간)
      j = 0: SD(0) = 0: k = 0
      Do
        bf = GetJD0(NewMoon(yJD0 - 96 + j * 28)) + 0.5
        If bf >= Junggi(0).RealDay Then
          If k > 0 Then
            If bf > SD(k - 1) Then SD(k) = bf: k = k + 1
          Else
            SD(0) = bf: k = 1
          End If
        End If
        j = j + 1
      Loop Until bf > yJD0 + 427
      j = k: k = j - 1
    
    '삭망월 변수를 초기화(삭망월 변수에 정삭일만 저장)
      For i = 0 To 25
        LD(i).StartDay = 0
        LD(i).StartDay = SD(i)
        LD(i).MonName = 100 '100은 아무 값도 지정되지 않았음을 나타냄
      Next i
      PreWinter = Junggi(1).RealDay '천정동지일
      ThisWinter = Junggi(13).RealDay '계산하는 연도의 동지일
    
    '입기시각과 정삭일을 비교해 삭망월 변수에 달 번호 매기기
      idx1 = 0: idx2 = 0
      For i = 0 To 24
        LD(i).Junggi = False
        For j = 0 To 15
          If LD(i + 1).StartDay > Junggi(j).RealDay And Junggi(j).RealDay >= LD(i).StartDay Then
            LD(i).Junggi = True '무중월인지 유중월인지 파악
            If LD(i).MonName = 100 Then
              LD(i).MonName = Junggi(j).MonNumber '중기에 따른 달 이름 넣기(초기값)
            ElseIf Junggi(j).MonNumber = 11 Or LD(i).MonName = 11 Then '동짓달은 무조건 11월로 입력
              LD(i).MonName = 11
            End If
          End If
        Next j
      Next i
    
    '천정동지가 포함된 달과 계산하려는 해의 동짓달 사이에 포함된 달의 수 찾기
      Count1 = 0
      For i = 0 To k - 1
        If PreWinter < LD(i).StartDay And LD(i).StartDay <= ThisWinter Then Count1 = Count1 + 1
        If PreWinter < LD(i + 1).StartDay And LD(i).StartDay <= PreWinter Then idx1 = i 'idx1에 천정동지월의 변수번호 저장
        If ThisWinter < LD(i + 1).StartDay And LD(i).StartDay <= ThisWinter Then idx2 = i 'idx2에 동지월의 변수번호 저장
      Next i

      '포함된 달의 개월수에 따라 치윤 시작(4개의 경우로 윤달의 형태 지정)
      If Count1 = 12 Then
        LeapType = 4 '조건 4. 천정동지월과 동지월 사이에 12개월이 있으면 이 사이의 달은 모두 평월.
      Else '13개월이면 윤달이 있음.
        If LD(idx1 + 1).Junggi = True And LD(idx1 + 2).Junggi = True Then LeapType = 1  '조건 1
        If LD(idx1 + 1).Junggi = False And LD(idx1 + 2).Junggi = True Then LeapType = 2  '조건 2
        If LD(idx1 + 1).Junggi = True And LD(idx1 + 2).Junggi = False Then LeapType = 3  '조건 3
    '조건 1: 천정동지월 다음 2달이 모두 유중월, 평 12월과 평 1월임.
    '조건 2: 천정동지월 다음 달은 무중월, 그 다음은 유중월, 윤 11월과 평 12월.
    '조건 3: 천정동지월 다음 달이 유중월, 그 다음은 무중월, 평 12월과 윤 12월.  

        '여기서부터 윤달의 위치 추정

        Leap13 = False
        For i = (idx1 + 3) To (idx2 - 1)  '(천정동지월+3개월)째부터 금년 동지월까지 검사
          If LD(i).Junggi = False Then Leap13 = True '이 기간에 무중월이 있는지를 탐색
        Next i
        If LeapType = 2 Or LeapType = 3 Then Leap13 = False '조건 2와 3일 때는 이 기간동안 모두 평월임.
      End If


      '천정동지월에서 (천정동지월+2개월)까지 치윤, 월 번호 매기기.
      LCount = 0
      Select Case LeapType
       Case 1, 4
         LD(idx1 + 1).MonName = 12: LD(idx1 + 1).LYear = dYear - 1: LD(idx1 + 1).Junggi = True
         LD(idx1 + 2).MonName = 1: LD(idx1 + 2).LYear = dYear: LD(idx1 + 2).Junggi = True
        
       Case 2
          LD(idx1 + 1).MonName = 11: LD(idx1 + 1).LYear = dYear - 1: LD(idx1 + 1).Junggi = False
          LD(idx1 + 2).MonName = 12: LD(idx1 + 2).LYear = dYear - 1
        
       Case 3
         LD(idx1 + 1).MonName = 12: LD(idx1 + 1).LYear = dYear - 1
         LD(idx1 + 2).MonName = 12: LD(idx1 + 2).LYear = dYear - 1: LD(idx1 + 2).Junggi = False
     
      End Select
      LD(idx1).MonName = 11: LD(idx1).LYear = dYear - 1
    
      '(천정동지월+3개월)부터 동지월까지 치윤, 월 번호 매기기
      fMON = 1: A = 0
      If LeapType = 4 Then A = 1 '조건 4는 조건 1과 같은 방법으로 처리
      For i = idx1 + 3 To idx2
        LD(i).LYear = dYear
       
        If LeapType = 1 Then '조건 1(또는 4)이면 첫번째 나오는 무중월을 윤달로 간주.
          If LD(i).Junggi = True Or LCount > 0 Then
            LD(i).Junggi = True
            A = A + 1
          Else
            LCount = 1
          End If
          LD(i).MonName = fMON + A
       
        Else '조건 2, 3이면 (천정동지월+3개월)부터 동지월까지는 모두 평월로 처리.
          LD(i).MonName = fMON + A
          LD(i).Junggi = True
          A = A + 1
        End If
      Next i
     
      For i = 0 To 24  '달 길이 계산
        If Abs(LD(i + 1).StartDay - LD(i).StartDay) < 31 Then
          LD(i).MonLength = LD(i + 1).StartDay - LD(i).StartDay
        End If
      Next i

      For i = 0 To k - 1 '입력일이 포함된 달을 찾아 음력 날짜 출력
        If jd0 >= LD(i).StartDay And jd0 < LD(i + 1).StartDay Then
          LunarYear = LD(i).LYear
          LunarMon = LD(i).MonName
          LunarDay = jd0 - LD(i).StartDay + 1
          IsLeap = Not LD(i).Junggi
          Exit For
        End If
      Next i
    End Sub


음력을 산출하는 구체적인 과정은 이 글에서 설명한대로 입니다. 추후에 실제 예를 들어가며 더욱 자세히 설명토록 하겠습니다.