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}