也谈农历置闰
在看王晓华所著《算法的乐趣》(王晓华著. 算法的乐趣[M]. 北京:人民邮电出版社, 2015.04.)第11章中给出的农历程序源代码时,发现在农历置闰方面似乎有点问题,值得商榷。
书中给出的农历闰月设置规则与《GB/T 33661-2017 农历的编算和颁行》相一致,但长篇大论的比国标长多了,所以直接引用国标原文吧:
4 农历的编排规则
4.1 以北京时间为标准时间。
4.2 朔日为农历月的第一个农历日。
4.3 包含节气冬至在内的农历月为农历十一月。
4.4 若从某个农历十一月开始到下一个农历十一月(不含)之间有13个农历月,则需要置闰。置闰规则为:取其中最先出现的一个不包含中气的农历月为农历闰月。
4.5 农历十一月之后第2个(不计闰月)农历月为农历年的起始月。
书中判断是否需要置闰的代码是CChineseCalendar::CalcLeapChnMonth(),没有该书或没有该书源代码的可以看这里,有相同的文字说明和源代码:
https://blog.csdn.net/orbit/article/details/9337377
CalcLeapChnMonth函数代码初看没有问题,与国标中4.4相一致,但其实存在一个逻辑漏洞: 代码中的m_NewMoonJD数组是从前一年的十一月(冬至月)开始的,因此代码
if(int(m_NewMoonJD[13] + 0.5) <= int(m_SolarTermsJD[24] + 0.5)) //第13月的月末没有超过冬至,说明今年需要闰一个月
考虑了本年前11个月是否需要置闰,但漏掉了另外一个考虑选项:本年的第12个月(m_NewMoonJD[14])是否需要置闰?
要判断本年第12个月是否需要置闰(闰冬月),就需要从本年冬至月计算到下年冬至月,发现有13个月时再看本年第12个月是否无中气。简单一点说,其实就是在用书中的代码计算发现本年无闰月时,还要用相同的代码再算一遍第二年的闰月看是不是本年的第十二个月。第二遍计算时m_NewMoonJD数组中存储的是从本年冬至到下年冬至之间的朔时刻。而在用现有的代码计算发现本年需要置闰时,也要检查一下去年的冬月是否已经闰过,如果闰过就别再闰了。
如果不做这样的修正,在碰到2033年这样需要闰冬月的年份就会出现错误。