A fully featured and high performance Rust date and time library with attosecond precision that provides astronomical and civil timekeeping.
- Auto-parsers for datetimes and durations that handle thousands of formats, relative dates and multiple languages, requires the
parsefeature - No std, no alloc, and wide-spread const fn
- Extensively validated against outputs from Astropy, Jiff, and other libraries and sources
- Fast multi-format string parser (ISO-like civil, SEC/JD/MJD, …)
- Time scales e.g. UTC with leap seconds support, including historical, TT, TAI, TDB, NAIF ET, LTC, GPS, etc. An optional feature
tdb-hican be enabled which provides the ERFA TDB model - Strptime
- Strftime (multi-language day and month names available)
- First class timezone support provided by the Rust library jiff enabled with the
jiff-tzfeature - To and from all kinds of inputs and outputs, functions mostly prefixed with
toandfrom, available on the library's types, see the main time types functions: Dt. Including JD, MJD, Unix, NTP, etc. - Calendar aware and, with the
jiff-tzfeature, timezone aware math - To and from jiff, chrono, hifitime, time, and ICU4X types
- No-alloc string return type
- Const fn libm math functions
- Safe, saturating arithmetic throughout
- No
unsafein the library -#![forbid(unsafe_code)] - Lunar and Mars modules
- Sidereal time with a const fn implementation of ERFA Equation of the Origins / Equinoxes
- UT1 and EOP
- Proper time along trajectories — usage guide docs/trajectory.md. Requires the
physicsfeature - Relativity types: Drift, Spacetime, Position, Velocity — rate-model theory docs/relativity.md. Import via
deep_time::physics. Requires thephysicsfeature. - CCSDS CUC, CDS, and CCS
- Binary size is mainly controlled through feature gating
#![allow(clippy::all, clippy::pedantic, clippy::restriction, warnings)]
use deep_time::macros::{from_ns, from_ymd};
use deep_time::{BufStr, Dt, Lang, ParseCfg, Scale, YmdHms};
/*
.unwrap() is used here so that panics can be detected
during automated testing
in your actual code you do not have to use unwrap() and
should perhaps instead handle errors using Result<> etc.
*/
fn main() {
// ============================================
// 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 = from_ymd!(2026, 6, 16; 12, on=Scale::UTC);
let en_cfg = ParseCfg {
ref_time: Some(ref_time),
..Default::default()
};
// from_ymd! macro defaults to Scale::UTC
let dt = Dt::from_str_parse("2 days from now at 9am", &en_cfg).unwrap();
assert_eq!(dt, from_ymd!(2026, 6, 18; 9));
let dt = Dt::from_str_parse("next Monday at 14:00", &en_cfg).unwrap();
assert_eq!(dt, from_ymd!(2026, 6, 22; 14));
// 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", &en_cfg).unwrap();
// Fast ISO parsing with time scale and no alloc output
let dt = Dt::from_str("2000-01-01T12:00:00 TAI").unwrap();
let buf_str: BufStr<512> = dt.to_str_b_iso8601();
assert_eq!("2000-01-01T12:00:00+00:00", buf_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_b_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 = from_ymd!(1970);
let unix = dt.to_unix().to_sec_f();
assert_eq!(unix, 0.0);
let dt = Dt::from_unix(from_ns!(0, on = Scale::UTC));
assert_eq!(dt, Dt::UNIX_EPOCH);
// or to milliseconds
let unix: i128 = dt.add_ms(1000).to_unix().to_ms().0;
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 = from_ymd!(-2000, 1, 31; 12, on=Scale::TAI);
let ymd = dt.add_months(1).to_ymd();
assert_eq!(ymd.day(), 29);
// Timezone-aware calendar math (respects DST transitions, requires jiff-tz feature)
let dt = Dt::from_str("2025-03-30T00:30:00Z").unwrap(); // Just before London DST start
// Normal (naive) addition — ignores DST rules
let normal = dt.add_hours(1);
// Timezone-aware addition — correctly handles the transition
let aware = dt.add_hours_tz(1, "Europe/London").unwrap();
assert_eq!(
normal.to_str_rfc9557("Europe/London").unwrap(),
"2025-03-30T02:30:00+01:00[Europe/London]"
);
assert_eq!(
aware.to_str_rfc9557("Europe/London").unwrap(),
"2025-03-30T03:30:00+01:00[Europe/London]"
);
// ============================================
// 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);
}- This crate has no default features.
- The minimum Rust version is
1.90and minimum Rust edition is2024. This is mainly due to someconstfunctionality that only became stable recently.
To add deep-time to your Rust project with the parse and timezone features, go to your project folder and run this terminal command:
cargo add deep-time --features "parse,jiff-tz"
| Feature | Description | Requires |
|---|---|---|
parse |
Enables the auto-parsers (from_str_parse, from_str_duration, etc.) |
alloc |
jiff-tz |
Enables timezone features such as tz parsing, tz calendar math, formatting | alloc |
jiff-tz-bundle |
Same as jiff-tz but bundles the full timezone database |
alloc |
jiff |
Enables jiff interop |
— |
chrono |
Enables chrono interop |
— |
hifitime |
Enables hifitime interop |
— |
time |
Enables time interop |
— |
icu |
Enables ICU4X interop (DateTime<Iso>) |
— |
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 non-En languages requires alloc, formatting doesn't | — |
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 |
— |
wire |
Enables wire format (serialization) support | — |
tdb-hi |
Replaces the fast TDB and TCB conversions with the full ERFA TDB model | — |
physics |
Enables relativistic physics support (Drift, Spacetime, Position, Velocity, proper-time trajectory APIs). Import via deep_time::physics. |
— |
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 |
deep-time supports no_std + no_alloc environments. When targeting bare-metal or embedded systems, you can enable a minimal panic handler:
[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.
- The fast multi-format string parser (
Dt::from_str/Parts::from_str) works without theparsefeature. - Multi-language parsing requires the
parsefeature, but multi-language formatting works without it. - The
.parse()implementation onDtautomatically chooses between the full parser and the ISO parser depending on enabled features.
| Example | What it shows | Run |
|---|---|---|
precision_control |
Compare, and format times at a chosen resolution (here: one minute) | cargo run --example precision_control |
sidereal_time |
Astropy-style GMST/GAST/LMST/LAST with UT1 from IERS finals, plus hour angle | cargo run --example sidereal_time --features "sidereal-earth eop std" |
proper_time_path |
Proper time / craft-vs-ground from (t, v, Φ) samples |
cargo run --example proper_time_path --features physics |
Benchmarks were measured on an AMD Ryzen 7 7800X3D using:
cargo bench --bench perf --features "parse hifitime std jiff-tz"| deep-time vs jiff | Time | vs Jiff 0.2.31 |
|---|---|---|
Dt::from_str vs DateTime::parse |
47.6 ns | 73.6% slower |
Parts::from_strptime vs BrokenDownTime::parse |
36.3 ns | 8.8% faster |
Dt::from_strptime vs BrokenDownTime::parse+to_zoned |
194 ns | 20.8% slower |
Dt::to_str_b vs DateTime::strftime+.to_string |
75.2 ns | 23.0% slower |
Dt::to_str vs DateTime::strftime+.to_string |
87.7 ns | 43.4% slower |
Dt::from_str_parse |
517 ns | — |
| Conversion | deep-time | hifitime 4.3 | Relative Performance |
|---|---|---|---|
| TAI → UTC | 9.6 ns | 34.7 ns | 3.6× faster |
| UTC → TAI | 13.0 ns | 33.1 ns | 2.5× faster |
| TAI → TDB | 131 ns | 93.7 ns | 1.4× slower |
| TDB → TAI | 583 ns | 27.0 ns | 21.6× slower |
| GPS conversion | 20.2 ns | 5.5 ns | 3.7× slower |
| GPS week + TOW | 30.2 ns | 7.6 ns | 4.0× slower |
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.
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.
If for whatever reason you need to update the library's bundled leap seconds file and re-compile, follow these steps:
- Download the desired leap seconds file, for example from https://data.iana.org/time-zones/data/leap-seconds.list
- Place the downloaded file in the library, with the following location and filename:
deep-time/tests/assets/leap-seconds.list.txt - Then with a terminal open in the library run the command:
cargo gen-leap-seconds - This should overwrite the file
src/utc/leap_seconds_list.rsusing the data - Re-compile the library
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.