diff --git a/src/calendar.rs b/src/calendar.rs index b858092..432d5e7 100644 --- a/src/calendar.rs +++ b/src/calendar.rs @@ -7,7 +7,6 @@ use std::fmt; pub enum CalendarError { UnknownTimezone(String), WrongTimeFormat, - WrongDateFormat, TimezoneConversionFailed(String), } @@ -16,7 +15,6 @@ impl fmt::Display for CalendarError { let message = match *self { Self::UnknownTimezone(ref tz) => format!("Unknown timezone: {}", tz), Self::WrongTimeFormat => "Wrong time format".to_string(), - Self::WrongDateFormat => "Wrong date format".to_string(), Self::TimezoneConversionFailed(ref tz) => { format!("Could not convert time to timezone: {}", tz) } @@ -28,6 +26,11 @@ impl fmt::Display for CalendarError { impl Error for CalendarError {} +pub struct SimpleTimezone { + pub name: String, + pub offset: FixedOffset, +} + fn create_calendar_strings(time: &NaiveTime, padding_hrs: i8) -> Vec { let start = -padding_hrs; let end = padding_hrs; @@ -37,33 +40,62 @@ fn create_calendar_strings(time: &NaiveTime, padding_hrs: i8) -> Vec { .collect() } +pub fn get_local_timezone_offset( + time: &NaiveTime, + timezone: &String, +) -> Result { + if timezone == "Local" { + Ok(SimpleTimezone { + name: "Local".to_string(), + offset: Local::now() + .with_time(time.clone()) + .single() + .ok_or(CalendarError::WrongTimeFormat)? + .offset() + .clone(), + }) + } else { + let offset = timezone + .parse::() + .map_err(|_| CalendarError::UnknownTimezone(timezone.to_string()))? + .offset_from_utc_datetime(&Utc::now().naive_utc()); + + Ok(SimpleTimezone { + name: timezone.to_string(), + offset: offset.fix(), + }) + } +} + pub fn create_timetable( - target_time: &String, - target_date: &String, + target_time: &NaiveTime, + target_date: &NaiveDate, + local_timezone: &FixedOffset, in_timezone: &String, padding_hrs: i8, ) -> Result, CalendarError> { - let parsed_timezone = in_timezone - .parse::() - .map_err(|_| CalendarError::UnknownTimezone(in_timezone.clone()))?; - - let parsed_time = if let Ok(hour) = target_time.parse::() { - NaiveTime::from_hms_opt(hour, 0, 0).ok_or(CalendarError::WrongTimeFormat)? + let datetime = local_timezone.from_local_datetime(&NaiveDateTime::new( + target_date.clone(), + target_time.clone(), + )); + let time_in_timezone = if in_timezone == "Local" { + Local::now() + .with_time(target_time.clone()) + .single() + .ok_or(CalendarError::WrongTimeFormat)? + .naive_local() } else { - NaiveTime::parse_from_str(target_time, "%H:%M") - .map_err(|_| CalendarError::WrongTimeFormat)? + let parsed_timezone = in_timezone + .parse::() + .map_err(|_| CalendarError::UnknownTimezone(in_timezone.clone()))?; + + datetime + .single() + .ok_or(CalendarError::TimezoneConversionFailed(in_timezone.clone()))? + .with_timezone(&parsed_timezone) + .naive_local() }; - let parsed_date = NaiveDate::parse_from_str(target_date, "%d-%m-%Y") - .or_else(|_| NaiveDate::parse_from_str(target_date, "%d-%m")) - .map_err(|_| CalendarError::WrongDateFormat)?; - - let datetime = Local.from_local_datetime(&NaiveDateTime::new(parsed_date, parsed_time)); - let time_in_timezone = datetime - .single() - .ok_or(CalendarError::TimezoneConversionFailed(in_timezone.clone()))? - .with_timezone(&parsed_timezone); - Ok(create_calendar_strings( &time_in_timezone.time(), padding_hrs, diff --git a/src/cli.rs b/src/cli.rs index 000a929..f213e4c 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,5 +1,13 @@ -use super::calendar::create_timetable; +use crate::calendar::CalendarError; + +use super::calendar::{create_timetable, get_local_timezone_offset, get_todays_date}; + +use chrono::NaiveDate; +use chrono::NaiveTime; use clap::Parser; +use std::error::Error; +use std::fmt; +use std::iter; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] @@ -10,19 +18,78 @@ pub struct Args { #[arg(short, long)] pub date: Option, + #[arg(short, long, default_value = "Local")] + pub local: String, + pub time: String, } -pub fn print_timezones(timezones: &Vec, time: &String, date: &String) { - let max_len = timezones.iter().map(|t| t.len()).max().unwrap_or(0) as u32; - for timezone in timezones { +#[derive(PartialEq, Debug)] +pub enum CliError { + CouldNotParseTime, + CouldNotParseDate, + CouldNoGenerateTimetable(CalendarError), +} + +impl fmt::Display for CliError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let message = match *self { + Self::CouldNotParseTime => "Couldn't parse time".to_string(), + Self::CouldNotParseDate => "Couldn't parse date".to_string(), + Self::CouldNoGenerateTimetable(ref er) => { + format!("Could not generate timetable: {}", er) + } + }; + + f.write_str(&message) + } +} + +impl Error for CliError {} + +pub fn print_timezones( + timezones: &Vec, + time: &String, + date: Option<&String>, + local_timezone: &String, +) -> Result<(), CliError> { + let todays_date = &get_todays_date(); + let date = date.unwrap_or(todays_date); + + let parsed_time = if let Ok(hour) = time.parse::() { + NaiveTime::from_hms_opt(hour, 0, 0).ok_or(CliError::CouldNotParseTime)? + } else { + NaiveTime::parse_from_str(time, "%H:%M").map_err(|_| CliError::CouldNotParseTime)? + }; + + let parsed_date = NaiveDate::parse_from_str(date, "%d-%m-%Y") + .or_else(|_| NaiveDate::parse_from_str(date, "%d-%m")) + .map_err(|_| CliError::CouldNotParseDate)?; + + let local_timezone_offset = &get_local_timezone_offset(&parsed_time, local_timezone) + .map_err(|e| CliError::CouldNoGenerateTimetable(e))?; + + let all_timezones: Vec<&String> = iter::once(&local_timezone_offset.name) + .chain(timezones) + .collect(); + + let max_len = all_timezones.iter().map(|t| t.len()).max().unwrap_or(0) as u32; + + for timezone in all_timezones { let name_padding = (0..(max_len - timezone.len() as u32)) .map(|_| " ".to_string()) .collect::>() .join(""); - match create_timetable(time, date, timezone, 5) { + match create_timetable( + &parsed_time, + &parsed_date, + &local_timezone_offset.offset, + timezone, + 5, + ) { Ok(timetable) => println!("{}:{}\t{}", timezone, name_padding, timetable.join("\t")), Err(error) => panic!("Failed: {}", error), } } + Ok(()) } diff --git a/src/main.rs b/src/main.rs index 6cf19b7..2363dda 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ mod calendar; mod cli; -use calendar::get_todays_date; use clap::Parser; use cli::{Args, print_timezones}; @@ -9,7 +8,10 @@ fn main() { let args = Args::parse(); if args.timezone.len() > 0 { - let date = args.date.unwrap_or(get_todays_date()); - print_timezones(&args.timezone, &args.time, &date); + let res = print_timezones(&args.timezone, &args.time, args.date.as_ref(), &args.local); + + if let Err(error) = res { + println!("Error: {}", error); + } } }