这篇文章本意只是为了大家的万年历的实现方式提供一种思路,代码本身并不成熟,需要源代码的同学,本人已经上传到网站上了,设置的免费下载,csdn本身设置的免费下载需要做任务,但是绝对是可以下载的,请不要私信问作者资源需要付费下载的问题。并且文件在dev-c++环境下是可以运行的。如果你不会解压当我没说。字体重叠是因为没有全屏,或者屏幕缩放率不为100%造成的,请自行百度修改。

重新上传了资源,正在审核,为了方便大家 ,评论区也添加了百度云盘链接

一、先看效果图

1.年度显示

2.月度显示

3.查询日

二、思路及代码实现

       做这个程序我个人的思路是“查询日→→→查询月→→→查询年”这样的流程。因为我作业老师要求英文注释,我发到CSDN里面改成了中文,所以代码里面可能会有没删干净的英文注释。

1.查询周几

       查询日需要做的就是可以计算出需要查询的日期是周几,以及农历日期,和节假日。我个人认为农历日期的查询是最难做的,因为它的算法乱七八糟的,我这里用了一种比较取巧的方式。

             先说查询周几吧,这个应该都会,我就比较粗糙说一下。

a.确定基准。

我们以2000年1月1日作为基准,这一天是周六,冬月廿五,元旦。如果你想查之前的日子,那么你就把基准往前定。

b.计算要查的日期与基准所差的天数

这个部分的核心比较简单,只需要会判断中间每个年份是不是闰年,最后一年每个月都有几天,然后加起来就可以了。

