在许多程序设计竞赛中,常常会考察到日期问题。在遇到日期问题时,做题之前应先想清楚以下五个问题:
- 今天是周几?
- 闰年的计算。
- 今天是什么日子,过多少天后是什么日子?
- 今天是什么日子,过多少天前是什么日子?
- 日期的一天天遍历。
想明白这五个问题,关于日期问题的题目也就不在话下了。于是我们就上述五点介绍两种求解日期问题的两种方法:
如何算今天是周几呢?
方法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即可。
于是,我们就解决了一开始我们提出的问题:“今天是周几”。通过这个问题的解决,我们开头所讲的其他四个问题也在不知不觉中迎刃而解了。
未完待续……