编程示例:公农历转换的算法
我国农历的编制与表示,在2017年,由中国科学院的南京的紫金山国家天文台发布了国家标准。
其中提到了编制规则。
第一以东八区的北京时间为准。
第二 朔日为农历月的第一个农历日
第三包含节气冬至在内的农历月为农历十一月。
第四若人某个农历十一月开始到下一个农历十一月(不含)之间有13个农历月,
则需要置闰月。置闰规则为取其中最先出现的一个不包含中气的农历月为农历闰月。
第五农历十一月之后第2个(不计闰月)农历月为农历年的起始月。
规则是既简明又准确的,就是不太好懂,我先解释一下,第一条定义了时间标准。
第二条定义了每个月的初一。第三条,俗称冬至规则,就是说,冬至那一天必须是在冬月,即农历十一月。
这一条至关重要,它是用公式推导其它农历月的基础。第四条包括了两个规则,(1)是是否置闰月的规则。
(2)是如何置闰月,这被叫做中气规则。这也是极为重要的一条规则。
第五条是定义了正月。
难点有两个,一是是否置闰月,它规定的年度期间为前一年的冬至到本年的冬至。然后看这个期间有多少个
朔日,也就是初一的数量,如果有十一个,没有闰月,如果是十二个,就是要闰月了。最佳的例子是2033年,
在公历的期间内也就是元旦到12月31日,它是有闰月的,闰11月。如果按照前一年的冬至到这一年的冬至期间
来看,它是没有闰月的。
二是置闰月的中气规则。还是举一个例子。以1987年为例,1987年8月24日为处暑,是农历七月初一,闰六月
如果是8月23日为处暑,则8月23日为农历七月的最后一天,8月24日是农历闰七月初一。
因为23日为处暑,它后面一个月之内缺中气秋分,所以闰七月。
因为24日为处暑,它前面一个月之内缺中气处暑,所以闰六月。
解释一下,什么是中气,24个节气中,出现在一个月的下旬的节气,称为中气,出现在一个月的上旬的节气,
称为节令。所以有12个节令,12个中气,节令和中气交替出现。它们合称24个节气。
下面要实现的功能是给出一个公历的年月日,返回一个农历的月和日。
javascript版本的api函数如下的调用方式
//输入阳历 2020 11 28 输出阴历 10月14日
sun_to_moon(yyyy,mm,dd)
程序实现的源代码如下
//以1900年0月31日为起点,计算节气的积日
function accumute_day_for_year_term(year,xterm)
{
var y=year-1900;
return Math.floor(365.242*y+6.2+15.22*xterm-1.9*Math.sin(0.262*xterm));
}
以1900年0月31日为起点,计算某年元旦时的积日
function accumute_day_for_year_begin_date(year)
{ var d1=0;
var y=year-1900;
var ym4=y%4;
var yd4=(y-ym4)/4;
if(ym4==0)
{d1=1461*yd4-1;}
else
{d1=1461*yd4+365*ym4;}
return d1;
}
//求该积日的之前的最近的朔日的积日
function all_dark_day_for_coldest(d)
{
var m=Math.floor(d/29.5306);
var M_day=Math.floor(1.6+29.5306*m+0.4*Math.sin(1-0.45058*m));
return M_day;
}
//判断是否是公历闰年,闰年为1,平年为0
function is_leap_year(years)
{
if((years&3)!=0)
{return 0;}
else
{if (years%100!=0)
{return 1;}
else
{
if (year%400!=0)
{return 0;}
else
{return 1;}
}
}
}
//判断是否是农历的有闰月年 ,有闰月为1,否则为0
function is_have_leap_month_of_year(year)
{
var d0=accumute_day_for_year_term(year-1,23);
var m0=Math.floor(d0/29.5306);
var d=accumute_day_for_year_term(year,12*2-1);
var m=m0+12+1;
var str=all_dark_day_for_coldest(d);
var M_day=Math.floor(1.6+29.5306*m+0.4*Math.sin(1-0.45058*m));
if(M_day>d)
{
var d1=accumute_day_for_year_term(year,1*2-1);
var str1=all_dark_day_for_coldest(d1);
if(str1>d1)
{return 1;}
else
{return 0;}
}
else {return 1;}
}
//判断一个阴历月份范围内有没有中气,中气是冬至,大寒等位于下半月的节气。
// 结果为没有0,有中气返回1,中气在朔日结果是2。闰月之后的情况为3
function is_have_xterm_of_month(year,i)
{
var d0=accumute_day_for_year_term(year-1,23);
var m0=Math.floor(d0/29.5306);
var d=accumute_day_for_year_term(year,i*2-1);
var m=m0+i+1;
var str=all_dark_day_for_coldest(d);
var M_day=Math.floor(1.6+29.5306*m+0.4*Math.sin(1-0.45058*m));
if(str<d&&d<M_day) {return 1;}
else if(str==d&&d<=M_day){return 2;}
else if(str>d&&M_day>d){return 0;}
else if(str<d&&str==M_day) {return 3;}
}
//在元旦作为起点 日期在年度中的天数 month 范围是1~12 日子范围是1——31。
function days_of_year_for_january(year,month,day)
{ var result=0;
var monthadd=[0,31,59,90,120,151,181,212,243,273,304,334];
var temp=is_leap_year(year);
if(month==4||month==6||month==9||month==11)
{if(day>30||day<1)
{result="错误:日期超过范围";}
}
else if(month==2)
{if(day>(28+temp)||day<1)
{result="错误:日期超过范围";}
}
else
{
if(day>31||day<1)
{result="错误:日期超过范围";}
}
if((month==1||month==2)&&result==0)
{
result=monthadd[month-1]+day;
}
else if((month>2&&month<13)&&result==0)
{
result=monthadd[month-1]+day+temp;
}
else
{
if(month<1||month>12)
{result+="错误:月份超过范围";}
}
return result;
}
//以3月1日作为起点 日期在年度中的天数
function days_of_year_for_march(month,day)
{
var result=0;
var monthadd=[306,337,0,31,61,92,122,153,184,214,245,275];
if(month==4||month==6||month==9||month==11)
{if(day>30||day<1)
{result="错误:日期超过范围";}
}
else if(month==2)
{if(day>29||day<1)
{result="错误:日期超过范围";}
}
else
{
if(day>31||day<1)
{result="错误:日期超过范围";}
}
if((month>0&&month<13)&&result==0)
{
result=monthadd[month-1]+day;
}
else
{
if(month<1||month>12)
{result+="错误:月份超过范围";}
}
return result;
}
以1700年2月28日为起点,计算某年3月1日时的积日
function accumute_day_for_march(year)
{ var d1=0;
var y=year-1700;
var ym4=y%4;
var yd4=(y-ym4)/4;
if(ym4==0)
{d1=1461*yd4;}
else
{d1=1461*yd4+365*ym4;}
return d1;
}
//0为星期天,1-6为星期一到星期六
function day_of_week(y,m,d)
{
var yy=y;
if(m==1||m==2)
{yy=y-1;}
var acc= accumute_day_for_march(yy)+days_of_year_for_march(m,d);
return (acc+5)%7;
}
//输入阳历 2020 11 28 输出阴历 10月14日
function sun_to_moon(yyyy,mm,dd)
{
var year=yyyy;
var month=mm;
var day=dd;
var d0=accumute_day_for_year_term(year-1,23);
var m0=Math.floor(d0/29.5306);
var result=[];
var result_xterm=[];
var result_month=0;
var result_day=0;
var d=accumute_day_for_year_term(year,1*2-1);
var m=m0+1+1;
var str=all_dark_day_for_coldest(d);
var M_day=Math.floor(1.6+29.5306*m+0.4*Math.sin(1-0.45058*m));
var xterm=is_have_xterm_of_month(year,1);
result_xterm.push(xterm);
//result.push(str+" "+d+" "+M_day+" 腊月 "+xterm+String.fromCharCode(13)+String.fromCharCode(10));
var current=accumute_day_for_year_begin_date(year)+days_of_year_for_january(year,month,day);
if(str<=current&¤t<=M_day)
{result_month=12;
result_day=current-str+1;
}
for(var i=2;i<13;i++)
{var message='';
var y_month=i-2;
d=accumute_day_for_year_term(year,i*2-1);
m=m0+i+1;
str=all_dark_day_for_coldest(d);
M_day=Math.floor(1.6+29.5306*m+0.4*Math.sin(1-0.45058*m));
var xterm=is_have_xterm_of_month(year,i);
if(result_xterm[result_xterm.length-1]==1&&xterm==2)
{message="闰"+y_month+"月来了"
m=m0+i;
M_day=Math.floor(1.6+29.5306*m+0.4*Math.sin(1-0.45058*m));
if(M_day<=current&¤t<str)
{result_month="闰"+y_month;
result_day=current-M_day+1;
}
var ii=i-1;
m=m0+i+2;
M_day=Math.floor(1.6+29.5306*m+0.4*Math.sin(1-0.45058*m));
if(str<=current&¤t<M_day)
{result_month=ii;
result_day=current-str+1;
}
}
else if(xterm==3)
{ var ii=i-1;
m=m0+i+2;
M_day=Math.floor(1.6+29.5306*m+0.4*Math.sin(1-0.45058*m));
if(str<=current&¤t<=M_day)
{result_month=ii;
result_day=current-str+1;
}
}
else if(xterm==1)
{var ii=i-1;
if(str<=current&¤t<=M_day)
{result_month=ii;
result_day=current-str+1;
}
}
//result.push(str+" "+d+" "+M_day+" "+ii+"月 "+xterm+" "+message+String.fromCharCode(13)+String.fromCharCode(10));
}
//document.getElementById("txt2").innerText=
return result_month+"月"+result_day+"日";
}