c语⾔农历,C++算法系列之中国农历的算法
所谓的“天⽂算法”,就是利⽤经典⼒学定律推导⾏星运转轨道,对任意时刻的⾏星位置进⾏精确计算,从⽽获得某种天⽂现象发⽣时的时间,⽐如⽇⽉合朔这⼀天⽂现象就是太阳和⽉亮的地⼼黄经(视黄经)差为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年(公历年)有关的朔望⽉关系表:
表(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个节⽓时间,时间可以跨年份,内部判断过冬⾄节⽓后⾃动转到下⼀年的节⽓继续计算:
void CChineCalendar::GetAllSolarTermsJD(int year, int start, double *SolarTerms)
{
int i = 0;
int st = start;
while(i < 25)
{
double jd = CalculateSolarTerms(year, st * 15);
if(st == WINTER_SOLSTICE)
{
妨的组词
year++;
}
st = (st + 1) % SOLAR_TERMS_COUNT;
}
}
start参数是节⽓的索引,定义⼆⼗四节⽓的索引如下:
const int VERNAL_EQUINOX = 0; // 春分
const int CLEAR_AND_BRIGHT = 1; // 清明
const int GRAIN_RAIN = 2; // ⾕⾬
const int SUMMER_BEGINS = 3; // ⽴夏
const int GRAIN_BUDS = 4; // ⼩满
const int GRAIN_IN_EAR = 5; // 芒种
const int SUMMER_SOLSTICE = 6; // 夏⾄
问题件const int SLIGHT_HEAT = 7; // ⼩暑
const int GREAT_HEAT = 8; // ⼤暑
const int AUTUMN_BEGINS = 9; // ⽴秋
const int STOPPING_THE_HEAT = 10; // 处暑
const int WHITE_DEWS = 11; // ⽩露
const int AUTUMN_EQUINOX = 12; // 秋分
const int COLD_DEWS = 13; // 寒露
const int HOAR_FROST_FALLS = 14; // 霜降
const int WINTER_BEGINS = 15; // ⽴冬
const int LIGHT_SNOW = 16; // ⼩雪
const int HEAVY_SNOW = 17; // ⼤雪
const int WINTER_SOLSTICE = 18; // 冬⾄
const int SLIGHT_COLD = 19; // ⼩寒
const int GREAT_COLD = 20; // ⼤寒
const int SPRING_BEGINS = 21; // ⽴春
const int THE_RAINS = 22; // ⾬⽔
const int INSECTS_AWAKEN = 23; // 惊蛰
节⽓索引乘以15就是节⽓在黄道上对应的度数。GetNewMoonJDs()函数从指定时间开始连续计算15个朔⽇时间,从第⼀个冬⾄节⽓前的第⼀个朔⽇开始。15个朔⽇可以形成14个完整的朔望⽉,保证在有闰⽉的情况下也能包含两个冬⾄节⽓:
void CChineCalendar::GetNewMoonJDs(double jd, double *NewMoon)
{关于莲花的古诗句
for(int i = 0; i < NEW_MOON_CALC_COUNT; i++)
{
double shuoJD = CalculateMoonShuoJD(jd);
NewMoon[i] = shuoJD;
jd += 29.5; /*转到下⼀个最接近朔⽇的时间*/
}
}
BuildAllChnMonthInfo()函数根据15个朔⽇时间组成14个朔望⽉,根据相邻朔⽇的间隔计算出农历⽉天数⽤来判定⼤⼩⽉,并且从“⼗⼀⽉”开始依次为每个朔望⽉命名(⽉建名称):
bool CChineCalendar::BuildAllChnMonthInfo()
{
CHN_MONTH_INFO info; //⼀年最多可13个农历⽉
int i;
int yuejian = 11; //采⽤夏历建寅,冬⾄所在⽉份为农历11⽉
for(i = 0; i < (NEW_MOON_CALC_COUNT - 1); i++)
{
毕业证明
info.shuoJD = m_NewMoonJD[i];
info.mdays = JD + 0.5) - int(info.shuoJD + 0.5);
info.leap = 0;
CChnMonthInfo cm(&info);
m_ChnMonthInfo.push_back(cm);
yuejian++;
}
游褒禅山记朗读return (m_ChnMonthInfo.size() == (NEW_MOON_CALC_COUNT - 1));
}
CalcLeapChnMonth()函数根据节⽓和朔⽇时间判断在两个冬⾄节⽓之间的农历年是否有闰⽉,判断的依据就是看第⼗四个朔⽇是否在第⼆个冬⾄节⽓之前,如果第⼗四个朔⽇发⽣在第⼆个冬⾄节⽓之前,就说明在两个冬⾄节⽓之间发⽣了⼗三次朔⽇,需要置闰⽉。因为农历中⼗⼆个中⽓属于哪个农历⽉是固定的,因此置闰⽉的过程就是依次判断⼗⼆个中⽓是否在对应的农历⽉中,如果本应该属于某个农历⽉的中⽓却没有落在这个农历⽉中,则这个农历⽉就是闰⽉,需要设置闰⽉标志,同时调整这个⽉之后的⽉名。调整农历⽉名的⽅法就是⽉名减⼀,⽐如原来是⼋⽉就要调整为七⽉,这样就将⼗三个⽉对应上了⼗⼆个⽉名(其中多出来的⼀个农历⽉被命名为闰某⽉)。如果节⽓和朔⽇发⽣在同⼀天,CalcLeapChnMonth()函数采⽤的是民间历法的规则,与现⾏历法⼀致:
void CChineCalendar::CalcLeapChnMonth()
{
asrt(m_ChnMonthInfo.size() > 0); /*阴历⽉的初始化必须在这个之前*/
int i;
if(int(m_NewMoonJD[13] + 0.5) <= int(m_SolarTermsJD[24] + 0.5)) //第13⽉的⽉末没有超过冬⾄,说明今年需要闰⼀个⽉
{
//找到第⼀个没有中⽓的⽉
i = 1;
while(i < (NEW_MOON_CALC_COUNT - 1))
{
/*m_NewMoonJD[i + 1]是第i农历⽉的下⼀个⽉的⽉⾸,本该属于第i⽉的中⽓如果⽐下⼀个⽉
的⽉⾸还晚,或者与下个⽉的⽉⾸是同⼀天(民间历法),则说明第i⽉没有中⽓*/
if(int(m_NewMoonJD[i + 1] + 0.5) <= int(m_SolarTermsJD[2 * i] + 0.5))