tempoch_core/scale/mod.rs
1// SPDX-License-Identifier: AGPL-3.0-only
2// Copyright (C) 2026 Vallés Puig, Ramon
3
4//! Frozen scale set.
5//!
6//! Every scale is a zero-sized marker type that identifies a scientifically
7//! distinct time axis. The [`Scale`] trait is sealed — downstream crates
8//! cannot add new scales.
9//!
10//! * Coordinate scales (`TAI`, `TT`, `TDB`, `TCG`, `TCB`, `UT1`, `UTC`)
11//! implement [`CoordinateScale`] and support raw-axis constructors,
12//! accessors, and instant arithmetic on `Time<S>`.
13//! * The civil scale `UTC` still does **not** implement [`ContinuousScale`]:
14//! it shares the internal instant axis used by `TAI`, but civil labels and
15//! leap-second interpretation remain table-driven.
16
17use super::sealed::Sealed;
18
19pub(crate) mod conversion;
20
21/// Marker trait for a scientifically distinct time scale.
22///
23/// Sealed: implementations live in this crate only — downstream crates cannot
24/// add new scales.
25#[allow(private_bounds)]
26pub trait Scale: Sealed + Copy + Clone + core::fmt::Debug + 'static {
27 /// Display name of the scale. Used by `Debug` on `Time`.
28 const NAME: &'static str;
29}
30
31// ── Scale macros ─────────────────────────────────────────────────────────
32
33macro_rules! define_continuous_scale {
34 ($(#[$meta:meta])* $ident:ident = $name:literal) => {
35 $(#[$meta])*
36 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
37 pub struct $ident;
38 impl Sealed for $ident {}
39 impl Scale for $ident {
40 const NAME: &'static str = $name;
41 }
42 };
43}
44
45macro_rules! define_civil_scale {
46 ($(#[$meta:meta])* $ident:ident = $name:literal) => {
47 $(#[$meta])*
48 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
49 pub struct $ident;
50 impl Sealed for $ident {}
51 impl Scale for $ident {
52 const NAME: &'static str = $name;
53 }
54 };
55}
56
57// ── Scale definitions ────────────────────────────────────────────────────
58
59define_civil_scale!(
60 /// Coordinated Universal Time.
61 ///
62 /// Leap-second-aware civil scale. `Time<UTC>` stores a continuous instant;
63 /// leap-second interpretation is computed on demand from the UTC-TAI
64 /// segment table. Raw-axis arithmetic acts on that stored instant.
65 ///
66 /// # Storage invariant
67 ///
68 /// `Time<UTC>` and `Time<TAI>` store **the same continuous instant** for
69 /// the same physical event. Scale conversion between them is therefore a
70 /// numeric no-op at the storage layer.
71 ///
72 /// UTC participates in [`CoordinateScale`], so `Time<UTC>` exposes the
73 /// same raw J2000/JD/MJD instant-axis helpers and second-based arithmetic
74 /// as the other built-in scales. Those operations act on the stored
75 /// continuous instant, not on a leap-second-labelled civil clock.
76 ///
77 /// UTC still does **not** implement [`ContinuousScale`]. Generic code that
78 /// wants a scale with no civil semantics should keep using that stricter
79 /// bound; generic code that merely needs a raw coordinate axis can use
80 /// [`CoordinateScale`].
81 ///
82 /// # Authoritative UTC API
83 ///
84 /// Use the civil layer for any operation that depends on the UTC-TAI
85 /// offset (i.e., leap seconds):
86 ///
87 /// * [`Time::<UTC>::from_chrono`] / [`try_from_chrono`] / [`try_to_chrono`]
88 /// * [`Time::<UTC>::from_unix_seconds`] / [`unix_seconds`]
89 ///
90 /// [`try_from_chrono`]: crate::Time::try_from_chrono
91 /// [`try_to_chrono`]: crate::Time::try_to_chrono
92 /// [`from_unix_seconds`]: crate::Time::from_unix_seconds
93 /// [`unix_seconds`]: crate::Time::unix_seconds
94 UTC = "UTC"
95);
96
97define_continuous_scale!(
98 /// International Atomic Time. Continuous SI-second clock.
99 TAI = "TAI"
100);
101
102define_continuous_scale!(
103 /// Terrestrial Time. The dynamical reference scale in this crate.
104 ///
105 /// Related to TAI by `TT = TAI + 32.184 s` (exact).
106 TT = "TT"
107);
108
109define_continuous_scale!(
110 /// Barycentric Dynamical Time.
111 ///
112 /// Differs from TT by a modeled periodic term using the seven-term
113 /// Fairhead–Bretagnon truncation from USNO Circular 179.
114 ///
115 /// The built-in approximation is context-free because the model has no
116 /// runtime-settable parameters, but its advertised high-accuracy regime is
117 /// finite: the implementation is documented to stay within about 10 µs only
118 /// over the interval bracketed by
119 /// [`TDB_TT_MODEL_HIGH_ACCURACY_START_JD`] and
120 /// [`TDB_TT_MODEL_HIGH_ACCURACY_END_JD`] (roughly 1600-01-01 to
121 /// 2200-01-01 TT). Outside that interval conversions remain available, but
122 /// the crate does not claim microsecond-level scientific accuracy.
123 ///
124 /// [`TDB_TT_MODEL_HIGH_ACCURACY_START_JD`]: crate::constats::TDB_TT_MODEL_HIGH_ACCURACY_START_JD
125 /// [`TDB_TT_MODEL_HIGH_ACCURACY_END_JD`]: crate::constats::TDB_TT_MODEL_HIGH_ACCURACY_END_JD
126 TDB = "TDB"
127);
128
129define_continuous_scale!(
130 /// Geocentric Coordinate Time (IAU 2000 B1.9). Linear rate difference to TT.
131 TCG = "TCG"
132);
133
134define_continuous_scale!(
135 /// Barycentric Coordinate Time (IAU 2006 B3). Linear relation to TDB.
136 TCB = "TCB"
137);
138
139define_continuous_scale!(
140 /// Universal Time 1 — Earth-rotation time axis.
141 ///
142 /// Continuous in SI seconds, but `UT1 ↔ TT` requires a `TimeContext`
143 /// because the mapping depends on the compiled ΔT model (and, in future
144 /// phases, observed-ΔT data).
145 ///
146 /// # Accuracy and modeling limitations
147 ///
148 /// UT1 conversions are backed by a piecewise ΔT model:
149 ///
150 /// * **Historical (pre-1973)**: polynomial approximations (Stephenson &
151 /// Houlden 1986; Meeus *Astronomical Algorithms*). Accuracy varies from
152 /// ±15 s (1620–1973) to ±hundreds of seconds (pre-948).
153 /// * **Modern (1973 – horizon)**: USNO monthly determinations with linear
154 /// interpolation. For the currently compiled bundle fetched 2026-04-18,
155 /// the default monthly-ΔT path differs from the bundled daily
156 /// IERS-derived path by less than 10 ms over the observed overlap
157 /// through 2026-04-16, and by less than 0.2 s over the compiled
158 /// short-range prediction overlap through 2027-04-24. See
159 /// [`DELTA_T_PREDICTION_HORIZON_MJD`] for the hard stop.
160 ///
161 /// This model is suitable for archival astronomy and telescope scheduling,
162 /// but **not** for precision geodesy, VLBI, or pulsar timing, which
163 /// require daily IERS EOP (DUT1) solutions. Use
164 /// [`crate::TimeContext::with_builtin_eop`] when you want the most accurate
165 /// bundled UT1 route.
166 ///
167 /// [`DELTA_T_PREDICTION_HORIZON_MJD`]: crate::DELTA_T_PREDICTION_HORIZON_MJD
168 UT1 = "UT1"
169);
170
171// ── ContinuousScale witness ──────────────────────────────────────────────
172
173/// Witness that a scale is continuous and supports direct arithmetic.
174/// `UTC` deliberately does not implement this: it has raw-axis accessors
175/// through [`CoordinateScale`], but its civil interpretation remains
176/// leap-second-aware and table-driven.
177///
178/// Sealed — downstream cannot implement it.
179#[allow(private_bounds)]
180pub trait CoordinateScale: Scale + Sealed {}
181
182macro_rules! coordinate {
183 ($($scale:ty),+ $(,)?) => {
184 $(impl CoordinateScale for $scale {})+
185 };
186}
187coordinate!(TAI, TT, TDB, TCG, TCB, UT1, UTC);
188
189/// Witness that a scale is both coordinate-bearing and physically continuous.
190///
191/// Sealed — downstream cannot implement it.
192#[allow(private_bounds)]
193pub trait ContinuousScale: CoordinateScale + Sealed {}
194
195macro_rules! continuous {
196 ($($scale:ty),+ $(,)?) => {
197 $(impl ContinuousScale for $scale {})+
198 };
199}
200continuous!(TAI, TT, TDB, TCG, TCB, UT1);