cyberex 0.3.48

Utilities Library
Documentation
use anyhow::{Result, anyhow, bail};
use chrono::{DateTime, Local, NaiveDateTime, TimeZone as _};
use dateparser::DateTimeUtc;

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum XTimeStrFormat {
    YMDHMZ1, // "%Y%m%d%H%M%S"
    YMDHMZ2, // "%Y-%m-%d %H:%M:%S"
    YMDHMZ3, // "%Y-%m-%dT%H:%M:%S"
    Custom(String),
}

impl XTimeStrFormat {
    pub fn format_str(&self) -> &str {
        match self {
            XTimeStrFormat::YMDHMZ2 => "%Y-%m-%d %H:%M:%S",
            XTimeStrFormat::YMDHMZ3 => "%Y-%m-%dT%H:%M:%S",
            XTimeStrFormat::YMDHMZ1 => "%Y%m%d%H%M%S",
            XTimeStrFormat::Custom(s) => s.as_str(),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct XTime {
    inner: DateTime<Local>,
}

impl XTime {
    pub fn from_now() -> Self {
        let inner = Local::now();
        Self { inner }
    }

    pub fn from_timestamp_auto(timestamp: impl TryInto<i64>) -> Result<Self> {
        let timestamp = timestamp.try_into().map_err(|_e| anyhow!("Covert to i64 fail"))?;
        if timestamp > 9999999999 {
            Self::from_timestamp_ms(timestamp)
        } else {
            Self::from_timestamp_s(timestamp)
        }
    }
    pub fn from_timestamp_s(timestamp_secs: impl TryInto<i64>) -> Result<Self> {
        let timestamp_secs = timestamp_secs.try_into().map_err(|_e| anyhow!("Covert to i64 fail"))?;
        let inner = match Local.timestamp_opt(timestamp_secs, 0) {
            chrono::offset::LocalResult::Single(dt) => dt,
            _ => bail!("timestamp_opt fail"),
        };
        Ok(Self { inner })
    }
    pub fn from_timestamp_s_nocheck(timestamp_secs: impl TryInto<i64>) -> Self {
        Self::from_timestamp_s(timestamp_secs).unwrap()
    }
    pub fn from_timestamp_ms(timestamp_millis: impl TryInto<i64>) -> Result<Self> {
        let timestamp_millis = timestamp_millis
            .try_into()
            .map_err(|_e| anyhow!("Covert to i64 fail"))?;
        let inner = match Local.timestamp_millis_opt(timestamp_millis) {
            chrono::offset::LocalResult::Single(dt) => dt,
            _ => bail!("timestamp_millis_opt fail"),
        };
        Ok(Self { inner })
    }
    //
    pub fn from_timestr(time_str: &str, time_format: XTimeStrFormat) -> Result<Self> {
        let format_str = time_format.format_str();
        let naive = NaiveDateTime::parse_from_str(time_str, format_str)
            .map_err(|e| anyhow::anyhow!("Parse time_str error: {}, please check format match: {}", e, format_str))?;

        let inner = match Local.from_local_datetime(&naive) {
            chrono::offset::LocalResult::Single(dt) => dt,
            chrono::offset::LocalResult::Ambiguous(_, _) => anyhow::bail!("Ambiguous"),
            chrono::offset::LocalResult::None => anyhow::bail!("Invalid LocalTime setting in system"),
        };
        Ok(Self { inner })
    }
    pub fn from_timestr_guess(time_str: &str) -> Result<Self> {
        let inner = time_str
            .parse::<DateTimeUtc>()
            .map_err(|e| {
                anyhow::anyhow!(
                    "Check time_str format match, see: {}, error: {}",
                    "https://docs.rs/dateparser/latest/dateparser",
                    e
                )
            })?
            .0
            .with_timezone(&Local);
        Ok(Self { inner })
    }

    pub fn from_timestamp_ms_nocheck(timestamp_millis: impl TryInto<i64>) -> Self {
        Self::from_timestamp_ms(timestamp_millis).unwrap()
    }

    pub fn format_timestr(&self, time_format: XTimeStrFormat) -> String {
        self.inner.format(time_format.format_str()).to_string()
    }
    pub fn as_timestamp_ms(&self) -> u64 {
        self.inner.timestamp_millis() as _
    }

    pub fn as_timestamp_s(&self) -> u64 {
        self.inner.timestamp() as _
    }
    pub fn drag_hours(&self, hours: i64) -> Self {
        let inner = self.inner + chrono::Duration::hours(hours);
        Self { inner }
    }
    pub fn drag_minutes(&self, minutes: i64) -> Self {
        let inner = self.inner + chrono::Duration::minutes(minutes);
        Self { inner }
    }
    pub fn drag_seconds(&self, seconds: i64) -> Self {
        let inner = self.inner + chrono::Duration::seconds(seconds);
        Self { inner }
    }
    pub fn drag_millis(&self, millis: i64) -> Self {
        let inner = self.inner + chrono::Duration::milliseconds(millis);
        Self { inner }
    }
}

impl std::str::FromStr for XTime {
    type Err = anyhow::Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        XTime::from_timestr_guess(s)
    }
}

impl TryFrom<&str> for XTime {
    type Error = anyhow::Error;
    fn try_from(value: &str) -> Result<Self, Self::Error> {
        XTime::from_timestr_guess(value)
    }
}
impl TryFrom<String> for XTime {
    type Error = anyhow::Error;
    fn try_from(value: String) -> Result<Self, Self::Error> {
        XTime::from_timestr_guess(&value)
    }
}

#[cfg(test)]
mod tests {

    use std::str::FromStr as _;

    use super::*;
    #[test]
    fn test_timestamp_ms_to_local_time() {
        let timestamp_ms = 1699598631608i64;
        let xtime = XTime::from_timestamp_ms_nocheck(timestamp_ms);

        assert_eq!(
            xtime.format_timestr(XTimeStrFormat::YMDHMZ1).to_string(),
            "20231110144351"
        );
        assert_eq!(
            xtime.format_timestr(XTimeStrFormat::YMDHMZ2).to_string(),
            "2023-11-10 14:43:51"
        );

        assert_eq!(
            xtime.format_timestr(XTimeStrFormat::YMDHMZ3).to_string(),
            "2023-11-10T14:43:51"
        );
        assert_eq!(xtime.as_timestamp_ms(), timestamp_ms as u64);
        assert_eq!(XTime::from_timestamp_auto(timestamp_ms).unwrap(), xtime);
        // test after
        assert_eq!(
            xtime.drag_hours(-1).format_timestr(XTimeStrFormat::YMDHMZ2).to_string(),
            "2023-11-10 13:43:51"
        );
        assert_eq!(
            xtime.drag_hours(1).format_timestr(XTimeStrFormat::YMDHMZ2).to_string(),
            "2023-11-10 15:43:51"
        );
        assert_eq!(
            xtime
                .drag_minutes(30)
                .format_timestr(XTimeStrFormat::YMDHMZ2)
                .to_string(),
            "2023-11-10 15:13:51"
        );
        assert_eq!(
            xtime
                .drag_seconds(90)
                .format_timestr(XTimeStrFormat::YMDHMZ2)
                .to_string(),
            "2023-11-10 14:45:21"
        );
        // test before
        assert_eq!(
            xtime.drag_hours(-1).format_timestr(XTimeStrFormat::YMDHMZ2).to_string(),
            "2023-11-10 13:43:51"
        );
        assert_eq!(
            xtime
                .drag_minutes(-30)
                .format_timestr(XTimeStrFormat::YMDHMZ2)
                .to_string(),
            "2023-11-10 14:13:51"
        );
        assert_eq!(
            xtime
                .drag_seconds(-90)
                .format_timestr(XTimeStrFormat::YMDHMZ2)
                .to_string(),
            "2023-11-10 14:42:21"
        );
    }

    #[test]
    fn test_timestamp_s_to_local_time() {
        let timestamp_s = 1699598631i64;

        let xtime = XTime::from_timestamp_s(timestamp_s).unwrap();

        assert_eq!(
            xtime.format_timestr(XTimeStrFormat::YMDHMZ1).to_string(),
            "20231110144351"
        );
        assert_eq!(
            xtime.format_timestr(XTimeStrFormat::YMDHMZ2).to_string(),
            "2023-11-10 14:43:51"
        );
        assert_eq!(
            xtime.format_timestr(XTimeStrFormat::YMDHMZ3).to_string(),
            "2023-11-10T14:43:51"
        );
        assert_eq!(xtime.as_timestamp_s(), timestamp_s as u64);
        assert_eq!(
            xtime.format_timestr(XTimeStrFormat::Custom("%Y-%m-%d/%H%M%S/996_%H%M%S%.3f.ts".to_string())),
            "2023-11-10/144351/996_144351.000.ts"
        );
        assert_eq!(XTime::from_timestamp_auto(timestamp_s).unwrap(), xtime);
    }

    #[test]
    fn test_case_xtime_from_now() {
        let xtime = XTime::from_now();
        println!("xtime now: {:?}", xtime.format_timestr(XTimeStrFormat::YMDHMZ2));
    }

    #[test]
    fn test_case_from_str() {
        let xtime = XTime::from_timestr("2023-11-10 14:43:51", XTimeStrFormat::YMDHMZ2).unwrap();
        assert_eq!(
            xtime.format_timestr(XTimeStrFormat::YMDHMZ2).to_string(),
            "2023-11-10 14:43:51"
        );

        assert_eq!(
            xtime,
            XTime::from_timestr("20231110144351", XTimeStrFormat::YMDHMZ1).unwrap()
        );
    }
    #[test]
    fn test_case_from_str_guess_traits() {
        assert_eq!(
            "2023-11-10 14:43:51"
                .parse::<XTime>()
                .unwrap()
                .format_timestr(XTimeStrFormat::YMDHMZ2)
                .to_string(),
            "2023-11-10 14:43:51"
        );
        assert_eq!(
            XTime::from_str("2023-11-10 14:43:51")
                .unwrap()
                .format_timestr(XTimeStrFormat::YMDHMZ2)
                .to_string(),
            "2023-11-10 14:43:51"
        );
        assert_eq!(
            XTime::try_from("2023-11-10 14:43:51")
                .unwrap()
                .format_timestr(XTimeStrFormat::YMDHMZ2)
                .to_string(),
            "2023-11-10 14:43:51"
        );
        assert_eq!(
            XTime::try_from("2023-11-10 14:43:51".to_string())
                .unwrap()
                .format_timestr(XTimeStrFormat::YMDHMZ2)
                .to_string(),
            "2023-11-10 14:43:51"
        );
    }

    #[test]
    fn test_case_from_str_guess() {
        assert_eq!(
            XTime::from_timestr_guess("2023-11-10 14:43:51")
                .unwrap()
                .format_timestr(XTimeStrFormat::YMDHMZ2)
                .to_string(),
            "2023-11-10 14:43:51"
        );
    }
    #[test]
    fn test_case_xtime_compare() {
        assert!(
            XTime::from_timestr("2023-11-10 14:43:52", XTimeStrFormat::YMDHMZ2).unwrap()
                == XTime::from_timestr("2023-11-10 14:43:52", XTimeStrFormat::YMDHMZ2).unwrap()
        );
        assert!(
            XTime::from_timestr("2023-11-10 14:43:52", XTimeStrFormat::YMDHMZ2).unwrap()
                > XTime::from_timestr("2023-11-10 14:43:51", XTimeStrFormat::YMDHMZ2).unwrap()
        );
        assert!(
            XTime::from_timestr("2023-11-10 14:43:51", XTimeStrFormat::YMDHMZ2).unwrap()
                < XTime::from_timestr("2023-11-10 14:43:52", XTimeStrFormat::YMDHMZ2).unwrap()
        );
    }
}