//判断闰年
int leapyear (int y){
	int i=0;
	if(y%100==0){        //年份在可以被100整除的情况下必须被400整除才算闰年
		if(y%400==0){
			i=1;
		}
	} else {
	    if (y%4==0){
		    i=1;
	    }
	}
	return i;   //如果是闰年就返回1 平年就返回0
	
}
int week (int y,int m,int d){   //y年 m月 d天
    int i,j,w,sum1,sum2;        //w 周几
    int a[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
    sum1=0;
    sum2=0;
    w=6;                        //基准那天是周六 
    if(leapyear(y)==1){
	    a[2]=29;                //如果是闰年那么2月就有29天
    }     
    for(i=1;i<m;i++){
        sum2+=a[i];
    }    
    sum2+=d;                   //这时候是最后一年过了多少天
    sum1+=sum2;
    for(j=2000;j<y;j++){
	    if(leapyear(j)==1){
    	        sum2=366;
	    }   else {
		        sum2=365;
	    }
        sum1+=sum2;             //加上中间每年的天数
    }       
    sum1+=w;
    sum1-=1;				 //2000年1月1日已经过去了
    w=sum1%7;
	return w;               //把周几返回去  
}

c.需要打印周几的时候使用的函数


void printweek(int w){    //这个w是上个函数返回的w
	switch(w)        
	{
	case 0:printf("周日");break;
	case 1:printf("周一");break;
	case 2:printf("周二");break;
	case 3:printf("周三");break;
	case 4:printf("周四");break;
	case 5:printf("周五");break;
	case 6:printf("周六");break;
	}
	
}

 2.查询农历

农历的数据都是南京紫金山天文台历算室算出来的,有的节气是精确到时辰的,我弄了好长时间都不太会,所以我网上找了一个农历数据表。把他变成了txt文本,只保留了需要的农历日期。

 这是原来的文本

我改过后的

因为我们老师只要求常见的节假日,那些节气不做要求,我都去掉了,只留下了农历日期,节假日单独出一个函数。

然后农历闰x月我是直接在月前面加了一个3

比如闰5月就会变成35,闰冬月会变成311

把农历也变成数字是因为txt读取中文的时候可能会出错,我的作业肯定要求稳(狗头

此外因为我的农历是取决于我这个txt的范围的,如果你想扩大查询范围,你要先找到足够大的农历数据。

这个读取主要是使用

ch=fgetc(p);t=atoi(a);switch(x);这三个代码实现的。      

//注释看的时候要和修改后的txt文档结合看
void lunarcalendar(int y,int m,int d,int *p1,int *p2)    //y 年 m 月 d 天 p1指向lm即农历月 p2指向农历天
{
	int i,t,z,q;
	char a[10]={0};                                      //这个是用来存储和年份有关的字符的          
字符定义范围搞大点没什么影响
	char b[10]={0};                                      //这个是用来存储和月份有关的字符的   
	char c[40]={0};                                      //这个是用来存储和天份有关的字符的
	i=0;
	t=0;                                                  //atoi转化出来的数字年份
	q=0;                                                  //atoi转化出来的数字月份
	z=0;                                                    //atoi转化出来的数字天
	FILE*p;
	char ch;
	
	if((p=fopen("calendar.txt","r"))==NULL)                //打开txt文本
	{
		printf("ERROR\n");
	}
	for(; ;){                                            //这里开始一个循环
		for(;ch!='a';){                                  //持续不断地读入字符,读到“a”的时候进行下一步
			ch=fgetc(p);	
		}
		for(i=0;i<10;i++){
			a[i]=0;                                     //每次都想先把储存年份有关的字符都归零,这步很关键
		}
		for(i=0;;){	
			ch=fgetc(p);
			if(ch!='/'){                                //将字符依次赋值给字符数组,直到读到“/”的时候停止赋值
				a[i]=ch;
				i++; 
			}else {
				break;
			}	
		}
		t=atoi(a);                                     //把字符年份变成数字年份并赋值		
		for(i=0;i<10;i++){                             //月份初始化,后面和年份操作相同
			b[i]=0;
		}
		for(i=0; ;){	
			ch=fgetc(p);
			if(ch!='/'){   
				b[i]=ch;
				i++;
			}else {
				break;
			}	
		}
		q=atoi(b);                                     //换成数字月份
			
		for(i=0;i<40;i++){
			c[i]=0;        
		}
		for(i=0;;){	
			ch=fgetc(p);
			if(ch!=','){   
				c[i]=ch;
				i++;
			}else {
				break;
			}	
		}
		z=atoi(c);                                    //换成数字天数
		
		if(t==y){
			if(q==m){
				if(z==d){
					break;                            //当年月日都和你想查的对应上之后,再进行下一部,否则会持续不断进行下去,如果你查了txt中不存在的月份,那么就会出bug,程序不会显示结果
				}
			}
		}				
	} 
	for(i=0;i<10;i++){
			b[i]=0;                                    //初始化数组,现在这个数组用来储存农历月份
		}
		for(i=0;i<40;i++){
			c[i]=0;                                    //初始化数组,现在这个数组用来储存农历日期
		}	
	
	for(i=0;;){      
		ch=fgetc(p);
		if(ch!=','){                                   //读入农历月份
			b[i]=ch;
			i++;
		} else{
			break;
			}
		}
		q=atoi(b);                                       //转化为数字农历月份
		for(i=0;;){
			ch=fgetc(p);
			if(ch!=','){                                 //读入农历天数				
				c[i]=ch;
                i++;
				}else{
					break;
			}
		}
		z=atoi(c);                                      // 转化为数字农历天数
				
	fclose(p);                                             //关闭txt文件
	*p1=q;
	*p2=z;                                            //用指针赋值,这样就可以把结果弄出函数
}

可能有人看见代码,会问:“为什么每次读入字符前初始化字符数组很重要?”

for(i=0;i<10;i++){
            a[i]=0;
        }

因为每次程序都在读入年月日,不匹配的时候重新进行循环(循环的时候读入并不会初始化),所以这个循环势必要循环很多次,因此不进行初始化的时候 a[1],a[2],a[3],a[4]其实都是有值的,读入年份的时候不受影响,因为年都是四位数,但是读入月份的时候,如果不进行初始化,可能当你读到1月,并进行赋值,通过atoi转换到数字的时候会变成12月,这是因为a[2]的赋值在上一个循环读入12月的时候被赋值了12中的2,这次只读入了1个数字但是a[2]不是0,atoi读到的还是12,转化出来的数字就还是12.

void lunarcalendarmonth(int lm)
//lm 是p1指向的变量,农历月份
{
	switch(lm){
		case 1:printf("正月");break;
		case 2:printf("二月");break;
		case 3:printf("三月");break;
		case 4:printf("四月");break;
		case 5:printf("五月");break;
		case 6:printf("六月");break;
		case 7:printf("七月");break;
		case 8:printf("八月");break;
		case 9:printf("九月");break;
		case 10:printf("十月");break;
		case 11:printf("冬月");break;
		case 12:printf("腊月");break;
		case 31:printf("闰正月");break;
		case 32:printf("闰二月");break;
		case 33:printf("闰三月");break;
		case 34:printf("闰四月");break;
		case 35:printf("闰五月");break;
		case 36:printf("闰六月");break;
		case 37:printf("闰七月");break;
		case 38:printf("闰八月");break;
		case 39:printf("闰九月");break;
		case 310:printf("闰十月");break;
		case 311:printf("闰冬月");break;
		case 312:printf("闰腊月");break; 
	}
}
void lunarcalendarday(int ld)             
//ld是上上个函数p2所指向的变量,是农历天数
{
	switch(ld){
		case 1:printf("初一");break; 
		case 2:printf("初二");break; 
		case 3:printf("初三");break; 
		case 4:printf("初四");break; 
		case 5:printf("初五");break; 
		case 6:printf("初六");break; 
		case 7:printf("初七");break; 
		case 8:printf("初八");break; 
		case 9:printf("初九");break; 
		case 10:printf("初十");break; 
		case 11:printf("十一");break; 
		case 12:printf("十二");break; 
		case 13:printf("十三");break; 
		case 14:printf("十四");break; 
		case 15:printf("十五");break; 
		case 16:printf("十六");break; 
		case 17:printf("十七");break; 
		case 18:printf("十八");break; 
		case 19:printf("十九");break; 
		case 20:printf("二十");break; 
		case 21:printf("廿一");break; 
		case 22:printf("廿二");break; 
		case 23:printf("廿三");break; 
		case 24:printf("廿四");break; 
		case 25:printf("廿五");break;
		case 26:printf("廿六");break; 
		case 27:printf("廿七");break;
		case 28:printf("廿八");break;
		case 29:printf("廿九");break; 
		case 30:printf("三十");break;   
	}
}

3.查询节假日

做到这里的时候,我们已经可以查出一天的周几,以及农历信息了,然后可以做出一个查询节假日的功能。给出的代码写法只可以查固定日期的节假日,如果想查二十四节气这种的则需要上一步的txt文本进行修改,添加数据,并且对读取代码做一些修改。

int festival(int y,int m,int d,int lm,int ld,int mod) //mod 是两个模式,输入1是这一天如果有节假日就只输出一个。主要用来查询整个月的时候 。输入2 是如果这一天有多个节假日,所有的都会输出。主要用于查询天
{
	int count=0;                                       //count记录这一天有几个节假日
	color(3);                                          //一般的节日输出颜色变为蓝色
	if(m==1){
		if(d==1){
			printf("元旦");
			count++; 
		}
	}
	if(m==2){
		if(d==14){
			if(count!=0){
				if(mod==1){
					goto end;                           //mod 1 情况下遇到第二个节日直接结束这个函数,
				}
				printf("|");                            //mod2下有多个节日用“|”分割
			}
			printf("情人节");
			count++;                                    //Add one to the number of festivals
			
		}
	}
    end:
	color(7);	                                        //节日输出之后的输出字符颜色变为白色
	return count;                                     //把今天有几个节日传回去
}

这里的color函数是自己定义的一个函数,可以改变输出字符的颜色。

0 = 黑色 8 = 灰色
1 = 蓝色 9 = 淡蓝色
2 = 绿色 A = 淡绿色
3 = 浅绿色 B = 淡浅绿色
4 = 红色 C = 淡红色
5 = 紫色 D = 淡紫色
6 = 黄色 E = 淡黄色
7 = 白色 F = 亮白色

#include <windows.h>                                                //定义的时候加上这行

void color(int x) 
	if(x>=0&&x<=15){
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), x);
	}else{
		color(7);
}

 4.按月输出

