Skip to main content

vtcode_commons/
message_timestamp.rs

1//! Wall-clock timestamps for agent message responses.
2//!
3//! Every completed agent response carries a short local-time footer (e.g.
4//! `· 3:12 PM`) so per-message time replaces the ticking status-line clock.
5//! Formatting lives here so the TUI transcript, plain CLI output, and future
6//! desktop/export surfaces share one shape.
7
8use chrono::{DateTime, Local};
9
10/// Format a local timestamp the way agent footers display it (`3:12 PM`).
11///
12/// Twelve-hour clock without seconds or leading zeros, matching the chat-style
13/// reference. Takes the instant as a parameter so tests stay deterministic.
14pub fn format_message_time(now: DateTime<Local>) -> String {
15    now.format("%-I:%M %p").to_string()
16}
17
18/// Build the full footer line for a completed agent response (`· 3:12 PM`).
19///
20/// The leading middot marks the line as chrome in no-color output where
21/// dimming is unavailable.
22pub fn message_timestamp_footer(now: DateTime<Local>) -> String {
23    format!("· {}", format_message_time(now))
24}
25
26/// Build the footer for the current local time.
27pub fn current_message_timestamp_footer() -> String {
28    message_timestamp_footer(Local::now())
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34    use chrono::TimeZone;
35
36    fn local_at(hour: u32, minute: u32) -> DateTime<Local> {
37        Local
38            .with_ymd_and_hms(2026, 11, 2, hour, minute, 0)
39            .single()
40            .expect("fixture timestamp must be unambiguous")
41    }
42
43    #[test]
44    fn formats_afternoon_without_leading_zero() {
45        assert_eq!(format_message_time(local_at(15, 12)), "3:12 PM");
46    }
47
48    #[test]
49    fn formats_single_digit_morning_hour() {
50        assert_eq!(format_message_time(local_at(9, 5)), "9:05 AM");
51    }
52
53    #[test]
54    fn formats_double_digit_morning_hour() {
55        assert_eq!(format_message_time(local_at(11, 55)), "11:55 AM");
56    }
57
58    #[test]
59    fn midnight_is_twelve_am_not_zero() {
60        assert_eq!(format_message_time(local_at(0, 5)), "12:05 AM");
61    }
62
63    #[test]
64    fn noon_is_twelve_pm_not_zero() {
65        assert_eq!(format_message_time(local_at(12, 0)), "12:00 PM");
66    }
67
68    #[test]
69    fn footer_prefixes_time_with_middot() {
70        assert_eq!(message_timestamp_footer(local_at(15, 12)), "· 3:12 PM");
71    }
72
73    #[test]
74    fn current_footer_has_footer_shape() {
75        let footer = current_message_timestamp_footer();
76        let time = footer.strip_prefix("· ").expect("current footer must carry the middot");
77        assert!(time.contains(':'), "current footer must show clock time: {footer:?}");
78        assert!(time.ends_with("AM") || time.ends_with("PM"), "current footer must show meridiem: {footer:?}");
79    }
80}