日期问题(C语言实现)

在许多程序设计竞赛中,常常会考察到日期问题。在遇到日期问题时,做题之前应先想清楚以下五个问题:

  1. 今天是周几?
  2. 闰年的计算。
  3. 今天是什么日子,过多少天后是什么日子?
  4. 今天是什么日子,过多少天前是什么日子?
  5. 日期的一天天遍历。

想明白这五个问题,关于日期问题的题目也就不在话下了。于是我们就上述五点介绍两种求解日期问题的两种方法:

如何算今天是周几呢?

方法1:蔡基姆拉尔森计算公式:

w = (d+2*m + 3*(m+1)/5 + y + y/4 - y/100 + y/400) % 7. 

假设星期为w,年份为y,月份为m,日期为d;然后把计算出来的w加上1就是真正的星期几了。

注意每年的1,2月要当成上一年13,14月计算,上述的除法均为整除。

于是,我们就可以利用蔡基姆拉尔森计算公式,写出这样一段代码:

#include<stdio.h>
//w = (d+2*m + 3*(m+1)/5 + y + y/4 - y/100 + y/400) % 7.
//注意每年的1,2月要当成上一年13,14月计算,上述的除法均为整除。
int WeekCalc(int y,int m,int d)
{
	int w;
	if(m==1 || m==2)
	{
		m = m + 12;
		y = y - 1;
	}
	w = (d+2*m + 3*(m+1)/5 + y + y/4 - y/100 + y/400) % 7 + 1;
	return w;
}

int main()
{
	
	int w;
	int year,month,day;
	scanf("%d-%d-%d",&year,&month,&day);
	w = WeekCalc(year,month,day);
	printf("%d",w);
	return 0;
}

上面的代码其实很简单,就是将上面加粗文字用C语言“翻译”一遍,我们只要严格遵守公式和注意事项就能得出。因此,我们应牢记蔡基姆拉尔森计算公式。

笔者写到这里的时候,正是2021年8月20日,我们就来算下这一天是星期几:

 在这里,尤其要注意“每年的1,2月要当成上一年13,14月计算,上述的除法均为整除”这一句话,于是我们必须对1,2月的情况进行处理。

另外,还要注意主函数和WeekCale中的“w”并不是一回事,两个“w”是不同的,要注意分辨。


还有没有其他的办法呢?

法2:我们还可以从具体的某一天开始计算。我们举一个例子,以讨论这种方法的使用。

例1:大数学家高斯有个好习惯,无论如何都要记日记。

他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替。比如:4210。

后来人们知道那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人,日子又过去一天还有多少时光可以用于浪费呢?

高斯出生于 1777 年 4 月 30 日。

在高斯发现的一个重要定理的日记上标注着 5343。因此可算出那天是 1791 年 12 月 15 日。

高斯获得博士学位的那天日记上标着 8113,请你算出高斯获得博士学位的年月日,。

提交答案的格式是 yyyy-mm-dd,例如 1980-03-21。

这就是典型的“今天是什么日子,过多少天是什么日子?”的问题。

我们先建一个数组int month[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31},用以储存每月的天数。我们知道,一年有12个月,但我们所建的数组中一共有13个元素,这是因为我们将数组的第一个元素设置为month[0] = 0。

这看似奇怪的设置其实是为了遵从我们日常的使用习惯,month[1]就是1月的天数,month[2]就是2月的天数,使其能够更好地被人们所接受(因此这段代码也可以写成:int month[12] = {31,28,31,30,31,30,31,31,30,31,30,31}):

由于这种方法是某一天开始计算,我们就将y(year),m(month),d(day),w(week)的初值置为确切的某一天(在这里,我们用的是高斯出生的日期,也就是1777年4月30日)。同时,我们定义一个计数器iCount(我们为了验证程序是否正确,算的是高斯出生后的第5343天):

#include<stdio.h>

int month[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};

int main()
{
	int iCount;
	int y = 1777;
	int m = 4;
	int d = 30;
	for(iCount=1;iCount<5343;iCount++)
	{
		
	}
	printf("%d-%d-%d",y,m,d);
	return 0;
}

我们看到,计数器iCount在for循环中每次增加1,我们也可以让d在每次循环中增加1(d++)。

当d增加到一定时候,也就是d大于这个月的天数的时候,我们便要做出一定的反应,也就是这个月算完了,接下来要从下个月1日开始算(d = 1;m++)。

随着d的增加,带动m的增加,总有这一年算完的时候(也就是12月31日),如果再加一天,我们便要从明年1月1日开始算(m = 1, y++):

#include<stdio.h>

int month[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};

int main()
{
	int iCount;
	int y = 1777;
	int m = 4;
	int d = 30;
	for(iCount=1;iCount<5343;iCount++)
	{
		d++;
		if(d>month[m])
		{
			d = 1;
			m++;
			if(m>12)
			{
				m = 1;
				y++;
			}
		}	
	}
	printf("%d-%d-%d",y,m,d);
	return 0;
}

接下来,我们又会遇到一个问题,如果是闰年该怎么办?

闰年:能被4整除但不能被100整除,或能被400整除的年份。

 由此,我们便能知道如何计算闰年

#include<stdio.h>

bool IsLeapyear(int year)
{
	if(year%400==0 || (year%4==0 && year%100 != 0))
		return true;
	else
		return false;
}

int month[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};

