C语言笔记:与日期相关问题的集合

原文链接: http://c.biancheng.net/c/

问题一:n天后日期问题

问题描述:
已知一个日期以及任意整数n,求n天后的日期。

方法一

问题分析:
可通过求每一日的下一日实现,即每向后推移一日就求一次日期,总共需要循环n次求下一日,即可得到已知n天后的日期。故设计函数void NextDay(Date *pd)。此时还需要考虑3种情况:(1)、如果当前日期不是月末(即每月的最后一天),则第二天的日期只需要将当前日期的日子加1即可;(2)、当前日期是月末但月份不是12月,则第二天的日期则是下一个月的1月;(3)、当前日期是月末而且月份是12月,则下一天的日期就是下一年的1月1日。如何判断当前日期是否是所在年所在月的月末呢?还需要知道每个月的天数,每个月的天数还与平年与闰年有关。定义了一个二维数组monthdays[2][13],行标为0的存放平年的个月的天数,行标为1的存放闰年的每个月的天数(每个月的天数有一个记忆口诀:一,三,五,七,八,十,腊(也就是12月)是31天,除二月外剩下都是30天,二月只能28或29天)。且下标从a[1]开始存储。

实现代码:

/*
已知一个日期以及任意整数n,求n天后的日期 
*/ 
#include <stdio.h>
#include <time.h>
typedef struct Date
{
 int year;
 int month;
 int day;
} Date;
int IsLeap(int y);//判断y年份是否为闰年,是则返回1,否则返回0
void NextDay(Date *pd);//将pd所指向的日期变成下一天的日期
int monthdays[2][13]=
{
 {0,31,28,31,30,31,30,31,31,30,31,30,31},//1,3,5,7,8,10,腊(也就是12月)是31天
 {0,31,29,31,30,31,30,31,31,30,31,30,31}//下标从1开始,第一行为平年月份,第二行为闰年月份 
};
int main(void)
{
 Date today;//存放当前日期 
 int n;//存放当前日期向后推移的天数
 
 time_t now;
 struct tm *localtp;
 time(&now);
 localtp=localtime(&now);  
 printf("本地日期是:%d年%d月%d日\n",localtp->tm_year+1900,localtp->tm_mon+1,localtp->tm_mday);
 
 today.year=localtp->tm_year+1900;
 today.month=localtp->tm_mon+1;
 today.day=localtp->tm_mday;
 
 printf("请输入向后推移的天数:");
 scanf("%d",&n);
 for(int i=1;i<=n;i++)
 {
  NextDay(&today);//通过指针参数返回日期d的下一天日期 
 }
 printf("显示推移后的日期:%d-%02d-%02d\n",today.year,today.month,today.day) ;
 return 0;
}
int IsLeap(int y)
{
 return y % 400 == 0 || y % 4 == 0 && y % 100 != 0 ;//逻辑表达式返回值只有true or false转换为1或0 
}
void NextDay(Date *pd)
{
 int leap=0;//初始假设pd->year不是闰年
 if(IsLeap(pd->year)) 
 {
  leap=1;
 }
 if(pd->day<monthdays[leap][pd->month])//如果pd所指日期不是月末
 {
  pd->day++;//下一天日期仅修改日 
 } 
 else//pd所指日期是月末 
 {
  if(pd->month<12)//pd所指日期是月末 ,但不是年末,下一天日期为下月1日 
  {
   pd->month++;
   pd->day=1;
  }
  else//所指日期是月末 ,也是年末,则下一天日期变为下一年的1月1日 
  {
   pd->year++;
   pd->month=1;
   pd->day=1; 
  }
 }
}

运行结果:
在这里插入图片描述
此时n=90,故要调用函数NextDay()函数就要90次,当n(向后推移的天数越大时),运行效率越低。

方法二

问题分析:
方法二的大的思路就是先算得 当前日期所在年的天数 加上 推移后的天数所获得的总天数,设计了函数int Days(Date dd)来获得总天数,再将总天数一次性转换为对应的年月份,设计了函数void NextNDate(int days,Date *pd),该函数函数体相较方法一有较大改动,该方法运行效率比方法一高,因为不是日期每向后推移一次就做一次日期转换。

实现代码:

