use chrono::{DateTime, Utc};
use std::time::{SystemTime, UNIX_EPOCH};
pub trait Clock {
fn now_iso8601(&self) -> String;
fn now_compact(&self) -> String;
fn now_unix(&self) -> u64 {
DateTime::parse_from_rfc3339(&self.now_iso8601())
.map(|d| d.timestamp().max(0) as u64)
.unwrap_or(0)
}
}
pub trait IdGen {
fn short(&self) -> String;
}
#[derive(Debug, Clone, Copy)]
pub struct SystemClock;
impl Clock for SystemClock {
fn now_iso8601(&self) -> String {
let now: DateTime<Utc> = Utc::now();
now.format("%Y-%m-%dT%H:%M:%SZ").to_string()
}
fn now_compact(&self) -> String {
let now: DateTime<Utc> = Utc::now();
now.format("%Y%m%dT%H%M%SZ").to_string()
}
}
#[derive(Debug, Clone, Copy)]
pub struct NanoIdGen;
impl IdGen for NanoIdGen {
fn short(&self) -> String {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.subsec_nanos())
.unwrap_or(0);
format!("{nanos:08x}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn system_clock_formats_match_spec() {
let c = SystemClock;
let iso = c.now_iso8601();
assert_eq!(iso.len(), 20, "{iso}");
assert!(iso.ends_with('Z'));
let compact = c.now_compact();
assert_eq!(compact.len(), 16, "{compact}");
assert!(compact.ends_with('Z'));
}
#[test]
fn nano_id_gen_produces_eight_hex_chars() {
let id = NanoIdGen.short();
assert_eq!(id.len(), 8);
assert!(id.chars().all(|c| c.is_ascii_hexdigit()));
}
}