0. 本文的初衷及蔡勒公式的用处
前一段时间,我在准备北邮计算机考研复试的时候,做了几道与日期计算相关的题目,在这个过程中我接触到了蔡勒公式。先简单的介绍一下蔡勒公式是干什么用的。
我们有时候会遇到这样的问题:看到一个日期想知道这一天是星期几,甚至看到一个历史日期或纪念日,我们想快速的知道这一天是星期几。对于这个问题,如果用编程的方式,应该怎么实现呢?你可能已经有思路了,比如你知道某个日期是星期几,把这个日期作为原点,然后计算目标日期和这个原点之间相差多少天,再除以 7 求余数,最后通过余数判断目标日期的星期数。通过这样的过程,你确实可以得到正确的结果,但这不够快速也不够优雅。对于这个问题,如果你懂得蔡勒公式,那就变得异常简单了,你只需要将年月日等数据代入公式,然后计算出结果,这一结果就是目标日期对应的星期数。
当我知道蔡勒公式之后,我觉得它非常有趣也很酷,所以我不仅希望掌握公式的使用,也希望可以掌握公式背后的推导过程。然而,当我在网上搜索相关的文章时,我发现几乎所有向我展示的博客(从零几年到最近的 19 年)大多是转载、复制于这篇文章:
旧作:不查日历怎么知道任何一天是星期几(葛民勤)_刘夙_新浪博客blog.sina.com.cn星期制度是一种有古老传统的制度。据说因为《圣经·创世纪》中规定上帝用了六天时间创世纪,第七天休息,所以人们也就以七天为一个周期来安排自己的工作和生活,而星期日是休息日……
这篇文章质量很不错,讲解过程自然流畅,但是在一些细节上存在错误,有些推导步骤让人感到困惑。因此,当我掌握蔡勒公式后,很希望可以将我的理解输出出来,让想要学习蔡勒公式推导过程的人看到一些新的材料。好了,废话少说,我们开始吧。
1. 蔡勒公式的形式
如果你对公式的推导过程不感兴趣,只是希望使用蔡勒公式,那么只看此小节即可。蔡勒公式的形式如下:
其中:
- W 是星期数。
- c 是世纪数减一,也就是年份的前两位。
- y 是年份的后两位。
- m 是月份。m 的取值范围是 3 至 14,因为某年的 1、2 月要看作上一年的 13、14月,比如 2019 年的 1 月 1 日要看作 2018 年的 13 月 1 日来计算。
- d 是日数。
- [] 是取整运算。
- mod 是求余运算。
注意:这些符号在后面的推导中还会使用。举一个实际的计算例子:计算 1994 年 12 月 13 日是星期几。显然 c = 19,y = 94,m = 12,d = 13,带入公式:
由此可得 1994 年 12 月 13 日是星期二,通过查询日历可以验证正确性。
最后关于蔡勒公式,还需要做两点补充说明:
- 在计算机编程中,W 的计算结果有可能是负数。我们需要注意,数学中的求余运算和编程中的求余运算不是完全相同的,数学上余数不能是负数,而编程中余数可以是负数。因此,在计算机中 W 是负数时,我们需要进行修正。修正方法十分简单:让 W 加上一个足够大的 7 的整数倍,使其成为正数,得到的结果再对 7 取余即可。比如 -15,我可以让其加上 70,得到 55,再除以 7 余 6,通过余数可知这一天是星期六。
- 此公式只适用于格里高利历(也就是现在的公历)。关于历法的问题,大家有兴趣可以自行查阅。
下面是公式的推导。
2. 推导过程
推导蔡勒公式之前,我们先思考一下,如果我们不知道这一公式,我们如何将一个日期转化为星期数呢?
我们可能会很自然地想到:先找到一个知道是星期几的日子,把这个日期作为“原点”,然后计算目标日期和这个原点相差几天,将相差的天数对 7 取余,再根据余数判断星期数。举一个实际例子,比如我们知道 2019 年 5 月 1 日是星期三,把这一天当作原点,现在我们想知道 2019 年 5 月 17 日是星期几,显然这两个日期之间相差 16 天,用 16 除 7 余 2,因为原点是星期三,加上作为偏移量的余数 2,可知 2019 年 5 月 17 日是星期五。
从这个思路出发,经过优化扩展,我们就可以得到神奇的蔡勒公式了。首先,如果我们仔细观察一下可以发现,这个思路中比较麻烦的是计算相差天数(设为
-
:从原点到原点所在年份末尾的天数。
-
:原点所在年份和目标日期所在年份之间所有年份的天数和。
-
:目标日期所在年份的第一天到目标日期的天数。
显然,
我们再去观察上述思路中的实际例子,可以发现,因为原点是星期三,所以得到余数后,我们需要加上 3 才是正确的星期数。这启示我们可以把原点选定在星期日,这样算出来的余数是几就是星期几(余数 0 代表星期日)。
经过这样的分析。我们希望可以优化原点的日期,使其满足下面两个条件:
- 是某一年的 12 月 31 日。
- 是星期日。
我们按照现在使用的公历的规则逆推,可以发现公元元年的前一年的 12 月 31 日恰好是星期日,满足我们想要的两个条件,是一个完美的原点。
现在假设目标日期是 y 年 m 月 d 日,我们已经可以很容易的计算
简单的解释一下。因为一年最少有 365 天,所以
现在,我们需要得到
仔细观察,可以发现 Excess 从 3 月份开始是 3、2、3、2、3 的循环,因此,当
我们将
我们可以发现,当
可知相差的常数为 7。由此可得,当
这里多说两句,实际上,的函数形式是不唯一的,使用其他的构造方法,可以得到形式不同的,只要符合要求即可。
进一步,我们可以得到
其中,平年时,
现在,我们已经得到了
注意!这个公式离正确形式还差一小步。因为在当前的公式中,每年的 1 月和 2 月被当作上一年的 13 月和 14 月计算,因此当前公式中计算闰日的部分(
计算出 D 的值后,对 7 取模即可得到星期数。
根据同余定理,D 除以 7 所得的余数等于 D 式的各项分别除以 7 所得余数之和(余数之和大于等于 7 时,再对 7 取余),因此可以消去 D 式中能被 7 整除的项,进行化简:
简单说明一下:
显然,结果中的第一项是 7 的倍数,除以 7 余数为 0,因此
公式(2)还不是最简练的形式,我们还可以对年份进行处理。我们现在用公式(2)计算出每个世纪第一年 3 月 1 日的星期数,得到如下结果:
可以发现,每隔 4 个世纪,星期数就会重复一次。因为在数学上,-2 和 5 除以 7 的余数相同,所以我们不妨把这个重复序列中的 5 改为 -2。这样,4、2、0、-2 恰好构成了一个等差数列。利用等差公式,我们可以得到计算每个世纪第一年的 3 月 1 日星期数的公式:
其中,
利用同余定理,经过变换得到:
其中,
注意!现在的计算公式只能适用于每个世纪的第一年。但是,有个这个公式,再加上计算一个世纪中闰日的部分,我们就可以很容易地得到计算这个世纪其他年份的日期的星期数的公式了。令 c 等于世纪数减一,y 等于世纪中的年份数(如 1994 年,则 c = 19,y = 94)。因为一个世纪中只有一百年,所以不用考虑“四百年又闰”的情况;因为每百年,即每个世纪最后一年的 y = 00,而
在公式(6)中,
最后,我们来把公式中的取模运算改成四则运算。设商为
又因为,
可得:
代入公式(6)可得:
至此,我们就得到了蔡勒公式的最终形式。