算法系列:日历算法锐洋智能

日历在我们的生活中扮演着十分重要的角色,上班、上学、约会都离不开日历。每年新年开始,人们都要更换新的日历,你想知道未来一年的这么多天是怎么被确定下来的吗?为什么去年的国庆节是星期五而今年的国庆节是星期三?那就来研究一下日历算法吧。本文将介绍日历的编排规则,确定某日是星期几的计算方法,以及如何在计算机上打印某一年的年历。

1、如果年份是4的倍数,且不是100的倍数,则是闰年;

2、如果年份是400的倍数,则是闰年;

3、不满足1、2条件的就是平常年。

总结成一句话就是:四年一闰,百年不闰,四百年再闰。

中国公历关于月的规则是这样的,一年分为十二个月,其中一月、三月、五月、七月、八月、十月和十二月是大月,一个月有31天。四月、六月、九月和十一月是小月,一个月有30天。二月天数要根据是否是闰年来定,如果是闰年,二月是29天,如果是平常年,二月是28天。

2、计算星期

如何知道某一天到底是星期几?除了查日历之外,是否有办法推算出来某一天是星期几呢?答案是肯定的,星期不象年和月那样有固定的历法规则,但是星期的计算也有自己的规律。星期是固定的7天周期,其排列顺序固定,不随闰年、平常年以及大小月的天数变化影响。因此,只要确切地知道某一天是星期几,就可以推算出其它日期是星期几。推算的方法很简单,就是计算两个日期之间相差多少天,用相差的天数对7取余数,这个余数就是两个日期的星期数的差值。举个例子,假设已经知道1977年3月27日是星期日,如何得知1978年3月27日是星期几?按照前面的方法,计算出1977年3月27日到1978年3月27日之间相差365天,365除以7余数是1,所以1978年3月27日就是星期一。

上述方法计算星期几的关键是求出两个日期之间相隔的天数。有两种常用的方法计算两个日期之间相隔的天数,一种是利用公历的月和年的规则直接计算,另一种是利用儒略日计算。利用公历规则直接计算两个日期之间相差的天数,简单地讲就是将两个日期之间相隔的天数分成三个部分:前一个日期所在年份还剩下的天数、两个日期之间相隔的整数年所包含的天数和后一个日期所在的年过去的天数。如果两个日期是相邻两个年份的日期,则第二部分整年的天数就是0。以1977年3月27日到2005年5月31日为例,1977年还剩下的天数是279天,中间整数年是从1978年到2005年(不包括2005年),共26年,包括7个闰年和20个平常年,总计9862天,最后是2005年从1月1日到5月31日经过的天数151天。三者总和10292天。直接利用公历规则计算日期相差天数的算法实现如下(为了简化算法复杂度,这个实现假设用于定位星期的那个日期总是在需要计算星期几的那个日期之前):

另一种计算两个日期相差天数的方法是利用儒略日(JulianDay,JD)进行计算。首先介绍一下儒略日,儒略日是一种不记年,不记月,只记日的历法,是由法国学者JosephJustusScaliger(1540-1609)在1583年提出来的一种以天数为计量单位的流水日历。儒略日和儒略历(JulianCalendar)没有任何关系,命名为儒略日也仅仅他本人为了纪念他的父亲--意大利学者JuliusCaesarScaliger(1484-1558)。简单来讲,儒略日就是指从公元前4713年1月1日UTC12:00开始所经过的天数,JD0就被指定为公元前4713年1月1日12:00到公元前4713年1月2日12:00之间的24小时,依次顺推,每一天都被赋予一个唯一的数字。例如从1996年1月1日12:00开始的一天就是儒略日JD2450084。使用儒略日可以把不同历法的年表统一起来,很方便地在各种历法中追溯日期。如果计算两个日期之间的天数,利用儒略日计算也很方便,先计算出两个日期的儒略日数,然后直接相减就可以得到两个日期相隔的天数。

由公历的日期计算出儒略日数是一个很简单的事情,有多个公式可以计算儒略日,本文选择如下公式计算儒略日:

其中y是年份,m是月份,d是日期,如果m小于或等于2,则m修正为m+12,同时年份修正为y-1。c值由以下方法计算:

下面就是由公历日期计算儒略日的算法实现:

119intCalculateJulianDay(intyear,intmonth,intday)

120{

121intB=0;

122

123if(month<=2)

124{

125month+=12;

126year-=1;

127}

128if(IsGregorianDays(year,month,day))

129{

130B=year/100;

131B=2-B+year/400;

132}

133

134doubledd=day+0.5000115740;/*本日12:00后才是儒略日的开始(过一秒钟)*/

135returnint(365.25*(year+4716)+0.01)+int(30.60001*(month+1))+dd+B-1524.5;

136}

儒略日的计算通常精确到秒,得到的JD数也是一个浮点数,本文仅仅是为了计算日期相隔的整数天数,因此都采用整数计算。由于儒略日的周期开始与每天中午12:00,而历法中的天数通常是从0:00开始的,因此儒略日计算上对日期的天数进行了修正。1977年3月27日的儒略日是2443230,2005年5月31日的儒略日是2453522,差值是10292,和前一种方法计算的结果一致。

我们用两种方法计算出两个日期之间的天数都是10292,现在用10292除以7得到余数是2,也就是说2005年5月31日与1977年3月27日星期数差两天,所以2005年5月31日就是是星期二。

w=(L*366+N*365+D)%7(公式2)

公式中的L是从公元元年到y年m月d日所在的年之间的闰年次数,N是平常年次数,D是y年内的积累天数。将整年数y-1=L+N带入上式,可得:

w=((y-1)*365+L+D)%7(公式3)

根据闰年规律,从公元元年到y年之间的闰年次数是可以计算出来的,即:

将L带入公式2,得到星期w的最终计算公式:

还以2005年5月31日为例,利用公式5计算w的值为:

得到2005年5月31日是星期二,和前面的计算方法得到的结果一致。根据上述分析,可得写出使用公式5计算星期的算法实现:

146intTotalWeek(intyear,intmonth,intday)

147{

148intd=CalcYearPassedDays(year,month,day);

149inty=year-1;

150intw=y*DAYS_OF_NORMAL_YEAR+y/4-y/100+y/400+d;

151

152returnw%7;

153}

公式5的问题在于计算量大,不利于口算星期结果。于是人们就在公式5的基础上继续推导更简单的公式。德国数学家克里斯蒂安·蔡勒(ChristianZeller,1822-1899)在1886年推导出了著名的蔡勒(Zeller)公式:

对计算出的w值除以7,得到的余数就是星期几,如果余数是0,则为星期日。蔡勒公式中各符号的含义如下:

w:星期;

c:世纪数–1的值,如21世纪,则=20;

m:月数,的取值是大于等于3,小于等于14。在蔡勒公式中,某年的1月和2月看作上一年的13月和14月,比如2001年2月1日要当成2000年的14月1日计算;

y:年份,取公元纪念的后两位,如1998年,=98,2001年,=1;

d:某月内的日数

目前人们普遍认为蔡勒公式是计算某一天是星期几的最好的公式。但是蔡勒公式有时候可能计算出的结果是负数,需要对结果+7进行修正。比如2006年7月1日,用蔡勒公式计算出的结果是-1,实际上这天是星期六。根据前面分析的结果整理出的蔡勒公式算法实现如下:

155intZellerWeek(intyear,intmonth,intday)

156{

157intm=month;

158intd=day;

159

160if(month<=2)/*对小于2的月份进行修正*/

161{

162year--;

163m=month+12;

164}

165

166inty=year%100;

167intc=year/100;

168

169intw=(y+y/4+c/4-2*c+(13*(m+1)/5)+d-1)%7;

170if(w<0)/*修正计算结果是负数的情况*/

171w+=7;

172

173returnw;

174}

蔡勒公式(公式6)和前面提到的公式5都只适用于格里历法。罗马教皇在1582年修改历法,将10月5日指定为10月15日,从而正式废止儒略历法,开始启用格里历法。因此,上述求星期几的公式只适用于1582年10月15日之后的日期,对于1582年将10月4日之前的日期,蔡勒也推导出了适用与儒略历法的星期计算公式:

