mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-09 04:22:20 +00:00
refactor(cos): consolidate duplicate civil_from_days onto pdfcraft_cos::civil_from_days (#6540)
Fixes #6466
This commit is contained in:
1 parent
ca21968e64
commit
1f48670fce
7 files changed
+29
-79
No files matched your search
@@ -102,25 +102,12 @@ pub fn timestamp(t: SystemTime) -> String {
|
||||
format!("{y:04}-{m:02}-{d:02}T{:02}:{:02}:{:02}.{:03}Z", rem / 3600, rem % 3600 / 60, rem % 60, since.subsec_millis())
|
||||
}
|
||||
|
||||
/// Days since 1970-01-01 → proleptic Gregorian (year, month, day).
|
||||
///
|
||||
/// `timestamp` clamps a pre-1970 clock to the epoch, so `days` is an unsigned count that can run
|
||||
/// past `NaiveDate`'s ±262143-year range. The Gregorian calendar repeats every 400 years
|
||||
/// (146_097 days), so chrono converts the day within one cycle and whole cycles go back into the
|
||||
/// year (an `i128`, then saturated into `i64`), keeping chrono as the only calendar logic and
|
||||
/// reproducing the old `civil_from_days` result exactly without hand-rolled day math. 1970-01-01
|
||||
/// is `num_days_from_ce` 719_163.
|
||||
/// Days since 1970-01-01 → proleptic Gregorian (year, month, day): `pdfcraft_engine::civil_from_days`,
|
||||
/// which keeps the date past chrono's range by whole 400-year cycles. `days` is a `u64` second
|
||||
/// count over 86_400, at most about 2.1e14, so it always fits an `i64`.
|
||||
fn civil_from_days(days: u64) -> (i64, u32, u32) {
|
||||
use chrono::Datelike;
|
||||
let (cycles, rest) = (days / 146_097, days % 146_097);
|
||||
// `rest + 719_163` is 719_163..865_259, always a valid CE day.
|
||||
match i32::try_from(rest + 719_163).ok().and_then(chrono::NaiveDate::from_num_days_from_ce_opt) {
|
||||
Some(d) => {
|
||||
let y = i128::from(d.year()) + i128::from(cycles) * 400;
|
||||
(i64::try_from(y).unwrap_or(i64::MAX), d.month(), d.day())
|
||||
}
|
||||
None => (1970, 1, 1), // unreachable
|
||||
}
|
||||
// The fallback is unreachable: `days` is at most `u64::MAX / 86_400`, far below `i64::MAX`.
|
||||
pdfcraft_engine::civil_from_days(i64::try_from(days).unwrap_or(i64::MAX))
|
||||
}
|
||||
|
||||
fn numbered(dir: &Path, n: usize) -> PathBuf {
|
||||
|
||||
@@ -30,7 +30,7 @@ pub use parser::{Lexer, is_whitespace, parse_indirect};
|
||||
pub use pdfcraft_crypt::{
|
||||
Algorithm, Auth, Method as CryptMethod, NewEncryption, Permissions, SecurityHandler, effective_password, pdfdoc_byte, pdfdoc_char,
|
||||
};
|
||||
pub use writer::{SaveOptions, pdf_date, reachable_objects, serialize, write_full, write_incremental};
|
||||
pub use writer::{SaveOptions, civil_from_days, pdf_date, reachable_objects, serialize, write_full, write_incremental};
|
||||
|
||||
#[derive(Debug, thiserror::Error, Clone, PartialEq)]
|
||||
pub enum CosError {
|
||||
|
||||
@@ -919,14 +919,14 @@ pub fn pdf_date(unix_secs: i64) -> String {
|
||||
format!("D:{y:04}{m:02}{d:02}{:02}{:02}{:02}Z", secs / 3600, secs / 60 % 60, secs % 60)
|
||||
}
|
||||
|
||||
/// Days since 1970-01-01 → proleptic Gregorian (year, month, day).
|
||||
/// Days since 1970-01-01 → proleptic Gregorian (year, month, day). Total for any `i64` count:
|
||||
/// dates past `NaiveDate`'s ±262143-year range keep their proleptic Gregorian year instead of
|
||||
/// falling back to the epoch.
|
||||
///
|
||||
/// A `from_timestamp` epoch fallback would change dates past `NaiveDate`'s ±262143-year range,
|
||||
/// which the old hand-rolled converter formatted directly. The Gregorian calendar repeats every
|
||||
/// 400 years (146_097 days), so chrono converts the day within one cycle and whole cycles go back
|
||||
/// into the year (an `i128` so the extremes can't overflow), keeping chrono as the only calendar
|
||||
/// logic and reproducing the old result exactly. 1970-01-01 is `num_days_from_ce` 719_163.
|
||||
fn civil_from_days(days: i64) -> (i64, u32, u32) {
|
||||
/// The Gregorian calendar repeats every 400 years (146_097 days), so chrono converts the day
|
||||
/// within one cycle and whole cycles go back into the year (an `i128` so the extremes can't
|
||||
/// overflow), keeping chrono as the only calendar logic. 1970-01-01 is `num_days_from_ce` 719_163.
|
||||
pub fn civil_from_days(days: i64) -> (i64, u32, u32) {
|
||||
use chrono::Datelike;
|
||||
let (cycles, rest) = (days.div_euclid(146_097), days.rem_euclid(146_097));
|
||||
// `rest + 719_163` is 719_163..865_259, always a valid CE day.
|
||||
@@ -1110,6 +1110,12 @@ mod tests {
|
||||
// its sign, exactly as before).
|
||||
assert_eq!(pdf_date(10_000_000_000_000), "D:3188570520174640Z");
|
||||
assert_eq!(pdf_date(-10_000_000_000_000), "D:-3149180813061320Z");
|
||||
assert_eq!(crate::civil_from_days(0), (1970, 1, 1));
|
||||
assert_eq!(crate::civil_from_days(-1), (1969, 12, 31));
|
||||
assert_eq!(crate::civil_from_days(11_016), (2000, 2, 29));
|
||||
assert_eq!(crate::civil_from_days(19_782), (2024, 2, 29));
|
||||
assert_eq!(crate::civil_from_days(i64::MAX), (25_252_734_927_768_524, 7, 27));
|
||||
assert_eq!(crate::civil_from_days(i64::MIN), (-25_252_734_927_764_585, 6, 7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -86,7 +86,7 @@ pub use pdfcraft_organize::Standard;
|
||||
pub use pdfcraft_organize::check_custom_key;
|
||||
pub use pdfcraft_organize::view::{InitialView, Layout as InitialLayout, Magnification, Navigation};
|
||||
|
||||
pub use pdfcraft_cos::Algorithm;
|
||||
pub use pdfcraft_cos::{Algorithm, civil_from_days};
|
||||
pub use pdfcraft_create::{CONVERTIBLE, CreateError, ImageResolution, SourceKind, decode_text, source_kind};
|
||||
pub use pdfcraft_edit::added::{
|
||||
CHAR_SPACING_LIMIT as ADDED_CHAR_SPACING_LIMIT, paste_place as added_paste_place, paste_rect as added_paste_rect, text_rect as added_text_rect,
|
||||
|
||||
+2
-12
@@ -349,19 +349,9 @@ impl Time {
|
||||
/// year is cast exactly as the old code did (`as u32`, wrapping) — no well-formed signing time
|
||||
/// is anywhere near that range, and this preserves byte-for-byte the old output everywhere.
|
||||
pub fn from_unix(t: i64) -> Time {
|
||||
use chrono::Datelike;
|
||||
let (days, secs) = (t.div_euclid(86_400), t.rem_euclid(86_400));
|
||||
let (cycles, rest) = (days.div_euclid(146_097), days.rem_euclid(146_097));
|
||||
// `rest + 719_163` is 719_163..865_259, always a valid CE day.
|
||||
let (year, month, day) = match i32::try_from(rest + 719_163).ok().and_then(chrono::NaiveDate::from_num_days_from_ce_opt) {
|
||||
Some(d) => {
|
||||
let y = i128::from(d.year()) + i128::from(cycles) * 400;
|
||||
// Match the old `as u32` cast: saturate i128→i64 first (the old year was i64), then wrap into u32.
|
||||
(i64::try_from(y).unwrap_or(if y < 0 { i64::MIN } else { i64::MAX }) as u32, d.month(), d.day())
|
||||
}
|
||||
None => (1970, 1, 1), // unreachable
|
||||
};
|
||||
Time { year, month, day, hour: (secs / 3600) as u32, minute: (secs % 3600 / 60) as u32, second: (secs % 60) as u32 }
|
||||
let (year, month, day) = pdfcraft_cos::civil_from_days(days);
|
||||
Time { year: year as u32, month, day, hour: (secs / 3600) as u32, minute: (secs % 3600 / 60) as u32, second: (secs % 60) as u32 }
|
||||
}
|
||||
|
||||
pub fn add_seconds(&self, s: i64) -> Time {
|
||||
|
||||
@@ -274,23 +274,10 @@ pub(super) fn utc_stamp(time: SystemTime) -> String {
|
||||
format!("{y:04}-{m:02}-{d:02} {:02}:{:02} UTC", rem / 3600, rem % 3600 / 60)
|
||||
}
|
||||
|
||||
/// Days since 1970-01-01 → proleptic Gregorian (year, month, day).
|
||||
///
|
||||
/// A pre-1970 time already clamps to the epoch (`unwrap_or(0)`), so `days` is an unsigned count
|
||||
/// that can run past `NaiveDate`'s ±262143-year range, where the old hand-rolled converter still
|
||||
/// produced a date. The Gregorian calendar repeats every 400 years (146_097 days), so chrono
|
||||
/// converts the day within one cycle and whole cycles go back into the year (an `i128`, then
|
||||
/// saturated into `i64`), keeping chrono as the only calendar logic and reproducing the old
|
||||
/// result exactly. 1970-01-01 is `num_days_from_ce` 719_163.
|
||||
/// Days since 1970-01-01 → proleptic Gregorian (year, month, day): `pdfcraft_cos::civil_from_days`,
|
||||
/// which keeps the date past chrono's range by whole 400-year cycles. `days` is a `u64` second
|
||||
/// count over 86_400, at most about 2.1e14, so it always fits an `i64`.
|
||||
fn civil_from_days(days: u64) -> (i64, u32, u32) {
|
||||
use chrono::Datelike;
|
||||
let (cycles, rest) = (days / 146_097, days % 146_097);
|
||||
// `rest + 719_163` is 719_163..865_259, always a valid CE day.
|
||||
match i32::try_from(rest + 719_163).ok().and_then(chrono::NaiveDate::from_num_days_from_ce_opt) {
|
||||
Some(d) => {
|
||||
let y = i128::from(d.year()) + i128::from(cycles) * 400;
|
||||
(i64::try_from(y).unwrap_or(i64::MAX), d.month(), d.day())
|
||||
}
|
||||
None => (1970, 1, 1), // unreachable
|
||||
}
|
||||
// The fallback is unreachable: `days` is at most `u64::MAX / 86_400`, far below `i64::MAX`.
|
||||
pdfcraft_cos::civil_from_days(i64::try_from(days).unwrap_or(i64::MAX))
|
||||
}
|
||||
@@ -77,29 +77,9 @@ impl Stamp {
|
||||
pub fn from_unix(secs: i64) -> Self {
|
||||
let (days, rem) = (secs.div_euclid(86_400), secs.rem_euclid(86_400));
|
||||
let (h, mi, s) = (rem / 3600, rem % 3600 / 60, rem % 60);
|
||||
let (y, m, d) = Self::civil_from_days(days);
|
||||
let (y, m, d) = pdfcraft_cos::civil_from_days(days);
|
||||
// `{y:04}` rather than `%Y`, which writes a `+` before years past 9999.
|
||||
Stamp { unix: secs, pdf: format!("D:{y:04}{m:02}{d:02}{h:02}{mi:02}{s:02}Z"), iso: format!("{y:04}-{m:02}-{d:02}T{h:02}:{mi:02}:{s:02}Z") }
|
||||
}
|
||||
|
||||
/// Days since 1970-01-01 → proleptic Gregorian (year, month, day).
|
||||
///
|
||||
/// Past `NaiveDate`'s ±262143-year range the old hand-rolled converter still produced a date,
|
||||
/// which both stamps formatted directly; a `from_timestamp` epoch fallback would change it.
|
||||
/// The Gregorian calendar repeats every 400 years (146_097 days), so chrono converts the day
|
||||
/// within one cycle and whole cycles go back into the year (an `i128` so the extremes can't
|
||||
/// overflow), keeping chrono as the only calendar logic. 1970-01-01 is `num_days_from_ce` 719_163.
|
||||
fn civil_from_days(days: i64) -> (i64, u32, u32) {
|
||||
use chrono::Datelike;
|
||||
let (cycles, rest) = (days.div_euclid(146_097), days.rem_euclid(146_097));
|
||||
// `rest + 719_163` is 719_163..865_259, always a valid CE day.
|
||||
match i32::try_from(rest + 719_163).ok().and_then(chrono::NaiveDate::from_num_days_from_ce_opt) {
|
||||
Some(d) => {
|
||||
let y = i128::from(d.year()) + i128::from(cycles) * 400;
|
||||
(i64::try_from(y).unwrap_or(if y < 0 { i64::MIN } else { i64::MAX }), d.month(), d.day())
|
||||
}
|
||||
None => (1970, 1, 1), // unreachable
|
||||
}
|
||||
Stamp { unix: secs, pdf: pdfcraft_cos::pdf_date(secs), iso: format!("{y:04}-{m:02}-{d:02}T{h:02}:{mi:02}:{s:02}Z") }
|
||||
}
|
||||
|
||||
/// The same moment shifted by `minutes` (used to order threaded replies).
|
||||
|
||||
Reference in new issue
Block a user