How do I handle dates and times in Rust?

Walkthrough

Date and time handling is essential for many applications. The chrono crate provides comprehensive datetime functionality including parsing, formatting, time zones, and arithmetic. It's the de facto standard for datetime operations in Rust.

Key types:

  1. NaiveDate / NaiveTime / NaiveDateTime — dates and times without timezone awareness
  2. DateTime<Tz> — timezone-aware date and time, commonly DateTime<Utc> or DateTime<Local>
  3. Duration — represents time spans for arithmetic
  4. ParseResult — handles parsing errors from string-to-datetime conversions

Use naive types when timezones don't matter, and timezone-aware types when they do.

Code Example

# Cargo.toml
[dependencies]
chrono = "0.4"
use chrono::{
    DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc,
    format::strftime::StrftimeItems,
};
 
fn main() {
    // ===== Current Date and Time =====
    let now = Local::now();
    println!("Local now: {}", now);
    
    let utc_now = Utc::now();
    println!("UTC now: {}", utc_now);
 
    // ===== Creating Specific Dates/Times =====
    // Naive types (no timezone)
    let date = NaiveDate::from_ymd_opt(2024, 3, 15).expect("Invalid date");
    let time = NaiveTime::from_hms_opt(14, 30, 45).expect("Invalid time");
    let datetime = NaiveDateTime::new(date, time);
    println!("Naive datetime: {}", datetime);
 
    // Timezone-aware datetime
    let utc_datetime = Utc.with_ymd_and_hms(2024, 3, 15, 14, 30, 45).unwrap();
    println!("UTC datetime: {}", utc_datetime);
 
    // ===== Parsing from Strings =====
    // Common format (ISO 8601)
    let parsed: DateTime<Utc> = "2024-03-15T14:30:45Z".parse().unwrap();
    println!("Parsed UTC: {}", parsed);
 
    // Custom format
    let custom = NaiveDateTime::parse_from_str("15-03-2024 14:30:45", "%d-%m-%Y %H:%M:%S")
        .unwrap();
    println!("Custom parsed: {}", custom);
 
    // Parse date only
    let just_date = NaiveDate::parse_from_str("2024-03-15", "%Y-%m-%d").unwrap();
    println!("Just date: {}", just_date);
 
    // ===== Formatting =====
    let dt = Utc::now();
    println!("Default: {}", dt);
    println!("RFC 2822: {}", dt.to_rfc2822());
    println!("RFC 3339: {}", dt.to_rfc3339());
    println!("Custom: {}", dt.format("%A, %B %d, %Y at %I:%M %p"));
 
    // ===== Date/Time Arithmetic =====
    let start = Utc::now();
    
    // Add/subtract durations
    let later = start + Duration::hours(3);
    let earlier = start - Duration::days(7);
    println!("3 hours later: {}", later);
    println!("7 days earlier: {}", earlier);
 
    // Duration between datetimes
    let diff = later.signed_duration_since(start);
    println!("Difference: {} hours", diff.num_hours());
 
    // ===== Date Components =====
    let dt = Local::now();
    println!("Year: {}", dt.year());
    println!("Month: {}", dt.month());
    println!("Day: {}", dt.day());
    println!("Hour: {}", dt.hour());
    println!("Minute: {}", dt.minute());
    println!("Weekday: {:?}", dt.weekday());
    println!("Day of year: {}", dt.ordinal());
 
    // ===== Date Range Operations =====
    let start_date = NaiveDate::from_ymd_opt(2024, 1, 1).unwrap();
    let end_date = NaiveDate::from_ymd_opt(2024, 12, 31).unwrap();
    
    let days_in_year = (end_date - start_date).num_days();
    println!("Days in 2024: {}", days_in_year);
 
    // Iterate over dates
    let mut current = start_date;
    while current <= end_date {
        if current.weekday() == chrono::Weekday::Fri && current.day() == 13 {
            println!("Friday the 13th: {}", current);
        }
        current = current + Duration::days(1);
    }
}

Working with Time Zones

use chrono::{DateTime, FixedOffset, Utc, TimeZone};
 
fn main() {
    // Create a fixed offset timezone (UTC+8)
    let singapore = FixedOffset::east_opt(8 * 3600).unwrap();
    
    // Convert UTC to Singapore time
    let utc_time = Utc::now();
    let singapore_time: DateTime<FixedOffset> = utc_time.with_timezone(&singapore);
    
    println!("UTC: {}", utc_time);
    println!("Singapore: {}", singapore_time);
}

Summary

  • Use Local::now() / Utc::now() for current time; NaiveDate::from_ymd_opt() for specific dates
  • parse() handles ISO 8601 by default; parse_from_str() accepts custom formats with strftime specifiers
  • Format dates with .format() using specifiers like %Y, %m, %d, %H, %M, %S
  • Add and subtract Duration for datetime arithmetic; use signed_duration_since() for differences
  • Access components like .year(), .month(), .weekday() directly on datetime objects
  • Use Naive* types when timezones don't matter; DateTime<Tz> when they do
  • The unresolved feature flag enables serde serialization support