Skip to main content

robit_agent/
datetime.rs

1//! Date and time formatting helpers.
2
3use time::macros::format_description;
4use time::{OffsetDateTime, UtcOffset};
5
6const DATE_FORMAT: &[time::format_description::FormatItem<'_>] =
7    format_description!("[year]-[month]-[day]");
8const ISO8601_FORMAT: &[time::format_description::FormatItem<'_>] =
9    format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]");
10
11/// Get today's date in local time as a string (YYYY-MM-DD).
12///
13/// Uses the system's local UTC offset (UTC fallback when it cannot be
14/// determined — same policy as robit-ai's log rotation), so the prompt's
15/// `{date}` and the daily memory filenames (`memory-YYYY-MM-DD.md`) roll at
16/// local midnight instead of UTC midnight.
17pub fn current_date() -> String {
18    date_with_offset(
19        OffsetDateTime::now_utc(),
20        robit_ai::logging::local_utc_offset(),
21    )
22}
23
24/// Format `now` as YYYY-MM-DD at the given offset. Split out for testability.
25fn date_with_offset(now: OffsetDateTime, offset: UtcOffset) -> String {
26    now.to_offset(offset)
27        .format(DATE_FORMAT)
28        .expect("date format should be valid")
29}
30
31/// Get the current UTC timestamp as an ISO 8601-like string.
32///
33/// Deliberately UTC: these values are persisted as DB timestamps
34/// (created_at / updated_at) and must stay comparable with existing rows.
35pub fn current_timestamp() -> String {
36    OffsetDateTime::now_utc()
37        .format(ISO8601_FORMAT)
38        .expect("timestamp format should be valid")
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44    use time::macros::datetime;
45
46    fn offset(hours: i8) -> UtcOffset {
47        UtcOffset::from_hms(hours, 0, 0).unwrap()
48    }
49
50    #[test]
51    fn date_with_offset_crosses_day_forward() {
52        // 2026-09-30 23:00 UTC is already 2026-10-01 in UTC+8
53        let now = datetime!(2026-09-30 23:00:00 UTC);
54        assert_eq!(date_with_offset(now, UtcOffset::UTC), "2026-09-30");
55        assert_eq!(date_with_offset(now, offset(8)), "2026-10-01");
56    }
57
58    #[test]
59    fn date_with_offset_crosses_day_backward() {
60        // 2026-10-01 00:30 UTC is still 2026-09-30 in UTC-1
61        let now = datetime!(2026-10-01 00:30:00 UTC);
62        assert_eq!(date_with_offset(now, UtcOffset::UTC), "2026-10-01");
63        assert_eq!(date_with_offset(now, offset(-1)), "2026-09-30");
64    }
65
66    #[test]
67    fn current_date_has_expected_shape() {
68        let date = current_date();
69        assert_eq!(date.len(), 10);
70        let bytes = date.as_bytes();
71        assert!(bytes[..4].iter().all(u8::is_ascii_digit));
72        assert_eq!(bytes[4], b'-');
73        assert!(bytes[5..7].iter().all(u8::is_ascii_digit));
74        assert_eq!(bytes[7], b'-');
75        assert!(bytes[8..].iter().all(u8::is_ascii_digit));
76    }
77}