Introduction to Chrono Crate in Rust

Qxf2 is writing about commonly used Rust libraries. This series of posts is aimed at testers (like us) who want to start using Rust as part of their daily work. In this post, we will show you how to use Chrono. Chrono aims to provide all functionality needed to do correct operations on dates and times.

Getting Started

Setting up with a Rust project:
First, open a terminal and navigate to folder to make your new project. Then, run this command to start a new project with Cargo.

cargo new chrono_crate

Here ‘chrono_crate’ is the project name where you can use any name to call your project.

Install Chrono:
To get ‘chrono’ crate, change the directory to chrono_crate by using ‘cd chrono_crate’ and run following command:

cargo add chrono

or as an alternate, add these lines to your project’s Cargo.toml file:

chrono = "0.4.34"

Chrono Modules

We will cover some of the most commonly used modules from the Chrono crate. In our limited experience, these were the modules that were useful to us when working with date and time objects.

Chrono:Format

The ‘format’ module in the Chrono crate gives functionality which is related to parsing and formatting DateTime strings. It includes types and functions for parsing DateTime strings from various formats and formatting DateTime values into strings according to specific formats.

// This program demonstrates parsing a DateTtime string and formatting it using Chrono crate.
// It has two functions:
// - `new_datetime`: Parses a date and time string with a given format and returns a `DateTime<Local>` object.
// - `format_with_items`: Formats a `DateTime<Local>` object using provided format items and returns a string.
 
use chrono::format::{ParseError, StrftimeItems};
use chrono::{DateTime, Local};
 
fn new_datetime(datetime_str: &str, format_str: &str) -> Result<DateTime<Local>, ParseError> {
    DateTime::parse_from_str(datetime_str, format_str)
        .map(|datetime| datetime.with_timezone(&Local))
}
 
fn format_with_items(datetime: DateTime<Local>, items: StrftimeItems) -> String {
    datetime.format_with_items(items).to_string()
}
 
fn main() {
    // Custom date and time format
    let custom_format = "%Y-%m-%d %H:%M:%S %:z";
 
    // Date time string in custom format with timezone
    let date_time_string = "2024-01-01 02:30:00 +05:30";
 
    match new_datetime(date_time_string, custom_format) {
        Ok(parsed_datetime) => {
            // Formatting the parsed DateTime object into a different format using StrftimeItems submodule
            let new_format_items = StrftimeItems::new("%A, %d %B %Y %H:%M:%S");
            let formatted_datetime = format_with_items(parsed_datetime, new_format_items);
 
            println!("Parsed and formatted date and time: {}", formatted_datetime);
        }
        Err(err) => {
            eprintln!("Error parsing date and time: {}", err);
        }
    }
}

Here in the example script, I am parsing a date and time string using a custom format and then formatting it again using a different custom format using the ‘format’ module in the ‘Chrono’ crate.

For that, I have defined two functions i.e ‘new_datetime’ and ‘format_with_items’. The new_datetime function is a constructor, where it parses a DateTime string using the given format string and provides a Result. This Result can either contain the parsed DateTime object or a ParseError. The format_with_items function formats the DateTime using StrftimeItems and returns the formatted string.

In the ‘Main’ function define a custom date and time format and a datetime string with a timezone offset. It then tries to parse the DateTime string using the custom format. If successful, it defines a new format for the datetime and formats it. Otherwise, it prints an error message.

Chrono:Offset

The offset module within Chrono gives functionality for working with time offsets, such as time zone offsets or offsets from UTC (Coordinated Universal Time). This module allows developers to perform operations like converting between different time zones or calculating time differences accounting for offsets.

// Timezome converstion script!
// It defines two time zones, `Local` and `New York`, calculates and prints the time difference between them.
// It also creates a vector of events in UTC time zone And converts a vector of events from UTC to both local and New York time zones.
 
use chrono::offset::FixedOffset;
use chrono::prelude::{Utc, Local, TimeZone};
use chrono::Duration;
 
