C语言实现根据阳历(公历)显示农历,干支,生肖

  • Post author:
  • Post category:其他





C语言实现根据阳历(公历)显示农历,干支,生肖



思路,代码仅供参考,如遇问题,发现错误请评论或邮件:609592946@qq.com




功能


求公历对应农历

输入一个公历日期,输出对应农历日期、干支和生肖。



提示:以下是本篇文章正文内容,下面案例可供参考



一、数据问题:


首先明确,农历的确定与天文关系紧密相连。储存的数据来自于网络,起源于天文台等权威机构公布的关于农历的数据。范围太大,如几千年后的农历,天文台也没有数据,无法计算表示。所以农历不像公历那样简单,有着强烈的规律性。天文台也会每年也会根据天体运动情况来更改数据,故关于未来日期的农历随后也可能会发生改变。



二、设计思想:

(1)首先明确,闰年和闰月是不同的。闰年是公历的名词;闰月是农历的名词。

(2)关于农历的计算,介于农历是阴阳合历,不像公历(阳历)那样容易确定,农历与天文观测联系更加紧密,需要综合考虑阳历与阴历关系。而通过天文学角度精确计算,对于目前知识储备来说过于困难。所以很难通过简单计算来推测确定某天农历。

(3)网上流传有通过经验公式计算,但经过测试,误差较大,会有一到两天误差。

(4)所以选择先储存一定过去的已经确定的年份农历信息和经天文台预测发布的未来年份的农历的信息,误差较小,用到时调用处理得出结果。

(5)对于如此多的数据,如何储存成了一个问题。这时上学期计算思维第一节课讲到的利用二进制储存代表信息解决实际问题的方法启发了我。可以将一些关键性数据储存起来,用到时调用。

(6)关于如此多的数据获取,采用从网上获得现成数据,一年年找过于麻烦,繁琐。

(7)农历中关键性字眼有朔望月大小,30或29天;是否闰月;具体闰几月。

(8)由于阳历一年中是包括涉及到两个农历年的,所以采用春节,也就是大年初一,正月初一为分界点。正月初一前,是一个农历年,正月初一(包括当天)后,是下一个农历年。

(9)把一些关键信息写出后,结合具体表示方法,发现用24个二进制数字,即六个十六进制数字就可以表示完整。具体代表含义如下:(24个数字,最右边第一位是bit0)

bit23 bit22 bit21 bit20:表示当年闰月月份,值为0为则表示当年无闰月

bit19 bit18 bit17 bit16 bit15 bit14 bit13 bit12 bit11 bit10 bit9 bit8 bit7

1 2 3 4 5 6 7 8 9 10 11 12 13

农历1-13 月大小 。月份对应位为1,农历月为大月(30 天),为0 表示小月(29 天)。

确切说是正月到腊月,中间穿插着闰月,比如,四月,闰四月,五月;

在一个阳历年开头涉及到得腊月,冬月的数据放在上一年储存。

这样,1901年春节前数据我们是无法得到的(因为那是上一个农历年数据,按照本方法,他应该在1900年数据中存储),就算根据此方法得到也是不准确的,随后会有验证解释。

bit6 bit5 春节的公历月份

bit4 bit3 bit2 bit1 bit0 春节的公历日期

春节月份用2位二进制数表示即可,肯定不会太大。

阳历日最大为31,用五位二进制数表示即可。

比如1901年的数据的判断:0x04AE53

0000 0100 1010 11100 10 10011

bit23到20为0000,0,没有闰月。bit19到7为0100 1010 11100,代表正月到腊月大小月分别为29、30、29、29、30、29、30、29、30、30、30、29。bit6到5为10,代表春节为公历2月份。bit4到0为10011,19,代表春节为公历19日。

(10)把数据存起来后,我们调用时,想到了位运算,和移位运算。可以提取指定位置的数字。

(11)公历农历直接的联系就是某两天在两个历法中的距离天数是一样的,这是代码能够实现的基础。

(12)那么随后的代码过程主要在于我们对于数据的提取处理部分

(13)干支纪年法,十天干,十二地支,单与单配,偶与偶合,单不与偶组合,按序相配,形成甲子、乙丑、丙寅……癸亥,所以60年一轮回。同时,生肖与地支是紧密相连的,子鼠、丑牛……亥猪。具有很好的规律性,容易实现。通过选定基准年,比如2020年,按照六十年一轮回,可以很好的得到任意一年干支。



三、函数的定义以及说明

int lunarLeapMonth(int y)判断当前年是否闰月,并得到闰的月份;不闰月就得到0。形参代表年份,需要判断的公历年份

