use chrono::{TimeDelta, prelude::*}; use chrono_tz::Tz; use std::error::Error; use std::fmt; #[derive(PartialEq, Debug)] pub enum CalendarError { UnknownTimezone(String), WrongTimeFormat, TimezoneConversionFailed(String), } impl fmt::Display for CalendarError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let message = match *self { Self::UnknownTimezone(ref tz) => format!("Unknown timezone: {}", tz), Self::WrongTimeFormat => "Wrong time format".to_string(), Self::TimezoneConversionFailed(ref tz) => { format!("Could not convert time to timezone: {}", tz) } }; f.write_str(&message) } } 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; (start..=end) .map(|v| time.overflowing_add_signed(TimeDelta::hours(v.into())).0) .map(|h| h.format("%H:%M").to_string()) .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: &NaiveTime, target_date: &NaiveDate, local_timezone: &FixedOffset, in_timezone: &String, padding_hrs: i8, ) -> Result, CalendarError> { 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 { 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() }; Ok(create_calendar_strings( &time_in_timezone.time(), padding_hrs, )) } pub fn get_todays_date() -> String { Local::now().format("%d-%m-%Y").to_string() } #[cfg(test)] mod tests { use super::*; #[test] fn test_calendar_string() { assert_eq!( create_calendar_strings(&NaiveTime::from_hms_opt(10, 0, 0).unwrap(), 4), vec![ "06:00", "07:00", "08:00", "09:00", "10:00", "11:00", "12:00", "13:00", "14:00" ] ); } #[test] fn test_calendar_string_overflow() { assert_eq!( create_calendar_strings(&NaiveTime::from_hms_opt(3, 0, 0).unwrap(), 4), vec![ "23:00", "00:00", "01:00", "02:00", "03:00", "04:00", "05:00", "06:00", "07:00" ] ); } #[test] fn test_calendar_string_underflow() { assert_eq!( create_calendar_strings(&NaiveTime::from_hms_opt(22, 0, 0).unwrap(), 4), vec![ "18:00", "19:00", "20:00", "21:00", "22:00", "23:00", "00:00", "01:00", "02:00" ] ); } #[test] fn test_create_timezone() { assert_eq!( create_timetable( &"10".to_string(), &"1-1-2000".to_string(), &"Europe/Prague".to_string(), 2 ) .unwrap(), vec!["08:00", "09:00", "10:00", "11:00", "12:00"] ); } #[test] fn test_create_timezone_nonzero_minutes() { assert_eq!( create_timetable( &"10:30".to_string(), &"1-1-2000".to_string(), &"Europe/Prague".to_string(), 2 ) .unwrap(), vec!["08:30", "09:30", "10:30", "11:30", "12:30"] ); } #[test] fn test_create_timezone_wrong_time() { assert_eq!( create_timetable( &"hello".to_string(), &"1-1-2000".to_string(), &"Europe/Prague".to_string(), 2, ) .unwrap_err(), CalendarError::WrongTimeFormat ); } #[test] fn test_create_timezone_wrong_date() { assert_eq!( create_timetable( &"10:00".to_string(), &"hello".to_string(), &"Europe/Prague".to_string(), 2, ) .unwrap_err(), CalendarError::WrongDateFormat ); } #[test] fn test_create_timezone_wrong_zone_name() { assert_eq!( create_timetable( &"10:00".to_string(), &"1-1-2000".to_string(), &"hello".to_string(), 2, ) .unwrap_err(), CalendarError::UnknownTimezone("hello".to_string()) ); } }