fn main() {
    // Define two time zones: Local and New York
    let local_timezone = Local;
    let ny_timezone = match FixedOffset::west_opt(5 * 3600) {
        Some(offset) => offset,
        None => {
            eprintln!("Failed to create New York time zone with west offset");
            match FixedOffset::east_opt(0) {
                Some(offset) => offset,
                None => {
                    eprintln!("Failed to create New York time zone with east offset");
                    let default_offset = match FixedOffset::east_opt(0) {
                        Some(offset) => offset,
                        None => {
                            eprintln!("Failed to create New York time zone with default east offset");
                            panic!("Failed to create New York time zone with any offset");
                        }
                    };
                    default_offset
                }
            }
        }
    };
 
    // Calculate and print the time difference between Local and New York
    let now_utc = Utc::now();
    let now_local = local_timezone.from_utc_datetime(&now_utc.naive_utc());
    let now_ny = ny_timezone.from_utc_datetime(&now_utc.naive_utc());
    let time_difference = now_local.signed_duration_since(now_ny);
    println!("Time difference between Local and New York: {}", time_difference);
 
    // Create a vector of events in UTC
    let events_utc = vec![
        Utc::now() + Duration::hours(1),
        Utc::now() + Duration::hours(5),
        Utc::now() + Duration::hours(10),
    ];
 
    // Convert and print events in Local time zone
    let events_local: Vec<_> = events_utc
        .iter()
        .map(|&dt| local_timezone.from_utc_datetime(&dt.naive_utc()))
        .collect();
 
    println!("Events in Local time zone: {:?}", events_local);
 
    // Convert and print events in New York time zone
    let events_ny: Vec<_> = events_utc
        .iter()
        .map(|&dt| ny_timezone.from_utc_datetime(&dt.naive_utc()))
        .collect();
 
    println!("Events in New York time zone: {:?}", events_ny);
}

In the above example, ‘chrono::prelude::{Utc, Local, TimeZone}’ and ‘chrono::offset::FixedOffset’ import the modules and traits necessary for handling dates, times, and time zones.

The ‘prelude’ module imports traits and types for date and time manipulation, while ‘offset::FixedOffset’ imports types and traits related to time zones and offsets.

Within the main function, methods for converting time zones and calculating time differences are defined. The function creates instances of time zones, specifically for Local and New York, using a ‘FixedOffset’. It calculates and prints the time difference between the given time zones and converts a set of events from UTC to both Local and New York time zones, prints the results.

Chrono:Prelude

The prelude module serves as a collection of commonly used types, traits, and functions. By importing ‘chrono::prelude’, developers can easily access components like DateTime, NaiveDateTime, Utc, and local time without repetitive imports. This module simplifies code readability and promotes efficiency by providing a convenient starting point for working with DateTime operations.

// This script demonstrate working with events and date/time calculations using the 'prelude' module.
 
// It provides methods to create a new event, calculate the number of days until the event occurs, and format the event timestamp.
// The main function creates an event, prints its details, calculates the days until the event
// occurs from the current date, and formats the event timestamp.
 
use chrono::prelude::{Local, TimeZone, DateTime};
 
struct Event {
    name: String,
    timestamp: DateTime<Local>,
}
 
impl Event {
    fn new(name: &str, timestamp: DateTime<Local>) -> Self {
        Event {
            name: name.to_string(),
            timestamp,
        }
    }
 
    fn days_until(&self, other: DateTime<Local>) -> i64 {
        let duration = other.signed_duration_since(self.timestamp);
        duration.num_days()
    }
}
 
fn main() {
    // Create an event
    let event_name = "Meeting";
    let event_timestamp = Local.ymd(2024, 2, 15).and_hms(14, 30, 0); 
    let event = Event::new(event_name, event_timestamp);
 
    // Print event details
    println!("Event: {} at {}", event.name, event.timestamp);
 
    // Calculate days until the event
    let today = Local::now(); 
    let days_until_event = event.days_until(today); 
    println!("Days until {}: {}", event.name, days_until_event);
 
    // Format the event timestamp
    let formatted_timestamp = event.timestamp.format("%Y-%m-%d %H:%M:%S");
    println!("Formatted timestamp: {}", formatted_timestamp);
}

In the above example, importing the ‘prelude’ module to use its date and time functionalities. The “Event’ struct has and event’s name and timestamp. The timestamp is a type ‘DateTime‘, which is a datetime type representing the local time zone.

Inside the ‘impl’ block, two methods like ‘new ‘ and ‘days_until’ are implemented for the ‘Event ‘ struct, this will allow for the creating of new instances and calculating the time difference betweent events.

In the ‘main’ function it creates an instance of ‘Event’ struct using the ‘Local::with_ymd_and_hms’ method provided by ‘prelude’ module to specify the date and time of the event and it prints the detauls of event including the name and date and timestamp.
After that it calculates the number of days until the event occurs by using ‘Local::now’ method to get the current local time.
The ‘event.timestamp.format’ is used to format the event’s timestamp provided bt ‘DateTime‘.