int lunarMonthDays(int y, int m)由于农历朔望月30或29是不确定的,此处用来判断输出。看下指定年份在指定月是多少天。y代表公历年份,m代表在二进制数据中存农历月份的第m个位置的月份的大小月情况。

int lunarLeapMonthDays(int y)如果闰月,得到闰月的大小,是30天还是29天。y代表公历年份。

int lunarSpringDate(int y, int

solar_m, int

solar_d)得到农历的春节,即正月初一在阳历中日期。在函数中,把数据提取出来,利用指针变量来获取在函数中得到的结果。y代表年份,solar_m用来得到公历下春节月份地址,solar_d用来得到公历下春节日地址

int toLunar(int year, int month, int day , lunar_t * lunar) 输入公历日期,得到对应的农历日期。调用lunarSpringDate函数,得到该公历年下春节

void gz_sx(int year)判断干支与生肖。形参year代表年份,需要判断的公历年份。

void nongli()输入想要判断的公历日期,得到农历并且输出。


图示如下(箭头代表函数调用):

在这里插入图片描述



四、完成代码

代码是不完整的,笔者只写了农历部分,不是一个完整程序,缺少主函数等。正常运行仍需补全,关键代码已有,补全也不难。


代码如下(示例):

/*农历转换部分*/ 
unsigned int lunar200y[199] =//把获得的数据按年份存起来。年份代表的日期什么的,没有负数。 
{
    0x04AE53,0x0A5748,0x5526BD,0x0D2650,0x0D9544,0x46AAB9,0x056A4D,0x09AD42,0x24AEB6,0x04AE4A,/*1901-1910*/
    0x6A4DBE,0x0A4D52,0x0D2546,0x5D52BA,0x0B544E,0x0D6A43,0x296D37,0x095B4B,0x749BC1,0x049754,/*1911-1920*/
    0x0A4B48,0x5B25BC,0x06A550,0x06D445,0x4ADAB8,0x02B64D,0x095742,0x2497B7,0x04974A,0x664B3E,/*1921-1930*/
    0x0D4A51,0x0EA546,0x56D4BA,0x05AD4E,0x02B644,0x393738,0x092E4B,0x7C96BF,0x0C9553,0x0D4A48,/*1931-1940*/
    0x6DA53B,0x0B554F,0x056A45,0x4AADB9,0x025D4D,0x092D42,0x2C95B6,0x0A954A,0x7B4ABD,0x06CA51,/*1941-1950*/
    0x0B5546,0x555ABB,0x04DA4E,0x0A5B43,0x352BB8,0x052B4C,0x8A953F,0x0E9552,0x06AA48,0x6AD53C,/*1951-1960*/
    0x0AB54F,0x04B645,0x4A5739,0x0A574D,0x052642,0x3E9335,0x0D9549,0x75AABE,0x056A51,0x096D46,/*1961-1970*/
    0x54AEBB,0x04AD4F,0x0A4D43,0x4D26B7,0x0D254B,0x8D52BF,0x0B5452,0x0B6A47,0x696D3C,0x095B50,/*1971-1980*/
    0x049B45,0x4A4BB9,0x0A4B4D,0xAB25C2,0x06A554,0x06D449,0x6ADA3D,0x0AB651,0x093746,0x5497BB,/*1981-1990*/
    0x04974F,0x064B44,0x36A537,0x0EA54A,0x86B2BF,0x05AC53,0x0AB647,0x5936BC,0x092E50,0x0C9645,/*1991-2000*/
    0x4D4AB8,0x0D4A4C,0x0DA541,0x25AAB6,0x056A49,0x7AADBD,0x025D52,0x092D47,0x5C95BA,0x0A954E,/*2001-2010*/
    0x0B4A43,0x4B5537,0x0AD54A,0x955ABF,0x04BA53,0x0A5B48,0x652BBC,0x052B50,0x0A9345,0x474AB9,/*2011-2020*/
    0x06AA4C,0x0AD541,0x24DAB6,0x04B64A,0x69573D,0x0A4E51,0x0D2646,0x5E933A,0x0D534D,0x05AA43,/*2021-2030*/
    0x36B537,0x096D4B,0xB4AEBF,0x04AD53,0x0A4D48,0x6D25BC,0x0D254F,0x0D5244,0x5DAA38,0x0B5A4C,/*2031-2040*/
    0x056D41,0x24ADB6,0x049B4A,0x7A4BBE,0x0A4B51,0x0AA546,0x5B52BA,0x06D24E,0x0ADA42,0x355B37,/*2041-2050*/
    0x09374B,0x8497C1,0x049753,0x064B48,0x66A53C,0x0EA54F,0x06B244,0x4AB638,0x0AAE4C,0x092E42,/*2051-2060*/
    0x3C9735,0x0C9649,0x7D4ABD,0x0D4A51,0x0DA545,0x55AABA,0x056A4E,0x0A6D43,0x452EB7,0x052D4B,/*2061-2070*/
    0x8A95BF,0x0A9553,0x0B4A47,0x6B553B,0x0AD54F,0x055A45,0x4A5D38,0x0A5B4C,0x052B42,0x3A93B6,/*2071-2080*/
    0x069349,0x7729BD,0x06AA51,0x0AD546,0x54DABA,0x04B64E,0x0A5743,0x452738,0x0D264A,0x8E933E,/*2081-2090*/
    0x0D5252,0x0DAA47,0x66B53B,0x056D4F,0x04AE45,0x4A4EB9,0x0A4D4C,0x0D1541,0x2D92B5          /*2091-2099*/
};
typedef struct _lunar//储存农历相关信息 
{
    int year;
    int month;
    int day;
    int is_leap;
} lunar_t;
int lunarLeapMonth(int y)//判断当前年是否闰月,并得到闰的月份;不闰月就得到0。 
{
	return((lunar200y[y-1901] & 0xf00000) >>20);//0xf00000:1111 0000 0000 0000 0000 0000;把储存的前四位数据取出来 
}
int lunarMonthDays(int y, int m)//由于农历朔望月30或29是不确定的,此处用来判断输出。看下指定年份在指定月是多少天。 
{
	return((lunar200y[y-1901] & (0x80000>>(m-1)))? 30: 29 );/*0x80000:1000 0000 0000 0000 0000;
	经过移位,可以把第m个月存的数据调用出来*/ 
}
int lunarLeapMonthDays(int y)//如果闰月,得到闰月的大小,是30天还是29天。 
{
	if(lunarLeapMonth(y))
	{
		return( (lunar200y[y-1901] & (0x80000>>(lunarLeapMonth(y)-1)))? 30: 29 );/*0x80000:1000 0000 0000 0000 0000;
		0x80000>>(lunarLeapMonth(y)-1)部分,移动1的位置 控制提取闰月在数据中的位置*/
	}
	else
	{
		return 0;
	}
}
int lunarSpringDate(int y, int*solar_m, int* solar_d)/*得到农历的春节,即正月初一在阳历中日期。
在函数中,把数据提取出来,利用指针变量来储存在函数中得到的结果。*/ 
{
	if(!solar_m||!solar_d)
		return -1;
	*solar_m =(lunar200y[y-1901]&0x0060) >> 5;//0x0060:0000 0000 0110 0000;调用储存的春节月份数据 
	*solar_d = lunar200y[y-1901]&0x1f;//0x1f:0001 1111;调用储存的春节日期数据
	return 0;
} 
int toLunar(int year, int month, int day , lunar_t * lunar)//输入公历日期,得到对应的农历日期。 
{
	int monthTotal[13] = {0,31,59,90,120,151,181,212,243,273,304,334,365};
	//平年,365天,相邻数据间隔代表月有多长,比如31-0=31,公历下1月31天。
    int bySpring,bySolar,daysPerMonth;
    //bySpring 记录春节离当年元旦的天数。
    //bySolar 记录阳历日离当年元旦的天数。
    int index,flag;
    //index 记录从哪个月开始来计算。
    //flag 是用来对闰月的特殊处理。
 	int spring_solar_d;
	int spring_solar_m;
    lunarSpringDate(year,&spring_solar_m,&spring_solar_d);
    if( spring_solar_m == 1)//春节在一月,与元旦1月1日一个月份,对日上处理就行 
        bySpring = spring_solar_d - 1;
    else//春节不在一月,就是在二月,不可能跑到三月去,中间差了一个完整的公历一月,还有在公历二月多出来那几天。 
        bySpring = spring_solar_d - 1 + 31;
    bySolar = monthTotal[month-1] + day - 1;
	//阳历日到春节天数,利用上面之前定义的一个变量。先算差的整个月份的天数,再算上不到一个月的天数 
    if( (!(year % 4)) && (month > 2))/*上面是按平年来说的,如果是公历闰年,并且,月份超过了2月,就得在天数上再加上一天。
	因为公历闰年是在2月上做一个处理,天数加一,由28天到29天*/ 
        bySolar++;
    //判断阳历日在春节前还是春节后
	if (bySolar >= bySpring) /*/阳历日在春节后(含春节那天)。*/
	{
		bySolar -= bySpring;//这样以元旦为基准就得到了阳历日距离春节的天数
		index = 1;/*春节开始,从农历正月开始判断,这个是对储存的数据中,月份对应的第几位的记录。*/
		month = 1; //初始化,记录月份,从正月开始。 
		flag = 0;//初始化
		//闰月,月份名字是不变的。只是在月份前加个闰字。所以引进index和month进行区分。 
		daysPerMonth =lunarMonthDays(year,month);//daysPerMonth记录大小月的天数 29 或30
		while(bySolar >= daysPerMonth) //阳历日距离春节的天数大于等于农历月得到的天数的话 
		{
			bySolar -= daysPerMonth;//距离天数减去已经知道的农历月的天数,不断逼近我们想要的结果 
			if(month == lunarLeapMonth(year) ) //农历闰月的情况。判断当前月份是不是农历闰月闰的那个月 
			{
				flag = ~flag;//按位取反后由0变成-1;由-1变为0;利用这个情况, 对闰月进行了处理。 
				if(flag == 0)
				{
					month++;
				}
			}
			else//不是闰月,月份加一,继续判断 
			{
				month++; 
			}
			index ++;//在不是闰月的情况下,index和month是同时变换的。只有在闰月情况下,index会比month多了一个一。 
			daysPerMonth =lunarMonthDays(year,index);//判断一个新的月份的天数。 
		}
			day = bySolar + 1;
	}
	else//阳历日在春节前 ,这样就相当于用的上一个农历年的数据。一个公历年涉及到两个农历年 
	{                                       
		bySpring -= bySolar;//这样以元旦为基准就得到了阳历日距离春节的天数   
		year--;//这个时间段对应的是上一个农历年,这个农历年大部分时间是在上一个公历年,年份减一。  
		month = 12;//农历年最后一个月是腊月 
		if (lunarLeapMonth(year) == 0)/*上面函数确定的,不闰月的返回0;
		由于上一年农历数据是在数据末尾储存,没有闰月,农历月总共是有12个月*/ 
			index = 12;     
		else      
			index = 13;//有闰月的存在,农历年就有13个月
		flag = 0;//初始化     
		daysPerMonth = lunarMonthDays(year, index);//daysPerMonth记录大小月的天数29或者30 
		while(bySpring > daysPerMonth)//差值大于一个月,继续循环 
		{         
			bySpring -= daysPerMonth;//差值减掉那一个正月的天数    
			index--;//继续判断下一个月份天数   
			if(flag == 0)//代表不是闰月      
			month--;//月份继续减一判断        
			if(month == lunarLeapMonth(year))//判断月份是不是闰月的那个月份 
			{
				flag =!flag;//对其进行处理,使得下次循环判断,月份不变。闰月,月份名字是不变的。只是在月份前加个闰字 比如,四月,闰四月 
			}
			daysPerMonth = lunarMonthDays(year, index);//接着判断大小月 
		}
		day = daysPerMonth - bySpring + 1;//那个月的天数减去那个差值再加上1,就是指定日期在这个月的日 
	}
	lunar->day = day;
	lunar->month = month;
	lunar->year = year;//赋值,为了便于后续输出 
	if(month == lunarLeapMonth(year)&&index!=month)//对闰月的一个处理 
		lunar->is_leap = 1;//代表这个时候有闰月的情况,后续输出,多输出一个“闰”字 
	else
		lunar->is_leap = 0;//没闰月 
	return 0;
}
 /*求某年干支生肖部分*/
