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