use anyhow::anyhow;
use chrono::{DateTime, Duration, NaiveDate, NaiveDateTime, NaiveTime, Utc};
use libesedb::Value;
use crate::esedb_utils::FromValue;
pub trait ToRfc3339 {
fn to_rfc3339(&self) -> String;
}
#[derive(Eq, PartialEq)]
pub struct DatabaseTime(DateTime<Utc>);
#[derive(Eq, PartialEq)]
pub struct WindowsFileTime(DateTime<Utc>);
#[derive(Eq, PartialEq)]
pub struct TruncatedWindowsFileTime(DateTime<Utc>);
impl From<i64> for DatabaseTime {
fn from(value: i64) -> Self {
let bytes = value.to_le_bytes();
let date = match NaiveDate::from_ymd_opt(
std::convert::Into::<i32>::into(bytes[5]) + 1900,
bytes[4].into(),
bytes[3].into(),
) {
Some(val) => val,
None => panic!("unable to convert '{bytes:?}' into a database date"),
};
let time = match NaiveTime::from_hms_opt(bytes[2].into(), bytes[1].into(), bytes[0].into())
{
Some(val) => val,
None => panic!("unable to convert '{bytes:?}' into a database time"),
};
let ts = DateTime::<Utc>::from_utc(NaiveDateTime::new(date, time), Utc);
Self(ts)
}
}
impl From<i64> for WindowsFileTime {
fn from(value: i64) -> Self {
let dt_base =
DateTime::<Utc>::from_utc(NaiveDate::from_ymd(1601, 1, 1).and_hms(0, 0, 0), Utc);
let duration = Duration::microseconds(value / 10);
Self(dt_base + duration)
}
}
impl From<i64> for TruncatedWindowsFileTime {
fn from(value: i64) -> Self {
let dt_base =
DateTime::<Utc>::from_utc(NaiveDate::from_ymd(1601, 1, 1).and_hms(0, 0, 0), Utc);
let duration = Duration::seconds(value);
Self(dt_base + duration)
}
}
impl From<DateTime<Utc>> for DatabaseTime {
fn from(value: DateTime<Utc>) -> Self {
Self(value)
}
}
impl From<DateTime<Utc>> for WindowsFileTime {
fn from(value: DateTime<Utc>) -> Self {
Self(value)
}
}
impl From<DateTime<Utc>> for TruncatedWindowsFileTime {
fn from(value: DateTime<Utc>) -> Self {
Self(value)
}
}
macro_rules! impl_timestamp {
($type: ident) => {
impl FromValue for $type {
fn from_value_opt(
value: &libesedb::Value,
attribute_name: &str,
) -> anyhow::Result<Option<Self>> {
match value {
Value::Currency(val) => Ok(Some($type::from(*val))),
Value::Null(()) => Ok(None),
_ => Err(anyhow!(
"invalid value detected: {:?} in field {}",
value,
attribute_name
)),
}
}
}
impl $type {
pub fn timestamp(&self) -> i64 {
self.0.timestamp()
}
}
impl ToRfc3339 for $type {
fn to_rfc3339(&self) -> String {
self.0.to_rfc3339()
}
}
};
}
impl_timestamp!(DatabaseTime);
impl_timestamp!(WindowsFileTime);
impl_timestamp!(TruncatedWindowsFileTime);