#include <stdio.h>
#include <time.h>
typedef struct Date
{
 int year;
 int month;
 int day;
}Date;
int IsLeap(int y);
int Days(Date dd);//计算日期dd是dd.year年的第几天
void NextNDate(int days,Date *pd);//计算pd->year年后days天的日期
int monthdays[2][13]=
{
 {0,31,28,31,30,31,30,31,31,30,31,30,31},//1,3,5,7,8,10,腊(也就是12月)是31天
 {0,31,29,31,30,31,30,31,31,30,31,30,31}//下标从1开始,第一行为平年月份,第二行为闰年月份 
}; 
int main(void)
{
 Date today;//存放当前日期 
 int n;//存放当前日期向后推移的天数
 
 time_t now;
 struct tm *localtp;
 time(&now);
 localtp=localtime(&now);  
 printf("本地日期是:%d年%d月%d日\n",localtp->tm_year+1900,localtp->tm_mon+1,localtp->tm_mday);
 
 today.year=localtp->tm_year+1900;
 today.month=localtp->tm_mon+1;
 today.day=localtp->tm_mday;
 
 printf("请输入向后推移的天数:");
 scanf("%d",&n);
 
 int sumdays=Days(today);//求today是today.year年的第几天 
 NextNDate(sumdays+n,&today);
 
 printf("显示推移后的日期:%d-%02d-%02d\n",today.year,today.month,today.day) ;
 return 0;
} 
int IsLeap(int y)
{
 return y % 400 == 0 || y % 4 == 0 && y % 100 != 0 ;
}
int Days(Date dd)
{
 int sum=0,leap=0;
 if(IsLeap(dd.year))
 {
  leap=1;
 }
 for(int i=1;i<dd.month;i++)//求1月至m-1月的总天数 
 {
  sum+=monthdays[leap][i];
 }
 sum+=dd.day;
 return sum;
}
void NextNDate(int days,Date *pd)
{
 int y,yeardays;//y存放当前日期所在年份,yeardays用于判断总天数是否大于当前日期所在年份的总天数 
 y=pd->year;
 yeardays=365;
 if(IsLeap(y))
 {
  yeardays=366;
 } 
 while(days>yeardays)//通过循环执行days减去y年的天数,计算所求日期的年份,若总天数不足一年则跳过该循环 
 {
  days-=yeardays;//不断减少 
  y++;//y用于最后的赋值
  
  yeardays=365;//重新判断一次自增后的y是平年还是闰年 
  if(IsLeap(y))
  {
   yeardays=366;
  }
 }
 int leap=0;
 if(IsLeap(y))
 {
  leap=1;
 }
 int m=1;//月份从1开始计算,m用于最后的赋值 
 while(days>monthdays[leap][m])//通过循环执行y(若有执行过上面的while循环,则y是更新后的值)年m月的天数,计算所求日期的月份 
 {
  days-=monthdays[leap][m];//不断减少 
  m++;
 }
 pd->year=y;
 pd->month=m;
 pd->day=days;
}

问题二:n天前的日期

问题描述:
已知某一日期以及n,求n天前的日期。

问题分析:
算法采用问题一的方法一思路,还是3种情况:(1)、如果当前日期不是每月的1日,则前一天日期只需要将当前日减去1;(2)、如果当前日期是该月的1日,但当前日期所在月份不是1月份,则前一天日期只需要将当前月减1,当前日赋值为前一月的月末值;(3)、如果当前日期是该月的1日,且当前日期所在月份是1月份(某年1月1日),则前一天日期为前一年的12月31日。

实现代码:

