星期算法
学习一下如何根据指定的年月日快速计算星期几,由于公历的规律性很强,将所有规律整理并不断简化就可以得到这种简便算法,有很多不同的变形算法,例如蔡勒公式,康威裁决日算法等,本文主要学习蔡勒公式。
公历
简要回顾一下公历:每年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
2weekday_str(2025, 4, 6)
# 'Sunday'
简单的理解
由于公历的置闰规律很明确,不考虑计算效率的前提下,算法的思路其实是非常清晰的:
- 找一个理想的基准日期,已知当天的星期,最好是星期天
- 计算当前日期与基准相差的天数
- 将相差的天数对 7 取模,就可以知道当前日期是星期几
由于2月的天数频繁变动,非常不好处理,最好从3月开始计算,将2月放在最后。
然后可以从这个直观的算法开始,不断寻找规律,简化运算,就可以得到优化后的算法了。