use chrono::{DateTime, Datelike, Timelike, Utc};
use jmap_types::UTCDate;
pub fn format_receipt_timestamp(dt: &UTCDate) -> String {
let now_str = Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
let now = UTCDate::from(now_str.as_str());
format_receipt_timestamp_at(dt, &now)
}
pub fn format_receipt_timestamp_at(dt: &UTCDate, now: &UTCDate) -> 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 now: DateTime<Utc> = match chrono::DateTime::parse_from_rfc3339(now.as_ref()) {
Ok(d) => d.with_timezone(&Utc),
Err(_) => return "Today".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 => {
format!(
"{} {:02}:{:02}",
parsed.format("%a"),
parsed.hour(),
parsed.minute()
)
}
_ => {
if parsed.year() != now.year() {
format!("{} {} {}", parsed.format("%b"), parsed.day(), parsed.year())
} else {
format!("{} {}", parsed.format("%b"), parsed.day())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn now() -> UTCDate {
UTCDate::from("2024-03-20T15:00:00Z")
}
#[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"
);
}
#[test]
fn format_parse_error_on_now() {
let dt = UTCDate::from("2024-03-20T13:00:00Z");
let bad_now = UTCDate::from("not-a-date");
assert_eq!(format_receipt_timestamp_at(&dt, &bad_now), "Today");
}
}