相关代码见:Github
中国农历,生活中也经常被称作阴历。但实际上,农历是一种阴阳历。阴阳历是指兼顾月相周期和太阳周运动所安排的历法,而 真正的阴历如伊斯兰历,则是只以月球周期为基准,而不考虑太阳运动,这样造成的结果是不能通过月份来判断季节。
现行公历是一种阳历,没有兼顾月球运动,故无法在通过日期判断月相。
无论是代码里还是生活中,常常都会用到公农历转换,而由于农历制定的复杂性,两者之间没有直接的对应函数关系,而之前代码里得出农历,往往是事先给定了公农历对应关系,通过查表等方式找到对应的日期。但实际上,只要了解了农历的制定方法,直接计算得到公农历对应日期也是可行的。
月球运动与置闰
首先回顾一下高中简单的天文学知识,月亮绕地球一周的时间是27.3217日,这个周期称为恒星月。而由于地球公转,月球两个朔的周期为29.5306天,这个周期称为朔望月周期。(如下图,两个朔月周期可以理解为月球从在地球和太阳中心连线的位置开始运动,到下一次再次回到这个中心连接位置的时间差,天文学上,是指月球与太阳黄经相等的那一瞬时时间。)农历的指定便与朔望月有关。
我们都知道,对于阳历,一年有12个月,而农历首先以月球周期为基准,那么农历的一个月必定接近朔望月周期29.5306天。那么一年12个月便是$$12\times29.5306=354.3672天$$
而一个回归年有365.2422天,那么农历12个月的一年与实际的一年便相差了约10.875天。一年差了10.875天,那么三年便差了32.625天也就是整整少了一个月。
所以为了弥补这一个月,每隔一段时期,农历中便会置闰一次,使得一年变成13个月,用来弥补这缺少的30多天。
那么如何确定具体的置闰时间呢?在现代,我们主要采用的是中气置闰法。讲到中气,那么就需要再说明一下太阳的运动了。
黄道与节气
地理物理课上学过,地球是沿着一条椭圆的轨道绕着太阳运动,但由于我们在地球上,以地球为参考系看太阳,就仿佛太阳在沿着一条“椭圆轨道”绕着地球运动,我们便把这条轨道在天上的圆形投影叫做黄道,并把春分点那个时刻太阳在黄道上的位置定位0度,夏至日为90度,秋分为180度,冬至为270度。之后,再细分整个黄道为24份,每一份15度,将太阳沿黄道每运行15度所经历的日期称为“一个节气”。这也就是节气的由来。
节气有24个,人们的再把节气细分为十二节气和十二中气。二十四节气按照顺序排在奇数位置上的就是节气,排在偶数位置上的就是中气。(顺序从立春开始,算法上说,假设此节气的黄经为X,那么求(x/15) mod 2,若为0则是中气,为1,则是节气。)具体对照表如下:
序号 | 大致日期(公历) | 黄经 | 节气/中气 | 名称 |
---|---|---|---|---|
1 | 2月4日前后 | 315 | 节气 | 立春 |
2 | 2月18日前后 | 330 | 中气 | 雨水 |
3 | 3月5日前后 | 345 | 节气 | 惊蛰 |
4 | 3月20前后 | 0 | 中气 | 春分 |
5 | 4月5日前后 | 15 | 节气 | 清明 |
6 | 4月20前后 | 30 | 中气 | 谷雨 |
7 | 5月5日前后 | 45 | 节气 | 立夏 |
8 | 5月20前后 | 60 | 中气 | 小满 |
9 | 6月5日前后 | 75 | 节气 | 芒种 |
10 | 6月21日前后 | 90 | 中气 | 夏至 |
11 | 7月7日前后 | 105 | 节气 | 小暑 |
12 | 7月22日前后 | 120 | 中气 | 大暑 |
13 | 8月7日前后 | 135 | 节气 | 立秋 |
14 | 8月22日前后 | 150 | 中气 | 处暑 |
15 | 9月7日前后 | 165 | 节气 | 白露 |
16 | 9月23日前后 | 180 | 中气 | 秋分 |
17 | 10月7日前后 | 195 | 节气 | 寒露 |
18 | 10月23日前后 | 210 | 中气 | 霜降 |
19 | 11月7日前后 | 225 | 节气 | 立冬 |
20 | 11月22日前后 | 240 | 中气 | 小雪 |
21 | 12月6日前后 | 255 | 节气 | 大雪 |
22 | 12月22日前后 | 270 | 中气 | 冬至 |
23 | 1月6日前后 | 285 | 节气 | 小寒 |
24 | 1月21日前后 | 300 | 中气 | 大寒 |
农历制定规则
农历的制定规则与上述的中气、朔望月周期密切相关。
农历制定规则如下:
1.冬至所在的月一定为农历11月(相当于农历岁首)。
2.月球朔(日月黄经相等)的那一天定为初一(月首)。
3.如果两个冬至日之间有12个朔望月则不置闰,如果有13个朔望月,则置闰。
4.若有十三个朔望月,农历十一月后,第一个朔望月内无中气的月份即为置闰月,跟在哪个月份的后面即为闰几月。
为什么有的朔望月会没有中气?
这是因为地球的轨道是椭圆形,在近日点的时候运动的快,两个中气相隔时间短(比如29天);在远日点的时候运动的慢,两个中气时间相隔时间长(比如31天)。而朔望月的周期几乎是恒定的29.5306天。
举个例子假设这样一种情况:
xx年6月21日夏至,xx年7月23日大暑。
xx年6月22日朔,7月22日朔
如果出现这种状况,那么这个朔望月内便没有中气。
从上述农历制定规则可以看出,如果需要计算公历对应的农历,需要下述步骤
假设计算公历x年y月z日对应的农历
1.计算x-1年的冬至日时刻
2.从x-1年开始计算25个节气时间与15个朔望月时间(这是为了防止公历在12月的情况)
3.判断两个冬至日之间是否有13个朔望月?
4.若没有,以上年冬至日为基准,通过朔望月推算得到对应农历
5.若存在,判断此置闰月在所求月份之前或之后,合理控制一个置闰计数符,推算得到日期。
6.如果求得日期不再两个冬至月对应的范围内,建议继续迭代计算,防止出现2033年置闰问题
关于第六点,2033年置闰问题:占坑后补
算法的主要难点在于如何拿到精确地24节气时间与朔日的日期。采用VSOP-87行星理论与ELP-2000/82月球理论能够解决此问题,但若如此,代码长度超过4000行且需要许多累加运算。而如果采用经验公式,也能解决此问题。
相关经验公式与算法放在了Github上,其中月球算法为经验公式算法,节气算法及其修正只在21世纪有效,也就是说,本算法只能进行2000-2099年的公历转农历。
农历转公历算法与公历转农历基本相同,只在步骤4上有所区别。