int main()
{
	int iCount;
	int y = 1777;
	int m = 4;
	int d = 30;
	for(iCount=1;iCount<5343;iCount++)
	{
		d++;
		if(d>month[m])
		{
			d = 1;
			m++;
			if(m>12)
			{
				m = 1;
				y++;
			}
		}	
	}
	printf("%d-%d-%d",y,m,d);
	return 0;
}

有了计算闰年的方法后,我们让程序一边使d增加,一边判断闰年。

注意:我们判断闰年,最根本的原因是闰年与非闰年的2月的天数不同。如果是闰年,2月有29天;如果不是闰年,2月有28天。因此,我们要将2月份的天数单独处理。

#include<stdio.h>

bool IsLeapyear(int year)
{
	if(year%400==0 || (year%4==0 && year%100 != 0))
		return true;
	else
		return false;
}

int month[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};

int main()
{
	int iCount;
	int y = 1777;
	int m = 4;
	int d = 30;
	for(iCount=1;iCount<5343;iCount++)
	{
		d++;
        if(IsLeapyear(y))
			month[2] = 29;
		else
			month[2] = 28;
		if(d>month[m])
		{
			d = 1;
			m++;
			if(m>12)
			{
				m = 1;
				y++;
			}
		}	
	}
	printf("%d-%d-%d",y,m,d);
	return 0;
}

不过,上面这段代码的效率很低,d每加一次,就要判断一次该年是不是闰年,这样做完全没有必要。

我们知道,在同一年内,d++对年份y是没有改变的,于是在这期间所做的一切闰年判断的结果一定是相同的。只有到12月31日,d再增加1,就变成了下一年的1月1日,在这时y才发生改变。我们仅在年份更迭的时候进行闰年判断,是完全可行的:

#include<stdio.h>

bool IsLeapyear(int year)
{
	if(year%400==0 || (year%4==0 && year%100 != 0))
		return true;
	else
		return false;
}

int month[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};

int main()
{
	int iCount;
	int y = 1777;
	int m = 4;
	int d = 30;
	for(iCount=1;iCount<5343;iCount++)
	{
		d++;
		if(d>month[m])
		{
			d = 1;
			m++;
			if(m>12)
			{
				m = 1;
				y++;
				if(IsLeapyear(y))
					month[2] = 29;
				else
					month[2] = 28;
			}
		}	
	}
	printf("%d-%d-%d",y,m,d);
	return 0;
}

这样,我们的程序就完成了。

结果完全一致!

接下来,做个简单的替换,将“5343”改为“8113”,再次编译运行程序:

#include<stdio.h>

bool IsLeapyear(int year)
{
	if(year%400==0 || (year%4==0 && year%100 != 0))
		return true;
	else
		return false;
}

int month[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};

int main()
{
	int iCount = 0;
	int y = 1777;
	int m = 4;
	int d = 30;
	for(iCount=1;iCount<8113;iCount++)
	{
		d++;
		if(d>month[m])
		{
			d = 1;
			m++;
			if(m>12)
			{
				m = 1;
				y++;
				if(IsLeapyear(y))
					month[2] = 29;
				else
					month[2] = 28;
			}
		}	
	}
	printf("%d-%d-%d",y,m,d);
	return 0;
}

 这道题我们就完成了。


上面的题目是日期加,那么遇到日期减的问题,我们又该怎么解决呢?

我们只需要对上一题的代码稍稍修改(笔者写到这里的时候,正是2021年8月20日,于是计算了一下1天前的日期):

#include<stdio.h>

bool IsLeapyear(int year)
{
	if(year%400==0 || (year%4==0 && year%100 != 0))
		return true;
	else
		return false;
}

int month[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};

int main()
{
	int iCount = 0;
	int y = 2021;
	int m = 8;
	int d = 20;
	for(iCount=1;iCount<2;iCount++)
	{
		d--;
		if(d<=0)
		{
			m--;
			if(m<=0)
			{
				y--;
				m==12;
				if(IsLeapyear(y))
					month[2] = 29;
				else
					month[2] = 28;
			}
			d = month[m];
		}	
	}
	printf("%d-%d-%d",y,m,d);
	return 0;
}

 想法与上一题相差不大,这里就只给出代码供读者比较,相关细节不再赘述。


我们怎么使用这种方法求某一天是星期几呢?

我们再用2021年8月20日举例:

#include<stdio.h>

bool IsLeapyear(int year)
{
	if(year%400==0 || (year%4==0 && year%100 != 0))
		return true;
	else
		return false;
}

int month[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};

int main()
{
	int iCount = 0;
	int y = 1;
	int m = 1;
	int d = 1;
	int w = 1;
	while(y!=2021 && m!=8 && d!=20)
	{
		w++;
		d++;
		if(d>month[m])
		{
			d = 1;
			m++;
			if(m>12)
			{
				m = 1;
				y++;
				if(IsLeapyear(y))
					month[2] = 29;
				else
					month[2] = 28;
			}
		}	
	}
	printf("%d",(w-1)%7);
	return 0;
}

在这里我们要注意的是:while(y!=2021 && m!=8 && d!=20),这表示循环到2021年8月20日为止。

另外,我们发现:最后输出的是(w-1)%7的值,而不是w%7。这是因为第一次进循环的时候是1年1月1日,然后我们进循环之后又立即给w加上了个1,于是导致w多加了个1。我们在输出时减掉1即可。

于是,我们就解决了一开始我们提出的问题:“今天是周几”。通过这个问题的解决,我们开头所讲的其他四个问题也在不知不觉中迎刃而解了。


未完待续……

  • 9
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值