按照月输出的时候,我们已经解决了最复杂的农历,现在只需要设置输出的位置就可以解决这个问题。

按月输出的核心难题是光标的移动。这里使用和color()函数相同的定义方法。

我们只需要会用这个函数就行了,万年历并不需要我们有深入的理解。(其实我也不会,我只是一个废物大学生)

#include <windows.h>
int wherex()
{
    CONSOLE_SCREEN_BUFFER_INFO pBuffer;
    GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &pBuffer);
    return (pBuffer.dwCursorPosition.X+1);
}
//得到光标的x坐标
int wherey()
{
    CONSOLE_SCREEN_BUFFER_INFO pBuffer;
    GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &pBuffer);
    return (pBuffer.dwCursorPosition.Y+1);
}
//得到光标的y坐标


void gotoxy(int x,int y) 
{
    COORD c;
    c.X=x-1;
    c.Y=y-1;
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),c);
} 
//移动光标到指定位置


void month (int y ,int m){
	int d,ld,lm,*p1,*p2;
	d=1; 
	ld=25;
	lm=11;
	p1=&lm;
	p2=&ld;
    //上面几个数的定义和之前的函数一样,注意不要把y当成坐标y
	int i,j,w,z,x1,y1,x2,y2,y3,t;
	t=0;
	d=1;                                                //输出月从1号开始(废话)
	int a[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};  
	if(leapyear(y)==1){
		a[2]=29;
	}                                                   //把这年的二月变成正确的天数,输出年的时候有用。
	w=week(y,m,d);                                      //利用函数求出这天是周几.
	z=wherex();                                         //得到现在光标的x坐标
	z+=5;                                               //记录的数据加5,可以根据自己的电脑进行修改
	gotoxy(z,wherey());                                 //将光标向右移动5,输出月的时候可以与左边流出空隙,这里可以忽略      
	printf("-----------------------------------------------");//分割线
	gotoxy(z,wherey()+1);                                //换行的时候不要简单使用“\n”,输出月的时候没有影响,但是会对年产生影响
	printf("                %d年%d月",y,m);              //月份头
	gotoxy(z,wherey()+1);                                //利用函数进行换行
	printf("-----------------------------------------------");//分割线
	gotoxy(z,wherey()+1);                                //换行
	printf("周日   周一   周二   周三   周四   周五   周六 ");
	gotoxy(z,wherey()+2);                                //一次换两行,留出空隙,使输出结果美观一些
	for(i=0,j=0;i<w;i++,j++){
		printf("       ");
	}                                                    //利用空格将第一天与周几对齐
	
	for(d=1;d<=a[m];d++,j++){
		lunarcalendar(y,m,d,p1,p2);                     //先查一下这个农历信息
		x1=wherex();                                          
		y1=wherey();                                    //记录现在输出数字时光标的位置
		if(d<10){
			
			printf("  %d    ",d);
		}else{
			printf(" %d    ",d);
		}                                               //为了美观留有空格,并输出x号
			x2=wherex();
			y2=wherey();                                //记录第二个坐标
			gotoxy(x1,y1+1);                            //往数字的下一行相同位置进行移动光标
			t=festival(y,m,d,lm,ld,1);
			                                            //输出有颜色节日
			if(t==0){
				color(8);
				if(ld==1){
					lunarcalendarmonth(lm);
				}else{
				lunarcalendarday(ld);
			}
		    //如果没有节日就输出灰色的农历日,但如果则这一天是初一,就输出月份,这样才能知道农历是几月(比如正月初一就输出正月 正月初二就输出初二)(老师要求的真细)
			}
			color(7);//之后的输出内容变成白色
			gotoxy(x2,y2);                                //把坐标移到下一个数字应该开始的地方													
		if(j==6){
			if(d<a[m]){
			gotoxy(z,wherey()+3);
			j=-1;
			}
		}
	}
//如果这一行已经满了,并且这一个月还没有结束,那就换行。
	gotoxy(z,wherey()+2);                                //这个月日期都结束后,光标移到下一个分割线位置
	printf("-----------------------------------------------");//分割线
	gotoxy(z,wherey()+1);                                //换行
}


