//! 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() } /// Current unix timestamp in seconds — the single definition behind /// every store's `created_at` / `sent_at` / … stamp (this module owns /// the epoch-seconds convention; a dozen local copies of this fn used /// to float around both binaries). Clamps to 0 on a pre-epoch clock. #[must_use] pub fn now_unix() -> i64 { std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .ok() .and_then(|d| i64::try_from(d.as_secs()).ok()) .unwrap_or(0) } #[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)); } }