#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "std")]
use std::time::SystemTime;
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::{string::String, format};
use core::ops::{Add, Sub};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Duration {
seconds: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DurationError {
InvalidFormat,
InvalidValue,
}
impl Duration {
pub fn from_seconds(seconds: u64) -> Self {
Self { seconds }
}
pub fn from_minutes(minutes: u64) -> Self {
Self {
seconds: minutes * 60,
}
}
pub fn from_hours(hours: u64) -> Self {
Self {
seconds: hours * 3600,
}
}
pub fn from_hms(hours: u64, minutes: u64, seconds: u64) -> Self {
Self {
seconds: hours * 3600 + minutes * 60 + seconds,
}
}
pub fn parse(s: &str) -> Result<Self, DurationError> {
let parts: Vec<&str> = s.split(':').collect();
if parts.len() != 3 {
return Err(DurationError::InvalidFormat);
}
let hours = parts[0].parse::<u64>().map_err(|_| DurationError::InvalidValue)?;
let minutes = parts[1].parse::<u64>().map_err(|_| DurationError::InvalidValue)?;
let seconds = parts[2].parse::<u64>().map_err(|_| DurationError::InvalidValue)?;
if minutes >= 60 || seconds >= 60 {
return Err(DurationError::InvalidValue);
}
Ok(Self::from_hms(hours, minutes, seconds))
}
pub fn as_seconds(&self) -> u64 {
self.seconds
}
pub fn as_minutes(&self) -> u64 {
self.seconds / 60
}
pub fn as_hours(&self) -> u64 {
self.seconds / 3600
}
pub fn seconds_part(&self) -> u64 {
self.seconds % 60
}
pub fn minutes_part(&self) -> u64 {
(self.seconds % 3600) / 60
}
pub fn hours_part(&self) -> u64 {
self.seconds / 3600
}
pub fn format(&self) -> String {
format!("{:02}:{:02}:{:02}", self.hours_part(), self.minutes_part(), self.seconds_part())
}
pub fn zero() -> Self {
Self { seconds: 0 }
}
pub fn is_zero(&self) -> bool {
self.seconds == 0
}
pub fn saturating_add(self, other: Self) -> Self {
Self {
seconds: self.seconds.saturating_add(other.seconds),
}
}
pub fn saturating_sub(self, other: Self) -> Self {
Self {
seconds: self.seconds.saturating_sub(other.seconds),
}
}
}
#[cfg(feature = "std")]
impl Duration {
pub fn from_system_time_diff(start: SystemTime, end: SystemTime) -> Option<Self> {
end.duration_since(start)
.ok()
.map(|std_duration| Self::from_seconds(std_duration.as_secs()))
}
}
impl Add for Duration {
type Output = Self;
fn add(self, other: Self) -> Self::Output {
self.saturating_add(other)
}
}
impl Sub for Duration {
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
self.saturating_sub(other)
}
}
impl core::fmt::Display for Duration {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.format())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_constructors() {
let d1 = Duration::from_seconds(3661); assert_eq!(d1.as_seconds(), 3661);
assert_eq!(d1.seconds_part(), 1);
assert_eq!(d1.minutes_part(), 1);
assert_eq!(d1.hours_part(), 1);
let d2 = Duration::from_minutes(150); assert_eq!(d2.as_seconds(), 9000);
assert_eq!(d2.format(), "02:30:00");
let d3 = Duration::from_hours(3);
assert_eq!(d3.as_seconds(), 10800);
assert_eq!(d3.format(), "03:00:00");
let d4 = Duration::from_hms(2, 30, 45);
assert_eq!(d4.as_seconds(), 9045);
assert_eq!(d4.format(), "02:30:45");
}
#[test]
fn test_unit_conversions() {
let duration = Duration::from_seconds(3661);
assert_eq!(duration.as_seconds(), 3661);
assert_eq!(duration.as_minutes(), 61); assert_eq!(duration.as_hours(), 1);
assert_eq!(duration.seconds_part(), 1);
assert_eq!(duration.minutes_part(), 1);
assert_eq!(duration.hours_part(), 1);
let duration2 = Duration::from_seconds(7890); assert_eq!(duration2.as_minutes(), 131); assert_eq!(duration2.as_hours(), 2); assert_eq!(duration2.seconds_part(), 30);
assert_eq!(duration2.minutes_part(), 11);
assert_eq!(duration2.hours_part(), 2);
}
#[test]
fn test_string_parsing() {
let duration = Duration::parse("23:59:59").unwrap(); assert_eq!(duration.seconds_part(), 59);
assert_eq!(duration.minutes_part(), 59);
assert_eq!(duration.hours_part(), 23);
let duration_min = Duration::parse("00:00:00").unwrap();
assert_eq!(duration_min.seconds_part(), 0);
assert_eq!(duration_min.minutes_part(), 0);
assert_eq!(duration_min.hours_part(), 0);
assert!(Duration::parse("invalid").is_err());
assert!(Duration::parse("1:2").is_err()); assert!(Duration::parse("1:60:30").is_err()); assert!(Duration::parse("1:30:60").is_err()); assert!(Duration::parse("24:59:59").is_ok()); assert!(Duration::parse("00:59:59").is_ok()); assert!(Duration::parse("00:00:59").is_ok()); }
#[test]
fn test_formatting() {
let cases = [
(Duration::from_hms(1, 5, 30), "01:05:30"),
(Duration::from_hms(12, 0, 0), "12:00:00"),
(Duration::zero(), "00:00:00"),
];
for (duration, expected) in cases {
assert_eq!(duration.format(), expected);
assert_eq!(format!("{}", duration), expected);
}
}
#[test]
fn test_arithmetic() {
let d1 = Duration::from_seconds(100);
let d2 = Duration::from_seconds(50);
assert_eq!((d1 + d2).as_seconds(), 150);
assert_eq!((d1 - d2).as_seconds(), 50);
assert_eq!((d2 - d1).as_seconds(), 0);
let max_duration = Duration::from_seconds(u64::MAX);
let small_duration = Duration::from_seconds(1);
assert_eq!((max_duration + small_duration).as_seconds(), u64::MAX);
let large1 = Duration::from_seconds(u64::MAX - 10);
let large2 = Duration::from_seconds(20);
assert_eq!((large1 + large2).as_seconds(), u64::MAX);
let near_max = Duration::from_seconds(u64::MAX - 50);
let small = Duration::from_seconds(30);
assert_eq!((near_max + small).as_seconds(), u64::MAX - 20);
}
#[test]
fn test_utility_methods() {
let zero = Duration::zero();
assert!(zero.is_zero());
assert_eq!(zero.as_seconds(), 0);
let non_zero = Duration::from_seconds(1);
assert!(!non_zero.is_zero());
}
#[test]
fn test_comparison() {
let d1 = Duration::from_seconds(100);
let d2 = Duration::from_seconds(200);
assert!(d1 < d2);
assert!(d2 > d1);
assert_eq!(d1, Duration::from_seconds(100));
assert_ne!(d1, d2);
}
#[cfg(feature = "std")]
#[test]
fn test_system_time_integration() {
use std::time::SystemTime;
let start = SystemTime::now();
let end = start + std::time::Duration::from_secs(100);
let duration = Duration::from_system_time_diff(start, end).unwrap();
assert_eq!(duration.as_seconds(), 100);
}
}