公式7适用于对1582年10月4日之前的日期计算星期,1582年10月5日与1582年10月15日之间的日期是不存在的,因为它们都是同一天。

格里历历法简单,除二月外每月天数固定,二月则根据是否是闰年确定是28天还是29天,每天的星期数可以通过蔡勒公式(公式6)计算,有了这些信息,就可以按照一定的排版格式将某一年的日历打印出来。排版打印的算法非常简单,就是按照顺序打印12个月的月历,因此,打印月历的函数就是输出算法的重点。代码没什么特别之处,就是用一些小技巧确定每个月的第一天的开始位置,打印月历的核心代码如下:

229voidPrintMonthCalendar(intyear,intmonth)

230{

231intdays=GetDaysOfMonth(year,month);/*确定这个月的天数*/

232if(days<=0)

233return;

234

235PrintMonthBanner(nameOfMonth[month-1]);

236PrintWeekBanner();

237intfirstDayWeek=ZellerWeek(year,month,1);

238InsertRowSpace(firstDayWeek);

239intweek=firstDayWeek;

240inti=1;

241while(i<=days)

242{

243printf("%-10d",i);

244if(week==6)/*到一周结束,切换到下一行输出*/

245{

246SetNextRowStart();

247}

248i++;

249week=(week+1)%7;

250}

251}

GetDaysOfMonth()函数其实就是从daysOfMonth表中查一下每月的天数,如果是闰年,则对二月的天数修正(+1),daysOfMonth表定义如下:

intdaysOfMonth[MONTHES_FOR_YEAR]={31,28,31,30,31,30,31,31,30,31,30,31};

计算星期不必对每一天都计算一次,只要对每个月的第一天计算一次就可以了,以后的日期可以用week=(week+1)%7直接推算出星期几。下面就是我们的算法打印输出的效果:

********************************************************************************

Calendarof2012

----------January----------

SundayMondayTuesdayWednesdayThursdayFridaySaturday

1234567

891011121314

15161718192021

22232425262728

293031

----------February----------

1234

567891011

12131415161718

19202122232425

26272829

----------March----------

123

45678910

11121314151617

18192021222324

25262728293031

……

2、二十四节气

中国古代历法都是以月亮运行规律为主,严格按照朔望月长度定义月,但是由于朔望月长度和地球回归年长度无法协调,会导致农历季节和天气的实际冷暖无法对应,因此聪明的古人将月亮运行规律和太阳运行规律相结合制定了中国农历的历法规则。在这种特殊的阴阳结合的历法规则中,二十四节气就扮演着非常重要的作用,它是联系月亮运行规律和太阳运行规律的纽带。正是由于二十四节气结合置闰规则,使得农历的春夏秋冬四季和地球绕太阳运动引起的天气冷暖变化相一致,成为中国几千年来生产、生活的依据。

二十四节气起源于中国黄河流域。远在春秋时代,古人就开始使用仲春、仲夏、仲秋和仲冬四个节气指导农耕种植。后来经过不断地改进与完善,到秦汉年间,二十四节气已经基本确立。公元前104年,汉武帝颁布由邓平等人制定的《太初历》,正式把二十四节气订于历法,明确了二十四节气的天文位置。二十四节气天文位置的定义,就是从太阳黄经零度开始,沿黄经每运行15度所经历的时日称为“一个节气”。太阳一个回归年运行360度,共经历24个节气,每个公历月对应2个节气。其中,每月第一个节气为“节令”,即:立春、惊蛰、清明、立夏、芒种、小暑、立秋、白露、寒露、立冬、大雪和小寒等12个节令;每月的第二个节气为“中气”,即:雨水、春分、谷雨、小满、夏至、大暑、处暑、秋分、霜降、小雪、冬至和大寒等12个中气。“节令”和“中气”交替出现,各历时15天,人们习惯上把“节令”和“中气”统称为“节气”。

为了更好地理解二十四节气的天文位置,首先要解释几个天文学概念。“天球”是人们为了研究天体的位置和运动规律而引入的一个假象的球体,根据观察点(也就是球心)的位置不同,可分为“日心天球”、“地心天球”等等。图(1)就是天球概念的一个简单示意图:

图(1)天球概念示意图

黄道平面可以近似理解为地球绕太阳公转的平面,以黄道为基圈的黄道坐标系根据观测中心是太阳还是地球还可以区分为日心坐标系和地心坐标系,对应天体的黄道坐标分别被称为“日心黄经、日心黄纬”和“地心黄经、地心黄纬”。日心黄经和日心黄纬比较容易理解,因为太阳系的行星都是绕太阳公转,以太阳为中心将这些行星向天球上投影是最简单的确定行星位置关系的做法。但是人类自古观察太阳的周年运动,都是以地球为参照,以太阳的周年视运动位置来计算太阳的运行轨迹,使用的其实都是地心黄经和地心黄纬,要了解古代历法,理解这一点非常重要。图(2)就解释了造成这种视觉错觉的原因:

图(2)太阳黄道视觉位置原理图

古人定义二十四节气的位置,是太阳沿着黄道运行时的视觉位置,每个节气对应的黄道经度其实是地心黄经。从图(2)可以看出日心黄经和地心黄经存在180度的转换关系,同样可以理解,日心黄纬和地心黄纬在方向上是反的,因此可以很方便地将两类坐标相互转换,转换公式是:

太阳地心黄经=地球日心黄经+180°(3.1式)

太阳地心黄纬=-地球日心黄纬(3.2式)

A*cos(B+Cτ)(3.3式)

其中τ是儒略千年数,τ的计算公式如下:

τ=(JDE-2451545.0)/365250(3.4式)

L=(L0+L1*τ+L2*τ2+L3*τ3+L4*τ4+L5*τ5)/108(3.5式)

用同样的方法对地球日心黄纬的周期项系数表和计算行星和太阳距离的周期项系数表计算求和,可以得到地球日心黄纬B和日地距离R,B的单位是弧度,R的单位是天文单位(AU)[1]。由于3.5式的计算方法需要多次计算τ的乘方,浮点数的乘方计算的速度比较慢,实际计算时,通常对3.5式进行变换,用乘法和加法代替直接的乘方计算,这是一种常用的转换:

L=(((((L5*τ+L4)*τ+L3)*τ+L2)*τ+L1)*τ+L0)/108(3.6式)

本文就是使用3.6式代替3.5式进行计算。

VSOP82/87行星理论中的周期项系数对不同的行星具有不同的精度,对地球来说,在1900-2100年之间的200年跨度期间,计算精度是0.005"。前文曾说过,对于不需要这么高精度的计算应用时,可以适当减少一些系数比较小的周期项,减少计算量,提高计算速度。JeanMeeus在他的《天文算法》一书中就给出了一套精简后的VSOP87D表的周期项,将计算地球黄经的L0表由原来的559项精简到64项,计算地球黄纬的B0表甚至被精简到只有5项,从实际效果看,计算精度下降并不多,但是极大地减少了计算量。

使用VSOP87D周期项系数表计算得到的是J2000.0平黄道和平春分点(meandynamiceclipticandequinox)为基准的日心黄经(L)和日心黄纬(B),其值与标准FK5系统略有差别,如果对精度要求很高可以采用下面的方法将计算得到的日心黄经(L)和日心黄纬(B)转到FK5系统[2]:

首先然后L',单位是度:

L'=L-1.397*T-0.00031*T2(3.7式)

3.7式中的T是儒略世纪数,它与儒略千年数τ的关系是:T=10*τ。然后使用L'计算L和B的修正值ΔL和ΔB:

ΔL=-0.09033+0.03916*(cos(L')+sin(L'))*tan(B)(3.8式)

ΔB=+0.03916*(cos(L')-sin(L'))(3.9式)

ΔL和ΔB的单位都是",是度分秒角度单位体系,需要将3.6式计算出得L和B转换成以度(°)为单位的值后再进行修正。

CalcSunEclipticLongitudeEC()函数就是使用VSOP87行星理论计算行星日心黄经的代码实现,整个计算过程和前文描述一样,首先根据VSOP87D表的数据计算出L0-L5,然后用3.6式计算出地球的日心黄经,3.6式计算出来的单位是弧度,因此转换成度分秒单位,最后使用3.1式将结果转换成太阳的地心黄经:

