use std::ops::AddAssign;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Duration(i64);
impl Duration {
pub fn from_seconds(seconds: i64) -> Self {
Self(seconds)
}
pub fn from_days(days: i64) -> Self {
Self(days * 86_400)
}
pub fn as_seconds(self) -> i64 {
self.0
}
pub fn as_days(self) -> f64 {
self.0 as f64 / 86_400.0
}
pub fn as_whole_days(self) -> i64 {
self.0 / 86_400
}
pub fn is_positive(self) -> bool {
self.0 > 0
}
pub fn is_zero(self) -> bool {
self.0 == 0
}
}
impl AddAssign for Duration {
fn add_assign(&mut self, rhs: Self) {
self.0 += rhs.0;
}
}
#[cfg(test)]
pub mod strategy {
use super::Duration;
use proptest::prelude::*;
pub fn duration() -> impl Strategy<Value = Duration> {
any::<i64>().prop_map(Duration::from_seconds)
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn from_seconds_roundtrips_through_as_seconds(s in any::<i64>()) {
prop_assert_eq!(Duration::from_seconds(s).as_seconds(), s);
}
#[test]
fn is_positive_iff_strictly_greater_than_zero(d in strategy::duration()) {
prop_assert_eq!(d.is_positive(), d.as_seconds() > 0);
}
}
#[test]
fn as_days_converts_at_full_day_boundary() {
assert_eq!(Duration::from_seconds(86_400).as_days(), 1.0);
assert_eq!(Duration::from_seconds(172_800).as_days(), 2.0);
}
#[test]
fn as_days_handles_fractional_amounts() {
assert!((Duration::from_seconds(43_200).as_days() - 0.5).abs() < 1e-9);
}
#[test]
fn is_positive_excludes_zero_and_negative() {
assert!(Duration::from_seconds(1).is_positive());
assert!(!Duration::from_seconds(0).is_positive());
assert!(!Duration::from_seconds(-1).is_positive());
}
#[test]
fn add_assign_accumulates() {
let mut total = Duration::default();
total += Duration::from_seconds(60);
total += Duration::from_seconds(30);
assert_eq!(total.as_seconds(), 90);
}
}