Skip to content

sebastienrousseau/dtt

DateTime (DTT) logo

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.

Build Crates.io Docs.rs Coverage lib.rs


Table of Contents


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()?)? == dt always holds. No silent date-only truncation.
  • Unambiguous timezones: IST could 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 DateTime values that represent the same instant compare equal regardless of which offset they were stored in.
  • Strict validation: is_valid_year is bounded to the actual time::Date range (-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. EDT vs EST_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


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 ARCHITECTSebastien Rousseau THE ENGINEEUXIS ᴬ Enterprise Unified Execution Intelligence System


License

Dual-licensed under Apache 2.0 or MIT, at your option.

Back to Top

About

Rust crate for date, time, and timezone manipulation. Parse, format, validate, and convert RFC 3339 / ISO 8601 with guaranteed round-trip safety.

Topics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

  •  

Contributors