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
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
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.
My journey in software testing began with Calabash and Cucumber, where I delved into Mobile Automation using Ruby. As my career progressed, I gained experience working with a diverse range of technical tools. These include e-Discovery, Selenium, Python, Docker, 4G/5G testing, M-CORD, CI/CD implementation, Page Object Model framework, API testing, Testim, WATIR, MockLab, Postman, and Great Expectation. Recently, I’ve also ventured into learning Rust, expanding my skillset further. Additionally, I am a certified Scrum Master, bringing valuable agile expertise to projects.
On the personal front, I am a very curious person on any topic. According to Myers-Briggs Type Indicator, I am described as INFP-T. I like playing soccer, running and reading books.
One thought on “%1$s”