# 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://img.shields.io/docsrs/deep-time)](https://docs.rs/deep-time)
[![Crates.io](https://img.shields.io/crates/v/deep-time)](https://crates.io/crates/deep-time)
[![License](https://img.shields.io/crates/l/deep-time)](https://github.com/ragardner/deep-time/blob/main/LICENSE)

### Overview

A non-exhaustive list of functionality:

- 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
- [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
use deep_time::{Dt, DtErr, Lang, LiteStr, ParseCfg, Scale, YmdHms};

fn main() -> Result<(), DtErr> {
    // ============================================
    // 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)?;
    let s = dt.to_str_rfc9557("Europe/Paris")?;
    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()?; // 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)?;
    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)?;
    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)?;

    // Fast ISO parsing with time scale and no alloc output
    let dt = Dt::from_str_iso("2000-01-01T12:00:00 TAI")?;
    let lite_str: LiteStr<512> = dt.to_str_lite_iso8601()?;
    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)?;
    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)?;
    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)?;
    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")?; // 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")?;

    println!("Normal: {}", normal.to_str_rfc9557("Europe/London")?);
    println!("Aware:  {}", aware.to_str_rfc9557("Europe/London")?);

    // ============================================
    // Leap seconds
    // ============================================

    // genuine leap second input round trips
    let dt: Dt = "2015-06-30T23:59:60".parse()?;
    let s = dt.to_str_iso8601();
    assert_eq!("2015-06-30T23:59:60+00:00", s);

    Ok(())
}
```

### 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`    |
| `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.

### Performance

Benchmarks were measured on an AMD Ryzen 7 7800X3D.

#### Parsing and Formatting

| Operation                              | Time          | vs Jiff 0.2.28           |
|----------------------------------------|---------------|--------------------------|
| ISO datetime parsing                   | 20.4 ns       | 22.5% faster             |
| `strptime`                             | 34.3 ns       | 19.4% faster             |
| TZ `strptime` -> `Dt` vs `jiff:Zoned`  | 187 ns        | 9.8% slower              |
| `strftime`                             | 96.8 ns       | 57.5% slower             |
| Auto parser (`from_str_parse`)         | 615 ns        | —                        |

#### Time Scale Conversions

| Conversion       | deep-time     | hifitime 4.3  | Relative Performance      |
|------------------|---------------|---------------|---------------------------|
| TAI → UTC        | 9.7 ns        | 44.8 ns       | 4.6× faster               |
| UTC → TAI        | 13.1 ns       | 45.4 ns       | 3.5× faster               |
| TAI → TDB        | 136 ns        | 90.4 ns       | 1.5× slower               |
| TDB → TAI        | 610 ns        | 27.3 ns       | 22.3× slower              |
| GPS conversion   | 21.1 ns       | 5.4 ns        | 3.9× slower               |
| GPS week + TOW   | 27.5 ns       | 6.7 ns        | 4.1× slower               |

The tests were run with:

```sh
cargo test --release --features "parse hifitime std jiff-tz perf-tests" -- --nocapture perf_tests
```