tempoch_core/lib.rs
1// SPDX-License-Identifier: AGPL-3.0-only
2// Copyright (C) 2026 Vallés Puig, Ramon
3
4//! Typed astronomical time primitives.
5//!
6//! The central type is [`Time<S, F>`] (default `F` = [`J2000s`]), where `S` is a [`Scale`]
7//! marker (`TT`, `TAI`, `UTC`, `UT1`, `TDB`, `TCG`, `TCB`) and `F` is a [`TimeFormat`]
8//! tag (`JD`, `MJD`, [`J2000s`], [`Unix`], [`GPS`]).
9//!
10//! `tempoch` makes a few explicit modeling decisions:
11//!
12//! - [`Time<S, F>`] is an instant on a scale-specific axis; `F` only types external
13//! views (`raw()`, conversion targets), not a second storage layout.
14//! - Time arithmetic follows affine rules: instant minus instant yields a
15//! duration; shifting an instant by a duration yields another instant.
16//! - Internal storage is always a compensated `(hi, lo)` pair of J2000-based seconds
17//! so large epoch values can retain small corrections and sub-second detail.
18//! - `JD`, `MJD`, `J2000s`, `Unix`, and `GPS` are conversion targets, not alternate
19//! stored encodings.
20//! - `UTC` keeps special civil semantics: it is stored as a continuous instant
21//! and interpreted through the active UTC-TAI table when civil labels are
22//! needed.
23//!
24//! - [`Time::new`] builds from a raw scalar when `F` is [`InfallibleFormatForScale`] for `S` (**NaN panics**; ±∞ allowed at rest). POSIX [`Unix`] instants still use [`Time::try_new`] / [`Time::try_new_with`] because decoding depends on leap-second tables.
25//! - [`Time::try_new`] / [`Time::try_new_with`] surface **domain** decode failures only (UTC policy, leap seconds, …); callers must not pass **NaN**.
26//! - `UTC`: civil (`chrono`) and POSIX ([`Unix`]); `TAI`: GPS ([`GPS`])
27//! - Unified targets: [`Time::to`], [`Time::try_to`], [`Time::to_with`]. Prefer
28//! [`try_to`](Time::try_to) or [`to_with`](Time::to_with) for [`Unix`] so positive
29//! leap-second instants are rejected when they are not representable as POSIX.
30//! - [`Time::to_j2000s`], [`Time::reinterpret`], and aliases
31//! such as [`crate::JulianDate`].
32//! - [`JulianDate`], [`ModifiedJulianDate`], [`UnixTime`], and [`GpsTime`] implement [`Into`] into default-tagged
33//! [`Time<S>`] / [`Time<UTC>`] / [`Time<TAI>`], so APIs such as [`Period::try_new`](crate::Period) accept encoded
34//! endpoints without spelling [`Time::to_j2000s`]. [`J2000Seconds<S>`] is already [`Time<S>`]; no conversion needed.
35//!
36//! See [`constats`] for epoch [`Time`] helpers, day/second scratch constants, and offsets.
37//!
38//! # Module map
39//!
40//! - [`foundation`]: shared sealed traits, typed constants, and error types.
41//! - [`model`]: [`Time`], scale markers, and conversion targets.
42//! - `format`: external format markers and format conversion traits.
43//! - `encoding`: crate-local JD/MJD/J2000/Unix arithmetic helpers.
44//! - [`earth`]: ΔT, EOP, and [`TimeContext`] Earth-rotation policy.
45//! - [`data`]: runtime access to bundled and optionally refreshed time-data tables.
46//! - [`period`]: interval and period-list algebra.
47//! - [`features`]: optional serde/tagged/time-instant integration helpers.
48//!
49//! Reference modules:
50//!
51//! - [`earth::delta_t`]: piecewise ΔT (`TT - UT1`) model and modern tabular segment.
52//! - [`earth::eop`]: public EOP sampling API over bundled IERS series.
53//! - [`earth::context`]: immutable time-data snapshot plus conversion policy.
54//!
55//! ## `no_std` support
56//!
57//! `tempoch-core` is written against `core` / `alloc`. Feature flags:
58//!
59//! - **`std`** (default): full API, including runtime table swap and
60//! `std::error::Error`. Implies `alloc`. Forwards `siderust-archive/std`.
61//! - **`alloc`**: heap-backed period lists, shared time-data handles, and
62//! `serde` helpers. Sufficient for bundled IERS tables on `no_std` targets
63//! (including bare metal) via `siderust-archive` 0.1.5+.
64//!
65//! ```toml
66//! # Default (std)
67//! tempoch = "0.7"
68//!
69//! # no_std + alloc (including bare-metal targets)
70//! tempoch = { version = "0.7", default-features = false, features = ["alloc"] }
71//! ```
72
73#![cfg_attr(not(feature = "std"), no_std)]
74
75#[cfg(not(feature = "alloc"))]
76compile_error!(
77 "tempoch-core requires the `alloc` feature (time-data tables and period lists are heap-backed); \
78 enable `alloc` or the default `std` feature"
79);
80
81#[cfg(feature = "alloc")]
82#[macro_use]
83extern crate alloc;
84
85pub mod data;
86pub mod earth;
87pub(crate) mod encoding;
88pub mod features;
89pub mod format;
90pub mod foundation;
91pub mod model;
92pub mod period;
93pub use affn::qtty;
94
95pub(crate) use siderust_archive as archive;
96
97// Compiled ΔT tables live in `siderust-archive`; these are crate-local shims.
98use crate::archive::time::bundled::snapshot;
99use crate::qtty::Day;
100
101#[allow(unused_imports)]
102pub(crate) use snapshot as time_data;
103pub(crate) const MODERN_DELTA_T_START_MJD: Day = Day::new(snapshot::MODERN_DELTA_T_START_MJD);
104pub(crate) const MODERN_DELTA_T_END_MJD: Day = Day::new(snapshot::MODERN_DELTA_T_END_MJD);
105
106pub use earth::eop;
107pub use foundation::{constats, error};
108
109#[cfg(feature = "runtime-data-fetch")]
110pub use data::runtime_data::{
111 fetch_latest_time_data, refresh_runtime_time_data, update_runtime_time_data,
112};
113pub use data::status::{
114 assert_fresh as assert_time_data_fresh, time_data_status, ActiveTimeDataSource, DataHorizons,
115 FreshnessError, TimeDataStatus,
116};
117pub use earth::context::TimeContext;
118pub use earth::delta_t::{
119 delta_t_seconds, delta_t_seconds_extrapolated, DELTA_T_PREDICTION_HORIZON_MJD,
120};
121pub use earth::eop::{eop_end, eop_observed_end, eop_start};
122pub use features::TimeInstant;
123pub use format::{
124 FormatForScale, FormatOptions, FormatPrecision, GnssWeek, GnssWeekScale, GpsTime,
125 InfallibleFormatForScale, J2000Seconds, J2000s, JulianDate, ModifiedJulianDate, TimeFormat,
126 Unix, UnixTime, GPS, JD, MJD,
127};
128pub use foundation::constats::{
129 gps_epoch_jd_tai, gps_epoch_jd_utc, gps_epoch_tai, iau_time_epoch_t0_jd, j2000_jd_tt,
130 tdb_tt_model_high_accuracy_end_jd, tdb_tt_model_high_accuracy_start_jd, unix_epoch_jd,
131 unix_epoch_mjd, utc_defined_from_mjd, GPS_EPOCH_JD_UTC_DAY, GPS_EPOCH_TAI_MINUS_UTC,
132 IAU_TIME_EPOCH_T0_JD_DAY, J2000_JD_TT_DAY, TDB_TT_MODEL_HIGH_ACCURACY_END_JD_DAY,
133 TDB_TT_MODEL_HIGH_ACCURACY_START_JD_DAY, TT_MINUS_TAI, UNIX_EPOCH_JD_DAY,
134 UTC_DEFINED_FROM_MJD_DAY,
135};
136pub use foundation::duration::{DurationError, ExactDuration, NANOS_PER_SECOND};
137pub use foundation::error::ConversionError;
138#[cfg(feature = "std")]
139pub use foundation::error::TimeDataError;
140pub use model::scale::{
141 ContinuousScale, CoordinateScale, Scale, BDT, ET, GPST, GST, QZSST, TAI, TCB, TCG, TDB, TT,
142 UT1, UTC,
143};
144pub use model::target::{ContextConversionTarget, ConversionTarget, InfallibleConversionTarget};
145pub use model::time::Time;
146pub use period::{
147 complement_within, series::TimeSeries, series::TimeSeriesError, Interval, InvalidIntervalError,
148 Period, PeriodListError,
149};
150pub const MODERN_DELTA_T_OBSERVED_END_MJD: Day =
151 Day::new(snapshot::MODERN_DELTA_T_OBSERVED_END_MJD);
152
153#[cfg(feature = "serde")]
154pub use features::tagged;
155
156#[cfg(test)]
157mod size_tests {
158 use super::*;
159 #[test]
160 fn time_uses_compensated_pair_storage() {
161 assert_eq!(core::mem::size_of::<Time<TT>>(), 16);
162 assert_eq!(core::mem::size_of::<Time<TAI>>(), 16);
163 assert_eq!(core::mem::size_of::<Time<TDB>>(), 16);
164 assert_eq!(core::mem::size_of::<Time<TCG>>(), 16);
165 assert_eq!(core::mem::size_of::<Time<TCB>>(), 16);
166 assert_eq!(core::mem::size_of::<Time<UT1>>(), 16);
167 assert_eq!(core::mem::size_of::<Time<UTC>>(), 16);
168 }
169}