一、先约定三件事
- 含不含首日:从 1 号到 3 号,是 2 天还是 3 天?
- 含不含末日:截止日期当天算不算?
- 按自然日还是工作日:周末与法定节假日是否跳过?
不约定清楚,同一个问题会有多个「正确」答案。合同、工期、倒计时场景尤其要写明。
二、相差天数
最稳的算法是转成「时间戳」再相减:
from datetime import date
(date(2026, 12, 31) - date(2026, 1, 1)).days # 364
注意结果是 364 而不是 365——因为 1 月 1 日到 12 月 31 日之间隔了 364 天,不含首日。若业务上要「含首尾」,加 1。
另一种常见错误是用「月份差 × 30」估算,跨月长度不同会累积误差。
三、加 N 天
from datetime import date, timedelta
date(2026, 1, 31) + timedelta(days=30) # 2026-03-02
timedelta 会自动处理月末、闰年、跨年。手写「日 + 30,超过 31 就减 31、月加 1」这种逻辑,在 2 月和小月必然出错。
JavaScript 里用 Date 也有坑:
const d = new Date('2026-01-31');
d.setDate(d.getDate() + 30); // 自动进位,正确
但 new Date('2026-01-31') 会按 UTC 解析,在东八区可能显示成 1 月 31 日 08:00 或前一天的本地时间。建议显式构造:new Date(2026, 0, 31)(月份从 0 开始)。
四、工作日计算
需要跳过周末(以及法定节假日、调休):
- 仅跳周末:循环加天数,判断
weekday()是否为 5/6(周一为 0)。 - 含法定节假日:需要维护一张节假日表(每年国务院公布),不能只靠周末规则。
- 调休:某些周末要上班,同样需要表。
这也是「工作日计算器」必须每年更新数据的原因——纯算法算不出调休。
粗略估算可用 自然日 × 5/7,但跨长假误差很大。
五、闰年规则
能被 4 整除,且(不能被 100 整除 或 能被 400 整除)
- 2024 是闰年(能被 4 整除,不能被 100)。
- 1900 不是(能被 100 但不能被 400)。
- 2000 是(能被 400)。
2 月 29 日出生的人,非闰年通常按 2 月 28 日或 3 月 1 日过生日,法律与业务系统要明确约定。
六、时区与夏令时
- 两个日期相减,如果涉及跨时区,结果可能差一天。
- 夏令时切换当天只有 23 或 25 小时,某些时刻「不存在」或「重复」。
- 中国目前不实行夏令时,但处理国际业务时要考虑。
建议:存储用 UTC 或带时区的时间戳,展示时再转本地。日期(不含时间)的运算尽量用「日期类型」而不是「时间戳」,避免时区偏移把 00:00 变成前一天 23:00。
七、常见业务场景
- 倒计时:活动开始、考试、纪念日。要注意「今天算不算一天」。
- 工期:合同写「30 个工作日」,必须明确是否含节假日。
- 账期:月结 30 天、60 天,按自然日还是工作日差别很大。
- 年龄:周岁按生日是否已过计算,虚岁是「年份差 + 1」。
- 保质期:生产日期 + 保质期天数,跨月要注意月末。
八、工具
用 日期计算器 可以算两个日期的差值、加減天数、得到星期;倒计时用 倒计时工具;需要把日期和时间戳互转用 时间戳转换。
小结
先约定是否含首尾、按自然日还是工作日;加减天数用日期库而不是手算;工作日要维护节假日表(含调休);闰年按「4 的倍数,百年不闰,四百年再闰」;跨时区用 UTC 存储。