[转]计算中国农历

来源:互联网 发布:展板排版软件 编辑:程序博客网 时间:2024/04/29 23:22

转自吹泡泡的小猫 原文地址:http://blog.csdn.net/orbit/article/details/9210413

        世界各国的日历都是以天为最小单位,但是关于年和月的算法却各不相同,大致可以分为三类:

 

阳历--以天文年作为日历的主要周期,例如:中国公历(格里历)

阴历--以天文月作为日历的主要周期,例如:伊斯兰历

阴阳历--以天文年和天文月作为日历的主要周期,例如:中国农历

 

我国古人很早就开始关注天象,定昼夜交替为“日”,月轮盈亏为“月”,寒暑交替为“年”,在总结日月变化规律的基础上制定了兼有阴历月和阳历年性质的历法,称为中国农历。本文将介绍中国农历的历法规则、天干地支(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个朔望月的天数相等,即:

 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秒以内精度的时间。要想精确地获得几千年乃至更长时间范围内任意一年的节气发生时间和日月合朔时间,就只能采用“天文算法”。

 

       所谓的“天文算法”,就是利用经典力学定律推导行星运转轨道,对任意时刻的行星位置进行精确计算,从而获得某种天文现象发生时的时间,比如日月合朔这一天文现象就是太阳和月亮的地心黄经(视黄经)差为0的那一瞬间。能够计算任意时刻行星位置的一套理论就被称为星历表,比较著名的星历表有美国国家航空航天局下属的喷气推进实验室发布的DE系列星历表,还有瑞士天文台在DE406基础上拓展的瑞士星历表等等。根据行星运行轨道直接计算行星位置通常不是很方便,更何况大多数民用天文计算用不上那么多精确的轨道参数,于是天文学家在这些星历表的基础上推导出了很多可以做简便计算,但是又能保证一定精度的行星运行理论,比较著名的有VSOP82/87太阳系行星运行理论和ELP-2000/82月球运行理论,这两套理论在精度上已经很接近DE系列星历表了。关于如何应用这两套伦理进行天文历法计算,请参考“日历生成算法”系列文章的第三篇《用天文方法计算二十四节气》和第四篇《用天文方法计算日月合朔》,本文介绍的农历年历推算是在已经通过天文算法获得了精确的节气时间和日月合朔时间的基础上进行的。

        中国的官方纪时采用的是中国公历(格里历),因此农历年历的推导应以公历年的周期为主导,附上农历年的信息,也就是说,年历以公历的1月1日为起始,至12月31日结束,根据农历历法推导出的农历日期信息,附加在公历日期信息上形成双历。通常情况下,一个公历年周期都不能完整地对应到一个农历年周期上,二者的偏差也不固定,因此不存在稳定的对应关系,也就是说,不存在从公历的日期到农历日期的转换公式,只能根据农历的历法规则推导出农历日期与公历日期的对应关系。由农历历法规则可知,上一个公历年的冬至()所在的朔望月是上一个农历年的十一月(冬月),所以在进行节气计算时,需要计算包括上一年冬至节气在内的二十五个节气,才能对应上上一个农历年的十一月和当前农历年的十一月。在计算与之对应的朔日时,考虑到有闰月的情况,需要从上一年冬至节气前的第一个朔日,连续计算15个朔日才能保证覆盖两个冬至之间的一整年时间,图(1)显示了2011年没有闰月的情况下朔日和冬至的关系:

图(1)没有闰月情况下朔日与冬至节气关系图

图中上排数字是公历月的编号,黑色圆点代表朔日,黑色三角形代表冬至节气。图(2)显示了2012年有闰月的情况下朔日和冬至的关系:

图(2)有闰月情况下朔日与冬至节气关系图

 

通过计算得到能够覆盖两个冬至节气的所有朔日时间后,就可以着手建立公历日期与农历日期的对应关系。以图(1)所示的2011年为例,首先根据计算得到的15个朔日(2011年只会用到其中的前14个时间)时间,建立与2011年(公历年)有关的朔望月关系表:

朔日编号

合朔时间

对应公历日期

月长

月名

1

01:35:39.90

2010-12-06

29

冬月

2

17:02:34.26

2011-01-04

30

腊月

3

10:30:42.67

2011-02-03

30

正月

4

04:45:59.44

2011-03-05

29

二月

5

22:32:15.13

2011-04-03

30

三月

6

14:50:31.79

2011-05-03

30

四月

7

05:02:32.51

2011-06-02

29

五月

8

16:53:54.10

2011-07-01

30

六月

9

02:39:45.06

2011-07-31

29

七月

10

11:04:06.43

2011-08-29

29

八月

11

19:08:50.09

2011-09-27

30

九月

12

03:55:54.64

2011-10-27

29

十月

13

14:09:40.97

2011-11-25

30

冬月

14

02:06:27.05

2011-12-25

29

腊月

15

15:39:23.99

2012-01-23

30

正月

表(2)2011年朔望月与公历日期关系表

 

编号为1和2的两个朔日之间的朔望月是十一月,因为冬至节气落在这个朔望月,其它月的月名依次类推,正月的朔日就是春节。输出公历和农历双历时,以月(公历)为单位,从每月第一天开始,依次判断每一天属于哪个朔望月,确定这一天的农历月名,然后比较这一天和这个朔望月的朔日之间相差几天,记为农历日期。以2011年1月1日为例,这一天在2010年12月6日(2010年农历十一月的朔日)和2011年1月4日之间(2010年农历十二月的朔日),查表(1)可知对应的农历月是十一月,这一天和2010年12月6日相差26天,因此这一天的农历日期就是“廿七”。再以2011年2月3日(春节)这一天为例,查朔望月表得知2月3日属于从2月3日开始的朔望月,这个朔望月的月名是正月,而2月3日就是月首,农历日期是初一,正月初一就是春节。

先来介绍两个函数,这两个函数分别用于计算节气和日月合朔发生的时间,函数算法的具体描述将在“日历生成算法”系列文章的第三篇《用天文方法计算二十四节气》和第四篇《用天文方法计算日月合朔》中介绍,此处只是简单介绍一下用法。首先是计算节气时间的函数

    5 double CalculateSolarTerms(int year, int angle);

 

这个函数用于计算指定的年份(year参数)中,太阳在黄道上运行(视运动)到指定角度时的时间,angle可以设定节气发生时的角度,比如CalculateSolarTerms(2011, 270)就是计算2011年冬至的时间。这个函数返回的时间类型是儒略日,关于儒略日的说明请参考“日历生成算法”系列文章的第一篇《中国公历(格里历)》。

        接下来介绍计算日月合朔时间的函数:

    8 double CalculateMoonShuoJD(double tdJD);

 

这个函数返回指定时间附近的朔日时间,搜索的范围是tdJD参数指定时间的前一天到后29.5305天,tdJD参数和返回值的时间类型都是儒略日。

        生成指定公历年份的公历和农历的双历年历的流程如下:

 

图(3)计算公农历双历年历的流程

 

GetAllSolarTermsJD()函数从指定年份的指定节气开始,连续计算25个节气时间,时间可以跨年份,内部判断过冬至节气后自动转到下一年的节气继续计算:

  139 void CChineseCalendar::GetAllSolarTermsJD(int year, int start, double*SolarTerms)

  140 {

  141     int i = 0;

  142     int st = start;

  143     while(< 25)

  144     {

  145         double jd = CalculateSolarTerms(year, st * 15);

  147         if(st == WINTER_SOLSTICE)

  148         {

  149             year++;

  150         }

  151         st = (st + 1) % SOLAR_TERMS_COUNT;

  152     }

  153 }

start参数是节气的索引,定义二十四节气的索引如下:

   38 const int VERNAL_EQUINOX      = 0;    // 春分

   39 const int CLEAR_AND_BRIGHT    = 1;    // 清明

   40 const int GRAIN_RAIN          = 2;    // 谷雨

   41 const int SUMMER_BEGINS       = 3;    // 立夏

   42 const int GRAIN_BUDS          = 4;    // 小满

   43 const int GRAIN_IN_EAR        = 5;    // 芒种

   44 const int SUMMER_SOLSTICE     = 6;    // 夏至

   45 const int SLIGHT_HEAT         = 7;    // 小暑

   46 const int GREAT_HEAT          = 8;    // 大暑

   47 const int AUTUMN_BEGINS       = 9;    // 立秋

   48 const int STOPPING_THE_HEAT   = 10;   // 处暑

   49 const int WHITE_DEWS          = 11;   // 白露

   50 const int AUTUMN_EQUINOX      = 12;   // 秋分

   51 const int COLD_DEWS           = 13;   // 寒露

   52 const int HOAR_FROST_FALLS    = 14;   // 霜降

   53 const int WINTER_BEGINS       = 15;   // 立冬

   54 const int LIGHT_SNOW          = 16;   // 小雪

   55 const int HEAVY_SNOW          = 17;   // 大雪

   56 const int WINTER_SOLSTICE     = 18;   // 冬至

   57 const int SLIGHT_COLD         = 19;   // 小寒

   58 const int GREAT_COLD          = 20;   // 大寒

   59 const int SPRING_BEGINS       = 21;   // 立春

   60 const int THE_RAINS           = 22;   // 雨水

   61 const int INSECTS_AWAKEN      = 23;   // 惊蛰

 

节气索引乘以15就是节气在黄道上对应的度数。GetNewMoonJDs()函数从指定时间开始连续计算15个朔日时间,从第一个冬至节气前的第一个朔日开始。15个朔日可以形成14个完整的朔望月,保证在有闰月的情况下也能包含两个冬至节气:

  137 void CChineseCalendar::GetNewMoonJDs(double jd, double *NewMoon)

  138 {

  139     for(int i = 0; i < NEW_MOON_CALC_COUNT; i++)

  140     {

  141         double shuoJD = CalculateMoonShuoJD(jd);

  142         NewMoon[i] = shuoJD;

  143 

  144         jd += 29.5; /*转到下一个最接近朔日的时间*/

  145     }

  146 }

BuildAllChnMonthInfo()函数根据15个朔日时间组成14个朔望月,根据相邻朔日的间隔计算出农历月天数用来判定大小月,并且从“十一月”开始依次为每个朔望月命名(月建名称):

  170 bool CChineseCalendar::BuildAllChnMonthInfo()

  171 {

  172     CHN_MONTH_INFO info; //一年最多可13个农历月

  173     int i;

  174     int yuejian = 11;   //采用夏历建寅,冬至所在月份为农历11月

  175     for(= 0; i < (NEW_MOON_CALC_COUNT - 1); i++)

  176     {

  177         info.mmonth = i;

  178         info.mname = (yuejian <= 12) ? yuejian : yuejian - 12;

  179         info.shuoJD = m_NewMoonJD[i];

  180         info.nextJD = m_NewMoonJD[+ 1];

  181         info.mdays = int(info.nextJD + 0.5) - int(info.shuoJD + 0.5);

  182         info.leap = 0;

  183 

  184         CChnMonthInfo cm(&info);

  185         m_ChnMonthInfo.push_back(cm);

  186 

  187         yuejian++;

  188     }

  189 

  190     return (m_ChnMonthInfo.size() == (NEW_MOON_CALC_COUNT - 1));

  191 }

 

CalcLeapChnMonth()函数根据节气和朔日时间判断在两个冬至节气之间的农历年是否有闰月,判断的依据就是看第十四个朔日是否在第二个冬至节气之前,如果第十四个朔日发生在第二个冬至节气之前,就说明在两个冬至节气之间发生了十三次朔日,需要置闰月。因为农历中十二个中气属于哪个农历月是固定的,因此置闰月的过程就是依次判断十二个中气是否在对应的农历月中,如果本应该属于某个农历月的中气却没有落在这个农历月中,则这个农历月就是闰月,需要设置闰月标志,同时调整这个月之后的月名。调整农历月名的方法就是月名减一,比如原来是八月就要调整为七月,这样就将十三个月对应上了十二个月名(其中多出来的一个农历月被命名为闰某月)。如果节气和朔日发生在同一天,CalcLeapChnMonth()函数采用的是民间历法的规则,与现行历法一致:

  194 void CChineseCalendar::CalcLeapChnMonth()

  195 {

  196     assert(m_ChnMonthInfo.size() > 0); /*阴历月的初始化必须在这个之前*/

  197 

  198     int i;

  199

  200     if(int(m_NewMoonJD[13] + 0.5) <= int(m_SolarTermsJD[24] + 0.5)) //第13月的月末没有超过冬至,说明今年需要闰一个月

  201     {

  202         //找到第一个没有中气的月

  203         i = 1;

  204         while(< (NEW_MOON_CALC_COUNT - 1))

  205         {

  206 

  207             /*m_NewMoonJD[i + 1]是第i农历月的下一个月的月首,本该属于第i月的中气如果比下一个月

  208               的月首还晚,或者与下个月的月首是同一天(民间历法),则说明第i月没有中气*/

  209             if(int(m_NewMoonJD[+ 1] + 0.5) <= int(m_SolarTermsJD[2 * i] +0.5))

  210                 break;

  211             i++;

  212         }

  213         if(< (NEW_MOON_CALC_COUNT - 1)) /*找到闰月,对后面的农历月调整月名*/

  214         {

  215             m_ChnMonthInfo[i].SetLeapMonth(true);

  216             while(< (NEW_MOON_CALC_COUNT - 1))

  217             {

  218                 m_ChnMonthInfo[i++].ReIndexMonthName();

  219             }

  220         }

  221     }

  222 }

        从理论上讲,本文介绍的算法在精度允许的范围内可以计算前后几千年的农历年历,但是对古代的农历计算需要小心。首先是“平朔”和“定朔”的问题,唐代以前使用的是平朔方法定月首,本文介绍的计算方法采用的是“定朔”方法,因此计算出的年历与唐代以前的历史会不一致。另外,即是在唐代以后采用“定朔”的历法,因为古代天文观测和计算受条件限制,可能不够精确,因此与现在用天文算法计算出的结果可能并不一致。所以对历史农历的计算应该以历史事实为主,天文计算为辅,当计算与历史不一致时,要根据历史数据进行校正。Calendar.exe是根据本文介绍的算法编写的日历小程序,没有太多的功能,主要是为了验证算法,因为没有历史数据用于修正结果,因此不支持1601年以前的农历计算(也就是说按照天文算法计算出来的结果可能和实际历史上的历法不符)。

图(5)演示程序的界面

 

小知识1:民间历法和历理历法

    新中国成立以后没有颁布新的“官方农历历法”,将历法和政治分离体现了时代的进步,但是由于没有 “官方历法”,也引起了一些问题。比如我国现在采用的农历历法是《时宪历》,它源于清朝顺治年间(公元1645)颁布的《顺治历》,它有两个不足之处:一个是日月合朔和节气的时间以北京当地时间为准,也就是东经116度25分的当地时间,其节气和新月的观察只适用于中原地区。其它经度的地方,因为时间的关系,对导致日月合朔和节气时间的差异导致置闰和月顺序各不相同。另一个不足之处就是日月合朔时间和节气时间判断不精确,如果日月合朔时间和节气时间在同一天,不管具体的时间是否有先后,一律将此节气算做新月中的节气,这样一来,如果这个节气是中气,就会影响到闰月的设置。历理历法针对这两点进行了改进,对节气时间和日月合朔时间统一采用东经120度即东八区标准时,这样在任何时区的节气和置闰结果都是一样的,以东八区标准时为准。对于节气时间和日月合朔时间在同一天的情况,精确计算到时、分、秒,只有日月合朔时间在节气时间之前,这个节气才包含在次月内。历理历法从理论上讲更符合现代天文学的精确计算,但是需要注意的是,历理历法仍然只是存在于理论上的历法,我国现行的农历历法依然是民间历法《时宪历》或《顺治历》

 

 

小知识2:通式寿星公式

“通式寿星公式”是前人整理出来的一个用于计算每年立春日期的经验公式:

Date = 向下取整(Y * D + C) - L

其中,Y是年份,D的值是0.2422,C是经验值,取决于节气和年份,对于21世纪,立春节气的C值是4.475,春分节气的C值是20.646等等;

L是闰年数,其计算公式为:

L = 向下取整(Y/4) - 向下取整(Y/100) + 向下取整(Y/400)

 

用“通式寿星公式”确定2011年立春日期的过程如下:

L = int(2011/4) – int(2011/100) + int(2011/400) = 502  20 + 5 = 487

 

Date = int(2011×0.2422+4.475)- 487 = 491  487 = 4

 

所以,2011年的立春日期是2月4日。

 

 

小知识3计算节气和朔日的经验公式

    以1900年1月0日(星期日)为基准日,之后的每一天与基准日的差值称为“积日”, 1900年1月1日的积日是1,以后的时间依次类推,则计算第y年第x个节气的积日公式是:

 

F = 365.242 * (y – 1900) + 6.2 + 15.22 *x - 1.9 * sin(0.262 * x)

 

其中x是节气的索引,0代表小寒,1代表大寒,其它节气按照顺序类推。

 

计算从1900年开始第m个朔日的公式是:

 

M = 1.6 + 29.5306 * m + 0.4 * sin(1 - 0.45058 * m)

 

 

小知识4:平朔和定朔

    中国农历的朔望月长度是平均29.5305天,所以农历月就有大月30天,小月29天之分,从先秦时期到唐代,农历历法均是采用大小月轮流交替的方式设置每个农历月的天数,只有少数情况下才出现连续两个大月的情况,采用这种方式的历法就称为“平朔”。“平朔”历法简单,但是不能保证日月合朔发生在初一这一天,有可能是上月的月末一天,也有可能是本月初二。南北朝时期,一种新的历法被提出来,这种历法严格按照日月合朔为月初制定农历月,采用这种方式的历法就称为“定朔”。“定朔”历法严格将日月合朔时间确定月初,因为月球公转是椭圆轨道,速度并不是均匀,所以会发生连续多个大月或连续多个小月的情况,导致“定朔”历法推广遇到很大的阻力,直到唐代,中国历法才全面弃用“平朔”,改用“定朔”。

 

 

小知识5:正月初一和立春节气

    立春是二十四节气之首,所以古代民间都是在“立春”这一天过节,相当于现代的春节(中国古代即是节气也是节日的情况很多,比如清明、冬至等等)。1911年,孙中山领导的辛亥革命建立了中华民国,在从历法上正式把农历正月初一定为“春节”,把公历1月1日定为“元旦”,也就是“新年”。农历年从正月初一开始没有争议,但是农历生肖年从何时开始却一直有争议,目前多数人都认为“立春”节气是农历生肖年的开始。因为在中国古代历法中,十二生肖的计算与天干地支有很大关系,所以在“论天干地支、计算廿四节气”的情况下,“立春”节气应该是新生肖的开始。对于普通老百姓来说,习惯于认为正月初一是生肖年的开始,因此,正月初一和“立春”节气之间出生的小孩,在确定属相的时候就有点麻烦了。属龙还是属蛇?这是个问题。


0 0