chrono & time
Rust has two mature datetime ecosystems: chrono (widely used, feature-rich) and time (stdlib-aligned, conservative API). Pick one per workspace and serialize with serde.
Recipe
# Option A: chrono
chrono = { version = "0.4", features = ["serde"] }
# Option B: time
time = { version = "0.3", features = ["serde", "formatting", "parsing"] }use chrono::{DateTime, Utc};
fn now_rfc3339() -> String {
Utc::now().to_rfc3339()
}use time::{OffsetDateTime, format_description::well_known::Rfc3339};
fn now_rfc3339_time() -> time::Result<String> {
OffsetDateTime::now_utc().format(&Rfc3339)
}When to reach for this:
- API timestamps in JSON
- Billing periods, TTLs, cron windows
- Parsing user-facing date strings
Working Example
use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct Event {
#[serde(with = "chrono::serde::ts_seconds")]
created_at: DateTime<Utc>,
}
fn expires_in(event: &Event, days: i64) -> bool {
Utc::now() > event.created_at + Duration::days(days)
}What this demonstrates:
- Store UTC in services; convert at UI edge
- Serde adapters control wire format (epoch vs RFC3339)
- Duration math for business rules
Deep Dive
Choosing chrono vs time
| Factor | chrono | time |
|---|---|---|
| Ecosystem | sqlx, many crates default | Growing, #![no_std] friendly |
| API style | DateTime<Utc> | OffsetDateTime |
| Legacy code | Common in older repos | Preferred for new security-sensitive parsers |
RFC3339 for APIs
Prefer ISO-8601 strings in JSON for human debugging. Use epoch seconds only when bandwidth matters.
Gotchas
- Local timezone in servers - DST bugs. Fix: UTC internally;
chrono-tzortimeoffset only at display. - Mixing chrono and time - conversion pain. Fix: workspace standard.
- NaiveDateTime for instants - ambiguous. Fix: always attach offset or use UTC.
- Parsing without validation - silent garbage. Fix: strict parsers and
Resultpropagation. - Subsecond precision in DB - truncation. Fix: match column type (TIMESTAMPTZ).
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
std::time::Instant | Benchmarks, timeouts | User-visible dates |
humantime | CLI duration strings | Typed domain models |
DB NOW() | Persistence authority | Client-generated timestamps |
FAQs
Is chrono deprecated?
Maintenance continues; time is an alternative, not a forced migration.
How do I serialize RFC3339?
chrono: to_rfc3339() or serde with chrono::serde::ts_seconds_option variants.
Timestamps in sqlx?
Use chrono::DateTime<Utc> or time::OffsetDateTime with compatible DB types.
Testing time-dependent code?
Inject a clock trait or pass DateTime<Utc> from caller; avoid hard-coded Utc::now() in core logic.
Related
- serde - wire formats
- reqwest & sqlx - API and DB boundaries
- Essential Crates Best Practices
- Configuration in prod
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.