mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-24 10:16:59 -06:00
* Use chrono for UTC/calendar conversion instead of hand-rolled copies Replaces the hand-rolled civil / civil_from_days / days_from_civil copies (the 146_097 / 719_468 magic numbers) across the logging timestamp, text-variable dates, EPUB dcterms:modified, PDF metadata dates and PPTX core.xml dates with chrono, added as a workspace dependency (default-features = false, std only, so wasm is unaffected). - apps/designcraft logging `timestamp` (private civil_from_days deleted). - doc text variables: `civil` and `days_from_civil` use chrono::NaiveDate; `format_date` and `parse_iso_date` keep their own format/parse logic and still call these helpers. - epub `iso8601`, pdf `civil`, pptx `civil`: UTC datetime via chrono::DateTime::from_timestamp. Output is byte-identical and pinned by the existing date, ISO-date, EPUB, image, EPS and logging tests. The OOXML/EPUB dcterms timestamps keep their exact strings. Each converted helper keeps a proleptic-Gregorian fallback for dates outside chrono's NaiveDate range (and for the serial/overflow dates parse_iso_date can hand days_from_civil), so no input changes or panics. AGENTS.md now says time goes through chrono. Follow-up to storytold/cadcraft#1785 * Never panic on a clock outside chrono's range; keep 5-digit years unprefixed logging.rs timestamp(): chrono's From<SystemTime> unwraps, so a clock past year 262143 panicked inside the logger. Use the fallible DateTime::from_timestamp and read such a clock, or a pre-epoch clock, as the epoch like the old saturating code did. Years past 9999 are written with {:04} like before, because %Y adds a '+'. Tests cover both cases. epub iso8601(): write the year with {:04} instead of %Y, matching the old {year:04} output, so years past 9999 are not prefixed with '+'. Test added. AGENTS.md: the "Time goes through chrono" rule now notes the narrow fallback for dates outside chrono::NaiveDate's range, which crates/doc/src/vars.rs retains (Howard Hinnant's algorithm). * Don't build an unrepresentable SystemTime in the far-future test UNIX_EPOCH + Duration::from_secs(u64::MAX / 2) overflows Windows' SystemTime and panicked before timestamp() ran, failing the Windows test job. Build it with checked_add and only assert where it exists. * Route out-of-range dates through chrono's 400-year cycle, not hand-rolled copies doc/vars.rs kept hand-rolled Hinnant `civil_fallback`/`days_from_civil_fallback` copies for dates outside chrono's ±262143-year range, and AGENTS.md carved a "narrow fallback" exception for them, which left calendar arithmetic outside chrono after all. Replace both copies with the Gregorian 400-year cycle (146_097 days): chrono converts the day/year within one cycle and whole cycles are added back via i128 (so i64::MIN/MAX can't overflow). `civil` is reached by the system clock through `format_date(now(), ..)`, so it must stay exact; `days_from_civil` only ever sees a 4-digit parsed year via `parse_iso_date`, but it uses the same cycle form for consistency. Both reproduce the pre-chrono proleptic Gregorian result, so outputs are unchanged (verified against the old formula over wide ranges and at the i64 extremes). New test pins dates past chrono's range on both sides of the epoch, through the public `format_date` and round-tripping `days_from_civil`/`civil`, with values from the old formula. Removed the AGENTS.md "narrow fallback" exception. epub/pdf/pptx `civil`/`iso8601` are left as-is: the PR already dropped their hand-rolled copies for pure chrono, and their remaining epoch (`unwrap_or_default`) fallback only triggers for a metadata/clock timestamp past year 262143 -- the live-clock/file-mtime case FredApps accepted, with outputs that clamp the year anyway (pptx 0..9999, PDF DateTime). No hand-rolled copy remains there to replace. Tested: cargo test -p designcraft-doc; fmt --check; clippy --all-targets -D warnings. * Carry an overflowing day into the next month again in parse_iso_date parse_iso_date accepts any day 1..=31, and the old days_from_civil carried a day past the end of its month into the next one (2019-02-30 is 2019-03-02). from_ymd_opt rejects such a day, and unwrap_or(0) turned it into 1970-01-01. chrono now places the 1st of the month and the day is added on, which restores the old normalization. Tested through days_from_civil and parse_iso_date. * time: preserve old out-of-range date conversion (no epoch fallback) `DateTime::from_timestamp(...).unwrap_or_default()` silently reset any instant past chrono's year range to 1970-01-01, changing output the old hand-rolled Howard-Hinnant code converted exactly (e.g. 10_000_000_000_000 s was 318857-05-20 17:46:40, not the epoch). Restore the old conversion domain with a cycle-based helper that routes the calendar through chrono (the calendar repeats every 400 years = 146_097 days, so month/day/time come from chrono and only the year is carried, keeping every i64 exact), then apply each format's existing year clamp afterwards. - apps/designcraft/src/logging.rs: `timestamp` via `utc_parts`; keep the pre-1970 epoch clamp; u64 secs past i64::MAX saturate, not epoch. - crates/epub/src/lib.rs: `iso8601` via `utc_parts`. - crates/pdf/src/export.rs: `civil` cycle-based; `date_time` keeps its 0..=9999 year clamp. - crates/pptx/src/package.rs: `civil` cycle-based, keeps its 0..=9999 year clamp on the year only (month/day/time keep the real instant). Pinned old outputs in tests (through the public helpers): logging u64::MAX/2 s -> 292277026596-12-04T15:30:07.000Z (was epoch) epub 10_000_000_000_000 -> 318857-05-20T17:46:40Z -10_000_000_000_000 -> -314918-08-13T06:13:20Z i64::MAX -> 292277026596-12-04T15:30:07Z i64::MIN -> -292277022657-01-27T08:29:52Z pdf civil(10_000_000_000_000) -> (318857,5,20,17,46,40) pptx civil(10_000_000_000_000) -> (9999,5,20,17,46,40) (year clamped) * Don't build a year-318857 SystemTime unchecked in the logging test Windows' SystemTime only reaches about year 30828, so UNIX_EPOCH + Duration::from_secs(10_000_000_000_000) panicked before timestamp() ran and failed the Windows test job. Build it with checked_add and assert where the platform can hold it. The year-10000 case fits everywhere and stays unconditional. * Name the 400-year range extension as the one exception to the time rule The rule said never to hand-roll calendar math, but the range fix (shifting by whole 400-year Gregorian cycles so dates past chrono's year range keep their old output) is cycle arithmetic, so the rule and the code disagreed. The rule now names that exception: the library still computes month, day and time, and only whole cycles are carried in the year. * Say the library computes month and day; time-of-day is plain arithmetic Narrows the range-extension wording: the date library computes the calendar fields, and splitting seconds into hours, minutes and seconds is ordinary arithmetic, which is what the code does.
21 lines
522 B
TOML
21 lines
522 B
TOML
[package]
|
|
name = "designcraft-doc"
|
|
version.workspace = true
|
|
edition.workspace = true
|
|
license.workspace = true
|
|
description = "DesignCraft document model: spreads, pages, parents, layers, frames, stories, styles, swatches"
|
|
|
|
[lints]
|
|
workspace = true
|
|
|
|
[dependencies]
|
|
designcraft-geom = { workspace = true }
|
|
designcraft-color = { workspace = true }
|
|
serde = { workspace = true }
|
|
serde_json = { workspace = true }
|
|
thiserror = { workspace = true }
|
|
chrono = { workspace = true }
|
|
|
|
[dev-dependencies]
|
|
proptest = { workspace = true }
|