deep_time/lib.rs
1//! # deep-time
2//!
3//! A fully featured and high performance **Rust date and time library** with attosecond precision that provides **astronomical** and **civil** timekeeping.
4//!
5//! [docs.rs](https://docs.rs/deep-time)
6//! [Crates.io](https://crates.io/crates/deep-time)
7//! [License](https://github.com/ragardner/deep-time/blob/main/LICENSE)
8//!
9//! ### Overview
10//!
11//! - Auto-parsers for [datetimes](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html#method.from_str_parse) and [durations](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html#method.from_str_duration) that handle thousands of formats, relative dates and multiple languages, requires the `parse` feature
12//! - No std, no alloc, and wide-spread [const fn](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html#method.from_ymd)
13//! - [Extensively validated](https://github.com/ragardner/deep-time/tree/main/tests) against outputs from **Astropy**, **Jiff**, and other libraries and sources
14//! - Fast [ISO](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html#method.from_str_iso) parser
15//! - [Time scales](https://docs.rs/deep-time/latest/deep_time/enum.Scale.html) e.g. UTC with leap seconds support, including historical, TT, TAI, TDB, NAIF ET, LTC, GPS, etc. An optional feature `tdb_fairhead1990` can be enabled which provides the ERFA TDB model
16//! - [Strptime](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html#method.from_str)
17//! - [Strftime](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html#method.to_str) (multi-language day and month names available)
18//! - First class [timezone](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html#method.to_str_in_tz) support provided by the Rust library [jiff](https://github.com/BurntSushi/jiff) enabled with the `jiff-tz` feature
19//! - To and from all kinds of inputs and outputs, functions mostly prefixed with `to` and `from`, available on the library's types, see the main time types functions: [Dt](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html). Including [JD](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html#method.to_jd_f), [MJD](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html#method.to_mjd_f), [Unix](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html#method.to_unix), [NTP](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html#method.to_ntp), etc.
20//! - [Calendar aware](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html#method.add_days) and, with the `jiff-tz` feature, [timezone aware](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html#method.add_days_tz) math
21//! - To and from [jiff](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html#method.to_jiff_timestamp), [chrono](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html#method.to_chrono_datetime_utc), and [hifitime](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html#method.to_hifitime_epoch) types
22//! - No-alloc [string return type](https://docs.rs/deep-time/latest/deep_time/struct.LiteStr.html)
23//! - Const fn [libm math](https://docs.rs/deep-time/latest/deep_time/math/index.html) functions
24//! - Safe, saturating arithmetic throughout
25//! - **No** `unsafe` in the library - [`#![forbid(unsafe_code)]`](https://github.com/ragardner/deep-time/blob/main/src/lib.rs)
26//! - [Lunar](https://docs.rs/deep-time/latest/deep_time/lunar/index.html) and [Mars](https://docs.rs/deep-time/latest/deep_time/mars/index.html) modules
27//! - [Sidereal time](https://docs.rs/deep-time/latest/deep_time/sidereal/struct.Sidereal.html) with a const fn implementation of ERFA Equation of the Origins / Equinoxes
28//! - [UT1 and EOP](https://docs.rs/deep-time/latest/deep_time/eop/index.html)
29//! - [Light-time (Shapiro delay, etc.)](https://docs.rs/deep-time/latest/deep_time/struct.Observer.html), requires the `physics` feature
30//! - [Proper time along trajectories](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html#method.proper_time_from_states), requires the `physics` feature
31//! - Relativity: [Drift](https://docs.rs/deep-time/latest/deep_time/struct.Drift.html), [Spacetime](https://docs.rs/deep-time/latest/deep_time/struct.Spacetime.html), [Position](https://docs.rs/deep-time/latest/deep_time/struct.Position.html), and [Velocity](https://docs.rs/deep-time/latest/deep_time/struct.Velocity.html) — see [docs/relativity.md](docs/relativity.md) for the underlying model. Requires the `physics` feature.
32//! - CCSDS [CUC](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html#method.to_ccsds_cuc), [CDS](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html#method.to_ccsds_cds), and [CCS](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html#method.to_ccsds_ccs)
33//! - Binary size is mainly controlled through feature gating
34//!
35//! ### Examples
36//!
37//! ```rust
38//! # #[cfg(all(feature = "jiff-tz", feature = "parse", feature = "lang"))]
39//! # {
40//! use deep_time::{Dt, Lang, LiteStr, ParseCfg, Scale, YmdHms};
41//!
42//! // ============================================
43//! // Parsing
44//! // ============================================
45//!
46//! // Smart auto-parsing (multi-language + timezone)
47//! let cfg = ParseCfg {
48//! lang: Lang::Fr,
49//! ..Default::default()
50//! };
51//! let dt = Dt::from_str_parse("15 août 2024 à 14:30 [Europe/Paris]", &cfg).unwrap();
52//! let s = dt.to_str_rfc9557("Europe/Paris").unwrap();
53//! assert_eq!("2024-08-15T14:30:00+02:00[Europe/Paris]", s);
54//!
55//! // or with .parse
56//! let dt: Dt = "1 jan 2000 07:00 [America/New_York] TAI".parse().unwrap(); // noon
57//! assert_eq!(Dt::ZERO, dt);
58//!
59//! // Relative dates are also supported
60//! let ref_time = Dt::from_ymd(2026, 6, 16, Scale::UTC, 12, 0, 0, 0);
61//! let en_cfg = ParseCfg {
62//! ref_time: Some(ref_time),
63//! ..Default::default()
64//! };
65//!
66//! let dt = Dt::from_str_parse("2 days from now at 9am", &en_cfg).unwrap();
67//! assert_eq!(dt, Dt::from_ymd(2026, 6, 18, Scale::UTC, 9, 0, 0, 0));
68//!
69//! let dt = Dt::from_str_parse("next Monday at 14:00", &en_cfg).unwrap();
70//! assert_eq!(dt, Dt::from_ymd(2026, 6, 22, Scale::UTC, 14, 0, 0, 0));
71//!
72//! // Relative dates use Dt::now if the `std` feature is enabled and no
73//! // ref_time is provided in the ParseCfg
74//! let _ = Dt::from_str_parse("next Monday at 14:00", &ParseCfg::DEFAULT).unwrap();
75//!
76//! // Fast ISO parsing with time scale and no alloc output
77//! let dt = Dt::from_str_iso("2000-01-01T12:00:00 TAI").unwrap();
78//! let lite_str: LiteStr<512> = dt.to_str_lite_iso8601().unwrap();
79//! assert_eq!("2000-01-01T12:00:00+00:00", lite_str.as_str());
80//!
81//! // ============================================
82//! // Formatting
83//! // ============================================
84//!
85//! let s = dt.to_str_in_tz("%A, %d %B %Y %I:%M%P", "America/New_York", Lang::En).unwrap();
86//! assert_eq!("Saturday, 01 January 2000 07:00am", s);
87//!
88//! let s = dt.to_str_in_tz("%A, %-d de %B de %Y %H:%M", "America/New_York", Lang::Es).unwrap();
89//! assert_eq!("Sábado, 1 de enero de 2000 07:00", s);
90//!
91//! // ============================================
92//! // Duration parsing
93//! // ============================================
94//!
95//! let span: Dt = Dt::from_str_duration("3 days 12 hours", Lang::En).unwrap();
96//! let dur = span.to_str_lite_media_duration();
97//! assert_eq!("3:12:00:00", dur.to_string());
98//!
99//! // ============================================
100//! // Time scale conversions + round-tripping
101//! // ============================================
102//!
103//! let dt = Dt::from_ymd(2000, 1, 1, Scale::TAI, 0, 0, 0, 123456789);
104//! let tt = dt.to(Scale::TT);
105//! let tdb = tt.to(Scale::TDB);
106//! let ltc = tdb.to(Scale::LTC);
107//! let utc = ltc.to(Scale::UTC);
108//! let tcl = utc.to(Scale::TCL);
109//! let tcg = tcl.to(Scale::TCG);
110//! let tai = tcg.to_tai();
111//!
112//! // round trips work for pretty much everything except UTCHist
113//! assert_eq!(dt, tai);
114//! let ymd: YmdHms = tai.to_ymd();
115//! assert_eq!(ymd.attos(), 123456789);
116//!
117//! // ============================================
118//! // Other conversions
119//! // ============================================
120//!
121//! // unix
122//! let dt = Dt::from_ymd(1970, 1, 1, Scale::UTC, 0, 0, 0, 0);
123//! let unix = dt.to_unix().to_sec_f();
124//! assert_eq!(unix, 0.0);
125//!
126//! // or to milliseconds
127//! let unix: i128 = dt.add_ms(1000).to_unix().to_ms();
128//! assert_eq!(unix, 1000);
129//!
130//! // to and from jd
131//! let jd = Dt::ZERO.to_jd_f_raw();
132//! assert_eq!(2451545.0, jd);
133//! let dt = Dt::from_jd_f(jd, Scale::TAI);
134//! assert_eq!(0, dt.attos);
135//!
136//! // ============================================
137//! // Calendar math
138//! // ============================================
139//!
140//! // calendar math and negative year
141//! let dt = Dt::from_ymd(-2000, 1, 31, Scale::TAI, 12, 0, 0, 0);
142//! let ymd = dt.add_mo(1).to_ymd();
143//! assert_eq!(ymd.day(), 29);
144//!
145//! // Timezone-aware calendar math (respects DST transitions, requires jiff-tz feature)
146//! let dt = Dt::from_str_iso("2025-03-30T00:30:00Z").unwrap(); // Just before London DST start
147//!
148//! // Normal (naive) addition — ignores DST rules
149//! let normal = dt.add_hr(1);
150//!
151//! // Timezone-aware addition — correctly handles the transition
152//! let aware = dt.add_hr_tz(1, "Europe/London").unwrap();
153//!
154//! println!("Normal: {}", normal.to_str_rfc9557("Europe/London").unwrap());
155//! println!("Aware: {}", aware.to_str_rfc9557("Europe/London").unwrap());
156//!
157//! // ============================================
158//! // Leap seconds
159//! // ============================================
160//!
161//! // genuine leap second input round trips
162//! let dt: Dt = "2015-06-30T23:59:60".parse().unwrap();
163//! let s = dt.to_str_iso8601();
164//! assert_eq!("2015-06-30T23:59:60+00:00", s);
165//! # }
166//! ```
167//!
168//! ### Documentation
169//!
170//! - [Library's main documentation page](https://docs.rs/deep-time/latest)
171//! - [Changelog](https://github.com/ragardner/deep-time/blob/main/docs/CHANGELOG.md)
172//! - [The main time type](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html)
173//! - [Time scales](https://docs.rs/deep-time/latest/deep_time/enum.Scale.html)
174//!
175//! ### Installation
176//!
177//! - This crate has no default features.
178//! - Enable `parse` for the auto-parsers and `jiff-tz` for timezone support and DST-aware calendar math.
179//!
180//! For example, add this to your `Cargo.toml` in the `dependencies` section:
181//!
182//! ```toml
183//! [dependencies]
184//! deep-time = { version = "0.1", features = ["parse", "jiff-tz"] }
185//! ```
186//!
187//! ### Feature Flags
188//!
189//! | Feature | Description | Requires |
190//! |----------------------|-----------------------------------------------------------------------------|--------------|
191//! | `parse` | Enables the auto-parsers (`from_str_parse`, `from_str_duration`, etc.) | `alloc` |
192//! | `jiff-tz` | Enables timezone-aware calendar math (`add_days_tz`, `add_hr_tz`, etc.) and `to_str_in_tz` | `std` |
193//! | `jiff-tz-bundle` | Same as `jiff-tz` but bundles the full timezone database | `std` |
194//! | `jiff` | Enables basic Jiff interop | `alloc` |
195//! | `chrono` | Enables Chrono interop | `alloc` |
196//! | `hifitime` | Enables Hifitime interop | — |
197//! | `serde` | Enables `Serialize` / `Deserialize` for `Dt` and other types | `alloc` |
198//! | `js` | WebAssembly support (includes `serde` and JS bindings) | `std` |
199//! | `tsify` | TypeScript definitions via `tsify` (for WASM) | `js` |
200//! | `std` | Enables `std` functionality including `Dt::now()` and file handling | — |
201//! | `alloc` | Enables allocation (required for parsing and some conversions) | — |
202//! | `es` / `de` / `fr` | Language support, parsing different languages requires alloc, formatting does not | — |
203//! | `euro` | Enables all European languages | |
204//! | `lang` | Enables all languages | `euro` |
205//! | `panic-handler` | Provides an optional simple `#[panic_handler]` for `no_std` environments | `no_std` |
206//! | `defmt` | Enables `defmt::Format` trait implementations the main types. Intended for use with the `defmt` logging framework on embedded systems. | — |
207//! | `wire` | Enables wire format (serialization) support | — |
208//! | `tdb_fairhead1990` | Replaces the fast TDB and TCB conversions with the full ERFA TDB model | — |
209//! | `physics` | Enables relativistic physics support (`Drift`, `Spacetime`, `Position`, `Velocity`, `Observer`, light-time, etc.) | — |
210//! | `mars` | Enables Mars time support (`to_msd`, `to_mars_ls`, etc.) | — |
211//! | `sidereal` | Enables sidereal time support | — |
212//! | `eop` | Enables Earth Orientation Parameters (UT1, etc.) | `alloc` |
213//! | `locale` | Enables system locale detection | `std` |
214//!
215//! #### Optional No-Alloc Panic Handler
216//!
217//! `deep-time` supports `no_std` + `no_alloc` environments. When targeting bare-metal or embedded systems, you can enable a minimal panic handler:
218//!
219//! ```toml
220//! [dependencies]
221//! deep-time = { version = "0.1", features = ["panic-handler"] }
222//! ```
223//!
224//! This provides a simple `#[panic_handler]` that uses `core::hint::spin_loop()` (more power-efficient than a plain `loop {}`).
225//!
226//! You only need this if you are building a binary crate in a `no_std` environment without your own panic handler.
227//!
228//! #### Notes
229//!
230//! - The fast ISO 8601 parser (`from_str_iso`) works **without** the `parse` feature.
231//! - Multi-language **parsing** requires the `parse` feature, but multi-language **formatting** works without it.
232//! - The `.parse()` implementation on `Dt` automatically chooses between the full parser and the ISO parser depending on enabled features.
233
234#![forbid(unsafe_code)]
235#![cfg_attr(test, allow(clippy::all))]
236#![cfg_attr(not(feature = "std"), no_std)]
237
238#[cfg(feature = "alloc")]
239extern crate alloc;
240#[cfg(feature = "std")]
241extern crate std;
242
243// ──────────────────────────────────────────────────────────────
244// Optional panic handler (opt-in via feature)
245// ──────────────────────────────────────────────────────────────
246#[cfg(all(feature = "panic-handler", not(feature = "std"), not(test)))]
247use core::panic::PanicInfo;
248
249#[cfg(all(feature = "panic-handler", not(feature = "std"), not(test)))]
250#[panic_handler]
251fn panic(_info: &PanicInfo) -> ! {
252 // Uses spin_loop() for better power characteristics than plain loop{}
253 loop {
254 core::hint::spin_loop();
255 }
256}
257
258/// Alias for f64, maybe upgrade one day
259pub type Real = f64;
260
261/// Convert a number to the crates [`Real`] type (f64).
262///
263/// Equivalent to `n as f64`.
264#[macro_export]
265macro_rules! f {
266 ($x:expr) => {
267 $x as $crate::Real
268 };
269}
270
271/// Safe Euclidean division.
272/// Returns `default` if `rhs == 0` or if `lhs == i128::MIN && rhs == -1`.
273macro_rules! safe_div_euc {
274 ($lhs:expr, $rhs:expr, $default:expr) => {{
275 match ($lhs).checked_div_euclid($rhs) {
276 Some(q) => q,
277 None => $default,
278 }
279 }};
280}
281
282/// Safe Euclidean remainder.
283/// Returns `$default` if `rhs == 0` or if `lhs == Self::MIN && rhs == -1`.
284macro_rules! safe_rem_euc {
285 ($lhs:expr, $rhs:expr, $default:expr) => {{
286 match ($lhs).checked_rem_euclid($rhs) {
287 Some(r) => r,
288 None => $default,
289 }
290 }};
291}
292
293/// Turns a list of string literals into an array of `&'static [u8]`.
294macro_rules! byte_arrays {
295 ( $( $s:literal ),+ $(,)? ) => {
296 [ $( $s.as_bytes() ),+ ]
297 };
298}
299
300// _________________________________________
301// FEATURE MOD
302// _________________________________________
303#[cfg(feature = "parse")]
304mod alloc_parse;
305#[cfg(feature = "physics")]
306mod physics;
307
308// _________________________________________
309// MOD
310// _________________________________________
311mod dt;
312mod lite_str;
313mod locale;
314mod scale;
315mod strtime;
316mod time_range;
317mod ymdhms;
318
319// _________________________________________
320// PUB MOD
321// _________________________________________
322pub mod an_err;
323pub mod civil_parts;
324pub mod constants;
325pub mod error;
326pub mod historical_utc;
327pub mod leap_seconds;
328pub mod math;
329pub mod tz;
330
331// _________________________________________
332// FEATURE PUB MOD
333// _________________________________________
334#[cfg(feature = "eop")]
335pub mod eop;
336
337#[cfg(feature = "sidereal")]
338pub mod sidereal;
339
340// _________________________________________
341// FEATURE CRATE USE
342// _________________________________________
343#[cfg(feature = "parse")]
344pub(crate) use alloc_parse::{
345 alloc_constants::*, date::*, date_classification::*, duration::*, helpers::*, parse_date::*,
346 types::*,
347};
348#[cfg(feature = "parse")]
349pub(crate) use locale::{lang_data::*, lang_map::*};
350
351// _________________________________________
352// CRATE USE
353// _________________________________________
354pub(crate) use civil_parts::*;
355pub(crate) use constants::*;
356pub(crate) use locale::*;
357#[allow(unused_imports)]
358pub(crate) use math::{
359 atan2::atan2,
360 cos::cos,
361 div::rem_euclid_f,
362 floor::floor_f,
363 log::log,
364 sin::sin,
365 sqrt::{hypot, sqrt},
366};
367pub(crate) use strtime::*;
368
369// _________________________________________
370// FEATURE PUB USE
371// _________________________________________
372#[cfg(feature = "tdb_fairhead1990")]
373pub use dt::tdb_fairhead1990;
374
375#[cfg(feature = "parse")]
376pub use alloc_parse::types::{Mode, Order, ParseCfg};
377
378#[cfg(feature = "mars")]
379pub use dt::mars;
380
381#[cfg(feature = "sidereal")]
382pub use sidereal::Sidereal;
383
384#[cfg(feature = "physics")]
385pub use physics::{
386 drift::Drift, observer::Observer, position::Position, spacetime::Spacetime, velocity::Velocity,
387};
388
389// _________________________________________
390// PUB USE
391// _________________________________________
392pub use an_err::AnErr;
393pub use dt::Dt;
394pub use dt::lunar;
395pub use dt::numbers_traits::{AttosTraits, TimeTraits};
396pub use error::{DtErr, DtErrKind};
397pub use lite_str::LiteStr;
398pub use locale::Lang;
399pub use scale::Scale;
400pub use strtime::StrPTimeFmt;
401pub use time_range::{Every, TimeRange};
402pub use ymdhms::YmdHms;