kodo 0.6.2

A CLI tool for analyzing Git commit statistics with TUI visualization
Documentation
use chrono::{DateTime, Local, Utc};
use chrono_tz::Tz;

#[derive(Debug, Clone)]
pub enum TimeZoneMode {
    Local,
    Utc,
    Named(Tz),
}

impl TimeZoneMode {
    pub fn parse(input: &str) -> Result<Self, String> {
        let normalized = input.trim();
        if normalized.eq_ignore_ascii_case("local") {
            return Ok(Self::Local);
        }
        if normalized.eq_ignore_ascii_case("utc") {
            return Ok(Self::Utc);
        }

        normalized
            .parse::<Tz>()
            .map(Self::Named)
            .map_err(|_| format!("invalid timezone: {input}. Use local, utc, or an IANA name like Asia/Tokyo"))
    }

    pub fn date_naive(&self, utc: DateTime<Utc>) -> chrono::NaiveDate {
        match self {
            Self::Local => utc.with_timezone(&Local).date_naive(),
            Self::Utc => utc.date_naive(),
            Self::Named(tz) => utc.with_timezone(tz).date_naive(),
        }
    }

    pub fn datetime(&self, utc: DateTime<Utc>) -> DateTime<chrono::FixedOffset> {
        match self {
            Self::Local => utc.with_timezone(&Local).fixed_offset(),
            Self::Utc => utc.fixed_offset(),
            Self::Named(tz) => utc.with_timezone(tz).fixed_offset(),
        }
    }

    pub fn now_date_naive(&self) -> chrono::NaiveDate {
        match self {
            Self::Local => Local::now().date_naive(),
            Self::Utc => Utc::now().date_naive(),
            Self::Named(tz) => Utc::now().with_timezone(tz).date_naive(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::TimeZone;

    #[test]
    fn parses_named_timezone() {
        let tz = TimeZoneMode::parse("America/New_York").expect("valid timezone");
        assert!(matches!(tz, TimeZoneMode::Named(_)));
    }

    #[test]
    fn handles_dst_offsets_for_named_timezone() {
        let tz = TimeZoneMode::parse("America/New_York").expect("valid timezone");

        let winter = Utc.with_ymd_and_hms(2024, 1, 15, 12, 0, 0).unwrap();
        let summer = Utc.with_ymd_and_hms(2024, 7, 15, 12, 0, 0).unwrap();

        let winter_offset = tz.datetime(winter).offset().local_minus_utc();
        let summer_offset = tz.datetime(summer).offset().local_minus_utc();

        assert_ne!(winter_offset, summer_offset);
    }
}