lifec 0.1.4

A CLI tool that displays your life calendar.
Documentation
use std::error::Error;

use colored::Colorize;
use jiff::{Unit, Zoned, civil::Date, tz::TimeZone};

static WEEKS_PER_YEAR: u32 = 52;

pub enum LifeCalendarStyle {
    Full,
    Short { rows: u32, offset: i32 },
}

pub struct LifeCalendarConfig {
    pub style: LifeCalendarStyle,
    pub years_total: u32,
    pub birth_date: Date,
}

pub struct LifeCalendar {
    style: LifeCalendarStyle,
    years_total: u32,
    weeks_total: u32,
    weeks_lived: u32,
}

impl LifeCalendar {
    pub fn from_config(config: LifeCalendarConfig) -> Self {
        let now = Zoned::now();

        let (birth_date, death_date) = (|| -> Result<(Zoned, Zoned), Box<dyn Error>> {
            let birth_date = config.birth_date.to_zoned(TimeZone::system())?;
            let death_date = birth_date
                .with()
                .year(birth_date.year() + config.years_total as i16)
                .build()?;

            Ok((birth_date, death_date))
        })()
        .expect("Failed to calculate birth and death dates.");

        let (weeks_lived, weeks_total) = (|| -> Result<(u32, u32), Box<dyn Error>> {
            let weeks_lived = now.since((Unit::Week, &birth_date))?.get_weeks() as u32;
            let weeks_total = birth_date.until((Unit::Week, &death_date))?.get_weeks() as u32;

            Ok((weeks_lived, weeks_total))
        })()
        .expect("Failed to calculate weeks.");

        Self {
            style: config.style,
            years_total: config.years_total,
            weeks_total,
            weeks_lived,
        }
    }

    pub fn print(&self) {
        LifeCalendar::print_delimiter();

        let years_lived = self.weeks_lived / WEEKS_PER_YEAR;

        let (start_year, end_year) = match self.style {
            LifeCalendarStyle::Short { rows, offset } => (
                (years_lived as i32 + offset) as u32,
                (years_lived as i32 + offset) as u32 + rows,
            ),
            LifeCalendarStyle::Full => (0, self.years_total),
        };

        for i in start_year..end_year {
            self.print_row(i);
        }

        LifeCalendar::print_delimiter();

        self.print_stats();
    }

    fn print_delimiter() {
        let delimiter = "".repeat((WEEKS_PER_YEAR + 12) as usize);
        println!("{}", delimiter);
    }

    fn print_row(&self, year: u32) {
        let lived_week_str = "";
        let future_week_str = "";

        let start_week = year * WEEKS_PER_YEAR;
        let end_week = start_week + WEEKS_PER_YEAR;

        // Print age
        print!(" {:2} - {:2} | ", year, year + 1);

        // Print weeks
        for i in start_week..=end_week {
            let week_str = if i <= self.weeks_lived {
                lived_week_str
            } else {
                future_week_str
            };

            print!("{}", week_str);
        }

        print!("\n");
    }

    fn print_stats(&self) {
        let life_progress = (self.weeks_lived as f32) / (self.weeks_total as f32) * 100.0;

        println!("Weeks {}: {}", "lived".green().bold(), self.weeks_lived);
        println!(
            "Weeks {}:  {}",
            "left".blue().bold(),
            self.weeks_total - self.weeks_lived
        );
        println!("Weeks {}: {}\n", "total".bold(), self.weeks_total);

        println!("Your life is {life_progress:.1}% over.");
    }
}