Documentation
use anyhow::*;
use chrono::{DateTime, Datelike, Local, TimeZone, Utc};
use std::time::{Duration, SystemTime};

use lazy_static::lazy_static;

lazy_static! {
    static ref EAST_8: chrono::FixedOffset = chrono::FixedOffset::east_opt(8 * 3600).unwrap();
}

fn east_8_zone() -> &'static chrono::FixedOffset {
    &EAST_8
}

fn from_utc_to_east_8(
    utc: &chrono::DateTime<chrono::Utc>,
) -> chrono::DateTime<chrono::FixedOffset> {
    utc.with_timezone(east_8_zone())
}

#[allow(dead_code)]
fn from_east_8_to_utc(
    east_8: &chrono::DateTime<chrono::FixedOffset>,
) -> chrono::DateTime<chrono::Utc> {
    east_8.with_timezone(&chrono::Utc)
}

pub fn get_current_time() -> chrono::DateTime<chrono::Utc> {
    chrono::Utc::now()
}

pub fn get_current_time_east_8() -> chrono::DateTime<chrono::FixedOffset> {
    from_utc_to_east_8(&get_current_time())
}

pub fn get_current_time_east_8_str() -> String {
    get_current_time_east_8()
        .format("%Y-%m-%d %H:%M:%S")
        .to_string()
}

pub fn get_current_time_east_8_rfc3339() -> String {
    get_current_time_east_8().to_rfc3339()
}

pub fn get_current_time_east_8_rfc2822() -> String {
    get_current_time_east_8().to_rfc2822()
}

pub fn get_current_ymd() -> String {
    get_current_time_east_8().format("%Y%m%d").to_string()
}

pub fn get_current_year() -> i32 {
    get_current_time_east_8().year()
}

pub fn get_current_month() -> u32 {
    get_current_time_east_8().month()
}

pub fn get_current_day() -> u32 {
    get_current_time_east_8().day()
}

#[derive(Debug, Default, Clone, Copy)]
pub struct TimeUtil {}

impl TimeUtil {
    pub fn get_now_duration() -> anyhow::Result<Duration> {
        let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
        Ok(now)
    }

    pub fn get_now_nanos_timestamp() -> anyhow::Result<u128> {
        let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
        Ok(now.as_nanos())
    }

    pub fn get_now_micros_timestamp() -> anyhow::Result<u128> {
        let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
        Ok(now.as_micros())
    }

    pub fn get_now_millis_timestamp() -> anyhow::Result<u128> {
        let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
        Ok(now.as_millis())
    }

    pub fn get_now_secs_timestamp() -> anyhow::Result<u64> {
        let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
        Ok(now.as_secs())
    }

    pub fn get_date_from_nano_timestamp(timestamp: u128) -> anyhow::Result<String> {
        let seconds = timestamp / 1000000000;
        let nano_se = timestamp % 1000000000;
        let d = DateTime::<Utc>::from_naive_utc_and_offset(
            DateTime::from_timestamp(seconds as i64, nano_se as u32)
                .unwrap()
                .naive_utc(),
            Utc,
        );
        let local_d = Local.from_utc_datetime(&d.naive_local());
        let d_str = local_d.format("%Y-%m-%d %H:%M:%S.%f").to_string();
        Ok(d_str)
    }

    pub fn get_rfc3339_from_micro_timestamp(timestamp: u128) -> String {
        let seconds = timestamp / 1000000;
        let nano_se = timestamp % 1000000;
        let d = DateTime::<Utc>::from_naive_utc_and_offset(
            DateTime::from_timestamp(seconds as i64, nano_se as u32)
                .unwrap()
                .naive_utc(),
            Utc,
        );
        let d = from_utc_to_east_8(&d);
        d.to_rfc3339()
    }

    pub fn get_rfc3339_from_nano_timestamp(timestamp: u128) -> String {
        let seconds = timestamp / 1000000000;
        let nano_se = timestamp % 1000000000;
        let d = DateTime::<Utc>::from_naive_utc_and_offset(
            DateTime::from_timestamp(seconds as i64, nano_se as u32)
                .unwrap()
                .naive_utc(),
            Utc,
        );
        let d = from_utc_to_east_8(&d);
        d.to_rfc3339()
    }
}

#[cfg(test)]
mod time_tests {
    use super::*;

    #[test]
    fn time_util_test() {
        let nano_time = TimeUtil::get_now_nanos_timestamp().unwrap();
        println!("nano time: {}", nano_time);

        let nano_str = TimeUtil::get_date_from_nano_timestamp(nano_time).unwrap();
        println!("nano str: {}", nano_str);
    }
}