void gz_sx(int year) //判断干支与生肖。 
{
    int a,b,chazhi;   
    char tgdz[6];   
    static char tiangan[10][3]={"甲","乙","丙","丁","戊","己","庚","辛","壬","癸"};   
    static char dizhi[12][3]={"子","丑","寅","卯","辰","巳","午","未","申","酉","戍","亥"}; 
	static char shengxiao[12][3]={"鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"};  
	chazhi=year-2020;//2020年农历年主要是庚子年,以其为基准,推算。  
    a=(chazhi+6)%10;//庚是定义天干数组中第七个汉字   
    b=(chazhi+0)%12;//子是定义地支数组中第一个汉字
	if (a<0) 
	a=a+10;
	if (b<0)
	b=b+12;   
    strcpy(tgdz,tiangan[a]);   
	strcat(tgdz,dizhi[b]); 
    printf("%s%s年",tgdz,shengxiao[b]);
}
void  nongli()//输入想要判断的公历日期,得到农历并且输出。 
{
    const char *ChDay[] = //农历月中日的叫法 
	{"*","初一","初二","初三","初四","初五",
        "初六","初七","初八","初九","初十",
        "十一","十二","十三","十四","十五",
        "十六","十七","十八","十九","二十",
        "廿一","廿二","廿三","廿四","廿五",
        "廿六","廿七","廿八","廿九","三十"};
    const char *ChMonth[] = {"*","正","二","三","四","五","六","七","八","九","十","冬","腊"};//农历月名称 
    int year,month,day;
    int year1,month1,day1;
    lunar_t lunar; 
	printf("请按格式输入想要查询的公历日期:(例如2020年7月1日,输入2020-7-1) \n注意:范围为1901-2-19到2099-12-31\n");
    scanf("%d-%d-%d",&year,&month,&day);
    year1=year;
	month1=month;
	day1=day;//后续求春节日期,会改变储存的,year,month,day的值,先储存下,便于后面判断。
	lunarSpringDate(year1,&month1,&day1);//得到春节日期,为了具体干支年的判断 
	toLunar(year,month,day, &lunar);
	printf("对应农历为 "); 
	if(month>month1)//一个公历年有涉及到两个农历年;以春节为界限。之前为上一农历年。之后为下一农历年 
		gz_sx(year);//用到下一个农历年干支 
	else if(month=month1 && day>=day1)
		gz_sx(year);//用到下一个农历年干支
	else gz_sx(year-1);//用到上一个农历年干支 
    if(lunar.is_leap==1) //对闰月的处理。 
	printf("闰%s月%s\n",ChMonth[lunar.month],ChDay[lunar.day]);
	else 
	printf("%s月%s\n",ChMonth[lunar.month],ChDay[lunar.day]);
}


