蔡勒(Zeller)公式及其推导:快速将任意日期转换为星期数

0. 本文的初衷及蔡勒公式的用处

前一段时间,我在准备北邮计算机考研复试的时候,做了几道与日期计算相关的题目,在这个过程中我接触到了蔡勒公式。先简单的介绍一下蔡勒公式是干什么用的。

我们有时候会遇到这样的问题:看到一个日期想知道这一天是星期几,甚至看到一个历史日期或纪念日,我们想快速的知道这一天是星期几。对于这个问题,如果用编程的方式,应该怎么实现呢?你可能已经有思路了,比如你知道某个日期是星期几,把这个日期作为原点,然后计算目标日期和这个原点之间相差多少天,再除以 7 求余数,最后通过余数判断目标日期的星期数。通过这样的过程,你确实可以得到正确的结果,但这不够快速也不够优雅。对于这个问题,如果你懂得蔡勒公式,那就变得异常简单了,你只需要将年月日等数据代入公式,然后计算出结果,这一结果就是目标日期对应的星期数。

当我知道蔡勒公式之后,我觉得它非常有趣也很酷,所以我不仅希望掌握公式的使用,也希望可以掌握公式背后的推导过程。然而,当我在网上搜索相关的文章时,我发现几乎所有向我展示的博客(从零几年到最近的 19 年)大多是转载、复制于这篇文章(链接):

星期制度是一种有古老传统的制度。据说因为《圣经·创世纪》中规定上帝用了六天时间创世纪,第七天休息,所以人们也就以七天为一个周期来安排自己的工作和生活,而星期日是休息日……

这篇文章质量很不错,讲解过程自然流畅,但是在一些细节上存在错误,有些推导步骤让人感到困惑。因此,当我掌握蔡勒公式后,很希望可以将我的理解输出出来,让想要学习蔡勒公式推导过程的人看到一些新的材料。好了,废话少说,我们开始吧。

1. 蔡勒公式的形式

如果你对公式的推导过程不感兴趣,只是希望使用蔡勒公式,那么只看此小节即可。蔡勒公式的形式如下:
D = [ c 4 ] − 2 c + y + [ y 4 ] + [ 13 ( m + 1 ) 5 ] + d − 1 W = D   m o d   7 \begin{aligned} D &= \left[ \frac{c}{4} \right] - 2c + y + \left[ \frac{y}{4} \right] + \left[ \frac{13(m+1)}{5} \right] + d - 1 \\[2ex] W &= D \bmod 7 \end{aligned} DW=[4c]2c+y+[4y]+[513(m+1)]+d1=Dmod7

其中:

  • 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,带入公式:
D = [ 19 4 ] − 2 × 19 + 94 + [ 94 4 ] + [ 13 × ( 12 + 1 ) 5 ] + 13 − 1 = 4 − 38 + 94 + 23 + 33 + 13 − 1 = 128 W = 128   m o d   7 = 2 \begin{aligned} D &= \left[ \frac{19}{4} \right] - 2 \times 19 + 94 + \left[ \frac{94}{4} \right] + \left[ \frac{13 \times (12 + 1)}{5} \right] + 13 - 1 \\[2ex] &= 4 - 38 + 94 + 23 + 33 + 13 - 1 \\[2ex] &= 128 \\[2ex] W &= 128 \bmod 7 = 2 \end{aligned} DW=[419]2×19+94+[494]+[513×(12+1)]+131=438+94+23+33+131=128=128mod7=2
由此可得 1994 年 12 月 13 日是星期二,通过查询日历可以验证正确性。

最后关于蔡勒公式,还需要做两点补充说明:

  1. 在计算机编程中,W 的计算结果有可能是负数。我们需要注意,数学中的求余运算和编程中的求余运算不是完全相同的,数学上余数不能是负数,而编程中余数可以是负数。因此,在计算机中 W 是负数时,我们需要进行修正。修正方法十分简单:让 W 加上一个足够大的 7 的整数倍,使其成为正数,得到的结果再对 7 取余即可。比如 -15,我可以让其加上 70,得到 55,再除以 7 余 6,通过余数可知这一天是星期六。
  2. 此公式只适用于格里高利历(也就是现在的公历)。关于历法的问题,大家有兴趣可以自行查阅。

下面是公式的推导。

2. 推导过程

推导蔡勒公式之前,我们先思考一下,如果我们不知道这一公式,我们如何将一个日期转化为星期数呢?

我们可能会很自然地想到:先找到一个知道是星期几的日子,把这个日期作为“原点”,然后计算目标日期和这个原点相差几天,将相差的天数对 7 取余,再根据余数判断星期数。举一个实际例子,比如我们知道 2019 年 5 月 1 日是星期三,把这一天当作原点,现在我们想知道 2019 年 5 月 17 日是星期几,显然这两个日期之间相差 16 天,用 16 除 7 余 2,因为原点是星期三,加上作为偏移量的余数 2,可知 2019 年 5 月 17 日是星期五。

从这个思路出发,经过优化扩展,我们就可以得到神奇的蔡勒公式了。首先,如果我们仔细观察一下可以发现,这个思路中比较麻烦的是计算相差天数(设为 D D D),我们可以把 D D D 的计算过程分解成三部分:

  1. D 1 D_1 D1:从原点到原点所在年份末尾的天数。
  2. D 2 D_2 D2:原点所在年份和目标日期所在年份之间所有年份的天数和。
  3. D 3 D_3 D3:目标日期所在年份的第一天到目标日期的天数。

在这里插入图片描述

显然, D = D 1 + D 2 + D 3 D = D_1 + D_2 + D_3 D=D1+D2+D3。如果我们把原点选择在某一年的 12 月 31 日,那么就可以省去 D 1 D_1 D1 的计算了,因为原点恰好就是所在年份的最后一天。现在, D = D 2 + D 3 D = D_2 + D_3 D=D2+D3

我们再去观察上述思路中的实际例子,可以发现,因为原点是星期三,所以得到余数后,我们需要加上 3 才是正确的星期数。这启示我们可以把原点选定在星期日,这样算出来的余数是几就是星期几(余数 0 代表星期日)。

经过这样的分析。我们希望可以优化原点的日期,使其满足下面两个条件:

  1. 是某一年的 12 月 31 日。
  2. 是星期日。

我们按照现在使用的公历的规则逆推,可以发现公元元年的前一年的 12 月 31 日恰好是星期日,满足我们想要的两个条件,是一个完美的原点。

现在假设目标日期是 y 年 m 月 d 日,我们已经可以很容易的计算 D 2 D_2 D2 了:
D 2 = ( y − 1 ) × 365 + [ y − 1 4 ] − [ y − 1 100 ] + [ y − 1 400 ] D_2 = (y-1) \times 365 + \left[ \frac{y-1}{4} \right] - \left[ \frac{y-1}{100} \right] + \left[ \frac{y-1}{400} \right] D2=(y1)×365+[4y1][100y1]+[400y1]
简单的解释一下。因为一年最少有 365 天,所以 D 2 D_2 D2 至少是 ( y − 1 ) × 365 (y-1) \times 365 (y1)×365。此外,因为闰年比平年多一天,我们还需要加上这些年份中闰年的数量。按照闰年的规则:每 4 年一闰,但每 100 年不闰,每 400 年又闰。可知闰年的数量为 [ y − 1 4 ] − [ y − 1 100 ] + [ y − 1 400 ] \left[ \frac{y-1}{4} \right] - \left[ \frac{y-1}{100} \right] + \left[ \frac{y-1}{400} \right] [4y1][

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值