chrono 的 from_isoywd_opt 仍会 panic:先挡住超范围年份
chrono 0.4.45 的 ISO 周日期构造会在极小年份上先计算 year-1,随后才检查日期范围。对不可信整数年份先做业务范围校验,再交给 _opt 判断周号与日期,不能依赖发布构建关闭溢出检查。
使用 chrono::NaiveDate::from_isoywd_opt 接收外部年份、ISO 周号和星期,本来准备处理 None,却在输入 i32::MIN、第 1 周、星期一时直接 panic:
attempt to subtract with overflow
正式 chrono 0.4.45 仍有这条边界。问题出在构造过程的整数运算,不能因为函数名带 _opt,就假定任意 i32 输入都会安全地转成 Some 或 None。
年份检查发生在减一之后
ISO 周所属年与最后得到的日历年可能不同。第一周的某些日期属于上一日历年,因此实现需要计算 year - 1;另一端也有 year + 1 的跨年分支。
0.4.45 先计算这些相邻年份,再构造并检查日期。极端输入可能在最终范围检查前发生整数溢出。开启相应溢出检查时表现为 panic;关闭检查并不能把这个超范围日期变成业务上合法的输入,也不能作为修复。
在调用点约束允许的年份
若接口本来只服务有限的业务年份,应在调用 _opt 前落实该约定。例如以下函数明确只接受 1900 至 9999 的 ISO 周所属年:
use chrono::{NaiveDate, Weekday};
fn business_week_date(year: i32, week: u32, day: Weekday) -> Option<NaiveDate> {
if !(1900..=9999).contains(&year) {
return None;
}
NaiveDate::from_isoywd_opt(year, week, day)
}
1900 和 9999 是这里选定的应用边界,不是 chrono 的完整取值范围。项目应使用自己的协议或业务范围,不能照搬后误删历史日期。校验也应放在整数解析之后、构造调用之前,避免先进行可能溢出的减法再检查结果。
周所属年合法,不代表周号一定合法。继续让 _opt 判断第 0 周、第 54 周,以及某一年是否存在第 53 周,不用手写“每年都允许 1 到 53”替代它。
如果协议限制的是最终日历日期范围,还要在得到 Some 后检查日期本身。年初第一周和年末最后一周可能跨到相邻日历年;仅限制传入 year 不能替代最终日期约束。
不把溢出处理改成日期修正
不要把极端年份截成最小允许年,也不要在 panic 后返回今天或某个默认日期。那会把无效输入变成一条看似正常的业务记录。外层若需要区分“年份超范围”和“周号不存在”,可以在原有错误类型中保留两类原因。
升级依赖时应核对所采用正式版本的这个构造分支。当前 0.4.45 的发布版本仍有前述运算,后续采用新版本时仍应检查这个分支,也无需为规避它直接修改依赖缓存中的源码。
验证要包含业务上下界、合法与非法第 53 周、跨年周,以及 i32::MIN、i32::MAX 这类非法输入。非法年份应在进入 chrono 之前被拒绝,正常周日期保持原来的 ISO 周含义。编译优化设置改变后也应保留相同的输入拒绝契约。


