use chrono::{DateTime, Datelike, Local, Timelike};
pub fn format_relative_timestamp(timestamp: DateTime<Local>) -> String {
let now = Local::now();
let today = now.date_naive();
let msg_date = timestamp.date_naive();
let time_str = format_time(timestamp);
if msg_date == today {
return format!("Today at {}", time_str);
}
let yesterday = today.pred_opt().unwrap_or(today);
if msg_date == yesterday {
return format!("Yesterday at {}", time_str);
}
let date_str = format_date(msg_date);
format!("{} at {}", date_str, time_str)
}
fn format_time(timestamp: DateTime<Local>) -> String {
let hour = timestamp.hour();
let minute = timestamp.minute();
let (hour_12, period) = match hour {
0 => (12, "am"),
1..=11 => (hour, "am"),
12 => (12, "pm"),
_ => (hour - 12, "pm"),
};
format!("{:}:{:02}{}", hour_12, minute, period)
}
fn format_date(date: chrono::NaiveDate) -> String {
let month = date.format("%B").to_string();
let day = date.day();
let year = date.year();
let ordinal = match day {
1 | 21 | 31 => "st",
2 | 22 => "nd",
3 | 23 => "rd",
_ => "th",
};
format!("{} {}{}, {}", month, day, ordinal, year)
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::Local;
#[test]
fn test_format_time() {
let ts = Local::now().with_hour(18).unwrap().with_minute(41).unwrap();
let time = format_time(ts);
assert_eq!(time, "6:41pm");
}
#[test]
fn test_format_time_am() {
let ts = Local::now().with_hour(9).unwrap().with_minute(26).unwrap();
let time = format_time(ts);
assert_eq!(time, "9:26am");
}
#[test]
fn test_format_date() {
use chrono::NaiveDate;
let date = NaiveDate::from_ymd_opt(2025, 3, 21).unwrap();
let formatted = format_date(date);
assert_eq!(formatted, "March 21st, 2025");
let date = NaiveDate::from_ymd_opt(2025, 3, 22).unwrap();
let formatted = format_date(date);
assert_eq!(formatted, "March 22nd, 2025");
let date = NaiveDate::from_ymd_opt(2025, 3, 23).unwrap();
let formatted = format_date(date);
assert_eq!(formatted, "March 23rd, 2025");
let date = NaiveDate::from_ymd_opt(2025, 3, 24).unwrap();
let formatted = format_date(date);
assert_eq!(formatted, "March 24th, 2025");
}
#[test]
fn test_format_relative_today() {
let now = Local::now();
let relative = format_relative_timestamp(now);
assert!(relative.starts_with("Today at"));
}
}