/*
已知一个日期以及任意整数n,求n天后的日期 
*/ 
#include <stdio.h>
#include <time.h>
typedef struct Date
{
 int year;
 int month;
 int day;
} Date;
int IsLeap(int y);//判断y年份是否为闰年,是则返回1,否则返回0
void YesterDay(Date *pd);//将pd所指向的日期变成前一天的日期
int monthdays[2][13]=
{
 {0,31,28,31,30,31,30,31,31,30,31,30,31},//1,3,5,7,8,10,腊(也就是12月)是31天
 {0,31,29,31,30,31,30,31,31,30,31,30,31}//下标从1开始,第一行为平年月份,第二行为闰年月份 
};
int main(void)
{
 Date today;//存放当前日期 
 int n;//存放当前日期向后推移的天数
 
 time_t now;
 struct tm *localtp;
 time(&now);
 localtp=localtime(&now);  
 printf("本地日期是:%d年%d月%d日\n",localtp->tm_year+1900,localtp->tm_mon+1,localtp->tm_mday);
 
 today.year=localtp->tm_year+1900;
 today.month=localtp->tm_mon+1;
 today.day=localtp->tm_mday;
 
 printf("请输入向前推移的天数:");
 scanf("%d",&n);
 for(int i=1;i<=n;i++)
 {
  YesterDay(&today);//通过指针参数返回日期d的下一天日期 
 }
 printf("显示向前推移后的日期:%d-%02d-%02d\n",today.year,today.month,today.day) ;
 return 0;
}
int IsLeap(int y)
{
 return y % 400 == 0 || y % 4 == 0 && y % 100 != 0 ;//逻辑表达式返回值只有true or false转换为1或0 
}
void YesterDay(Date *pd)
{
 int leap=0;//初始假设pd->year不是闰年
 if(IsLeap(pd->year)) 
 {
  leap=1;
 }
 if(pd->day!=1)//如果pd所指日期不是每月1号 
 {
  pd->day--;//日子减1则是前一天的日期 
 } 
 else//pd所指日期是每月1号 
 {
  if(pd->month>1)//但不是1月1号 
  {
   pd->month--;//则某月1日的前一天日期为前一月的月末 
   pd->day=monthdays[leap][pd->month];
  }
  else//pd所指日期是1月1号 
  {
   pd->year--;//则1月1日的前一天日期为前一年12月31日 
   pd->month=12;
   pd->day=31;
  }
 }
}

运行结果:
在这里插入图片描述

问题三:打印月历(对于给定的年月值,打印当月的月历表)

问题描述:
对于给定的年月值,打印当月的月历表。比如输入两个整数,第一个整数是year(1900<=year<=2099),第二个整数是月份month(1<=month<=12),中间用单个空格隔开,打印输出月历表。

问题分析:
月历(阴历)表由月历头和月历体构成。月历头第一行由输入的月month和年year构成,可定义一个字符型二维数组存放12个月的英文单词,即char months[13][15]。月历头的第二行是一个固定的字符串,直接用printf函数输出。月历主体分为两部分,先输出month月1日前的空白,再输出1号至月末,输出空白和日期时要记录个数,用对7求余思想,控制每行打印7个日期。

实现代码:

#include <stdio.h>
int IsLeap(int y);//判断y是否为闰年,是返回1,否则返回0
void WeekValue(int y);//求y年各个月1日的星期值,存到weekofmonth数组里面
void MonCalendar(int y,int m);//打印y年m月的日历表
int monthdays[13]={0,31,28,31,30,31,30,31,31,30,31,30,31}; //非闰年每个月的天数 
int weekofmonth[13];//存放某年各个月1日是星期几 
int main(void)
{
	int year,month;//存放要输入的年月值
	printf("请输入格式为\"年 月\":");
	scanf("%d %d",&year,&month);
	WeekValue(year);//用打表法求year年每月1日是星期几
	MonCalendar(year,month); 
	return 0;
}
int IsLeap(int y)
{
	return y % 400 == 0 || y % 4 == 0 && y % 100 != 0;
}
void WeekValue(int y)
{
	int days=0;//存放y年各个月1日的星期数是1900年到y年的总的天数对7求余所得
	for(int i=1900;i<y;i++)
	{
		if(IsLeap(i))
		{
			days+=366%7;//days+=2
		}
		else 
		{
			days+=365%7;//days+=1
		}
	}
	if(IsLeap(y)) 
	{
		monthdays[2]=29;
	}
	for(int i=1;i<=12;i++)
	{
		weekofmonth[i]=(days+1)%7;//days为前面1900年到y年1月1日的总的天数对7求余的总数,再加1对7求余,即为y年1月1日所在星期数
		days+=monthdays[i]; //i=2月1日所在星期数,需要在原来days的基础上再加上1月的天数再加1对7求余 ,得2月1日所在星期数,后面月份以此类推 
	} 
}
void MonCalendar(int y,int m)
{
	char months[13][15]={
		{" "},{"January"},{"February"},{"March"},{"April"},{"May"},{"June"},{"July"},{"August"},{"September"},{"October"},{"November"},{"December"}
	};//二维数组months存放各个月英文单词串 
	printf("\t%s(%d)\n",months[m],y);
	printf(" Sun Mon Tue Wed Thu Fri Sat\n");
	for(int i=0;i<weekofmonth[m];i++)//输出1日前的w各空白 
	{
		printf("    ");
	}
	if(IsLeap(y))
	{
		monthdays[2]=29;
	}
	else
	{
		monthdays[2]=28;
	}
	int k=weekofmonth[m];//k用于每输出7个日期输出一个换行 ,此处的
	初始化为1日前的空格数
	for(int i=1;i<=monthdays[m];i++)
	{
		printf("%4d",i);
		k++;//此处的k是空格数加上输出日期数
		if(k%7==0)//若k达到7个输出字符(包括空白字符),则输出一个换行符
		{
			printf("\n");
		}
	}
	printf("\n");
}

