修复(公历换算): days_from_civil 全量改用 i64 运算

- i32 年份区间内 era * 146_097 会超过 i32::MAX,debug 下 panic、release 下静默回绕
- 年、月、日中间量提升到 i64,返回类型仍是 i64;补 i32::MIN/MAX 边界回归测试
This commit is contained in:
2026-10-05 14:05:07 +08:00
parent 3a948c5f58
commit f1a6c1eaac
@@ -20,17 +20,20 @@ pub fn is_leap_year(year: i32) -> bool {
}
/// 公历年月日 → 1970-01-01 起的日序号。
///
/// 全程用 `i64` 运算:`i32` 年份区间内 `era * 146_097` 会超过 `i32::MAX`,在 `i32` 下
/// 会溢出(debug panic、release 静默回绕),因此这里的中间量一律提升到 `i64`。
pub fn days_from_civil(year: i32, month: u8, day: u8) -> i64 {
// 中文注释:Howard Hinnant civil calendar 算法,返回 1970-01-01 起的日序号。
let adjusted_year = year - if month <= 2 { 1 } else { 0 };
let adjusted_year = i64::from(year) - if month <= 2 { 1 } else { 0 };
let era = adjusted_year.div_euclid(400);
let year_of_era = adjusted_year - era * 400;
let month = i32::from(month);
let day = i32::from(day);
let month = i64::from(month);
let day = i64::from(day);
let month_prime = month + if month > 2 { -3 } else { 9 };
let day_of_year = (153 * month_prime + 2) / 5 + day - 1;
let day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year;
i64::from(era * 146_097 + day_of_era - 719_468)
era * 146_097 + day_of_era - 719_468
}
/// 1970-01-01 起的日序号 → 公历年月日。
@@ -79,6 +82,21 @@ mod tests {
}
}
#[test]
fn days_from_civil_handles_full_i32_year_range_without_overflow() {
// 中文注释:i32 年份区间内的 era 乘法会超过 i32::MAX,必须提升到 i64 才不溢出。
for (year, month, day) in [
(i32::MIN, 1, 1),
(i32::MIN, 2, 29),
(i32::MIN, 12, 31),
(i32::MAX, 1, 1),
(i32::MAX, 12, 31),
] {
let date_key = days_from_civil(year, month, day);
assert_eq!(civil_from_days(date_key), (year, month, day));
}
}
#[test]
fn days_in_month_applies_leap_year_rules() {
assert_eq!(days_in_month(2024, 2), 29);