Chrono Serde:

Serialization and deserialization are important while working with data. In Rust, the serde does that by providing a powerful framework for handling these tasks. When it is combined with the chrono crate, which is a date and time librsry in Rust, we can efficiently serialize and deserialize timestamps into a human-readable format.

Below is an example where we have used ts_seconds which serialize and deserialize to/from timestamps in seconds.

//In this script, Serde module is used form the chrono crate. 
//Serializing and Deserializing Timestamp in seconds
 
use serde::{Serialize, Deserialize};
use chrono::{DateTime, Utc};
 
#[derive(Debug, Serialize, Deserialize)]
pub struct SecStruct {
    #[serde(with = "chrono::serde::ts_seconds")]
    pub timestamp: DateTime<Utc>,
    pub message: String,
}
 
impl SecStruct {
    pub fn new(message: &str) -> Self {
        SecStruct {
            timestamp: Utc::now(),
            message: message.to_string(),
        }
    }
 
    pub fn to_json(&self) -> Result<String, serde_json::Error> {
        serde_json::to_string(self)
    }
 
    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
        serde_json::from_str(json)
    }
}
 
fn main() {
    // Create a new instance of SecStruct
    let sec_struct = SecStruct::new("Hello!");
 
    // Convert the struct to JSON
    let json_result = sec_struct.to_json();
    match json_result {
        Ok(json) => {
            println!("JSON representation: {}", json);
 
            // Parse the JSON back into a SecStruct
            let parsed_result = SecStruct::from_json(&json);
            match parsed_result {
                Ok(parsed_struct) => {
                    println!("Parsed struct: {:?}", parsed_struct);
                }
                Err(err) => {
                    eprintln!("Error parsing JSON: {}", err);
                }
            }
        }
        Err(err) => {
            eprintln!("Error converting struct to JSON: {}", err);
        }
    }
}

In this code, we define a SecStruct struct with two fields: timestamp and message.

The timestamp field is annotated with #[serde(with = “chrono::serde::ts_seconds”)], indicating that the ts_seconds module from the chrono crate should be used during serialization and deserialization.

The SecStruct struct also includes methods for creating a new instance, converting the struct to JSON (to_json), and parsing JSON back into a struct (from_json).

In The main function, we create an instance of SecStruct, convert the struct to JSON, and then parsing the JSON back into a SecStruct.

This code illustrates how to use the serde crate with the chrono crate’s ts_seconds module for efficient serialization and deserialization of Rust structs containing timestamp fields.

Chrono:Naive

The naive module in the chrono crate provides functionality for working with date and time without considering time zones or daylight saving time adjustments.

Below is an example of using naive from chrono crate and how it is being implemented using methods

// In this script the naive module is used to work with date and time
// without considering timezone and daylight saving adjustments.
use chrono::naive::NaiveDate;
use chrono::Local;
 
//Define a struct
struct EventDate {
    event_name: String,
    event_date: NaiveDate,
}
 
//Implement two methods for EventDate
impl EventDate {
 
    pub fn new(event_name: &str, year: i32, month: u32, day: u32) -> Result<Self, &'static str> {
 
        let event_date = NaiveDate::from_ymd_opt(year, month, day);
 
        match event_date {
            Some(date) => Ok(Self {
                event_name: String::from(event_name),
                event_date: date,
            }),
            None => Err("Invalid date provided"),
        }
    }
 
    //Check if the event is today
    fn is_today(&self) -> bool {
        let today = Local::now().date_naive();
        self.event_date == today
    }
}
 
fn main() {
 
    match EventDate::new("Rust Demo Meeting", 2023, 10, 27) {
        Ok(event) => {
 
            println!("Event: {}", event.event_name);
            println!("Event Date: {}", event.event_date);
 
            if event.is_today() {
                println!("This event is scheduled for today!");
            } else {
                println!("This event is not scheduled for today.");
            }
        }
        Err(err) => {
            eprintln!("Error: {}", err);
        }
    }
}

The code snippets used in this blog is available here.

Hire technical testers from Qxf2

Testers from Qxf2 continually learn and evolve. We have excellent testing fundamentals and constantly update our technical toolbelt. Engineering teams love working with us because we understand the problems they face and are in a position to compliment their technical abilities. If you are having a hard time hiring technical testers, get in touch with Qxf2.


One thought on “%1$s”

Leave a Reply

Your email address will not be published. Required fields are marked *