diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..61ae8af --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,319 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "cc" +version = "1.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "chrono-tz" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" +dependencies = [ + "chrono", + "phf", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "phf" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +dependencies = [ + "siphasher", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tzhelp" +version = "0.1.0" +dependencies = [ + "chrono", + "chrono-tz", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] diff --git a/Cargo.toml b/Cargo.toml index f852ef1..281ee65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] +chrono = "0.4.43" +chrono-tz = "0.10.4" diff --git a/src/calendar.rs b/src/calendar.rs new file mode 100644 index 0000000..9c08fad --- /dev/null +++ b/src/calendar.rs @@ -0,0 +1,176 @@ +use chrono::{TimeDelta, prelude::*}; +use chrono_tz::Tz; +use std::error::Error; +use std::fmt; + +#[derive(PartialEq, Debug)] +enum CalendarError { + UnknownTimezone(String), + WrongTimeFormat, + WrongDateFormat, + 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::WrongDateFormat => "Wrong date format".to_string(), + Self::TimezoneConversionFailed(ref tz) => { + format!("Could not convert time to timezone: {}", tz) + } + }; + + f.write_str(&message) + } +} + +impl Error for CalendarError {} + +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 create_timetable( + target_time: &String, + target_date: &String, + 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)? + } else { + NaiveTime::parse_from_str(target_time, "%H:%M") + .map_err(|_| CalendarError::WrongTimeFormat)? + }; + + 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 = NaiveDateTime::new(parsed_date, parsed_time); + let time_in_timezone = parsed_timezone + .from_local_datetime(&datetime) + .single() + .ok_or(CalendarError::TimezoneConversionFailed(in_timezone.clone()))?; + + Ok(create_calendar_strings( + &time_in_timezone.time(), + padding_hrs, + )) +} + +#[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()) + ); + } +} diff --git a/src/main.rs b/src/main.rs index e7a11a9..1f504fd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,3 @@ -fn main() { - println!("Hello, world!"); -} +mod calendar; + +fn main() {}