commemorate 0.0.1

A CLI tool to commemorate important events.
Documentation
use chrono::{DateTime, Duration, Local, TimeZone, Utc};
use chrono_tz;
use serde::{Deserialize, Serialize};

use crate::error::{CommemorateError, CommemorateResult};

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct TimeInfo {
    pub timestamp: i64,
    pub timezone: String,
}

impl TimeInfo {
    pub fn new(timestamp: i64, timezone: String) -> Self {
        Self {
            timestamp,
            timezone,
        }
    }

    pub fn now(timezone: String) -> Self {
        Self {
            timestamp: Utc::now().timestamp(),
            timezone,
        }
    }

    pub fn to_local_string(&self) -> CommemorateResult<String> {
        let utc_time = Utc
            .timestamp_opt(self.timestamp, 0)
            .single()
            .ok_or_else(|| CommemorateError::InvalidTimeFormat("Invalid timestamp".to_string()))?;
        let local_time: DateTime<Local> = utc_time.with_timezone(&Local);
        Ok(local_time.format("%Y-%m-%d %H:%M:%S").to_string())
    }

    pub fn to_original_timezone_string(&self) -> CommemorateResult<String> {
        let utc_time = Utc
            .timestamp_opt(self.timestamp, 0)
            .single()
            .ok_or_else(|| CommemorateError::InvalidTimeFormat("Invalid timestamp".to_string()))?;
        let tz: chrono_tz::Tz = self
            .timezone
            .parse()
            .map_err(|_| CommemorateError::InvalidTimezone(self.timezone.clone()))?;
        let original_time = utc_time.with_timezone(&tz);
        Ok(original_time.format("%Y-%m-%d %H:%M:%S %Z").to_string())
    }
}

pub fn parse_time(time_str: &str, timezone: &str) -> CommemorateResult<TimeInfo> {
    let tz: chrono_tz::Tz = timezone
        .parse()
        .map_err(|_| CommemorateError::InvalidTimezone(timezone.to_string()))?;
    let naive_time = chrono::NaiveDateTime::parse_from_str(time_str, "%Y-%m-%d-%H-%M-%S")
        .map_err(|_| CommemorateError::InvalidTimeFormat(time_str.to_string()))?;
    let local_time = tz
        .from_local_datetime(&naive_time)
        .single()
        .ok_or_else(|| CommemorateError::InvalidTimeFormat(time_str.to_string()))?;
    let utc_time = local_time.with_timezone(&Utc);
    Ok(TimeInfo::new(utc_time.timestamp(), timezone.to_string()))
}

pub fn format_duration(duration: Duration) -> String {
    let years = duration.num_days() / 365;
    let days = duration.num_days() % 365;
    let hours = duration.num_hours() % 24;
    let minutes = duration.num_minutes() % 60;
    let seconds = duration.num_seconds() % 60;

    let mut parts = Vec::new();
    if years > 0 {
        parts.push(format!(
            "{} year{}",
            years,
            if years > 1 { "s" } else { "" }
        ));
    }
    if days > 0 {
        parts.push(format!("{} day{}", days, if days > 1 { "s" } else { "" }));
    }
    if hours > 0 {
        parts.push(format!(
            "{} hour{}",
            hours,
            if hours > 1 { "s" } else { "" }
        ));
    }
    if minutes > 0 {
        parts.push(format!(
            "{} minute{}",
            minutes,
            if minutes > 1 { "s" } else { "" }
        ));
    }
    if seconds > 0 || parts.is_empty() {
        parts.push(format!(
            "{} second{}",
            seconds,
            if seconds != 1 { "s" } else { "" }
        ));
    }

    parts.join(", ")
}