//! Timestamp conventions for the wire types: fields are //! `chrono::DateTime` (serde serializes them as RFC 3339 UTC `Z` //! strings, e.g. `2026-07-02T18:30:00Z`), while sqlite storage and //! agent-facing *input* args stay unix-epoch seconds (`i64`). This //! module owns the two conversions at those boundaries. use chrono::{DateTime, Utc}; /// Convert unix-epoch seconds (the sqlite column / input-arg form) /// into the wire timestamp type. Out-of-range values (never produced /// by our clocks) clamp to the epoch rather than erroring — the db /// read path must not fail on a weird row. #[must_use] pub fn from_secs(secs: i64) -> DateTime { DateTime::::from_timestamp(secs, 0).unwrap_or_default() } // `now_unix()` (a hand-rolled-`SystemTime`-math-turned-`chrono` helper // that every store used to call for its `created_at` / `sent_at` / … // stamps) has been removed — every call site now uses // `chrono::Utc::now()` directly, either as a `DateTime` struct // field or via `.timestamp()` for the ephemeral i64-typed (sqlite // bind / cutoff arithmetic) call sites. hivectl's own separate // `now_unix()` copy in `dag_progress.rs` is unrelated and untouched. #[cfg(test)] mod tests { use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use super::from_secs; #[derive(Serialize, Deserialize, PartialEq, Debug)] struct Row { at: DateTime, #[serde(default, skip_serializing_if = "Option::is_none")] maybe_at: Option>, } #[test] fn serializes_as_rfc3339_z() { let json = serde_json::to_string(&Row { at: from_secs(1_751_480_000), maybe_at: None, }) .unwrap(); assert_eq!(json, r#"{"at":"2025-07-02T18:13:20Z"}"#); } #[test] fn round_trips_and_serializes_some() { let row = Row { at: from_secs(0), maybe_at: Some(from_secs(1_751_480_000)), }; let json = serde_json::to_string(&row).unwrap(); assert_eq!( json, r#"{"at":"1970-01-01T00:00:00Z","maybe_at":"2025-07-02T18:13:20Z"}"# ); assert_eq!(serde_json::from_str::(&json).unwrap(), row); } #[test] fn deserializes_offset_form_normalized_to_utc() { let row: Row = serde_json::from_str(r#"{"at":"2025-07-02T20:13:20+02:00"}"#).unwrap(); assert_eq!(row.at, from_secs(1_751_480_000)); } #[test] fn from_secs_clamps_out_of_range_to_epoch() { assert_eq!(from_secs(i64::MAX), from_secs(0)); } }