Skip to main content

ucal_core/
lib.rs

1//! # ucal-core — absolute time in Planck ticks
2//!
3//! An unsigned integer count of Planck-time units since a stipulated datum, and a
4//! positional calendar over that integer in base 5. Implements Part A of
5//! RFC UCAL-1 for profile `UC-1`.
6//!
7//! ## What tick 0 is
8//!
9//! Tick 0 is a **stipulated** datum, conventionally identified with the FLRW t→0
10//! limit. It is exact by declaration and unrevisable within a profile. It is not
11//! a measurement, not a derivation, and not an observed event (Rule Q, N17).
12//!
13//! The stipulation is a necessity rather than a shortcut, for three independent
14//! reasons, each sufficient:
15//!
16//! 1. Exactness cannot come from measurement. The published age carries
17//!    ±0.020 Gyr — about 1.17×10⁵⁸ ticks, 0.145% of the span. A datum inheriting
18//!    that error bar would make every timestamp uncertain relative to zero.
19//! 2. The t→0 limit is not an observable event; it is where the FLRW
20//!    extrapolation's coordinates degenerate, and classical time is undefined
21//!    below roughly one tick.
22//! 3. The extrapolation is model-dependent. Under inflation the FLRW t→0 limit is
23//!    not a physical event at all.
24//!
25//! This puts the datum in ordinary company: TAI's 1958-01-01, the Julian Day
26//! epoch, the Unix epoch. The parallel to the SI second is exact — 9 192 631 770
27//! caesium cycles was *chosen* to match the ephemeris second, and the definition
28//! does not inherit that provenance.
29//!
30//! What a profile does assert about physics is carried separately, as
31//! [`profile::Profile::big_bang_claim`], and no arithmetic can consume it.
32//!
33//! ## What this crate does not contain
34//!
35//! Rule A.2: no Earth-derived quantity is referenced, named or defined here. The
36//! Julian year, the day, the hour and every civil calendar live outside, in
37//! `ucal-civil`. The single exception is the declared bridge constant
38//! ([`profile::Bridge`]), which exists precisely so that the boundary is visible.
39//!
40//! Rule E: no floating-point type appears in any signature, field, constant or
41//! intermediate. A CI lint enforces it.
42//!
43//! ## Layout
44//!
45//! - [`backend`] — the integer backend and the [`backend::TickInt`] surface.
46//!   Rule W keeps the domain `[0, 2^512)` on both backends.
47//! - [`error`] — Appendix E diagnostics.
48//! - [`tier`] — the `5^(5k)` grid (Rule G) and tier naming (Rule N).
49//! - [`value`] — [`value::Instant`], [`value::Delta`], [`value::Window`] and the
50//!   inert [`value::SignedWindow`].
51//! - [`codec`] — the two text forms (§6, Rule D) and the Appendix F group codec.
52//! - [`ident`] — canonical binary (§7.1) and UCID (§7.2), and the Rule S
53//!   ordering property both depend on.
54//! - [`locale`] — tier-name tables (Appendix D). Names are display-only
55//!   (Rule N), so adding one cannot change what a value means.
56//! - [`num`] — Appendix H: widening `mul_div`, directed `isqrt`, exact rationals,
57//!   interval arithmetic, continued fractions.
58//! - [`profile`] — [`profile::UC1`], the datum, and provenance.
59//! - [`qualified`] — [`qualified::Kind`] and the calendar qualifier every local
60//!   rendering must carry (§6.6, §13.4).
61//!
62//! Rule S in one sentence: the binary form and UCID sort lexicographically in
63//! chronological order; the text forms do not, unless zero-padded to a fixed
64//! tier width.
65
66#![cfg_attr(not(feature = "std"), no_std)]
67#![forbid(unsafe_code)]
68#![warn(missing_docs)]
69// Rule E: enforced structurally here, and by `cargo run -p xtask -- lint` across
70// the workspace. There is no float type in this crate to deny.
71#![deny(clippy::float_arithmetic)]
72
73#[cfg(feature = "alloc")]
74extern crate alloc;
75
76pub mod backend;
77pub mod codec;
78pub mod error;
79pub mod ident;
80pub mod locale;
81pub mod num;
82pub mod profile;
83pub mod qualified;
84pub mod tier;
85pub mod value;
86
87pub use backend::{TickInt, Ticks, CANONICAL_BYTES, DOMAIN_BITS};
88pub use codec::{Fmt, Form};
89/// GE-5: text forms are radix formatting, so they need an allocator. Without
90/// one the type system, the arithmetic and the binary codec all remain — only
91/// the human-readable rendering goes.
92#[cfg(feature = "alloc")]
93pub use codec::{parse, render};
94pub use error::{Code, TimeError, Warning};
95pub use ident::{Ucid, CROCKFORD, UCID_BITS, UCID_LEN};
96pub use locale::LocaleId;
97pub use num::{isqrt_ceil, isqrt_floor, mul_div, RatInterval, Ratio};
98/// Continued fractions return a `Vec` of terms, so they too need an allocator.
99#[cfg(feature = "alloc")]
100pub use num::{cf_expand, convergents};
101pub use profile::{Bridge, Citation, Frame, MeasuredValue, Profile, Provenance, UC1};
102pub use qualified::{CalendarIdentity, CalendarQualifier, Kind, Qualified};
103pub use tier::{Tier, TierName, TierTable, GROUP_BASE};
104pub use value::{
105    Delta, Instant, IntervalOrdering, Precision, Rounding, Sign, Signed, SignedWindow, Span,
106    Stated, Window,
107};
108
109/// The RFC revision this crate implements.
110pub const RFC: &str = "UCAL-1 final draft, 2026-07-29";
111
112/// Deltas applied against the RFC text; see `spec/SPEC-DELTAS.md`.
113pub const SPEC_DELTAS: &[&str] = &[
114    "D-A2: ORIGIN_OFFSET has 61 trailing base-5 zeros, not 62 (editorial)",
115    "D-A3: Appendix B's seconds column is imprecise; the table is generated (editorial)",
116    "D-A4: Appendix C's human forms are truncated at T-5, not tick-exact (correction)",
117    "D-A5: grouping cycles are declared per body, not admitted by a global bound (amendment)",
118    "D-A6: Earth body parameters are chosen to reproduce Appendix I (editorial)",
119    "D-A7: full-width encode is 45 divmod steps, not 44 (correction)",
120    "D-A8: precision is the last group's tier; forms are anchored per-form (amendment)",
121    "D-A9: §6.6 needs a calendar-id grammar to disambiguate qualifier from body (amendment)",
122    "D-A10: Appendix A's implied age is the unrounded input, not the quotient (editorial)",
123    "D-A11: obliquity is an angle and cannot be a RatedParam under Rule C (correction)",
124    "D-A12: §9.6's synodic formula contradicts Appendix I.2; the year-relative form is correct (correction)",
125    "D-A13: a drift bound is a rate in local units, not a Delta (correction)",
126];