输出结果:
在这里插入图片描述
代码释疑:
此段程序的核心处理模块,要属void WeekValue(int y)函数,该函数的主要作用就是处理用户输入的year年(局部变量y)的1~12月1日这个日期所对应的是星期几。便于输出函数MonCalendar()打印每月1号前的空白符数量。

	for(int i=1900;i<y;i++)
	{
		if(IsLeap(i))
		{
			days+=366%7;//days+=2
		}
		else 
		{
			days+=365%7;//days+=1
		}
	}

以上代码块的作用即运算1900后n(假设的)年的1月1日取决于前一年的总天数(要么365或者366)%7,即剩余天数将占用后n年的星期数,假设一个例子(我们输入y=1901),1900年为平年,365%7余1,1将占用1901年1月1日的星期一,故1901的1月1日是星期二。
在这里插入图片描述

	for(int i=1;i<=12;i++)
	{
		weekofmonth[i]=(days+1)%7;//days为前面1900年到y年1月1日的总的天数对7求余的总数,再加1对7求余,即为y年1月1日所在星期数
		days+=monthdays[i]; //i=2月1日所在星期数,需要在原来days的基础上再加上1月的天数再加1对7求余 ,得2月1日所在星期数,后面月份以此类推 
	} 

以上代码块的作用就是操作整型数组weekofmonth[13],从下标为1开始存储,存储的是从1月到12月的1日所在星期数,用整数表示,0->Sunday,1->Monday。。。。

问题四:确定母亲节

问题描述:
母亲节是每年五月的第二个星期日,针对输入的年份,确定该年的哪一天是母亲节,已知1900年1月1日是星期一。

问题分析:
因为母亲节是每年五月的第二个星期日,所以我们所要求得就是给定年份的五月第二个星期日是几号。经过思考可以找到如下规律:
(如果某年的5月1日有如下情况)

某年5月1日是: 5月第一周的有前w天在4月 4月30号的星期 第二个星期日的日期为(两周14天)
星期一 5月第一周有w=0天在4月 星期日 14-w=14日
星期二 5月第一周有w=1天在4月 星期一 14-w=13日
星期三 5月第一周有w=2天在4月 星期二 14-w=12日
星期四 5月第一周有w=3天在4月 星期三 14-w=11日
星期五 5月第一周有w=4天在4月 星期四 14-w=10日
星期六 5月第一周有w=5天在4月 星期五 14-w=9日
星期日 5月第一周有w=6天在4月 星期六 14-w=8日

实现代码:

