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/blob/main/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_fairhead1990` 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), and [hifitime](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html#method.to_hifitime_epoch) 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();
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_mo(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_hr(1);
149//!
150//! // Timezone-aware addition — correctly handles the transition
151//! let aware = dt.add_hr_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_hr_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_fairhead1990` | 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//! ### Bundled Files
236//!
237//! 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.
238//!
239//! #### Leap Seconds
240//!
241//! 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.
242//!
243//! - [Dt::leap_sec_list_from_file](https://docs.rs/deep-time/latest/deep_time/struct.Dt.html#method.leap_sec_list_from_file)
244//! - [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)
245//! - [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)
246//!
247//! If for whatever reason you need to update the library's bundled leap seconds file and re-compile, follow these steps:
248//!
249//! 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)
250//! 2. Place the downloaded file in the library, with the following location and filename: `deep-time/tests/assets/leap-seconds.list.txt`
251//! 3. Then with a terminal open in the library run the command: `cargo gen-leap-seconds`
252//! 4. This should overwrite the file `src/utc/leap_seconds_list.rs` using the data
253//! 5. Re-compile the library
254
255#![forbid(unsafe_code)]
256#![cfg_attr(test, allow(clippy::all))]
257#![cfg_attr(not(feature = "std"), no_std)]
258
259#[cfg(feature = "alloc")]
260extern crate alloc;
261#[cfg(feature = "std")]
262extern crate std;
263
264// ──────────────────────────────────────────────────────────────
265// Optional panic handler (opt-in via feature)
266// ──────────────────────────────────────────────────────────────
267#[cfg(all(feature = "panic-handler", not(feature = "std"), not(test)))]
268use core::panic::PanicInfo;
269
270#[cfg(all(feature = "panic-handler", not(feature = "std"), not(test)))]
271#[panic_handler]
272fn panic(_info: &PanicInfo) -> ! {
273 // Uses spin_loop() for better power characteristics than plain loop{}
274 loop {
275 core::hint::spin_loop();
276 }
277}
278
279/// Alias for f64, maybe upgrade one day
280pub type Real = f64;
281
282/// Convert a number to the crates [`Real`] type (f64).
283///
284/// Equivalent to `n as f64`.
285#[macro_export]
286macro_rules! f {
287 ($x:expr) => {
288 $x as $crate::Real
289 };
290}
291
292/// Safe Euclidean division.
293/// Returns `default` if `rhs == 0` or if `lhs == i128::MIN && rhs == -1`.
294macro_rules! safe_div_euc {
295 ($lhs:expr, $rhs:expr, $default:expr) => {{
296 match ($lhs).checked_div_euclid($rhs) {
297 Some(q) => q,
298 None => $default,
299 }
300 }};
301}
302
303/// Safe Euclidean remainder.
304/// Returns `$default` if `rhs == 0` or if `lhs == Self::MIN && rhs == -1`.
305macro_rules! safe_rem_euc {
306 ($lhs:expr, $rhs:expr, $default:expr) => {{
307 match ($lhs).checked_rem_euclid($rhs) {
308 Some(r) => r,
309 None => $default,
310 }
311 }};
312}
313
314/// Turns a list of string literals into an array of `&'static [u8]`.
315macro_rules! byte_arrays {
316 ( $( $s:literal ),+ $(,)? ) => {
317 [ $( $s.as_bytes() ),+ ]
318 };
319}
320
321// _________________________________________
322// MOD
323// _________________________________________
324mod an_err;
325mod dt;
326mod error;
327mod lite_str;
328mod locale;
329mod scale;
330mod strtime;
331mod time_range;
332mod ymdhms;
333
334#[cfg(feature = "parse")]
335mod alloc_parse;
336
337#[cfg(feature = "physics")]
338mod physics;
339
340// _________________________________________
341// PUB MOD
342// _________________________________________
343pub mod civil_parts;
344pub mod constants;
345pub mod math;
346pub mod tz;
347pub mod utc;
348
349#[cfg(feature = "eop")]
350pub mod eop;
351
352#[cfg(feature = "sidereal")]
353pub mod sidereal;
354
355// _________________________________________
356// CRATE USE
357// _________________________________________
358pub(crate) use civil_parts::*;
359pub(crate) use constants::*;
360pub(crate) use locale::*;
361#[allow(unused_imports)]
362pub(crate) use math::{
363 atan2::atan2,
364 cos::cos,
365 div::rem_euclid_f,
366 floor::floor_f,
367 log::log,
368 sin::sin,
369 sqrt::{hypot, sqrt},
370};
371pub(crate) use strtime::*;
372
373#[cfg(feature = "parse")]
374pub(crate) use alloc_parse::{
375 alloc_constants::*, date::*, date_classification::*, duration::*, helpers::*, parse_date::*,
376 types::*,
377};
378
379#[cfg(feature = "parse")]
380pub(crate) use locale::{lang_data::*, lang_map::*};
381
382// _________________________________________
383// PUB USE
384// _________________________________________
385pub use an_err::AnErr;
386pub use dt::Dt;
387pub use dt::lunar;
388pub use dt::numbers_traits::{AttosTraits, TimeTraits};
389pub use error::{DtErr, DtErrKind};
390pub use lite_str::LiteStr;
391pub use locale::Lang;
392pub use scale::Scale;
393pub use strtime::StrPTimeFmt;
394pub use time_range::{Every, TimeRange};
395pub use ymdhms::YmdHms;
396
397#[cfg(feature = "tdb_fairhead1990")]
398pub use dt::tdb_fairhead1990;
399
400#[cfg(feature = "parse")]
401pub use alloc_parse::types::{Mode, Order, ParseCfg};
402
403#[cfg(feature = "mars")]
404pub use dt::mars;
405
406#[cfg(feature = "sidereal")]
407#[doc(hidden)]
408pub use sidereal::Sidereal;
409
410#[cfg(feature = "physics")]
411pub use physics::{
412 drift::Drift, observer::Observer, position::Position, spacetime::Spacetime, velocity::Velocity,
413};