是,MySQL 的 DATE_ADD 和 DATE_SUB 按“天”或“年”单位增减时会自动处理闰年,基于格里高利历推演,如 DATE_ADD('2024-02-28', INTERVAL 1 DAY) 返回 '2024-02-29',而 DATE_ADD('2023-02-28', INTERVAL 1 YEAR) 返回 '2024-02-28'。

MySQL 的 DATE_ADD 和 DATE_SUB 会自动处理闰年吗?
会,但仅限于按“天”或“年”单位增减时。MySQL 内部使用日历算法(Gregorian calendar),DATE_ADD('2024-02-28', INTERVAL 1 DAY) 正确返回 '2024-02-29',DATE_ADD('2023-02-28', INTERVAL 1 YEAR) 返回 '2024-02-28'(不是 29 日,因为源日期本身不存在 2023 年的 2 月 29 日)。关键在于:它不“预测”闰日,而是基于实际日历推演。
常见错误是误以为 DATE_ADD('2023-02-28', INTERVAL 1 YEAR) 应该变成 2024-02-29 —— 实际上 MySQL 按“同月同日”逻辑平移,2023-02-28 + 1 年 = 2024-02-28,这是符合预期的行为。
PostgreSQL 中用 INTERVAL '1 year' 遇到 2 月 29 日会怎样?
PostgreSQL 更严格:如果起始日期是 '2024-02-29',执行 '2024-02-29'::date + INTERVAL '1 year' 会直接报错 ERROR: date field value out of range,因为 2025 年没有 2 月 29 日。
规避方式只有两种:
- 先用
EXTRACT(DAY FROM ...)判断是否为 2 月 29 日,再手动 fallback 到 2 月 28 日 - 改用
MAKE_INTERVAL(years := 1)++运算符,行为与 MySQL 一致(即“同月同日”,不存在则取当月最后一天)——但需 PostgreSQL ≥ 14 - 更稳妥的做法是用
age()反向计算再加回,例如:(CURRENT_DATE + (age(CURRENT_DATE, '2024-02-29') * -1)),不过性能差、可读性低,仅作兜底
SQL Server 的 DATEADD(YEAR, 1, ...) 对 2 月 29 日的处理逻辑
SQL Server 采用“溢出修正”策略:若目标月份没有对应日期(如 2024-02-29 + 1 年 → 2025-02-29),它会自动退到该月最后一天,即返回 '2025-02-28'。这点和 PostgreSQL 默认行为不同,也不同于 MySQL 的“同月同日”(MySQL 不会自动跳到 28 日,除非你显式用 LAST_DAY)。
注意陷阱:
-
DATEADD(MONTH, 1, '2024-01-31')→'2024-02-29'(正确,因 2024 是闰年) -
DATEADD(MONTH, 1, '2023-01-31')→'2023-02-28'(自动截断,不是错误) - 但
DATEADD(YEAR, 1, '2023-02-28')→'2024-02-28',不会主动跳到 29 日,哪怕目标年是闰年
跨数据库安全计算“一年后”的通用写法
没有银弹,但可封装成确定性逻辑:优先用“年份+1,再取当月最小有效日期”。例如在任意数据库中模拟:
SELECT
CASE
WHEN EXTRACT(MONTH FROM d) = 2 AND EXTRACT(DAY FROM d) = 29
THEN DATEFROMPARTS(YEAR(d)+1, 2, 28) -- 强制转成 28 日
ELSE DATEADD(YEAR, 1, d)
END AS safe_next_year
FROM (VALUES ('2024-02-29'), ('2023-02-28')) t(d);真正容易被忽略的是「业务语义」:很多场景所谓“一年后”其实指“同一财务周期”或“合同整年”,这时硬套日历加减反而出错。比如保险续期,2024-02-29 生效的保单,2025 年续期日应是 2025-02-28 还是 2025-03-01?这得由业务规则定,SQL 只负责忠实执行规则,不负责定义规则。










