星期算法
学习一下如何根据指定的年月日快速计算星期几,由于公历的规律性很强,将所有规律整理并不断简化就可以得到这种简便算法,有很多不同的变形算法,例如蔡勒公式,康威裁决日算法等,本文主要学习蔡勒公式。
公历
简要回顾一下公历:每年12个月,每个月可能有28、29、30、31天。
区分普通的年和闰年,非闰年有365天,闰年有366天,年份满足以下情况时为闰年:
- 年份除以100的余数不为0,除以4的余数为0
- 年份除以100的余数为0,并且除以400的余数为0
非闰年的12个月的天数依次为:
- 28天:2月
- 30天:4月、6月、9月、11月
- 31天:1月、3月、5月、7月、8月、10月、12月
闰年的12个月的天数依次为:
- 29天:2月
- 30天:4月、6月、9月、11月
- 31天:1月、3月、5月、7月、8月、10月、12月
由于公历于1582年10月正式建立,在这段时间以及更早的年份可能存在日期修正,细节比较复杂,不作讨论。
蔡勒公式
公式如下
$$
w = \left(
y + \left[ \frac{y}4 \right]
+ \left[ \frac{c}4 \right]
- 2 c
+ \left[ \frac{13(m+1)}{5} \right]
+ d - 1
\right) \mod 7
$$
其中:
- $w$: 计算得到的星期几,取值范围是 $0 - 6$,对应星期日到星期六
- $c$: 年份除以100的商,也就是年份的前两位数
- $y$: 年份除以100的余数,也就是年份的后两位数
- $m$:月份,注意取值范围为 $3 - 14$,也就是说某年的 1 月和 2 月被视作前一年的 13 月和 14 月。
- $d$:日
例如:2006年4月4日,代入公式计算
$$
\begin{aligned}
w ={}& \left(
y + \left[ \frac{y}4 \right]
+ \left[ \frac{c}4 \right]
- 2 c
+ \left[ \frac{13(m+1)}{5} \right]
+ d - 1
\right) \mod 7 \
={}& \left(
6 + \left[ \frac{6}4 \right]
+ \left[ \frac{20}4 \right]
- 2 * 20
+ \left[ \frac{13 * (4+1)}{5} \right]
+ 4 - 1
\right) \mod 7 \
={} & (-12) \mod 7 \
={} & 2
\end{aligned}
$$
因此是星期二。
例如:2025年4月6日,代入公式计算
$$
\begin{aligned}
w ={}& \left(
y + \left[ \frac{y}4 \right]
+ \left[ \frac{c}4 \right]
- 2 c
+ \left[ \frac{13(m+1)}{5} \right]
+ d - 1
\right) \mod 7 \
={}& \left(
25 + \left[ \frac{25}4 \right]
+ \left[ \frac{20}4 \right]
- 2 * 20
+ \left[ \frac{13 * (4+1)}{5} \right]
+ 6 - 1
\right) \mod 7 \
={} & 14 \mod 7 \
={} & 0
\end{aligned}
$$
因此是星期日。
代码实现
在编程实现时,需要注意使用的整除对负数的处理,因为计算结果可能是负数,模7运算需要保证结果为0-6。
具体代码非常简单,几行代码即可
1 | def weekday_str(year, month, day): |
例如
1 | weekday_str(2025, 4, 6) |
简单的理解
由于公历的置闰规律很明确,不考虑计算效率的前提下,算法的思路其实是非常清晰的:
- 找一个理想的基准日期,已知当天的星期,最好是星期天
- 计算当前日期与基准相差的天数
- 将相差的天数对 7 取模,就可以知道当前日期是星期几
由于2月的天数频繁变动,非常不好处理,最好从3月开始计算,将2月放在最后。
然后可以从这个直观的算法开始,不断寻找规律,简化运算,就可以得到优化后的算法了。
