Skip to main content

objectiveai_cli/db/
time.rs

1//! Time conversion helpers for CLI command responses.
2//!
3//! Timestamps are stored in postgres as `BIGINT` unix-seconds, but the
4//! CLI surfaces them to callers as RFC3339 strings. These helpers do
5//! that conversion at the response boundary. The format
6//! (`chrono::to_rfc3339`, numeric `+00:00` offset — not a bare `Z`)
7//! matches what `db::query` emits for `TIMESTAMPTZ` columns, so every
8//! CLI-emitted timestamp is byte-uniform.
9
10/// Render unix-seconds as an RFC3339 string.
11///
12/// `from_timestamp` only returns `None` for seconds far outside
13/// chrono's representable range — impossible for real db unix
14/// timestamps. We fall back to the unix epoch so the result is always
15/// a valid RFC3339 string and this never panics.
16pub fn unix_to_rfc3339(secs: i64) -> String {
17    chrono::DateTime::<chrono::Utc>::from_timestamp(secs, 0)
18        .unwrap_or_else(|| {
19            chrono::DateTime::<chrono::Utc>::from_timestamp(0, 0)
20                .expect("unix epoch is in range")
21        })
22        .to_rfc3339()
23}
24
25/// `Option` counterpart of [`unix_to_rfc3339`] — `None` stays `None`.
26pub fn unix_to_rfc3339_opt(secs: Option<i64>) -> Option<String> {
27    secs.map(unix_to_rfc3339)
28}
29
30#[cfg(test)]
31mod tests {
32    use super::*;
33
34    #[test]
35    fn epoch_renders_rfc3339() {
36        assert_eq!(unix_to_rfc3339(0), "1970-01-01T00:00:00+00:00");
37    }
38
39    #[test]
40    fn option_passthrough() {
41        assert_eq!(unix_to_rfc3339_opt(None), None);
42        assert_eq!(
43            unix_to_rfc3339_opt(Some(0)),
44            Some("1970-01-01T00:00:00+00:00".to_string())
45        );
46    }
47
48    #[test]
49    fn out_of_range_falls_back_to_epoch_not_panic() {
50        // i64::MAX seconds is far outside chrono's range → fallback.
51        assert_eq!(unix_to_rfc3339(i64::MAX), "1970-01-01T00:00:00+00:00");
52    }
53}