1. 程式人生 > >算法系列之二十:計算中國農曆(一)

算法系列之二十:計算中國農曆(一)

        世界各國的日曆都是以天為最小單位,但是關於年和月的演算法卻各不相同,大致可以分為三類:

陽曆--以天文年作為日曆的主要週期,例如:中國公曆(格里曆)

陰曆--以天文月作為日曆的主要週期,例如:伊斯蘭曆

陰陽曆--以天文年和天文月作為日曆的主要週期,例如:中國農曆

我國古人很早就開始關注天象,定晝夜交替為“日”,月輪盈虧為“月”,寒暑交替為“年”,在總結日月變化規律的基礎上制定了兼有陰曆月和陽曆年性質的歷法,稱為中國農曆。本文將介紹中國農曆的歷法規則、天干地支(Heavenly StemsEarthly Branches)的計算方法以、二十四節氣與中國農曆的關係以及知道節氣和日月合朔的精確時間的情況下推算中國農曆年歷的方法。

        在介紹中國農曆的歷法之前,必須要先介紹一下中國古代的紀年方法。中國古代用天干地支紀年,嚴格來講,天干地支紀年以及十二屬相併不是中國農曆曆法的一部分,但是在中國歷史上直到今天,天干地支以及十二屬相一直都是做為中國農曆紀年關係密切的一部分而存在,因此這裡先介紹一下天干地支紀年法以及十二屬相。

        中國古代紀年不用數字,而是採用天干地支組合。天干有十個,分別是:甲、乙、丙、丁、戊、己、庚、辛、壬、癸;地支有十二個,分別是:子、醜、寅、卯、辰、巳、午、未、申、酉、戌、亥。使用時天干地支各取一字,天干在前,地支在後,組合成干支,例如甲子、乙丑、丙寅等等,依次輪迴可形成六十種組合,以這些天干地支組合紀年,每六十年一個輪迴,稱為一個甲子。實際上中國古代紀月、紀日以及紀時辰都採用干支方法,這些干支組合起來就是我們熟悉的生辰八字。十二屬相又稱“十二生肖”,由十一種源自自然界的動物:鼠、牛、虎、兔、蛇、馬、羊、猴、雞、狗、豬以及傳說中的龍組成,用於紀年時,按順序和十二地支組合成子鼠、丑牛、寅虎、卯兔、辰龍、巳蛇、午馬、未羊、申猴、酉雞、戌狗和亥豬。天干地支以及十二生肖常組合起來描述農曆年,比如公曆2011

