How to Get the Current Date and Time in Rust

Use the `chrono` crate to get the current date and time, as the standard library's `std::time::SystemTime` lacks convenient formatting and calendar methods.

When the clock isn't enough

You're building a distributed logger. Every node needs to stamp messages with the current time. You grab std::time::SystemTime::now(), get a struct, and realize there's no way to turn it into "2024-05-20T14:30:00Z" without writing a conversion function. You try to parse the seconds since the epoch, but now you're wrestling with leap seconds and timezone offsets. The standard library gives you the raw clock ticks. It doesn't know about calendars.

Rust separates the clock from the calendar. std::time::SystemTime tracks the system clock. It's perfect for measuring duration or storing a raw timestamp. It has no concept of months, years, or time zones. For human-readable dates, you need the chrono crate. chrono is the de facto standard for date and time in Rust. It wraps the system clock and adds the calendar layer. It handles leap years, time zones, and formatting.

Add chrono to your Cargo.toml:

[dependencies]
chrono = "0.4"

The basic pattern

chrono provides two primary ways to get the current time: Utc::now() and Local::now(). Utc::now() returns the current time in Coordinated Universal Time. Local::now() returns the current time in the system's configured timezone. Both return a DateTime struct, but the timezone is part of the type.

use chrono::{Local, Utc};

fn main() {
    // Utc::now() returns the current time in Coordinated Universal Time.
    // Use UTC for logging, databases, and network protocols.
    let utc_now = Utc::now();
    println!("UTC: {}", utc_now);

    // Local::now() returns the current time in the system's timezone.
    // Use Local for user-facing displays.
    let local_now = Local::now();
    println!("Local: {}", local_now);
}

The output uses ISO 8601 format by default. chrono implements the Display trait on DateTime, so you can print the time directly without calling .to_string(). This is a convention in Rust: types that represent values usually implement Display for a sensible default representation.

Timezones are types

In many languages, a datetime object holds a timezone as a field. In Rust, chrono uses the type system. DateTime<Utc> and DateTime<Local> are distinct types. You cannot pass a Local time to a function expecting Utc without an explicit conversion. This catches bugs at compile time.

use chrono::{DateTime, Local, Utc};

/// Expects a UTC timestamp for storage.
fn store_timestamp(dt: DateTime<Utc>) {
    println!("Storing: {}", dt);
}

fn main() {
    let local_time = Local::now();
    
    // This fails to compile. E0308 mismatched types.
    // store_timestamp(local_time);
    
    // Convert explicitly.
    let utc_time: DateTime<Utc> = local_time.with_timezone(&Utc);
    store_timestamp(utc_time);
}

If you try to pass the wrong timezone, the compiler rejects you with E0308 (mismatched types). The error message tells you exactly which types don't match. This forces you to think about timezones at the API boundary.

Trust the type system. If you have a Local time and need Utc, convert it explicitly. The compiler will stop you from passing the wrong timezone to a function.

Formatting dates

chrono uses a format method that takes a string pattern. The patterns follow the strftime convention, not Rust's format! macro syntax. %Y is the year, %m is the month, %H is the hour. The method returns a DelayedFormat wrapper, not a String. This is a performance optimization. DelayedFormat holds the date and the format string. It only allocates memory when you call .to_string() or print it.

use chrono::Utc;

fn main() {
    let now = Utc::now();
    
    // format() returns DelayedFormat, which implements Display.
    // You can print it directly without allocation.
    println!("{}", now.format("%Y-%m-%d %H:%M:%S.%3f"));
    
    // If you need a String, call .to_string().
    let timestamp_str = now.format("%Y-%m-%d").to_string();
    println!("Stored: {}", timestamp_str);
}

The %3f pattern adds milliseconds. chrono supports nanoseconds with %9f, but most databases and logs only need milliseconds. Using %3f keeps the output readable.

Convention aside: chrono's format method is case-sensitive. %Y is the year, %y is the two-digit year. %m is the month with leading zero, %b is the abbreviated month name. Check the documentation for the full list. A typo in the format string produces garbage output, not a compile error.

Call .to_string() on DelayedFormat only when you need to store the result. Otherwise, pass it directly to println! to avoid unnecessary allocation.

Realistic example: logging with context

A common use case is logging messages with timestamps. You want UTC for consistency, but you might also want to display the local time for debugging. Here's a pattern that handles both.

use chrono::{DateTime, Local, Utc};

/// Logs a message with a UTC timestamp.
fn log_message(message: &str) {
    // Get current UTC time.
    let now = Utc::now();
    
    // Format the timestamp for the log line.
    // %Y-%m-%d %H:%M:%S.%3f produces "2024-05-20 14:30:00.123".
    let formatted = now.format("%Y-%m-%d %H:%M:%S.%3f");
    
    println!("[{}] {}", formatted, message);
}

/// Converts a UTC time to local time for display.
fn show_local_time(utc_time: DateTime<Utc>) {
    // Convert to local timezone.
    let local_time: DateTime<Local> = utc_time.with_timezone(&Local);
    
    // Format for user display.
    let display = local_time.format("%B %d, %Y at %I:%M %p");
    println!("Local time: {}", display);
}

fn main() {
    log_message("Application started");
    
    let now = Utc::now();
    show_local_time(now);
}

The with_timezone method converts between timezones without changing the instant in time. It adjusts the clock reading to match the target timezone. This is safe and reversible.

Pitfalls and errors

Mixing SystemTime and DateTime is a common source of confusion. SystemTime::now() returns SystemTime. Utc::now() returns DateTime<Utc>. They are different types. You can convert SystemTime to DateTime<Utc> using .into(), but you can't just swap them.

use std::time::SystemTime;
use chrono::Utc;

fn main() {
    let sys_time = SystemTime::now();
    
    // Convert SystemTime to DateTime<Utc>.
    let chrono_time = sys_time.into();
    
    // This fails. E0308 mismatched types.
    // let wrong: chrono::DateTime<Utc> = sys_time;
}

If you try to assign SystemTime to a DateTime variable, the compiler rejects you with E0308. The fix is to call .into() or use Utc::now() directly.

Another pitfall is the timezone database. Local::now() relies on the system's timezone database. On minimal Docker containers or embedded systems, this database might be missing. If tzdata is not installed, Local::now() might panic or return incorrect results. Always install tzdata in production images.

Verify your timezone database in production containers. A missing tzdata file turns Local::now() into a panic.

Decision matrix

Use chrono::Utc::now() when you need a timestamp for logging, database storage, or network protocols. UTC eliminates timezone ambiguity and keeps your data consistent across servers.

Use chrono::Local::now() when you are displaying the time to a user in their local environment. The result respects the system's timezone and daylight saving rules.

Use std::time::SystemTime::now() when you only need to measure elapsed time or store a raw timestamp without any calendar logic. This avoids pulling in external dependencies for simple duration checks.

Use chrono::format when you need a custom string representation of a date. The crate handles the conversion from internal components to a formatted string, supporting standard strftime patterns.

Reach for DateTime::from_timestamp when you are receiving a Unix timestamp from an API and need to convert it to a readable date. This parses the seconds since the epoch into a full calendar date.

Where to go next