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!(" {:2} - {:2} | ", year, year + 1);
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.");
}
}