doubleCalcSunEclipticLongitudeEC(doubledt)

{

doubleL0=CalcPeriodicTerm(Earth_L0,sizeof(Earth_L0)/sizeof(VSOP87_COEFFICIENT),dt);

doubleL1=CalcPeriodicTerm(Earth_L1,sizeof(Earth_L1)/sizeof(VSOP87_COEFFICIENT),dt);

doubleL2=CalcPeriodicTerm(Earth_L2,sizeof(Earth_L2)/sizeof(VSOP87_COEFFICIENT),dt);

doubleL3=CalcPeriodicTerm(Earth_L3,sizeof(Earth_L3)/sizeof(VSOP87_COEFFICIENT),dt);

doubleL4=CalcPeriodicTerm(Earth_L4,sizeof(Earth_L4)/sizeof(VSOP87_COEFFICIENT),dt);

doubleL5=CalcPeriodicTerm(Earth_L5,sizeof(Earth_L5)/sizeof(VSOP87_COEFFICIENT),dt);

doubleL=(((((L5*dt+L4)*dt+L3)*dt+L2)*dt+L1)*dt+L0)/100000000.0;

/*地心黄经=日心黄经+180度*/

return(Mod360Degree(Mod360Degree(L/RADIAN_PER_ANGLE)+180.0));

}

Mod360Degree()函数将大于360°或小于0°的值调整到0-360°之间,便于转换显示。CalcPeriodicTerm()函数使用3.3式对一个周期项系数表进行求和计算,可以指定需要计算的周期项数:

doubleCalcPeriodicTerm(constVSOP87_COEFFICIENT*coff,intcount,doubledt)

doubleval=0.0;

