leap_sec/lib.rs
1//! # leap-sec
2//!
3//! Leap-second handling and continuous time mappings for flight and space systems.
4//!
5//! This crate provides type-safe conversions between UTC, TAI, and GPS time scales
6//! using an explicit leap-second table. The API is designed so that you cannot
7//! accidentally mix time scales at call sites.
8//!
9//! # Quick Start
10//!
11//! ```
12//! use leap_sec::prelude::*;
13//!
14//! let leaps = LeapSeconds::known();
15//!
16//! // Convert UTC to TAI
17//! let utc = UtcUnixSeconds(1_700_000_000);
18//! let tai = leaps.utc_to_tai(utc).unwrap();
19//! assert_eq!(tai, TaiSeconds(1_700_000_037));
20//!
21//! // Roundtrip
22//! let back = leaps.tai_to_utc(tai).unwrap();
23//! assert_eq!(back, utc);
24//! ```
25//!
26//! # Leap-Second History
27//!
28//! Since 1972, the IERS has inserted 27 leap seconds. Each insertion increases the
29//! TAI−UTC offset by 1 second. The most recent insertion was on **2016-12-31**
30//! at 23:59:60 UTC, making the new offset (TAI−UTC = 37s) effective from
31//! **2017-01-01** 00:00:00 UTC. No new leap seconds have been inserted since.
32//!
33//! The built-in [`LeapSeconds::known()`] table contains all 28 entries: the initial
34//! 1972-01-01 epoch (offset 10) plus the 27 subsequent insertions.
35//!
36//! # The Future of UTC (2030s)
37//!
38//! In 2022, the 27th CGPM adopted [Resolution 4](https://www.bipm.org/documents/20126/64894729/Resolutions27thCGPM-EN.pdf)
39//! on the future of UTC: the plan is to increase the maximum allowed value of
40//! `|UT1−UTC|` (currently kept within ±0.9s using leap seconds) at a date to be
41//! determined, with key decisions targeted by or before 2035.
42//!
43//! In practice, this is widely understood as the path to ending leap seconds.
44//! This crate treats leap seconds as explicit table data, so if they stop being
45//! inserted, the table stops growing and historical conversions remain correct.
46//!
47//! # Negative Leap Seconds
48//!
49//! The ITU-R Recommendation TF.460 allows *negative* leap seconds (where the
50//! TAI−UTC offset decreases by 1 and UTC skips from `23:59:58` directly to
51//! `00:00:00`). No negative leap second has ever been applied — all 27
52//! historical insertions have been positive.
53//!
54//! The table format and conversion logic handle them correctly:
55//! - A table entry where the new offset is less than the previous offset
56//! represents a negative leap second.
57//! - [`LeapSeconds::is_during_leap_second()`] returns `false` for negative leap
58//! seconds (there is no extra second; instead a second is removed).
59//! - Roundtrip `utc_to_tai` → `tai_to_utc` remains correct.
60//!
61//! The custom table builder accepts negative entries for testing.
62
63#![cfg_attr(not(feature = "std"), no_std)]
64#![forbid(unsafe_code)]
65
66mod convert;
67mod error;
68mod table;
69mod types;
70
71#[cfg(feature = "std")]
72mod builder;
73
74// Re-export free functions at crate root.
75pub use convert::{gpst_to_tai, gpst_to_tai_nanos, tai_to_gpst, tai_to_gpst_nanos};
76pub use error::Error;
77pub use table::LeapSeconds;
78pub use types::{GpstNanos, GpstSeconds, TaiNanos, TaiSeconds, UtcUnixNanos, UtcUnixSeconds};
79
80#[cfg(feature = "std")]
81pub use builder::LeapSecondsBuilder;
82
83/// Prelude for convenient glob imports.
84///
85/// Brings all public types, free functions, and the error type into scope:
86///
87/// - **Table:** [`LeapSeconds`]
88/// - **Seconds types:** [`UtcUnixSeconds`], [`TaiSeconds`], [`GpstSeconds`]
89/// - **Nanos types:** [`UtcUnixNanos`], [`TaiNanos`], [`GpstNanos`]
90/// - **Free functions:** [`tai_to_gpst`], [`gpst_to_tai`], [`tai_to_gpst_nanos`], [`gpst_to_tai_nanos`]
91/// - **Error:** [`Error`]
92/// - **Builder:** [`LeapSecondsBuilder`] (requires `std` feature)
93///
94/// ```
95/// use leap_sec::prelude::*;
96///
97/// let leaps = LeapSeconds::known();
98/// let tai = leaps.utc_to_tai(UtcUnixSeconds(1_700_000_000)).unwrap();
99/// assert_eq!(tai, TaiSeconds(1_700_000_037));
100/// ```
101pub mod prelude {
102 pub use crate::convert::{gpst_to_tai, gpst_to_tai_nanos, tai_to_gpst, tai_to_gpst_nanos};
103 pub use crate::error::Error;
104 pub use crate::table::LeapSeconds;
105 pub use crate::types::{
106 GpstNanos, GpstSeconds, TaiNanos, TaiSeconds, UtcUnixNanos, UtcUnixSeconds,
107 };
108
109 #[cfg(feature = "std")]
110 pub use crate::builder::LeapSecondsBuilder;
111}