// yt - A fully featured command line YouTube client // // Copyright (C) 2024 Benedikt Peetz // SPDX-License-Identifier: GPL-3.0-or-later // // This file is part of Yt. // // You should have received a copy of the License along with this program. // If not, see . use std::str::FromStr; use anyhow::{Context, Result}; #[derive(Copy, Clone, Debug)] pub struct Duration { time: u32, } impl FromStr for Duration { type Err = anyhow::Error; fn from_str(s: &str) -> Result { fn parse_num(str: &str, suffix: char) -> Result { str.strip_suffix(suffix) .with_context(|| { format!("Failed to strip suffix '{}' of number: '{}'", suffix, str) })? .parse::() .with_context(|| format!("Failed to parse '{}'", suffix)) } if s == "[No Duration]" { return Ok(Self { time: 0 }); } let buf: Vec<_> = s.split(' ').collect(); let hours; let minutes; let seconds; assert_eq!(buf.len(), 2, "Other lengths should not happen"); if buf[0].ends_with('h') { hours = parse_num(buf[0], 'h')?; minutes = parse_num(buf[1], 'm')?; seconds = 0; } else if buf[0].ends_with('m') { hours = 0; minutes = parse_num(buf[0], 'm')?; seconds = parse_num(buf[1], 's')?; } else { unreachable!( "The first part always ends with 'h' or 'm', but was: {:#?}", buf ) } Ok(Self { time: (hours * 60 * 60) + (minutes * 60) + seconds, }) } } impl From> for Duration { fn from(value: Option) -> Self { Self { time: value.unwrap_or(0.0).ceil() as u32, } } } impl std::fmt::Display for Duration { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { const SECOND: u32 = 1; const MINUTE: u32 = 60 * SECOND; const HOUR: u32 = 60 * MINUTE; let base_hour = self.time - (self.time % HOUR); let base_min = (self.time % HOUR) - ((self.time % HOUR) % MINUTE); let base_sec = (self.time % HOUR) % MINUTE; let h = base_hour / HOUR; let m = base_min / MINUTE; let s = base_sec / SECOND; if self.time == 0 { write!(f, "[No Duration]") } else if h > 0 { write!(f, "{h}h {m}m") } else { write!(f, "{m}m {s}s") } } } #[cfg(test)] mod test { use super::Duration; #[test] fn test_display_duration_1h() { let dur = Duration { time: 60 * 60 }; assert_eq!("1h 0m".to_owned(), dur.to_string()); } #[test] fn test_display_duration_30min() { let dur = Duration { time: 60 * 30 }; assert_eq!("30m 0s".to_owned(), dur.to_string()); } }