9 releases
| 0.0.10 | Jun 7, 2026 |
|---|---|
| 0.0.9 | Dec 31, 2024 |
| 0.0.8 | Sep 2, 2024 |
| 0.0.6 | May 6, 2024 |
| 0.0.2 | Feb 10, 2023 |
#46 in Date and time
3,535 downloads per month
Used in 19 crates
(12 directly)
125KB
1.5K
SLoC
DateTime (DTT)
An ergonomic Rust library for parsing, validating, manipulating, and formatting dates, times, and timezones — with guaranteed round-trip safety and unambiguous timezone codes.
Table of Contents
- Install
- Quick Start
- Why DTT?
- Features
- Supported Timezone Abbreviations
- API Highlights
- Development
- Troubleshooting
- Documentation
- Contributing
- License
Install
cargo add dtt
Or add to Cargo.toml:
[dependencies]
dtt = "0.0.10"
Prerequisites
DTT requires Rust 1.88.0 or later (pinned by time = 0.3.47, which carries the upstream fix for RUSTSEC stack-exhaustion DoS in time < 0.3.47).
| Platform | Setup |
|---|---|
| macOS | brew install rustup-init && rustup-init -y |
| Linux | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y |
| WSL | Same as Linux, run inside your WSL distribution |
| Windows | Download rustup-init.exe from rustup.rs |
After install, verify with rustc --version (must be ≥ 1.88.0). Upgrade an existing toolchain with rustup update stable.
Quick Start
use dtt::prelude::*;
fn main() -> Result<(), AppError> {
// Current UTC time
let now = DateTime::new();
println!("Current time: {}", now);
// Parse — strict, offset-required for time-bearing inputs
let parsed = DateTime::parse("2024-01-15T10:30:00Z")?;
println!("Parsed: {}", parsed);
// Round-trip is guaranteed: parse(format(x)) == x
let s = parsed.format_rfc3339()?;
assert_eq!(parsed, DateTime::parse(&s)?);
// Arithmetic
let next_week = parsed.add_days(7)?;
let next_year = parsed.add_years(1)?;
println!("Next week: {next_week}, next year: {next_year}");
// Timezone conversion (note the explicit USA suffix)
let est = parsed.convert_to_tz("EST_USA")?;
println!("In US Eastern: {est}");
// Validation
assert!(DateTime::is_valid_iso_8601("2024-01-15T10:30:00Z"));
assert!(!DateTime::is_valid_year("10000")); // outside time crate range
Ok(())
}
Run the full demo:
cargo run --example dtt
Why DTT?
Most datetime libraries silently produce wrong answers in surprising places. DTT is designed to fail loudly rather than guess:
- Round-trip safety:
DateTime::parse(&dt.format_rfc3339()?)? == dtalways holds. No silent date-only truncation. - Unambiguous timezones:
ISTcould mean Indian (+05:30), Irish (+01:00), or Israel (+02:00). DTT requires explicit suffixes (IST_INDIA,IST_IRELAND,IST_ISRAEL) so you cannot accidentally use the wrong one. - Mixed-sign offsets rejected:
new_with_custom_offset(5, -30)returns an error instead of silently producing+05:30. - Deterministic
Default:DateTime::default()returns the Unix epoch, not wall-clock time, so tests are reproducible. - UTC-normalised equality: Two
DateTimevalues that represent the same instant compare equal regardless of which offset they were stored in. - Strict validation:
is_valid_yearis bounded to the actualtime::Daterange (-9999..=9999), so the validator and the builder always agree.
Features
| Parsing | RFC 3339 with offset, ISO 8601 date-only, custom format strings |
| Formatting | RFC 3339, custom format descriptors |
| Validation | Components, ranges, leap years, ISO 8601, time strings |
| Arithmetic | add_days, add_months, add_years (overflow-checked) |
| Comparisons | Eq, Ord, Hash — all UTC-normalised |
| Calendar helpers | start_of_week, end_of_month, iso_week, iso_year |
| Timezone support | 22 disambiguated abbreviations + custom offsets |
| Serialization | serde round-trip via canonical RFC 3339 strings |
| Cross-platform | macOS, Linux, WSL, Windows |
Supported Timezone Abbreviations
Common abbreviations are intentionally disambiguated. Bare codes like EST, CST, IST, and WADT are not accepted because they refer to multiple zones in the real world.
| Code | Offset | Region |
|---|---|---|
UTC, GMT |
+00:00 | Coordinated Universal Time |
EST_USA |
−05:00 | US Eastern Standard Time |
EDT |
−04:00 | US Eastern Daylight Time |
CST_USA |
−06:00 | US Central Standard Time |
CDT |
−05:00 | US Central Daylight Time |
MST / MDT |
−07/−06 | US Mountain |
PST / PDT |
−08/−07 | US Pacific |
CET / CEST |
+01/+02 | Central Europe |
EET / EEST |
+02/+03 | Eastern Europe |
IST_IRELAND |
+01:00 | Irish Standard Time |
IST_ISRAEL |
+02:00 | Israel Standard Time |
IST_INDIA |
+05:30 | Indian Standard Time |
JST |
+09:00 | Japan |
HKT |
+08:00 | Hong Kong |
CST_CHINA |
+08:00 | China Standard Time |
EST_AUS / AEST |
+10:00 | Australian Eastern |
AEDT |
+11:00 | Australian Eastern Daylight |
ACWST |
+08:45 | Australian Central Western |
For any other zone, use DateTime::new_with_custom_offset(hours, minutes).
Note: DST is not handled automatically. Pick the appropriate code (e.g.
EDTvsEST_USA) for the date range you care about.
API Highlights
Construction
use dtt::prelude::*;
use time::UtcOffset;
let now = DateTime::new(); // current UTC
let utc = DateTime::new_with_tz("UTC")?; // explicit
let mumbai = DateTime::new_with_tz("IST_INDIA")?; // disambiguated
let custom = DateTime::new_with_custom_offset(5, 30)?; // +05:30
let exact = DateTime::from_components(2024, 1, 15, 10, 30, 0, UtcOffset::UTC)?;
let epoch = DateTime::default(); // 1970-01-01T00:00:00Z
// Builder pattern
let dt = DateTimeBuilder::new()
.year(2024).month(1).day(15)
.hour(10).minute(30).second(0)
.offset(UtcOffset::UTC)
.build()?;
# Ok::<(), AppError>(())
Parsing & Formatting
# use dtt::prelude::*;
let dt1 = DateTime::parse("2024-01-15T10:30:00Z")?;
let dt2 = DateTime::parse("2024-01-15T10:30:00+05:30")?;
let dt3 = DateTime::parse("2024-01-15")?; // date-only OK
let custom = DateTime::parse_custom_format(
"15/01/2024 10:30",
"[day]/[month]/[year] [hour]:[minute]",
)?;
let s: String = dt1.format_rfc3339()?;
let pretty = dt1.format("[year]-[month]-[day]")?;
# Ok::<(), AppError>(())
Arithmetic & Calendar Math
# use dtt::prelude::*;
let dt = DateTime::parse("2024-01-31T00:00:00Z")?;
let next_day = dt.next_day()?;
let prev_day = dt.previous_day()?;
let next_week = dt.add_days(7)?;
let next_feb = dt.add_months(1)?; // → 2024-02-29 (leap year aware)
let next_year = dt.add_years(1)?;
let monday = dt.start_of_week()?;
let sunday = dt.end_of_week()?;
let last_day = dt.end_of_month()?;
# Ok::<(), AppError>(())
Macros
use dtt::prelude::*;
use dtt::{dtt_now, dtt_parse, dtt_add_days, dtt_diff, dtt_diff_seconds};
let now = dtt_now!();
let dt = dtt_parse!("2024-01-15T10:30:00Z")?;
let later = dtt_add_days!(dt, 7)?;
let secs: Option<i64> = dtt_diff_seconds!("1609459200", "1609459230");
assert_eq!(secs, Some(30));
# Ok::<(), AppError>(())
Development
Clone, build, and verify in under a minute on any platform:
git clone https://github.com/sebastienrousseau/dtt.git
cd dtt
make verify # fmt-check + lint + test in one command
make help # full task list
The Makefile is a thin wrapper around the underlying Cargo commands,
so the equivalent direct invocations also work:
cargo build # build the library and binary
cargo test # run all 240+ tests
cargo clippy --all-targets -- -D warnings # lint with strict warnings
cargo fmt --check # verify formatting
cargo doc --no-deps --open # open API docs in your browser
cargo run --example dtt # run the end-to-end demo
cargo bench # run criterion benchmarks
All commands work identically on macOS, Linux, and WSL. CI exercises
the same matrix on Linux, macOS, and Windows on every PR via
.github/workflows/cross-platform.yml.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
feature 'edition2024' is required from time-core |
Rust < 1.88.0 | rustup update stable |
Err(InvalidTimezone) for "EST", "CST", "IST" |
Bare ambiguous codes are rejected by design | Use a suffixed form (e.g. EST_USA, IST_INDIA) |
Err(InvalidFormat) parsing "2024-01-01T12:00:00" |
RFC 3339 requires an offset | Append Z or +HH:MM |
Err(InvalidTimezone) from new_with_custom_offset(5, -30) |
Mixed-sign offsets are rejected | Pass same-sign components, e.g. (4, 30) |
Err(InvalidDate) from from_components(10000, ...) |
time::Date only supports -9999..=9999 |
Use a year inside that range |
| Tests fail with environment-variable race | cargo test runs tests in parallel |
Already mitigated via serial_test; use cargo test -- --test-threads=1 if you have a custom env-var test |
Documentation
- API reference: https://docs.rs/dtt
- End-to-end example:
examples/dtt.rs - Benchmarks:
benches/criterion.rs— run withcargo bench - Changelog:
CHANGELOG.md - Security policy:
.github/SECURITY.md - Code of conduct:
.github/CODE-OF-CONDUCT.md
Contributing
Contributions are welcome. Please read CONTRIBUTING.md — in particular, all commits must be cryptographically signed (git commit -S).
Quick checklist before opening a PR:
cargo fmt
cargo clippy --all-targets -- -D warnings
cargo test
git commit -S -m "feat(dtt): your conventional commit message"
THE ARCHITECT ᴬ Sebastien Rousseau THE ENGINE ᵞ EUXIS ᴬ Enterprise Unified Execution Intelligence System
License
Dual-licensed under Apache 2.0 or MIT, at your option.
Dependencies
~1.2–2MB
~43K SLoC