use std::sync::Arc;
use cratefield_core::{Clock, IdGen, SystemClock, UlidIdGen};
use time::OffsetDateTime;
use time::format_description::well_known::Rfc3339;
pub(crate) fn iso(at: OffsetDateTime) -> String {
at.replace_nanosecond(0)
.unwrap_or(at)
.format(&Rfc3339)
.unwrap_or_default()
}
pub(crate) fn parse(stored: &str) -> OffsetDateTime {
OffsetDateTime::parse(stored, &Rfc3339).unwrap_or(OffsetDateTime::UNIX_EPOCH)
}
pub(crate) fn now_iso(clock: Option<&Arc<dyn Clock>>) -> String {
match clock {
Some(clock) => iso(clock.now()),
None => iso(SystemClock.now()),
}
}
pub(crate) fn now_unix(clock: Option<&Arc<dyn Clock>>) -> i64 {
match clock {
Some(clock) => clock.now().unix_timestamp(),
None => SystemClock.now().unix_timestamp(),
}
}
pub(crate) fn new_id(id_gen: Option<&Arc<dyn IdGen>>) -> String {
match id_gen {
Some(id_gen) => id_gen.ulid(),
None => UlidIdGen.ulid(),
}
}
pub(crate) fn plus_secs(at: &str, seconds: i64) -> String {
iso(parse(at).saturating_add(time::Duration::seconds(seconds)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_stored_spelling_drops_the_sub_second_part() {
let at = OffsetDateTime::from_unix_timestamp(1_800_000_000)
.expect("in range")
.replace_nanosecond(123_456_789)
.expect("in range");
assert_eq!(iso(at), "2027-01-15T08:00:00Z");
}
#[test]
fn a_stored_timestamp_round_trips_and_a_broken_one_is_due() {
let text = "2027-01-15T08:00:00Z";
assert_eq!(iso(parse(text)), text);
assert_eq!(parse("not a timestamp"), OffsetDateTime::UNIX_EPOCH);
}
#[test]
fn a_window_is_the_same_arithmetic_everywhere() {
assert_eq!(
plus_secs("2027-01-15T08:00:00Z", 300),
"2027-01-15T08:05:00Z"
);
assert_eq!(
plus_secs("2027-01-15T08:00:00Z", -3_600),
"2027-01-15T07:00:00Z"
);
}
}