Skip to Content
Course content

131: chrono for Date and Time

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was reviewing a PR for a distributed logging system. The developer had used std::time::SystemTime to timestamp events. On the surface, it looked fine. But once we deployed to a cluster spanning three different AWS regions, the logs became a nightmare. One region was logging in UTC, another was using the system local time of the instance, and we spent an entire weekend trying to reconstruct a sequence of events that looked like they were happening in the future or the past. It was a classic case of "treating time as a simple number" instead of a complex domain.

In Rust, the standard library is intentionally lean. std::time is great for measuring how long a function takes to run (using Instant) or getting a raw system timestamp, but it's completely inadequate for calendars, timezones, or formatting a date for a user. That's where the chrono crate comes in. It is the industry standard for almost every Rust project that needs to handle "human time."

Managing UTC and Local Time

The first thing you need to wrap your head around is the difference between Utc and Local. In a professional backend environment, the rule of thumb is: always store and move time in UTC. Only convert to local time at the very last moment when you're displaying it to a human.

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

fn main() {
    // Get the current time in UTC
    let now_utc: DateTime<Utc> = Utc::now();
    println!("UTC time: {}", now_utc);

    // Get the current time based on the system's local timezone
    let now_local: DateTime<Local> = Local::now();
    println!("Local time: {}", now_local);
}

I've seen too many bugs caused by developers using Local::now() in their database records. If your server migrates to a different data center or the OS timezone is misconfigured, your data is corrupted. Stick to Utc::now() unless you have a very specific reason not to.

The Danger of Naive Types

You'll see types like NaiveDate and NaiveDateTime in the documentation. The word "naive" isn't an insult—it's a technical term. A naive date/time is one that has no timezone associated with it. It's just a date and a time, like a wall clock in a room with no windows.

use chrono::{NaiveDate, NaiveDateTime};

fn main() {
    // A specific date without a timezone
    let birthday = NaiveDate::from_yml_opt(1990, 5, 15).expect("Invalid date");
    
    // A specific date and time without a timezone
    let appointment = NaiveDateTime::parse_from_str(
        "2023-12-25 09:00:00", 
        "%Y-%m-%d %H:%M:%S"
    ).expect("Parse error");

    println!("Birthday: {}", birthday);
    println!("Appointment: {}", appointment);
}

Naive types are useful when the timezone is implied or irrelevant—like a birthday or a store's "opening hours" that apply regardless of where the store is. But if you use them for a global event, you're asking for trouble. If you have a NaiveDateTime and you need to make it "aware," you'll have to explicitly tell Rust which timezone it belongs to before converting it to a DateTime<Utc> or DateTime<Local>.

Formatting and Parsing for Users

Eventually, you have to turn these objects into strings. chrono uses a formatting syntax similar to C's strftime. It can feel a bit cryptic at first, but you'll get used to it. The most common ones are %Y for year, %m for month, and %d for day.

use chrono::{Utc, Local};

fn main() {
    let now = Utc::now();
    
    // Custom formatting: "2023-10-27 14:30:05"
    let formatted = now.format("%Y-%m-%d %H:%M:%S").to_string();
    println!("Formatted: {}", formatted);

    // You can also use predefined formats or just the Debug trait
    println!("Debug view: {:?}", now);
}

One tip: if you're building an API, don't invent your own format. Use RFC 3339 (which chrono supports out of the box via the to_rfc3339() method). It's the standard for JSON APIs and ensures that any client—whether written in TypeScript, Python, or Go—can parse your timestamps without guessing.




📋 Practical Task

Exercise: Global Event Countdown Timer

You are building a feature for a global gaming tournament. The tournament starts at a fixed UTC time, and you need to display the countdown and the local start time for the user.

Requirements:

  • Define a constant or variable for the tournament start time: 2025-12-31 23:59:59 UTC.
  • Calculate the duration between the current time (Utc::now()) and the start time.
  • Print the remaining time in a human-readable format (days, hours, minutes).
  • Convert the UTC start time to the user's Local time and print it using the format: "Your local start time is: YYYY-MM-DD HH:MM".
  • Handle the case where the tournament has already started by printing "The tournament has already begun!" instead of a negative duration.

Hint: Use chrono::Duration to handle the subtraction of two DateTime objects.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.