代码仅供参考




五、结论与检验



1、选择几个日期(代表几种情况)进行验证。


(2020年闰年且存在闰月,闰四月,春节时间为1月25日)

(1)2020年7月5日(公历日在春节后的情况)

这是程序运行结果:

在这里插入图片描述

从百度上查询指定日期结果。

在这里插入图片描述

可以看到,结果都是庚子鼠年五月十五,说明程序运行正确。

(2)2020年1月3日(公历日在春节后的情况)

这是程序运行结果:

在这里插入图片描述

从百度上查询指定日期结果。

在这里插入图片描述

可以看到,结果都是己亥猪年腊月初九,说明程序运行正确

(3)2020年5月15日(对应农历四月,2020年闰四月)

这是程序运行结果:

在这里插入图片描述

从百度上查询指定日期结果。

在这里插入图片描述

可以看到,结果都是庚子鼠年四月廿三,说明程序运行正确

(4)2020年6月10日(此时对应农历闰四月,检验闰月情况)

这是程序运行结果:

在这里插入图片描述

从百度上查询指定日期结果。

在这里插入图片描述

可以看到,结果都是庚子鼠年闰四月十九,说明程序运行正确



1、1901年数据问题。

1901年涉及到两个农历年,1月1日开始时对应农历庚子鼠年十一月十一,十一月有29天,下一个农历月腊月有30天。但是按照上面所说的数据储存方法,并没有给这两个月数据进行储存。我们先看下代码在1901年春节(2月19日)前运行情况如何。(对代码稍做修改,便于观察,检验过后已删除)

在这里插入图片描述

可以看到,此时读取出的十六进制信息是0,而我们储存的数据是十六进制下的6位数。分析其原因,我们看到year-1901=-1,这个时候调用数组下标为-1的地址对于数组来说是越界访问了,但是这个地址是有意义的。

这个地址就是所申请的数组存储空间的首地址的向前偏移一个单位(也就是偏移一个当前数组类型所对应的字节数)所对应的地址。

这个地址由于没有跟着数组空间一起初始化,所以其中的数据是不一定的,如果是正在被系统或者其他APP使用中的地址空间,那么可以被访问,其中的数据的意义取决于被系统或者其他APP所写入的数据,但是访问后,有可能会引起系统或者其他APP异常。如果是没有被使用的地址,那么就是一个野地址,那么其中的数据是随机的,无意义的。

但本处,我们仅仅是调用访问了那个地址的数据,并没有对其进行修改,所以不会在其他方面产生影响,仅仅是得到的数据不准确。这也算是本方法的一个局限性。


希望这篇文章能帮助到您,您的点赞、评论、收藏、关注是对我最大的支持和鼓励。



版权声明:本文为qq_22529391原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。