以下就是输出一个月的日历的主代码

printf("请根据'200001'输入年月\n");
scanf("%4d%2d",&y,&m);
month(y,m);          //打出这一个月的日历
gotoxy(1,wherey());//让光标靠左

 4.按年输出 

当我们实现按月输出后,按年输出的代码就变得简单了起来,只需要一个简单的for循环加移动光标等就可以了。

按月输出的时候之所以不直接用换行符是因为按年输出的时候,一旦换行,这个月的输出结果就会跑到上个月数据的地方,有兴趣的可以试试直接用换行符会发生什么(狗头)。

void year(int y)
{
	int x1,y1,x2,y2,max;
	max=0;                                                            //纪录每个月分割线最靠下的y坐标
	int m=1;
	int count=0;
	int a[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
	if(leapyear(y)==1){
		a[2]=29;
	}
	for(m=1;m<13;m++){
		x1=wherex();
		y1=wherey(); 
		month(y,m);
		if(max<wherey())
		{
			max=wherey();  
		}                                                            //纪录每个月分割线最靠下的y坐标
		count++;
		if(count%4==0){
			x2=wherex();
			y2=wherey();
			gotoxy(1,max+3);                                    //为了排版,max是必要的,要不然容易挤掉其他月份的输出结果
		}else{
			gotoxy(x1+58,y1);                                    //将光标移到上个月开始的右边58字符的位置
		}
		
	}                                
//每四个月换一次行变成4*3的排列方式,自己也可以改成3*4或其他的
	
}

三、总结

                万年历想要做细,想要考虑的细节还真不少。这是我的大一作业,也是第一个真正意义上的程序,所以肯定有很多没必要的代码,很多不够简便的方法,希望各位大佬看到之后多多谅解。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