for(inti=0;i

val+=(coff[i].A*cos((coff[i].B+coff[i].C*dt)));

returnval;

同样的方法计算太阳的地心黄纬:

doubleCalcSunEclipticLatitudeEC(doubledt)

doubleB0=CalcPeriodicTerm(Earth_B0,sizeof(Earth_B0)/sizeof(VSOP87_COEFFICIENT),dt);

doubleB1=CalcPeriodicTerm(Earth_B1,sizeof(Earth_B1)/sizeof(VSOP87_COEFFICIENT),dt);

doubleB2=CalcPeriodicTerm(Earth_B2,sizeof(Earth_B2)/sizeof(VSOP87_COEFFICIENT),dt);

doubleB3=CalcPeriodicTerm(Earth_B3,sizeof(Earth_B3)/sizeof(VSOP87_COEFFICIENT),dt);

doubleB4=CalcPeriodicTerm(Earth_B4,sizeof(Earth_B4)/sizeof(VSOP87_COEFFICIENT),dt);

doubleB=(((((B4*dt)+B3)*dt+B2)*dt+B1)*dt+B0)/100000000.0;

/*地心黄纬=-日心黄纬*/

return-(B/RADIAN_PER_ANGLE);

AdjustSunEclipticLongitudeEC()函数根据3.8式计算黄经的修正量,longitude和latitude参数是由VSOP87理论计算出的太阳地心黄经和地心黄纬,单位是度,dt是儒略千年数,返回值单位是度:

doubleAdjustSunEclipticLongitudeEC(doubledt,doublelongitude,doublelatitude)

doubleT=dt*10;//T是儒略世纪数

doubledbLdash=longitude-1.397*T-0.00031*T*T;

//转换为弧度

dbLdash*=RADIAN_PER_ANGLE;

return(-0.09033+0.03916*(cos(dbLdash)+sin(dbLdash))*tan(latitude*RADIAN_PER_ANGLE))/3600.0;

经过上述计算转换得到坐标值是理论值,或者说是天体的几何位置,但是FK5系统是一个目视系统,也就是说体现的是人眼睛观察效果(光学位置),这就需要根据地球的物理环境、大气环境等信息做进一步的修正,使其和人类从地球上观察星体的观测结果一致。

小知识1:天文单位

天文单位(英文:AstronomicalUnit,简写AU)是一个长度的单位,约等于地球跟太阳的平均距离。天文单位是天文常数之一,是天文学中测量距离,特别是测量太阳系内天体之间的距离的基本单位。地球到太阳的平均距离大约为一个天文单位,约等于1.496亿千米。1976年,国际天文学联会把一天文单位定义为一颗质量可忽略、公转轨道不受干扰而且公转周期为365.2568983日(即一高斯年)的粒子与一个质量相等约一个太阳的物体的距离。当前普遍被接受并使用的天文单位的值是149,597,870,691±30米(约一亿五千万公里)。

小知识2:FK5系统

FK5常用的目视星表系统,又称第五基本星表,是在FK4(第四基本星表)的基础上发展出来的,对FK4星表进行了修正,于1984年正式启用。它定义了一个以太阳质心为中心,J2000.0平赤道和春分点为基准的天球平赤道坐标系。近年来国际上又编制了FK6星表(第六基本星表),但是还没有被正式启用。

首先需要进行章动修正。章动是指地球沿自转轴的指向绕黄道极缓慢旋转过程中,由于地球上物质分布不均匀性和月球及其它行星的摄动力造成的轻微抖动。英国天文学家詹姆斯·布拉德利(1693—1762)最早发现了章动,章动可以沿着黄道分解为水平分量和垂直分量,黄道上的水平分量记为Δψ,称为黄经章动,它影响了天球上所有天体的经度。黄道上的垂直分量记为Δε,称为交角章动,它影响了黄赤交角。目前编制天文年历所依据的章动理论是伍拉德在1953年建立的,它是以刚体地球模型为基础的。1977年,国际天文联合会的一个专家小组建议采用非刚体地球模型――莫洛坚斯基II模型代替刚体地球模型计算章动,1979年的国际天文学联合会第十七届大会正式通过了这一建议,并决定于1984年正式实施。

平距角(日月对地心的角距离):D=297.85036+455267.111480*T-0.0019142*T2+T3/189474(3.10式)太阳(地球)平近点角:M=357.52772+35999.050340*T-0.0001603*T2-T3/300000(3.11式)月球平近点角M'=134.96298+477198.867398*T+0.0086972*T2+T3/56250(3.12式)

月球纬度参数:F=93.27191+483202.017538*T-0.0036825*T2+T3/327270(3.13式)黄道与月球平轨道升交点黄经:Ω=125.04452-1934.136261*T+0.0020708*T2+T3/450000(3.14式)

以上各式中的T是儒略世纪数,计算出来的5个基本角距的单位都是度,在计算正弦或余弦时要转换为弧度单位。计算每一个周期项的黄经章动过程是这样的,首先将3.10-3.14式计算出来的值与对应的5个基本角距系数组合,计算出辐角。以本文使用的章动周期项系数表中的第七项为例,5个基本角距对应的系数分别是1、0、-2、2和2,辐角θ的值就是:-2D+M+2F+2Ω。计算出辐角后就可以计算周期项的值:

S=(S1+S2*T)*sin(θ)(3.15式)

仍以第七项为例,S的值就是(-517+1.2*T)*sin(θ)。对每一项的值S累加就可得到黄经章动,单位是0.0001"。交角章动的计算方法与黄经章动的计算类似,辐角θ的值是一样的,只是计算章动使用的是余弦系数:

C=(C1+C2*T)*cos(θ)(3.16式)

CalcEarthLongitudeNutation()函数就是计算黄经章动的实现代码:

doubleCalcEarthLongitudeNutation(doubledt)

doubleT=dt*10;

doubleD,M,Mp,F,Omega;

GetEarthNutationParameter(dt,&D,&M,&Mp,&F,&Omega);

doubleresulte=0.0;

for(inti=0;i

doublesita=nutation[i].D*D+nutation[i].M*M+nutation[i].Mp*Mp+nutation[i].F*F+nutation[i].omega*Omega;

resulte+=(nutation[i].sine1+nutation[i].sine2*T)*sin(sita);

/*先乘以章动表的系数0.0001,然后换算成度的单位*/

returnresulte*0.0001/3600.0;

GetEarthNutationParameter()辅助函数用于计算5个基本角距:

voidGetEarthNutationParameter(doubledt,double*D,double*M,double*Mp,double*F,double*Omega)

doubleT=dt*10;/*T是从J2000起算的儒略世纪数*/

doubleT2=T*T;

doubleT3=T2*T;

/*平距角(如月对地心的角距离)*/

*D=297.85036+445267.111480*T-0.0019142*T2+T3/189474.0;

/*太阳(地球)平近点角*/

*M=357.52772+35999.050340*T-0.0001603*T2-T3/300000.0;

/*月亮平近点角*/

*Mp=134.96298+477198.867398*T+0.0086972*T2+T3/56250.0;

/*月亮纬度参数*/

*F=93.27191+483202.017538*T-0.0036825*T2+T3/327270.0;

/*黄道与月亮平轨道升交点黄经*/

*Omega=125.04452-1934.136261*T+0.0020708*T2+T3/450000.0;

同样,计算交角章动的实现代码是:

doubleCalcEarthObliquityNutation(doubledt)

resulte+=(nutation[i].cosine1+nutation[i].cosine2*T)*cos(sita);

/*先乘以章动表的系数0.001,然后换算成度的单位*/

下面是一个粗略计算太阳地心黄经光行差修正量的公式,其中R是地球和太阳的距离:

AC=-20".4898/R(3.17式)

分子20.4898并不是一个常数,但是其只的变化非常缓慢,在0年是20".4893,在4000年是20".4904。前文提到过,太阳到地球的距离R可以用VSOP87D表的R0-R5周期项计算出来,R的单位是“天文单位(AU)”,和计算太阳地心黄经和地心黄纬类似,太阳到地球的距离可以这样算出来:

doubleCalcSunEarthRadius(doubledt)

doubleR0=CalcPeriodicTerm(Earth_R0,sizeof(Earth_R0)/sizeof(VSOP87_COEFFICIENT),dt);

doubleR1=CalcPeriodicTerm(Earth_R1,sizeof(Earth_R1)/sizeof(VSOP87_COEFFICIENT),dt);

doubleR2=CalcPeriodicTerm(Earth_R2,sizeof(Earth_R2)/sizeof(VSOP87_COEFFICIENT),dt);

doubleR3=CalcPeriodicTerm(Earth_R3,sizeof(Earth_R3)/sizeof(VSOP87_COEFFICIENT),dt);

doubleR4=CalcPeriodicTerm(Earth_R4,sizeof(Earth_R4)/sizeof(VSOP87_COEFFICIENT),dt);

doubleR=(((((R4*dt)+R3)*dt+R2)*dt+R1)*dt+R0)/100000000.0;

returnR;

也可以不使用VSOP,而用下面的公式直接计算日地距离R:

R=1.000001018(1-e2)/(1+e*cos(v))(3.18式)

其中e是地球轨道的离心率:

e=0.016708617-0.000042037*T-0.0000001236*T2(3.19式)

v的计算公式是v=M+C,其中M是太阳平近地角:

M=357.52910+35999.05030*T-0.0001559*T2-0.00000048*T3(3.20式)

中心C的太阳方程:

C=(1.914600-0.004817*T-0.000014*T2)*sin(M)

+(0.019993-0.000101*T)*sin(2M)

+0.000290*sin(3M)(3.21式)

以上各式中的T都是儒略世纪数,M和C的单位都是度,带入3.18式计算时需要转换成弧度单位,计算出R以后,就可以这样计算光行差修正量:

AC=K/R(K是光行差常数,K=20".49552)(3.22式)

无论是使用3.17式还是使用3.22式,最终计算出来的太阳光行差修正单位都是角秒。

由VSOP87理论计算出来的几何位置黄经,经过坐标转换,章动修正和光行差修正后,就可以得到比较准确的太阳地心视黄经,GetSunEclipticLongitudeEC()函数就是整个过程的代码:

doubleGetSunEclipticLongitudeEC(doublejde)

doubledt=(jde-JD2000)/365250.0;/*儒略千年数*/

//计算太阳的地心黄经

doublelongitude=CalcSunEclipticLongitudeEC(dt);

//计算太阳的地心黄纬

doublelatitude=CalcSunEclipticLatitudeEC(dt)*3600.0;

//修正精度

longitude+=AdjustSunEclipticLongitudeEC(dt,longitude,latitude);

//修正天体章动

longitude+=CalcEarthLongitudeNutation(dt);

//修正光行差

/*太阳地心黄经光行差修正项是:-20".4898/R*/

longitude-=(20.4898/CalcSunEarthRadius(dt))/(20*PI);

returnlongitude;

实际上,我们要做的事情就是求解方程的根,但是我们面临的这个方程没有求根公式。对此类问题,数学上通常采用的迭代求解方法有二分逼近法和牛顿迭代法,事实上二分逼近法可以用更好的策略,比如用黄金分割代替二分法进行逼近区间的选择。接下来我们将分别介绍这两种方法在计算二十四节气中的应用,首先介绍黄金分割逼近法。

C=((B-A)*0.618)+A(3.23式)

34doubleCalculateSolarTerms(intyear,intangle)

35{

36doublelJD,rJD;

38

39doublesolarTermsJD=0.0;

40doublelongitude=0.0;

41

42do

43{

44solarTermsJD=((rJD-lJD)*0.618)+lJD;

45longitude=GetSunEclipticLongitudeECDegree(solarTermsJD);

46/*

47对黄经0度迭代逼近时,由于角度360度圆周性,估算黄经值可能在(345,360]和[0,15)两个区间,

48如果值落入前一个区间,需要进行修正

49*/

50longitude=((angle==0)&&(longitude>345.0))longitude-360.0:longitude;

51

52(longitude>double(angle))rJD=solarTermsJD:lJD=solarTermsJD;

53}while((rJD-lJD)>0.0000001);

54

55returnsolarTermsJD;

56}

二分逼近或黄金分割逼近算法实现简单,很容易控制,但是也存在效率低,收敛速度慢的问题,现在我们介绍牛顿迭代法,牛顿迭代法是一种在实数域和复数域上近似求解方程的方法。假设我们要求解的方程是f(x)=0,如果f(x)的导函数f’(x)是连续的,则在真实解x附近的区域内任意一点x0开始迭代,则牛顿迭代法必收敛,特别当f’(x)不等于0的时候,牛顿迭代法是平方收敛的,也就是说,每迭代一次,结果的有效数字将增加一倍。

简单的说,对于方程f(x)=0,f(x)的导函数是f’(x),则牛顿迭代法的迭代公式是:

Xn+1=xn–f(xn)/f’(xn)(3.24式)

现在问题就是如何确定方程f(x)。对于我们面临的问题,可以理解为已知angle,通过GetSunEclipticLongitudeEC(solarTermsJD)函数反向求解solarTermsJD的值,因此我们的方程可以理解为:

f(x)=GetSunEclipticLongitudeEC(x)–angle=0

确定了方程f(x),剩下的问题就是求导函数f’(x)。严格的求解,应该根据GetSunEclipticLongitudeEC()函数,以儒略千年数dt为自变量,按照函数求导的规则求出导函数。因为GetSunEclipticLongitudeEC()函数内部是调用其他函数,因此可以理解为是一个多个函数组合的复合函数,类似f(x)=g(x)+h(x,k(x))+p(x)这样的形式,可以按照求导规则逐步对其求导得到导函数。但是我不打算这么做,因为我有更简单的方法,那就是使用计算导数的近似公式。其实求导函数的目的就是为了得到某一点的导数,如果有近似公式可以直接得到这一点的导数,就不用费劲求导函数了。

如果函数f(x)是单调函数,或者是在某个区间上是单调函数,则在此函数的其单调区间上某一点的导数值可以用近似公式计算,这个近似公式是:

f’(x0)=(f(x0+0.000005)–f(x0–0.000005))/0.00001(3.25式)

这是一个精度很高的近似公式,完全可以满足民用历法计算的精度要求。

根据以上分析结果,使用牛顿迭代法求解节气的算法就很容易实现了,以下就是牛顿迭代法求解节气的代码:

74doubleCalculateSolarTermsNewton(intyear,intangle)

75{

76doubleJD0,JD1,stDegree,stDegreep;

77

78JD1=GetInitialEstimateSolarTerms(year,angle);

79do

80{

81JD0=JD1;

82stDegree=GetSunEclipticLongitudeECDegree(JD0)-angle;

83stDegreep=(GetSunEclipticLongitudeECDegree(JD0+0.000005)

84-GetSunEclipticLongitudeECDegree(JD0-0.000005))/0.00001;

85JD1=JD0-stDegree/stDegreep;

86}while((fabs(JD1-JD0)>0.0000001));

87

88returnJD1;

89}

经过验证,牛顿迭代法具有非常好的收敛效果,一般只需3次迭代就可以得到满足精度的结果。

2012-01-06,06:43:54.28小寒

2012-01-21,00:09:49.08大寒

2012-02-04,18:22:22.53立春

2012-02-19,14:17:35.37雨水

2012-03-05,12:21:01.56惊蛰

2012-03-20,13:14:24.17春分

2012-04-04,17:05:34.65清明

2012-04-20,00:12:03.28谷雨

2012-05-05,10:19:39.54立夏

2012-05-20,23:15:30.28小满

2012-06-05,14:25:52.96芒种

2012-06-21,07:08:46.98夏至

2012-07-07,00:40:42.66小暑

2012-07-22,18:00:50.72大暑

2012-08-07,10:30:31.88立秋

2012-08-23,01:06:48.41处暑

2012-09-07,13:28:59.41白露

2012-09-22,22:48:57.14秋分

2012-10-08,05:11:41.45寒露

2012-10-23,08:13:32.83霜降

2012-11-07,08:25:56.47立冬

2012-11-22,05:50:08.09小雪

2012-12-07,01:18:55.23大雪

2012-12-21,19:11:35.61冬至

小知识3:力学时、原子时和国际协调时

[2]Jean.Meeus.Astronomical.Algorithms(天文算法)

[3]M.Chapront-TouzeandJ.Chapront.ELP2000-85-Asemi-analyticallunarephemerisadequateforhistoricaltimes.AstronomyAndAstrophysics,1998

[4]P.BretagnonandG.Francou.PlanetraytheoriesinrectangularandsphericalvariablesVSOP87solutions.AstronomyAndAstrophysics,1998

图(1)日月天文现象示意图

月亮绕太阳公转的白道面和地球绕太阳公转的黄道面存在一个最大约5°的夹角,因此大多数情况下,日月合朔时都不是严格在同一条直线上,不过也会发生在同一直线的情况,此时就会发生日食。图(1-b)显示了日月合朔时侧切面上月亮的三种可能的位置情况,当月亮处在位置2时就会发生日食。由图(1)可知,日月合朔的数学定义就是太阳和月亮的地心视黄经差为0的时刻。

要计算日月合朔,需要知道太阳地心视黄经和月亮地心视黄经的计算方法。“日历生成算法”系列的第三篇《用天文方法计算二十四节气》一文已经介绍了如何用VSOP82/87行星理论计算太阳的地心视黄经,本文将继续介绍如何用ELP-2000/82月球理论计算月亮的地心视黄经。ELP-2000/82月球理论是M.Chapront-Touze和J.Chapront在1983年提出的一个月球位置的半解析理论,和其它半解析理论一样,ELP-2000/82理论也包含一套计算方法和相应的迭代周期项。这套理论共包含37862个周期项,其中20560个用于计算月球经度,7684个用于计算月球纬度,9618个用于计算地月距离。但是这些周期项中有很多都是非常小的值,例如一些计算经纬度的项对结果的增益只有0.00001角秒,还有一些地月距离周期项对距离结果的增益只有0.02米,对于精度不高的历法计算,完全可以忽略。

有很多基于ELP-2000/82月球理论的改进或简化理论,《天文算法》一书的第四十五章就介绍了一种改进算法,其周期项参数都是从ELP-2000/82理论的周期项参数转换来的,忽略了小的周期项。使用该方法计算的月球黄经精度只有10”,月亮黄纬精度只有4”,但是只用计算60个周期项,速度很快,本文就采用这种修改过的ELP-2000/82理论计算月亮的地心视黄经。这种计算方法的周期项分三部分,分别用来计算月球黄经,月球黄纬和地月距离,三部分的周期项的内容一样,由四个计算辐角的系数和一个正弦(或余弦)振幅组成。计算月球黄经和月球黄纬使用正弦表达式求和:A*sin(θ),计算地月距离用余弦表达式求和:A*cos(θ),其中辐角θ的计算公式是:

θ=a*D+b*M+c*M’+d*F(4.1式)

4.1式中的四个辐角系数a、b、c和d由每个迭代周期项给出,日月距角D、太阳平近地角M、月亮平近地角M’以及月球生交点平角距F则分别有4.2式-4.5式进行计算:

D=297.8502042+445267.1115168*T-0.0016300*T2

+T3/545868-T4/113065000(4.2式)M=357.5291092+35999.0502909*T-0.0001536*T2

+T3/24490000(4.3式)M'=134.9634114+477198.8676313*T+0.0089970*T2

+T3/69699-T4/14712000(4.4式)F=93.2720993+483202.0175273*T-0.0034029*T2

-T3/3526000+T4/863310000(4.5式)

以上各式计算结果的单位是度,其中T是儒略世纪数,T计算由4.6式计算:

T=(JDE-2451545.0)/36525.0(4.6式)

以计算月球黄经的周期项第二项的计算为例,第二项数据如下,辐角系数a=2,b=0,c=-1,d=0,振幅A=1274027,黄经计算用正弦表达式,则I2的计算如下所示:

I2=1274027*sin(2D–M’)(4.7式)

E=1-0.002516*T-0.0000074*T2(4.8式)

其中T值由4.6式计算。上面的计算月球黄经的第二个周期项中M对应的系数是0,因此I2不需要修正,但是第五个周期项中M对应的系数是1,因此I5需要乘以E进行修正。套用4.7式计算出60个月球黄经周期项值I1-I60之和ΣI:

ΣI=I1+I2+…+I60(4.9式)

月球黄纬的周期项和Σb的计算方法与月球黄经周期项和ΣI的计算方法一样,每个月球黄纬周期项也包含振幅A和四个辐角系数a、b、c和d,对于太阳平近地角M的系数b不是0的情况也需要乘以E或E2进行修正。地月距离的周期项和Σr也可以按照上面的方法计算,计算地月距离的目的是为了计算月亮光行差,因为地月距离较小,从地球观察月亮产生的光行差也很小,相对于本文算法的精度(月球黄经精度10”,月亮黄纬精度4”)来说,可以忽略光行差修正,因此就不用计算地月距离。

由于金星和木星对月球的摄动影响,需要对计算出的月球黄经周期项和ΣI和月球黄纬周期项和Σb金星摄动修正,修正的方法如下:

ΣI+=+3958*sin(A1)+1962*sin(L'-F)+318*sin(A2)(4.10式)

Σb+=-2235*sin(L')+382*sin(A3)+175*sin(A1-F)+175*sin(A1+F)+127*sin(L'-M')-115*sin(L'+M')(4.11式)

其中M’和F分别由4.4式和4.5式计算得到,L’是月球平黄经,计算方法是:

L'=218.3164591+481267.88134236*T-0.0013268*T2

+T3/538841-T4/65194000(4.12式)

A1、A2和A4是摄动角修正量,计算方法如下:

A1=119.75+131.849*T(4.13式)A2=53.09+479264.290*T(4.14式)A3=313.45+481266.484*T(4.15式)

完成所有修正后,就可以用4.16式和4.17式最终得到月亮的地心视黄经和地心视黄纬:

λ=L'+ΣI/1000000.0(4.16式)

β=Σb/1000000.0(4.17式)

ΣI和Σb最后要除以1000000.0是因为周期项系数中振幅A的单位是0.000001度,因此λ和β的单位是度。下面给出计算月球地心视黄经的代码:

123doubleGetMoonEclipticLongitudeEC(doubledbJD)

125doubleLp,D,M,Mp,F,E;

126doubledt=(dbJD-JD2000)/36525.0;/*儒略世纪数*/

127

128GetMoonEclipticParameter(dt,&Lp,&D,&M,&Mp,&F,&E);

129

130/*计算月球地心黄经周期项*/

131doubleEI=CalcMoonECLongitudePeriodicTbl(D,M,Mp,F,E);

132

133/*修正金星,木星以及地球扁率摄动*/

134EI+=CalcMoonLongitudePerturbation(dt,Lp,F);

135

136/*计算月球地心黄经*/

137doublelongitude=Lp+EI/1000000.0;

138

139/*计算天体章动干扰*/

140longitude+=CalcEarthLongitudeNutation(dt/10.0);

141

142longitude=Mod360Degree(longitude);

143returnlongitude;

144}

42doubleCalcMoonECLongitudePeriodicTbl(doubleD,doubleM,doubleMp,doubleF,doubleE)

44doubleEI=0.0;

45

46for(inti=0;i

47{

48doublesita=Moon_longitude[i].D*D+Moon_longitude[i].M*M+Moon_longitude[i].Mp*Mp+Moon_longitude[i].F*F;

49sita=DegreeToRadian(sita);

50EI+=(Moon_longitude[i].eiA*sin(sita)*pow(E,fabs(Moon_longitude[i].M)));

51}

52

53returnEI;

54}

CalcMoonLongitudePerturbation()函数计算月球黄经摄动修正量,使用了4.13式和4.14式给出的计算方法:

87doubleCalcMoonLongitudePerturbation(doubledt,doubleLp,doubleF)

88{

89doubleT=dt;/*T是'ca从'b4J2000起'c6算'cb的'b5儒'c8略'c2世'ca纪'bc数'ca*/

90doubleA1=119.75+131.849*T;

91doubleA2=53.09+479264.290*T;

92

93A1=Mod360Degree(A1);

94A2=Mod360Degree(A2);

95

96doubleresult=3958.0*sin(DegreeToRadian(A1));

97result+=(1962.0*sin(DegreeToRadian(Lp-F)));

98result+=(318.0*sin(DegreeToRadian(A2)));

99

100returnresult;

101}

f(x)=GetMoonEclipticLongitudeECDegree(x)–GetSunEclipticLongitudeECDegree(x)=0

Xn+1=Xn–f(Xn)/f’(Xn)

这里也不多解释了。导函数仍然使用近似公式,也不解释了,直接上迭代求解的代码了:

102doubleCalculateMoonShuoJD(doubletdJD)

103{

104doubleJD0,JD1,stDegree,stDegreep;

105

106JD1=tdJD;

107do

108{

109JD0=JD1;

110doublemoonLongitude=GetMoonEclipticLongitudeECDegree(JD0);

111doublesunLongitude=GetSunEclipticLongitudeECDegree(JD0);

112

113stDegree=moonLongitude-sunLongitude;

114

115

116stDegreep=(GetMoonEclipticLongitudeECDegree(JD0+0.000005)-GetSunEclipticLongitudeECDegree(JD0+0.000005)-GetMoonEclipticLongitudeECDegree(JD0-0.000005)+GetSunEclipticLongitudeECDegree(JD0-0.000005))/0.00001;

117JD1=JD0-stDegree/stDegreep;

118}while((fabs(JD1-JD0)>0.00000001));

119

120returnJD1;

121}

2011-11-25,14:09:41.25

2011-12-25,02:06:27.25

2012-01-23,15:39:24.16

2012-02-22,06:34:40.84

2012-03-22,22:37:08.91

2012-04-21,15:18:22.12

2012-05-21,07:46:59.97

2012-06-19,23:02:06.39

2012-07-19,12:24:02.83

2012-08-17,23:54:28.03

2012-09-16,10:10:36.99

2012-10-15,20:02:30.98

2012-11-14,06:08:05.90

2012-12-13,16:41:37.60

2013-01-12,03:43:31.34

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

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

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

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

在介绍中国农历的历法之前,必须要先介绍一下中国古代的纪年方法。中国古代用天干地支纪年,严格来讲,天干地支纪年以及十二属相并不是中国农历历法的一部分,但是在中国历史上直到今天,天干地支以及十二属相一直都是做为中国农历纪年关系密切的一部分而存在,因此这里先介绍一下天干地支纪年法以及十二属相。

中国古代纪年不用数字,而是采用天干地支组合。天干有十个,分别是:甲、乙、丙、丁、戊、己、庚、辛、壬、癸;地支有十二个,分别是:子、丑、寅、卯、辰、巳、午、未、申、酉、戌、亥。使用时天干地支各取一字,天干在前,地支在后,组合成干支,例如甲子、乙丑、丙寅等等,依次轮回可形成六十种组合,以这些天干地支组合纪年,每六十年一个轮回,称为一个甲子。实际上中国古代纪月、纪日以及纪时辰都采用干支方法,这些干支组合起来就是我们熟悉的生辰八字。十二属相又称“十二生肖”,由十一种源自自然界的动物:鼠、牛、虎、兔、蛇、马、羊、猴、鸡、狗、猪以及传说中的龙组成,用于纪年时,按顺序和十二地支组合成子鼠、丑牛、寅虎、卯兔、辰龙、巳蛇、午马、未羊、申猴、酉鸡、戌狗和亥猪。天干地支以及十二生肖常组合起来描述农历年,比如公历2011年就是农历辛卯兔年、2012年是壬辰龙年等等。

计算某一年的天干地支,有很多经验公式,如果知道某一年的天干地支,也可以直接推算其它年份的天干地支。举个例子,如果知道2000年是庚辰龙年,则2012年的干支可以这样推算:(2012-2000)%10=2,2012年的天干就是从庚开始向后推2个天干,即壬;2012年的地支可以这样推算:(2012-2000)%12=0,2012年的地支仍然是辰,因此2012年的天干地支就是壬辰,十二生肖龙年。对于2000年以前的年份,计算出年份差后只要将天干和地支向前推算即可。例如1995年的干支可以这样计算:(2000–1995)%10=5,(2000–1995)%12=5,庚向前推算5即是乙,辰向前推算5即是亥,因此1995年的干支就是乙亥,十二生肖猪年。这个干支推算算法的实现如下:

202voidCalculateYearGanZhi(intyear,int*gan,int*zhi)

203{

204intsc=year-2000;

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

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

207

208if(*gan<0)

209*gan+=10;

210if(*zhi<0)

211*zhi+=12;

212}

获得2008年的干支纪年:

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

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

146intgan,zhi;

147

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

149

150text.Format(_T("农历【%s%s】%s年"),

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

结果是:农历戊子鼠年。

中国农历是以月亮运行周期为基础,结合太阳运行规律(二十四节气)制定的历法,农历月的定义规则就是中国农历历法的关键,因此要了解中国农历的历法规则,就必须知道如何定义月,如何设置闰月?中国农历的一年有十二个月或十三个月,但是正统的叫法只有十二个月,分别是正月、二月、三月、四月、五月、六月、七月、八月、九月、十月、冬月和腊月(注意,正统的中国农历是没有十一月和十二月的,如果你用的历法软件有显示农历十一月和农历十二月,就说明非常不专业)。中国民间常用“十冬腊月天”来形容寒冷的天气,其实指的就是十月,十一月和十二月这三个最冷的月份。一年有十三个月的情况是因为有闰月,多出来的这个闰月没有月名,只是跟在某个月后面,称为闰某月。比如公历2009年对应的农历乙丑年,就是闰五月,于是这一年可以过两个端午节。

中国农历为什么会有闰月?其实中国农历置闰月是为了协调回归年和农历年的矛盾。前面提到过,中国农历是一种阴阳历,农历的月分大月和小月,大月一个月是30天,小月一个月是29天。中国农历把日月合朔(太阳和月亮的黄经相同,但是月亮不可见)的日期定位月首,也就是“初一”,把月圆的时候定为望日,也就是“十五”,月亮绕地球公转一周称为一个朔望月。天文学的朔望月长度是29.5306日,中国农历以朔望月为基础,严格保证每个月的头一天是朔日,这就使得每个月是大月还是小月的安排不能固定,通常需要通过天文学观测和计算来确定。一个农历年由12个朔望月组成,这样一个农历年的长度就是29.530612=354.3672日,而阳历的一个天文学回归年是365.2422日,这样一个农历年就比一个回归年少10.88天,这个误差如果累计起来过16年就会出现“六月飞雪”的奇观了。为了协调农历年和回归年之间的矛盾,聪明的先人在天文观测的基础上,找到了“闰月”的方法,通过在适当的月份插入闰月来保证每个农历年的正月到三月是春季,四月到六月是夏季,七月到九月是秋季,十月到十二月是冬季,也就是说,让历法和天文气象能够基本对上,不至于出现“六月飞雪”。

m365.2422=n29.5306

这样m和n的比例就是29.5306:365.242219:235,按照这个最接近的整数倍数关系,每19个回归年需要添加的闰月就是:

235–1219=7

也就是“十九年七闰”的由来。但是需要注意的是,“十九年七闰”也并不是精确的结果,每19年就会有0.0892天的误差:

19365.2422-23529.53060.0892

现在,我们知道农历通过置闰月的方式协调农历年和回归年长度不相等的问题,也知道了置闰的方法是“中气置闰”法,那么到底什么是“中气”,又是如何定中气置闰月呢?要回答这个问题,就需要介绍另一个天文现象――节气。二十四节气起源于黄河流域,远在春秋时代,就定出仲春、仲夏、仲秋和仲冬等四个节气。以后不断地改进与完善,到秦汉年间,二十四节气已完全确立,汉武帝太初元年(公元前104年)制定的《太初历》,则第一次从历法上明确了二十四节气的天文位置。

春雨惊春清谷天,

夏满芒夏暑相连,

秋处露秋寒霜降,

冬雪雪冬小大寒,

每月两节不变更,

最多相差一两天。

由于节气在回归年中是均匀分布的,因此公历中的节气日期基本上是固定的,比如立春是在公历的2月3-5日,不会超出这个日期范围,这也就是《二十四节气歌》所说的:每月两节不变更,最多相差一两天。但是在中国农历中哪个中气属于哪个月是有规定的,雨水是正月的中气,春分是二月的中气,谷雨是三月的中气,小满是四月的中气,夏至是五月的中气,大暑是六月的中气,处暑是七月的中气,秋分是八月的中气,霜降是九月的中气,小月是十月的中气,冬至是十一月的中气,大寒是十二月的中气。

在了解了农历与节气的关系以及农历如何置闰月的方法之后,还需要解决一个问题才能着手农历年历的推算,那就是如何确定农历年的开始,或者说哪个月的初一是农历新年的开始?要回答这个问题,就需要了解中国农历特有的“月建”问题。

了解了“月建”问题,就解决了农历朔望月与公历月的对应关系,那就是冬至节气所在的朔望月就是农历的子月,对于目前适用的夏历建寅的月建体系,就意味着冬至节气所在的朔望月是农历的十一月,只要找到这个朔望月的起始日(日月合朔发生的时刻所在的那一日),就找到了公历的日期月农历日期的对应关系。下面总结一下中国农历历法的基本法则:

2、月以中气得名,冬至节气总是出现在农历十一月,包含雨水中气的月为正月(即寅月),月无中气者为闰月,与前一个月同名;

4、农历年以正月初一为岁首(关于农历岁首的说法,请参考文末附加的《小知识5:正月初一和立春节气》),以腊月(十二月)廿九或三十为除夕;

5、如果节气和日月合朔在同一天,则该节气是这个新朔望月的节气。(民间历法)

规则5对节气和朔日在同一天的处理,采用了民间历法的处理原则,关于民间历法和历理历法的区别,请参考文末附加的《小知识1:民间历法和历理历法》。

节气名

小寒

1271448.00

0.00

大寒

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

已知1900年小寒时刻为1月6日2:05:00,以这个节气时刻为基准,推算其它年份节气的算法实现如下:

8staticdoubles_stAccInfo[]=

9{

100.00,1272494.40,2548020.60,3830143.80,5120226.60,6420865.80,

117732018.80,9055272.60,10388958.00,11733065.40,13084292.40,14441592.00,

1215800560.80,17159347.20,18513766.20,19862002.20,21201005.40,22529659.80,

1323846845.20,25152606.00,26447687.40,27733451.40,29011921.20,30285477.60

14};

15

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

17constdoublebase1900_SlightColdJD=2415025.5868055555;

18

19doubleCalculateSolarTermsByExp(intyear,intst)

20{

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

22return0.0;

23

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

25

26returnbase1900_SlightColdJD+stJd;

27

28}

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

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

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

朔日编号

对应公历日期

月长

月名

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

正月

4

04:45:59.44

2011-03-05

二月

5

22:32:15.13

2011-04-03

三月

6

14:50:31.79

2011-05-03

四月

7

05:02:32.51

2011-06-02

五月

8

16:53:54.10

2011-07-01

六月

9

02:39:45.06

2011-07-31

七月

10

11:04:06.43

2011-08-29

八月

11

19:08:50.09

2011-09-27

九月

12

03:55:54.64

2011-10-27

十月

13

14:09:40.97

2011-11-25

14

02:06:27.05

2011-12-25

15:39:23.99

2012-01-23

表(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日就是月首,农历日期是初一,正月初一就是春节。

5doubleCalculateSolarTerms(intyear,intangle);

8doubleCalculateMoonShuoJD(doubletdJD);

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

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

139voidCChineseCalendar::GetAllSolarTermsJD(intyear,intstart,double*SolarTerms)

140{

141inti=0;

142intst=start;

143while(i<25)

144{

145doublejd=CalculateSolarTerms(year,st*15);

147if(st==WINTER_SOLSTICE)

148{

149year++;

150}

151st=(st+1)%SOLAR_TERMS_COUNT;

152}

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

38constintVERNAL_EQUINOX=0;//春分

39constintCLEAR_AND_BRIGHT=1;//清明

40constintGRAIN_RAIN=2;//谷雨

41constintSUMMER_BEGINS=3;//立夏

42constintGRAIN_BUDS=4;//小满

43constintGRAIN_IN_EAR=5;//芒种

44constintSUMMER_SOLSTICE=6;//夏至

45constintSLIGHT_HEAT=7;//小暑

46constintGREAT_HEAT=8;//大暑

47constintAUTUMN_BEGINS=9;//立秋

48constintSTOPPING_THE_HEAT=10;//处暑

49constintWHITE_DEWS=11;//白露

50constintAUTUMN_EQUINOX=12;//秋分

51constintCOLD_DEWS=13;//寒露

52constintHOAR_FROST_FALLS=14;//霜降

53constintWINTER_BEGINS=15;//立冬

54constintLIGHT_SNOW=16;//小雪

55constintHEAVY_SNOW=17;//大雪

56constintWINTER_SOLSTICE=18;//冬至

57constintSLIGHT_COLD=19;//小寒

58constintGREAT_COLD=20;//大寒

59constintSPRING_BEGINS=21;//立春

60constintTHE_RAINS=22;//雨水

61constintINSECTS_AWAKEN=23;//惊蛰

137voidCChineseCalendar::GetNewMoonJDs(doublejd,double*NewMoon)

138{

139for(inti=0;i

141doubleshuoJD=CalculateMoonShuoJD(jd);

142NewMoon[i]=shuoJD;

143

145}

146}

170boolCChineseCalendar::BuildAllChnMonthInfo()

171{

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

173inti;

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

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

176{

177info.mmonth=i;

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

179info.shuoJD=m_NewMoonJD[i];

180info.nextJD=m_NewMoonJD[i+1];

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

182info.leap=0;

183

184CChnMonthInfocm(&info);

185m_ChnMonthInfo.push_back(cm);

186

187yuejian++;

188}

189

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

191}

194voidCChineseCalendar::CalcLeapChnMonth()

195{

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

197

198inti;

199

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

201{

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

203i=1;

204while(i<(NEW_MOON_CALC_COUNT-1))

205{

206

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

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

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

210break;

211i++;

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

214{

215m_ChnMonthInfo[i].SetLeapMonth(true);

216while(i<(NEW_MOON_CALC_COUNT-1))

217{

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

219}

220}

221}

222}

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

图(5)演示程序的界面

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

小知识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:计算节气和朔日的经验公式

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:平朔和定朔

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

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

小知识1:公历的闰年

中国公历(也就是格里历)的置闰规则是四年一闰,百年不闰,四百年再闰,为什么会有这么奇怪的置闰规则呢?这实际上与天体运行周期与人类定义的历法周期之间的误差有关。地球绕太阳运转的周期是365.2422天,即一个回归年(TropicalYear),而公历的一年是365天,这样一年就比回归年短了0.2422日,四年积累下来就多出0.9688天(约1天),于是设置一个闰年,这一年多一天。这样一来,四个公历年又比四个回归年多了0.0312天,平均每年多0.0078天,这样经过四百年就会多出3.12天,也就是说每四百年要减少3个闰年才行,于是就设置了百年不闰,四百年再闰的置闰规则。

实际上公历的置闰还有一条规则,就是对于数值很大的年份,如果能整除3200,同时能整除172800则是闰年。这是因为前面即使四百年一闰,仍然多了0.12天,平均就是每天多0.0003天,于是每3200年就又多出0.96天,也就是说每3200年还要减少一个闰年,于是能被3200整除的年就不是闰年了。然而误差并没有终结,每3200年减少一个闰年(减少一天)实际上多减了0.04天,这个误差还要继续累计计算,这已经超出了本文的范围,有兴趣的读者可以自己计算。

小知识2:儒略历和格里历

在公元1582年10月15日之前,人们使用的历法是源自古罗马的儒略历,儒略历的置闰规则就是四年一闰,但是没有计算每年多出来的0.0078天,这样从公元前46年到公元1582年一共累积多出了10天,为此,当时的教皇格里十三世将1582年10月5日人为指定为10月15日,并开始启用新的置闰规则,这就是后来沿用至今的格里历。

小知识3:约化儒略日

由于儒略日数字位数太多,国际天文联合会于1973年8月决定对其修正,采用约化儒略日(MJD)进行天文计算,定义MJD=JD–2400000.5,MJD相应的起始点是1858年11月17日0:00。

小知识4:1752年9月到底是怎么回事儿

如果你用的操作系统是unix或linux,在控制台输入以下命令:

THE END
1.中国农历年份,rpa,机器人,自动化至此千百年来,中国传统历——汉历(农历)所采用得干支纪年方法,与皇帝年号纪年一样以新年正月朔(元旦)为开始。 从元明清三代的史书,可见干支纪年在正月初一更替的记录:更多内容请查看https://baike.baidu.com/item/%E5%B9%B2%E6%94%AF%E7%BA%AA%E5%B9%B4/3383226 https://wdlinux.cn/html/zonghe/20241128/20015.html
2.家谱中的公历农历是怎么记载?天干地支又是什么?族谱新闻家谱中的公历、农历是怎么记载?天干地支又是什么? 公历为国际通用纪年,书写为“XXXX年XX月XX日”;农历为我国传统历法,书写为“天干地支+月份美称+日期”。修谱时应尊重个人习惯,天干地支可保留并注明公元年份。 公历与农历的记载方式 公历:公历又称格里高利历或西历,是现在国际上通用的纪年方式。在家谱中,公历日期https://www.zupu.cn/zxzp/20241126/657384.html
3.农历纪年的计算方法(农历纪年怎样计算)农历,又称阴历、农事历,是中国传统历法之一。它的计算方式与西方的公历不同,是以月亮绕地球一周的时间为基础的。以下是农历纪年的计算方法的详细介绍。 一、农历的基本构成 农历是一种阴阳合历,它以月亮的阴晴圆缺为基础,同时兼顾太阳的运行周期。农历一年有12个月,每个农历月大致等于一个朔望月,即月亮相位从一https://www.zaixianjisuan.com/jisuanzixun/nonglijiniandejisuanfangfa.html
4.算法系列之二十:计算中国农历(一)农历算法算法系列之二十:计算中国农历(一) 本文介绍了中国农历的历法规则,包括阴阳历的结合、天干地支纪年法、闰月的设置原理以及农历与二十四节气的关系。农历以月相为基础,通过置闰月协调与回归年的差异。天干地支纪年法通过天干(10个)和地支(12个)组合,每60年一个轮回。农历的闰月依据“十九年七闰”或更精确的“中气https://blog.csdn.net/orbit/article/details/9210413
5.农历纪年怎么记的农历纪年怎么记的 农历纪年的方法是按我们中国的阴历时间来确定的。也就是说正月初一王一年的开始。它与现在世界通用的西历也就是阳历纪年法有很大的区别,阳历是按地球绕太阳转一圈所用的时间规定为一年的时间,而我们的阴历是按月亮绕地球一圈所用的时间为一年。 农历纪年怎么记的 农历也叫夏历,我国从夏朝开始采用https://edu.iask.sina.com.cn/jy/30xuOoGH78J.html
6.立春之后属相就变了吗生肖属相如何计算1、农历算法 以春节为分界点,也就是我们说的农历新年为起点计算生肖的方法出现比较晚,最早记载出现在北宋,现在很多人比较认同这个。民俗专家们认为,属相不应该按二十四节气来算,毕竟春节是农历新年的开始,也是农历生肖年的开始,属相自然应从正月初一算起。 http://qubaike.com/xingzuo/af9u1omt.html
7.农历计算方法所以计算农历需要先找出气朔,计算气朔则需计算太阳和月亮的黄经,现代天文学使用行星历表计算天体位置。python有第三方库提供相关功能,本文提供的算法以PyEphem库为例,利用太阳黄经计算节气,由SolarTerms函数处理,合朔则直接使用库中提供的next_newmoon函数进行计算。 https://www.jianshu.com/p/d3b63ee7492f
8.天干地支的纪年算法同样由于干支纪年的循环周期为60年,当余数大于57时,也需再借60。例如求公元前479年(孔子卒年)的干支:479除以60余59,用57减59不够减,加上60之后再减59等于58,查表一知该年对应的干支为壬戌。其余可以类推。 上述方法简便易行,只要记住表一,就完全可以不用纸笔,直接由心算推出结果。http://www.360doc.com/content/10/0829/15/655919_49649742.shtml
9.日历和生肖怎么算,生肖是按农历还是阳历算请问农历年腊月22日(国历年1月22日)出生的属相是什么 属相日历查询:十二生肖日期属相表 十二生肖年月日对照表。 新历出生和农历怎么算属相,生肖属相是按阴历还是阳历算? 生肖算法是按照农历。十二生肖也被称为十二年兽。在中国的历法上有十二只年兽依次轮流当值,所以我们的中国年就有以鼠,牛,虎,兔,龙,蛇,马,https://www.tai26.com/?p=126841
10.天干地支纪年法怎么算?需要注意哪些细节装修问答兹将搬屋选择吉日良辰之要点简述有这些:(一)迁移(搬屋)的日子不可与家人的「生肖」及「日柱」(农历https://ask.zx123.cn/show-6315273.html
11.农历纪年从哪一天开始算一年的第一天?–书格您现在的位置: 首页 / 交流区 / 水乐园 / 农历纪年从哪一天开始算一年的第一天? 正在查看 20 个帖子:1-20 (共 20 个帖子) 作者 帖子 2023年02月04日 17:19 @78892 回复 ?举报 oldestman 游客 看到有的日历从农历的正月初一开始(今年癸卯年),有的从立春开始算起,不知道以哪个为准的,都有什么依https://new.shuge.org/meet/topic/78892/
12.如果是公元前多少年,用干支纪年法怎么计算?应用公元年进行计算.应用公元年的某一年,除以60(指六十年甲子),余数小于60,再用余数减去3(干支纪年是从公元4年开始使用的),便知.如2002年:2002÷60,余数为22,再22-3,得数是19,查六十年甲子(干支表)19号干支,得知是壬午年.三.结合实际了解“十二地支” 1.用十二种动物分别与十二地支相配成为“十二生肖年https://www.zybang.com/question/636c16cd5f9ee133fb86781646888a60.html
13.阴历是怎么算的闰月加在某月之后叫“闰某月”,如刚刚过去的2009年农历闰月为己丑年闰五月(2009年6月23日——2009年7月21日)、即将来临的农历闰月为2012年的壬辰年闰四月(2012年5月21日——2012年6月19日)等。农历的算法是怎么算的? 5分一、农历规则计算: 节气和朔望的时间计算以东经120度,中国标准时间为准。https://www.360doc.cn/article/34990242_963225586.html