日期问题(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
//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天):

#includeint 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++):

#includeint 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整除的年份。

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

#includebool IsLeapyear(int year)
{if(year%400==0 || (year%4==0 && year%100 != 0))return true;elsereturn 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月份的天数单独处理。

#includebool IsLeapyear(int year)
{if(year%400==0 || (year%4==0 && year%100 != 0))return true;elsereturn 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;elsemonth[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才发生改变。我们仅在年份更迭的时候进行闰年判断,是完全可行的:

#includebool IsLeapyear(int year)
{if(year%400==0 || (year%4==0 && year%100 != 0))return true;elsereturn 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;elsemonth[2] = 28;}}	}printf("%d-%d-%d",y,m,d);return 0;
}

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

结果完全一致!

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

#includebool IsLeapyear(int year)
{if(year%400==0 || (year%4==0 && year%100 != 0))return true;elsereturn 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;elsemonth[2] = 28;}}	}printf("%d-%d-%d",y,m,d);return 0;
}

 这道题我们就完成了。


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

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

#includebool IsLeapyear(int year)
{if(year%400==0 || (year%4==0 && year%100 != 0))return true;elsereturn 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;elsemonth[2] = 28;}d = month[m];}	}printf("%d-%d-%d",y,m,d);return 0;
}

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


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

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

#includebool IsLeapyear(int year)
{if(year%400==0 || (year%4==0 && year%100 != 0))return true;elsereturn 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;elsemonth[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即可。

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


未完待续……


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部