#include <stdio.h>
int IsLeap(int y);
int monthdays[2][13]={//关注1~4月份 
{0,31,28,31,30},
{0,31,29,31,30}
};
int main(void)
{
	int year;
	printf("请输入要求哪一年的母亲节日期:");
	scanf("%d",&year);
	int days=0;//存放year年4月30日是1900年后总共多少天
	for(int i=1900;i<year;i++)
	{
		if(IsLeap(i))
		{
			days+=366;
		}
		else
		{
			days+=365;
		}
	} 
	int leap=0;
	if(IsLeap(year))
	{
		leap=1;
	}
	for(int i=1;i<5;i++)
	{
		days+=monthdays[leap][i];
	}
	int dayofweek=days%7;//计算4月30号是星期几(w=dayofweek=0->Sunday,1->Monday...) 
	int motherday=(14-dayofweek);//计算母亲节是5月多少日
	printf("%d年5月%d日是母亲节\n",year,motherday);
	return 0;
} 
int IsLeap(int y)
{
	if(y % 400 == 0 || y % 4 == 0 && y % 100 != 0)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

输出结果:
在这里插入图片描述

问题五:已知某一年的第几周的第几天,求出这一天的具体日期

问题分析:
假设已知year年的第n周的第d天,先要求出year年第一周有多少天在(year-1)年,即求出(year-1)年12月31日是星期几(w),然后用公式7*(n-1)+da-w,求得year年第n周的第d天是year年的第几天,然后通过while循环执行year年m月天数,计算所求日期的月份。

实现代码:

/*
如果已知某一年第几周的第几天,求这一天的具体日期 
*/ 
#include <stdio.h>
int monthdays[2][13]=
{
 {0,31,28,31,30,31,30,31,31,30,31,30,31},//1,3,5,7,8,10,腊(也就是12月)是31天
 {0,31,29,31,30,31,30,31,31,30,31,30,31}//下标从1开始,第一行为平年月份,第二行为闰年月份 
};
int isLeap(int y);
int main(void)
{
 int year,n,d;//存放year年第n周第d天
 printf("请输入年份以及概念的第几周第几天(3个参数之间用空格隔开):");
 scanf("%d %d %d",&year,&n,&d); 
 int w=0;
 for(int i=1;i<year;i++)
 {
  if(isLeap(i))
  {
   w+=366%7;
  }
  else
  {
   w+=365%7;
  }
 }
 w%=7;//先求出前一年(year-1)12月31日是星期几(w),若是w>0则不是星期日,那么会占用本年的星期数, 
 int days=(n-1)*7+d-w;//year年第n周第d天的总天数。减去w是减去前一年占用的星期数 
 int leap=0;
 if(isLeap(year))
 {
  leap=1;
 }
 int m=1;//m记录月份
 while(days>monthdays[leap][m])//days总天数减去每月的天数, 月份m自增1, 当days天数不足月份m自增1所在的天数,days就是几号了 
 {
  days-=monthdays[leap][m];
  m++;
 }
 printf("日期是:%d年%d月%d日\n",year,m,days);
 return 0;
}
int isLeap(int y)
{
 return y % 400 == 0 || y % 4 == 0 && y % 100 != 0 ;//逻辑表达式返回值只有true or false转换为1或0 
}

输出结果:
在这里插入图片描述

问题六:黑色星期五

问题描述:
在西方,星期五和数字13都代表着坏运气,两个不幸的个体最后结合成超级不幸的一天,所以不管哪个月的13日恰逢星期五就叫做“黑色星期五”。对于输入的年份,如果有黑色星期五,则输出黑色星期五具体日期以及个数;如果没有,则输出“本年无黑色星期五”。

问题分析:根据前面的内容,如果知道某日期,那么就能计算出这个日期是星期几。所以以下代码中定义了一个二维数组days[2][13],分别计算year(平年or闰年)中各个月13日是1900年至year年的第几天,最后再判断是否是星期五

实现代码:

#include <stdio.h>
int monthdays[2][13]=
{
 {0,31,28,31,30,31,30,31,31,30,31,30,31},//1,3,5,7,8,10,腊(也就是12月)是31天
 {0,31,29,31,30,31,30,31,31,30,31,30,31}//下标从1开始,第一行为平年月份,第二行为闰年月份 
};
int IsLeap(int y);//判断y年份是否为闰年,是则返回1,否则返回0
int DaysF(int year);//判断输出year年中的黑色星期五日期 
int main(void)
{
 int year,ans=0;//存放年份以及该年黑色星期五的个数
 int yeardays[2]={365,366};//存放非闰年,闰年一年的天数
 printf("请输入年份:");
 scanf("%d",&year);
 DaysF(year);
 return 0; 
} 
int IsLeap(int y)
{
 return y % 400 == 0 || y % 4 == 0 && y % 100 != 0 ;//逻辑表达式返回值只有true or false转换为1或0 
}
int DaysF(int year)
{
 int days=0;
 if(year<1900)
    {
       printf("必须大于1900年\n");
       return 1;
    }
    for(int i=1900;i<year;i++) //计算1900年到year年的总天数  
 {
  if(IsLeap(i))//闰年       
  {        
   days+=366;       
  }       
  else       
  {            
   days+=365;       
  }    
 }
 days+=13;//计算1900年到year年1月13日的总天数  
 int leap=0,i;
    if(IsLeap(year))
    {
        leap=1;
 }
    for(int month=1;month<=12;month++)
    {
       if(days%7==5)
       {
          printf("%d/%02d/13 是黑色星期五.\n",year,month);
       }
       //到下月13号的总天数
     days+=monthdays[leap][month];
    }
    return 0;
} 

输出结果:
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42124234/article/details/101997333