deep-time 0.1.0-beta.19

High-precision, no-std, no-alloc date-time library, leap-seconds, time scales, relativistic time, and a powerful date & duration parser
Documentation
//! # deep-time
//!
//! A fully featured and high performance **Rust date and time library** with attosecond precision that provides **astronomical** and **civil** timekeeping.
//!
//! [docs.rs](https://docs.rs/deep-time)
//! [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/docs/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.
//! - 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.

#![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() ),+ ]
    };
}

// _________________________________________
// FEATURE MOD
// _________________________________________
#[cfg(feature = "parse")]
mod alloc_parse;
#[cfg(feature = "physics")]
mod physics;

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

// _________________________________________
// PUB MOD
// _________________________________________
pub mod an_err;
pub mod civil_parts;
pub mod constants;
pub mod error;
pub mod historical_utc;
pub mod leap_seconds;
pub mod math;
pub mod tz;

// _________________________________________
// FEATURE PUB MOD
// _________________________________________
#[cfg(feature = "eop")]
pub mod eop;

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

// _________________________________________
// FEATURE CRATE USE
// _________________________________________
#[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::*};

// _________________________________________
// 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::*;

// _________________________________________
// FEATURE PUB USE
// _________________________________________
#[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")]
pub use sidereal::Sidereal;

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

// _________________________________________
// 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;