#![allow(clippy::option_map_unit_fn)]
use alloc::string::String;
use core::fmt::Write;
use super::data::DurationData;
use crate::{Duration, Vec};
impl core::fmt::Display for Duration {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let normalized = self.normalized();
let is_negative = normalized.seconds < 0 || normalized.nanos < 0;
if is_negative {
write!(f, "-")?;
}
let abs_seconds = normalized.seconds.abs();
let mut abs_nanos = normalized.nanos.abs();
write!(f, "{abs_seconds}")?;
if abs_nanos > 0 {
let mut width = 9;
while abs_nanos % 10 == 0 {
abs_nanos /= 10;
width -= 1;
}
write!(f, ".{abs_nanos:0width$}")?;
}
write!(f, "s")
}
}
impl Duration {
#[must_use]
pub fn to_human_readable_string(&self) -> String {
let DurationData {
months,
days,
hours,
minutes,
seconds,
is_negative,
..
} = self.get_data();
let mut str = String::new();
let mut parts = Vec::new();
months.format_if_nonzero().map(|p| parts.push(p));
days.format_if_nonzero().map(|p| parts.push(p));
hours.format_if_nonzero().map(|p| parts.push(p));
minutes.format_if_nonzero().map(|p| parts.push(p));
seconds.format_if_nonzero().map(|p| parts.push(p));
if parts.is_empty() {
str.push_str("0 seconds");
} else {
let sign = if is_negative { "- " } else { "" };
match parts.len() {
1 => str.push_str(&parts.remove(0)),
2 => {
let _ = write!(str, "{}{} and {}", sign, parts[0], parts[1]);
}
_ => {
let last = parts.pop().unwrap();
let _ = write!(str, "{}{} and {}", sign, parts.join(" "), last);
}
};
}
str
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::duration::duration_units::*;
use alloc::string::ToString;
fn dur(s: i64, n: i32) -> Duration {
Duration {
seconds: s,
nanos: n,
}
}
#[test]
fn test_canonical_display() {
let d = dur(10, 0);
assert_eq!(d.to_string(), "10s");
let d = dur(10, 500_000_000);
assert_eq!(d.to_string(), "10.5s");
let d = dur(0, 1_000);
assert_eq!(d.to_string(), "0.000001s");
let d = dur(-10, -500_000_000);
assert_eq!(d.to_string(), "-10.5s");
let d = dur(0, -500_000_000);
assert_eq!(d.to_string(), "-0.5s");
}
#[test]
fn test_unit_display_formatting() {
assert_eq!(Seconds { value: 1 }.to_string(), "1 second");
assert_eq!(Minutes { value: 1 }.to_string(), "1 minute");
assert_eq!(Hours { value: 1 }.to_string(), "1 hour");
assert_eq!(Days { value: 1 }.to_string(), "1 day");
assert_eq!(Weeks { value: 1 }.to_string(), "1 week");
assert_eq!(Months { value: 1 }.to_string(), "1 month");
assert_eq!(Years { value: 1 }.to_string(), "1 year");
assert_eq!(Seconds { value: 2 }.to_string(), "2 seconds");
assert_eq!(Seconds { value: 0 }.to_string(), "0 seconds");
assert_eq!(Years { value: 10 }.to_string(), "10 years");
}
#[test]
fn test_format_if_nonzero() {
let s_zero = Seconds { value: 0 };
let s_one = Seconds { value: 1 };
assert_eq!(s_zero.format_if_nonzero(), None);
assert_eq!(s_one.format_if_nonzero(), Some("1 second".to_string()));
}
#[test]
fn test_get_data_basic_units() {
let d = Duration {
seconds: 60,
nanos: 0,
};
let data = d.get_data();
assert_eq!(data.minutes.value, 1);
assert_eq!(data.seconds.value, 0);
let d = Duration {
seconds: 3600,
nanos: 0,
};
let data = d.get_data();
assert_eq!(data.hours.value, 1);
assert_eq!(data.minutes.value, 0);
let d = Duration {
seconds: 86400,
nanos: 0,
};
let data = d.get_data();
assert_eq!(data.days.value, 1);
assert_eq!(data.hours.value, 0);
}
#[test]
fn test_get_data_greedy_decomposition() {
let total_seconds = 86400 + 3600 + 60 + 1;
let d = Duration {
seconds: total_seconds,
nanos: 0,
};
let data = d.get_data();
assert_eq!(data.days.value, 1);
assert_eq!(data.hours.value, 1);
assert_eq!(data.minutes.value, 1);
assert_eq!(data.seconds.value, 1);
}
#[test]
fn test_get_data_negative() {
let d = Duration {
seconds: -65,
nanos: 0,
};
let data = d.get_data();
assert!(data.is_negative);
assert_eq!(data.minutes.value, 1);
assert_eq!(data.seconds.value, 5);
}
#[test]
fn test_duration_display_cases() {
let d = Duration {
seconds: 0,
nanos: 0,
};
assert_eq!(d.to_human_readable_string(), "0 seconds");
let d = Duration {
seconds: 10,
nanos: 0,
};
assert_eq!(d.to_human_readable_string(), "10 seconds");
let d = Duration {
seconds: 90,
nanos: 0,
};
assert_eq!(d.to_human_readable_string(), "1 minute and 30 seconds");
let d = Duration {
seconds: 3661,
nanos: 0,
};
assert_eq!(d.to_human_readable_string(), "1 hour 1 minute and 1 second");
let d = Duration {
seconds: 3605,
nanos: 0,
};
assert_eq!(d.to_human_readable_string(), "1 hour and 5 seconds");
}
#[test]
fn test_duration_display_negative() {
let d = Duration {
seconds: -90,
nanos: 0,
};
assert_eq!(d.to_human_readable_string(), "- 1 minute and 30 seconds");
}
}