deep-time 0.1.0-beta.20

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

#![forbid(unsafe_code)]
#![cfg_attr(test, allow(clippy::all))]
#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;

// ──────────────────────────────────────────────────────────────
// Optional panic handler (opt-in via feature)
// ──────────────────────────────────────────────────────────────
#[cfg(all(feature = "panic-handler", not(feature = "std"), not(test)))]
use core::panic::PanicInfo;

#[cfg(all(feature = "panic-handler", not(feature = "std"), not(test)))]
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    // Uses spin_loop() for better power characteristics than plain loop{}
    loop {
        core::hint::spin_loop();
    }
}

/// Alias for f64, maybe upgrade one day
pub type Real = f64;

/// Convert a number to the crates [`Real`] type (f64).
///
/// Equivalent to `n as f64`.
#[macro_export]
macro_rules! f {
    ($x:expr) => {
        $x as $crate::Real
    };
}

/// Safe Euclidean division.
/// Returns `default` if `rhs == 0` or if `lhs == i128::MIN && rhs == -1`.
macro_rules! safe_div_euc {
    ($lhs:expr, $rhs:expr, $default:expr) => {{
        match ($lhs).checked_div_euclid($rhs) {
            Some(q) => q,
            None => $default,
        }
    }};
}

/// Safe Euclidean remainder.
/// Returns `$default` if `rhs == 0` or if `lhs == Self::MIN && rhs == -1`.
macro_rules! safe_rem_euc {
    ($lhs:expr, $rhs:expr, $default:expr) => {{
        match ($lhs).checked_rem_euclid($rhs) {
            Some(r) => r,
            None => $default,
        }
    }};
}

/// Turns a list of string literals into an array of `&'static [u8]`.
macro_rules! byte_arrays {
    ( $( $s:literal ),+ $(,)? ) => {
        [ $( $s.as_bytes() ),+ ]
    };
}

// _________________________________________
// MOD
// _________________________________________
mod an_err;
mod dt;
mod error;
mod lite_str;
mod locale;
mod scale;
mod strtime;
mod time_range;
mod ymdhms;

#[cfg(feature = "parse")]
mod alloc_parse;

#[cfg(feature = "physics")]
mod physics;

// _________________________________________
// PUB MOD
// _________________________________________
pub mod civil_parts;
pub mod constants;
pub mod math;
pub mod tz;
pub mod utc;

#[cfg(feature = "eop")]
pub mod eop;

#[cfg(feature = "sidereal")]
pub mod sidereal;

// _________________________________________
// CRATE USE
// _________________________________________
pub(crate) use civil_parts::*;
pub(crate) use constants::*;
pub(crate) use locale::*;
#[allow(unused_imports)]
pub(crate) use math::{
    atan2::atan2,
    cos::cos,
    div::rem_euclid_f,
    floor::floor_f,
    log::log,
    sin::sin,
    sqrt::{hypot, sqrt},
};
pub(crate) use strtime::*;

#[cfg(feature = "parse")]
pub(crate) use alloc_parse::{
    alloc_constants::*, date::*, date_classification::*, duration::*, helpers::*, parse_date::*,
    types::*,
};

#[cfg(feature = "parse")]
pub(crate) use locale::{lang_data::*, lang_map::*};

// _________________________________________
// PUB USE
// _________________________________________
pub use an_err::AnErr;
pub use dt::Dt;
pub use dt::lunar;
pub use dt::numbers_traits::{AttosTraits, TimeTraits};
pub use error::{DtErr, DtErrKind};
pub use lite_str::LiteStr;
pub use locale::Lang;
pub use scale::Scale;
pub use strtime::StrPTimeFmt;
pub use time_range::{Every, TimeRange};
pub use ymdhms::YmdHms;

#[cfg(feature = "tdb_fairhead1990")]
pub use dt::tdb_fairhead1990;

#[cfg(feature = "parse")]
pub use alloc_parse::types::{Mode, Order, ParseCfg};

#[cfg(feature = "mars")]
pub use dt::mars;

#[cfg(feature = "sidereal")]
#[doc(hidden)]
pub use sidereal::Sidereal;

#[cfg(feature = "physics")]
pub use physics::{
    drift::Drift, observer::Observer, position::Position, spacetime::Spacetime, velocity::Velocity,
};