use chrono::{DateTime, Datelike, Timelike, Utc, Weekday};
use jmap_types::UTCDate;
pub fn format_receipt_timestamp(dt: &UTCDate) -> String {
format_receipt_timestamp_at(dt, Utc::now())
}
pub fn format_receipt_timestamp_at(dt: &UTCDate, now: DateTime<Utc>) -> String {
let parsed = match chrono::DateTime::parse_from_rfc3339(dt.as_ref()) {
Ok(d) => d.with_timezone(&Utc),
Err(_) => return dt.as_ref().to_string(),
};
let dt_date = parsed.date_naive();
let now_date = now.date_naive();
let days_diff = (now_date - dt_date).num_days();
match days_diff {
..=0 => "Today".to_string(),
1 => "Yesterday".to_string(),
2..=6 => {
let weekday = match parsed.weekday() {
Weekday::Mon => "Mon",
Weekday::Tue => "Tue",
Weekday::Wed => "Wed",
Weekday::Thu => "Thu",
Weekday::Fri => "Fri",
Weekday::Sat => "Sat",
Weekday::Sun => "Sun",
};
format!("{} {:02}:{:02}", weekday, parsed.hour(), parsed.minute())
}
_ => {
let month = match parsed.month() {
1 => "Jan",
2 => "Feb",
3 => "Mar",
4 => "Apr",
5 => "May",
6 => "Jun",
7 => "Jul",
8 => "Aug",
9 => "Sep",
10 => "Oct",
11 => "Nov",
_ => "Dec",
};
if parsed.year() != now.year() {
format!("{} {} {}", month, parsed.day(), parsed.year())
} else {
format!("{} {}", month, parsed.day())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::TimeZone;
fn now() -> DateTime<Utc> {
Utc.with_ymd_and_hms(2024, 3, 20, 15, 0, 0).unwrap()
}
#[test]
fn format_today() {
let dt = UTCDate::from("2024-03-20T13:00:00Z");
assert_eq!(format_receipt_timestamp_at(&dt, now()), "Today");
}
#[test]
fn format_yesterday() {
let dt = UTCDate::from("2024-03-19T14:00:00Z");
assert_eq!(format_receipt_timestamp_at(&dt, now()), "Yesterday");
}
#[test]
fn format_this_week() {
let dt = UTCDate::from("2024-03-17T08:30:00Z");
assert_eq!(format_receipt_timestamp_at(&dt, now()), "Sun 08:30");
}
#[test]
fn format_old() {
let dt = UTCDate::from("2024-03-12T09:00:00Z");
assert_eq!(format_receipt_timestamp_at(&dt, now()), "Mar 12");
}
#[test]
fn format_parse_error() {
let dt = UTCDate::from("not-a-date");
assert_eq!(format_receipt_timestamp_at(&dt, now()), "not-a-date");
}
#[test]
fn format_prior_year() {
let dt = UTCDate::from("2023-01-15T09:00:00Z");
assert_eq!(format_receipt_timestamp_at(&dt, now()), "Jan 15 2023");
}
#[test]
fn format_future_clock_skew() {
let dt = UTCDate::from("2024-03-21T10:00:00Z");
assert_eq!(
format_receipt_timestamp_at(&dt, now()),
"Today",
"future timestamp must display as Today, not as a past date"
);
}
}