use chrono::{DateTime, Utc};
use keetanetwork_asn1::Asn1Time;
#[cfg(feature = "std")]
use chrono::SubsecRound;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BlockTime(Asn1Time);
impl BlockTime {
#[cfg(feature = "std")]
pub fn now() -> Self {
Self(Asn1Time::new(Utc::now().trunc_subsecs(3)))
}
pub fn from_unix_millis(millis: i64) -> Option<Self> {
DateTime::from_timestamp_millis(millis).map(|value| Self(Asn1Time::new(value)))
}
pub fn unix_millis(&self) -> i64 {
self.0.as_datetime().timestamp_millis()
}
}
impl Default for BlockTime {
fn default() -> Self {
Self::from(DateTime::<Utc>::UNIX_EPOCH)
}
}
impl From<DateTime<Utc>> for BlockTime {
fn from(value: DateTime<Utc>) -> Self {
Self(Asn1Time::new(value))
}
}
impl From<BlockTime> for DateTime<Utc> {
fn from(value: BlockTime) -> Self {
*value.0.as_datetime()
}
}
impl From<Asn1Time> for BlockTime {
fn from(value: Asn1Time) -> Self {
Self(value)
}
}
impl From<BlockTime> for Asn1Time {
fn from(value: BlockTime) -> Self {
value.0
}
}
#[cfg(test)]
mod tests {
use super::*;
fn time_from_str(text: &str) -> BlockTime {
BlockTime::from(
text.parse::<DateTime<Utc>>()
.expect("test datetime string must parse"),
)
}
fn unix_millis_time(millis: i64) -> BlockTime {
BlockTime::from_unix_millis(millis).expect("test unix millis must map to block time")
}
#[test]
fn test_unix_millis_round_trip() {
let time = unix_millis_time(1735787045678);
assert_eq!(time.unix_millis(), 1735787045678);
}
#[test]
fn test_truncates_sub_millisecond_precision() {
let time = time_from_str("2025-01-02T03:04:05.123456Z");
let datetime: DateTime<Utc> = time.into();
assert_eq!(datetime.timestamp_subsec_millis(), 123);
}
#[test]
fn test_now_returns_recent() {
let time = BlockTime::now();
let now = Utc::now();
let diff = (now.timestamp_millis() - time.unix_millis()).abs();
assert!(diff < 5_000);
}
#[test]
fn test_asn1_time_round_trip() {
let time = time_from_str("2025-01-02T03:04:05.500Z");
let asn1: Asn1Time = time.into();
let restored: BlockTime = asn1.into();
assert_eq!(time, restored);
}
}