年就是農曆辛卯兔年、2012年是壬辰龍年等等。

        計算某一年的天干地支,有很多經驗公式,如果知道某一年的天干地支,也可以直接推算其它年份的天干地支。舉個例子,如果知道2000年是庚辰龍年,則2012年的干支可以這樣推算:(2012-2000% 10=22012年的天干就是從庚開始向後推2個天干,即壬;2012年的地支可以這樣推算:(2012 - 2000% 12 = 02012年的地支仍然是辰,因此2012年的天干地支就是壬辰,十二生肖龍年。對於2000年以前的年份,計算出年份差後只要將天干和地支向前推算即可。例如1995年的干支可以這樣計算:(2000 – 1995%10 = 5,(2000 – 1995%12 = 5,庚向前推算5

即是乙,辰向前推算5即是亥,因此1995年的干支就是乙亥,十二生肖豬年。這個干支推算演算法的實現如下:

  202 void CalculateYearGanZhi(int year, int *gan, int *zhi)

  203 {

  204     int sc = year - 2000;

  205     *gan = (7 + sc) % 10;

  206     *zhi = (5 + sc) % 12;

  207 

  208     if(*gan < 0)

  209         *gan += 10;

  210     if(*zhi < 0)

  211         *zhi += 12;

  212 }

獲得2008年的干支紀年:

    9 TCHAR *nameOfTianGan[COUNTS_FOR_TIANGAN] = { _T("甲"),_T("乙"),_T("丙"),_T("丁"),_T("戊"),_T("己"),_T("庚"),_T("辛"),_T("壬"),_T("癸") };

   10 TCHAR *nameOfDiZhi[COUNTS_FOR_DIZHI] = { _T("子"),_T("醜"),_T("寅"),_T("卯"),_T("辰"),_T("巳"),_T("午"),_T("未"),_T("申"),_T("酉"),_T("戌"),_T("亥") };

  146     int gan,zhi;

  147 

  148     CalculateYearGanZhi(2008, &gan, &zhi);

  149 

  150     text.Format(_T("農曆【%s%s】%s年"),

  151                 year, m_curMonth, nameOfTianGan[gan - 1], nameOfDiZhi[zhi - 1], nameOfShuXiang[zhi - 1]);

結果是:農曆戊子鼠年。

        中國農曆是以月亮執行週期為基礎,結合太陽執行規律(二十四節氣)制定的歷法,農曆月的定義規則就是中國農曆曆法的關鍵,因此要了解中國農曆的歷法規則,就必須知道如何定義月,如何設定閏月?中國農曆的一年有十二個月或十三個月,但是正統的叫法只有十二個月,分別是正月、二月、三月、四月、五月、六月、七月、八月、九月、十月、冬月和臘月(注意,正統的中國農曆是沒有十一月和十二月的,如果你用的歷法軟體有顯示農曆十一月和農曆十二月,就說明非常不專業)。中國民間常用“十冬臘月天”來形容寒冷的天氣,其實指的就是十月,十一月和十二月這三個最冷的月份。一年有十三個月的情況是因為有閏月,多出來的這個閏月沒有月名,只是跟在某個月後面,稱為閏某月。比如公曆2009年對應的農曆乙丑年,就是閏五月,於是這一年可以過兩個端午節。

        中國農曆為什麼會有閏月?其實中國農曆置閏月是為了協調回歸年和農曆年的矛盾。前面提到過,中國農曆是一種陰陽曆,農曆的月分大月和小月,大月一個月是30天,小月一個月是29天。中國農曆把日月合朔(太陽和月亮的黃經相同,但是月亮不可見)的日期定位月首,也就是“初一”,把月圓的時候定為望日,也就是“十五”,月亮繞地球公轉一週稱為一個朔望月。天文學的朔望月長度是29.5306日,中國農曆以朔望月為基礎,嚴格保證每個月的頭一天是朔日,這就使得每個月是大月還是小月的安排不能固定,通常需要通過天文學觀測和計算來確定。一個農曆年由12個朔望月組成,這樣一個農曆年的長度就是29.5306  12 = 354.3672日,而陽曆的一個天文學回歸年是365.2422日,這樣一個農曆年就比一個迴歸年少10.88天,這個誤差如果累計起來過16年就會出現“六月飛雪”的奇觀了。為了協調農曆年和迴歸年之間的矛盾,聰明的先人在天文觀測的基礎上,找到了“閏月”的方法,通過在適當的月份插入閏月來保證每個農曆年的正月到三月是春季,四月到六月是夏季,七月到九月是秋季,十月到十二月是冬季,也就是說,讓曆法和天文氣象能夠基本對上,不至於出現“六月飛雪”。

        那麼多長時間增加一個閏月比較合適呢?最早人們推算是“三年一閏”,後來是“五年兩潤”,隨著曆法計算的精確,最終定型為“十九年七閏”。這個“十九年七閏”又是怎麼算出來的呢?其實就是求出迴歸年日數和朔望月日數的最小公倍數,也就是m個迴歸年的天數和n個朔望月的天數相等,即:

m  365.2422 = n  29.5306

這樣mn的比例就是29.5306 : 365.2422  19 : 235,按照這個最接近的整數倍數關係,每19個迴歸年需要新增的閏月就是:

235 – 12  19 = 7

也就是“十九年七閏”的由來。但是需要注意的是,“十九年七閏”也並不是精確的結果,每19年就會有0.0892天的誤差:

19  365.2422 - 235  29.5306  0.0892

這樣每213年就會積累約1天的誤差,因此,即使按照“十九年七閏”計算,中國農曆每一兩百年就需要修正一次。正因為這樣,現行農曆從唐代以後就已經不再遵守“十九年七閏”法,而是採用更準確的“中氣置閏”法。“中氣置閏”法更準確的名稱應該是“定冬至”法,就是定兩個冬至節氣之間的時間為一個農曆年,這樣農曆年的長度就和太陽迴歸年長度對應,不會產生誤差。

        現在,我們知道農曆通過置閏月的方式協調農曆年和迴歸年長度不相等的問題,也知道了置閏的方法是“中氣置閏”法,那麼到底什麼是“中氣”,又是如何定中氣置閏月呢?要回答這個問題,就需要介紹另一個天文現象――節氣。二十四節氣起源於黃河流域,遠在春秋時代,就定出仲春、仲夏、仲秋和仲冬等四個節氣。以後不斷地改進與完善,到秦漢年間,二十四節氣已完全確立,漢武帝太初元年(公元前104年)制定的《太初曆》,則第一次從曆法上明確了二十四節氣的天文位置。

        地球沿著一個近似橢圓軌道繞太陽公轉,這個公轉軌道所在的平面就是“黃道面”,黃道面向外延伸與天球的交線就是“黃道”。古人由於觀測條件限制,只能根據視覺感覺認為是太陽沿著黃道繞地球運轉,因此設定太陽從黃經(黃道經度)零度起(以春分點為起點自西向東度量),將太陽沿黃經每執行15度所經歷的時日稱為“一個節氣”。太陽每年執行360度,共經歷二十四個節氣,春季的節氣有立春(315度)、雨水(330度)、驚蟄(345度)、春分(0度、360度)、清明(15度)和穀雨(30度),夏季的節氣有立夏(45度)、小滿(60度)、芒種(75度)、夏至(90度)、小暑(105度)和大暑(120度),秋季的節氣有立秋(135度)、處暑(150度)、白露(165度)、秋分(180度)、寒露(195度)和霜降(210度)。冬季的節氣有立冬(225度)、小雪(240度)、大雪(255度)、冬至(270度)、小寒(285度)和大寒(300度)。二十四節氣又細分為十二節氣和十二中氣,二十四節氣按照順序排在奇數位置上的就是節氣,排在偶數位置上的就是中氣。也就是說,立春、驚蟄、清明、立夏、芒種、小暑、立秋、白露、寒露、立冬、大雪和小寒就是十二個節氣,而雨水、春分、穀雨、小滿、夏至、大暑、處暑、秋分、霜降、小雪、冬至和大寒就是十二個中氣。二十四個節氣平分在公曆的12個月中,每月一節氣一中氣。二十四節氣反映了太陽的週年運動(以地球為參照物的視運動),所以節氣在現行的公曆中日期基本固定,上半年在6日、21日,下半年在8日、23日,前後不差 1~2天。中國民間流傳的《二十四節氣歌》就是為了方便記憶這些節氣:

春雨驚春清谷天,

夏滿芒夏暑相連,

秋處露秋寒霜降,

冬雪雪冬小大寒,

每月兩節不變更,

最多相差一兩天。

傳統上一個農曆年起於冬至,終於冬至,因此要確定在哪一年置閏,主要看那一年兩個冬至之間有幾個朔望月,如果是12個朔望月,則不置閏,如果是十三個朔望月,則置閏月,至於閏幾月,則要看節氣而定。對於有13個朔望月的農曆年,置閏月的規則就是從農曆二月開始到十月,第一個沒有中氣的月就是閏月,這個沒有中氣的朔望月跟在哪個月後面就是閏幾月。為什麼會有沒有中氣的朔望月呢?黃道上兩個中氣之間相隔30度,一個迴歸年的長度是365.2422日,則兩個中氣之間的平均間隔是365.2422 12 = 30.4368日,但是因為地球軌道是橢圓軌道,因此相鄰的兩個中氣的時間間隔是不均勻的,比如在遠地點附近的中氣間隔就會長一點,最長可能是31.45天。而農曆的朔望月平均長度是29.5306日,這樣就會出現某個朔望月剛好落在兩個中氣之間的情況,比如,某個月的上一個月月末是一箇中氣,但是下一個中氣落在這個月的下一個月的頭幾天裡,這樣這個月就沒有中氣了。舉個例子,2001年農曆辛已年的四月二十九(公曆5月21日)是小滿,農曆四月之後的這個朔望月從公曆5月23日持續到公曆6月20日,而小滿後的下一個中氣夏至是在公曆的6月21日,也就是農曆四月的下下個月的初一,這樣農曆四月後的這個月就沒有中氣,跟在四月之後,就稱為閏四月。

由於節氣在迴歸年中是均勻分佈的,因此公曆中的節氣日期基本上是固定的,比如立春是在公曆的2月3-5日,不會超出這個日期範圍,這也就是《二十四節氣歌》所說的:每月兩節不變更,最多相差一兩天。但是在中國農曆中哪個中氣屬於哪個月是有規定的,雨水是正月的中氣,春分是二月的中氣,穀雨是三月的中氣,小滿是四月的中氣,夏至是五月的中氣,大暑是六月的中氣,處暑是七月的中氣,秋分是八月的中氣,霜降是九月的中氣,小月是十月的中氣,冬至是十一月的中氣,大寒是十二月的中氣。

在瞭解了農曆與節氣的關係以及農曆如何置閏月的方法之後,還需要解決一個問題才能著手農曆年歷的推算,那就是如何確定農曆年的開始,或者說哪個月的初一是農曆新年的開始?要回答這個問題,就需要了解中國農曆特有的“月建”問題。

中國農曆是陰陽合曆,需要同時考慮太陽和月亮的位置。所以在確定歲首(元旦)時,需要先確定它在某個季節,然後再選定與這個季節相近的朔望月作為歲首。由於一歲(一個迴歸年)和12個陰曆月並不相等,相差約10.88天,因此每隔三年需要設定一個閏月調整季節。中國上古的天文學家想出了一個簡便的方法判斷月序與季節的關係,這就是以傍晚時北斗七星的斗柄的指向確定月序,稱為“十二月建”。從北方起向東轉,將地面劃分為十二個方位,傍晚時北斗所指的方位,就是該月的月建,其子月為冬至所在之月,對應十一月,丑月是冬至所在之月的次月,對應十二月,寅月在丑月之後,對應正月。中國在歷史上的不同時期,多次修改過歲首(元旦)的起始月份,上古時代就有“三正”之說,所謂“三正”,就是“夏正建寅、殷正建醜、周正建子”,意思是夏曆以寅月(正月)為歲首,殷歷以丑月(十二月)為歲首,周曆以子月(十一月)為歲首。從秦代到西漢前期又採用秦歷,秦歷建亥,也就是以亥月作為歲首之月,漢武帝太初元年(公元104年)改用太初曆,重新適用建寅的夏曆,以寅月(正月)為歲首。在這之後的兩千多年時間裡,除王莽和魏明帝一度改用建醜的殷歷,唐武后和肅宗時改用建子的周曆外,各個朝代均使用建寅的夏曆直到清朝末年。辛亥革命勝利以後,南京國民政府將公曆1月1日改為元旦,但是人們仍習慣稱農曆的正月初一為元旦。新中國成立初期召開的第一屆政治協商會議,正式將公曆的1月1日確定為元旦,將農曆的正月初一定為“春節”,也就是說,農曆的歲首仍然採用夏曆從寅月(正月)開始。

        瞭解了“月建”問題,就解決了農曆朔望月與公曆月的對應關係,那就是冬至節氣所在的朔望月就是農曆的子月,對於目前適用的夏曆建寅的月建體系,就意味著冬至節氣所在的朔望月是農曆的十一月,只要找到這個朔望月的起始日(日月合朔發生的時刻所在的那一日),就找到了公曆的日期月農曆日期的對應關係。下面總結一下中國農曆曆法的基本法則:

1、嚴格以日月合朔發生時刻為月首,這一天定為初一,通過計算兩次日月合朔的時間間隔確定每月是29天還是30天;

2、月以中氣得名,冬至節氣總是出現在農曆十一月,包含雨水中氣的月為正月(即寅月),月無中氣者為閏月,與前一個月同名;

3、從某一年的冬至後第一天開始,到下一個冬至這段時間內,如果有十三個朔望月出現,則此期間要增加一個閏月,從二月到十月,第一個沒有中氣的月就是閏月,如果在此期間有超過兩個朔望月沒有中氣,則只有第一個沒有中氣的朔望月是閏月;

4、農曆年以正月初一為歲首(關於農曆歲首的說法,請參考文末附加的《小知識5:正月初一和立春節氣》),以臘月(十二月)廿九或三十為除夕;

5、如果節氣和日月合朔在同一天,則該節氣是這個新朔望月的節氣。(民間曆法)

規則5對節氣和朔日在同一天的處理,採用了民間曆法的處理原則,關於民間曆法和歷理曆法的區別,請參考文末附加的《小知識1:民間曆法和歷理曆法》。

        瞭解了農曆曆法的基本法則後,就可以根據曆法進行農曆年歷的推算。農曆年歷的推算是一件很複雜的事情,需要知道每年二十四個節氣和本年內每次日月合朔的精確時間,這些時間的獲取比較困難。現在有很多可以顯示農曆的日曆軟體,其實並不計算這些時間,而是事先從權威機構(如紫金山天文臺)獲取這些經過推算的時間,然後用各種方法將這些資訊儲存在設計好的資料結構中。當計算農曆時採用查表的方法獲取每年的二十四節氣日期、大小月情況以及閏月情況,這樣的軟體受資料量的限制,往往只能顯示近一兩百年的年曆。

        還有一種確定節氣時間和朔日時間的方法,就是在已知某個節氣或朔日的精確時間後,通過某些規律先前或向後推算其它節氣或朔日的時間。有一些經驗公式可以用來計算節氣發生的日期,比如“通式壽星公式”,可以計算出某一年的某個節氣時間,但是隻能精確到日。關於“通式壽星公式”的詳細內容,請參考文末附加的《小知識2:通式壽星公式》。至於精確的節氣或朔日時間,也只能從權威機構獲取。以節氣的時間推算為例,二十四個節氣就是黃道上的24各點,由於地球運動受其它天體的影響,導致這些節氣在每年的時間是不固定的,但是這些節氣之間的間隔時間基本上可以看作是固定的,下表就是二十四節氣的時間間隔表:

節氣名

與上一節氣之間的時間差

與小寒節氣的累積時間差

小寒

1271448.00

0.00

大寒

1272494.40

1272494.40

立春

1275526.20

2548020.60

雨水

1282123.20

3830143.80

驚蟄

1290082.80

5120226.60

春分

1300639.20

6420865.80

清明

1311153.00

7732018.80

穀雨

1323253.80

9055272.60

立夏

1333685.40

10388958.00

小滿

1344107.40

11733065.40

芒種

1351227.00

13084292.40

夏至

1357299.60

14441592.00

小暑

1358968.80

15800560.80

大暑

1358786.40

17159347.20

立秋

1354419.00

18513766.20

處暑

1348236.00

19862002.20

白露

1339003.20

21201005.40

秋分

1328654.40

22529659.80

寒露

1317185.40

23846845.20

霜降

1305760.80

25152606.00

立冬

1295081.40

26447687.40

小雪

1285764.00

27733451.40

大雪

1278469.80

29011921.20

冬至

1273556.40

30285477.60

表(1)二十四節氣時間間隔表(單位:鍾)

已知1900年小寒時刻為1月6日2:05:00,以這個節氣時刻為基準,推算其它年份節氣的演算法實現如下:

    8 static double s_stAccInfo[] =

    9 {

   10     0.00, 1272494.40, 2548020.60, 3830143.80, 5120226.60, 6420865.80,

   11     7732018.80, 9055272.60, 10388958.00, 11733065.40, 13084292.40, 14441592.00,

   12     15800560.80, 17159347.20, 18513766.20, 19862002.20, 21201005.40, 22529659.80,

   13     23846845.20, 25152606.00, 26447687.40, 27733451.40, 29011921.20, 30285477.60

   14 };

   15 

   16 //已知1900年小寒時刻為1月6日02:05:00

   17 const double base1900_SlightColdJD = 2415025.5868055555;

   18 

   19 double CalculateSolarTermsByExp(int year, int st)

   20 {

   21     if((st < 0) || (st > 24))

   22         return 0.0;

   23 

   24     double stJd = 365.24219878 * (year - 1900) + s_stAccInfo[st] / 86400.0;

   25 

   26     return base1900_SlightColdJD + stJd;

   27 

   28 }

base1900_SlightColdJD是北京時間1900年1月6日凌晨2:05:00的儒略日數,CalculateSolarTermsByExp()函式返回指定年份的節氣的儒略日數。已知某個朔日的精確時間推算其它朔日時間的方法也類似,以朔望月的長度為單位向前或向後累加即可。

        這種推算的方法是建立在地球迴歸年的長度是固定365.2422天、節氣的間隔是絕對固定的、朔望月長度是平均的29.5305天等假設之上的,由於天體運動的互相影響,這種假設不是絕對成立的,因此這種推算方法的誤差很大。以CalculateSolarTermsByExp()函式為例,計算1900年前後30年內的節氣時間的誤差還可以控制在30分鐘以內,但是到2000年的時候誤差已經超過130分鐘了。人們還總結出了計算節氣和朔日時間的兩個經驗公式,本文末尾附加的《小知識3:計算節氣和朔日的經驗公式》一節會詳細介紹這兩個公式,不過這兩個公式的結果也只能精確到日,不能提供10秒以內精度的時間。要想精確地獲得幾千年乃至更長時間範圍內任意一年的節氣發生時間和日月合朔時間,就只能採用“天文演算法”。

《繼續:天文演算法計算農曆。。。》