| /* Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| use humantime::Duration as HumanDuration; |
| use humantime::format_duration; |
| use serde::de::Visitor; |
| use serde::{Deserialize, Deserializer, Serialize, Serializer}; |
| use std::{ |
| fmt::{Display, Formatter}, |
| ops::Add, |
| str::FromStr, |
| time::Duration, |
| }; |
| |
| pub const SEC_IN_MICRO: u64 = 1_000_000; |
| |
| /// A struct for representing time durations with various utility functions. |
| /// |
| /// This struct wraps `std::time::Duration` and uses the `humantime` crate for parsing and formatting |
| /// human-readable duration strings. It also implements serialization and deserialization via the `serde` crate. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use iggy_common::IggyDuration; |
| /// use std::str::FromStr; |
| /// |
| /// let duration = IggyDuration::from(3661_000_000_u64); // 3661 seconds in microseconds |
| /// assert_eq!(3661, duration.as_secs()); |
| /// assert_eq!("1h 1m 1s", duration.as_human_time_string()); |
| /// assert_eq!("1h 1m 1s", format!("{}", duration)); |
| /// |
| /// let duration = IggyDuration::from(0_u64); |
| /// assert_eq!(0, duration.as_secs()); |
| /// assert_eq!("0s", duration.as_human_time_string()); |
| /// assert_eq!("0s", format!("{}", duration)); |
| /// |
| /// let duration = IggyDuration::from_str("1h 1m 1s").unwrap(); |
| /// assert_eq!(3661, duration.as_secs()); |
| /// assert_eq!("1h 1m 1s", duration.as_human_time_string()); |
| /// assert_eq!("1h 1m 1s", format!("{}", duration)); |
| /// |
| /// let duration = IggyDuration::from_str("unlimited").unwrap(); |
| /// assert_eq!(0, duration.as_secs()); |
| /// ``` |
| #[derive(Debug, Clone, Copy, Eq, PartialEq)] |
| pub struct IggyDuration { |
| duration: Duration, |
| } |
| |
| impl IggyDuration { |
| pub const ONE_SECOND: IggyDuration = IggyDuration { |
| duration: Duration::from_secs(1), |
| }; |
| } |
| |
| impl IggyDuration { |
| pub fn new(duration: Duration) -> IggyDuration { |
| IggyDuration { duration } |
| } |
| |
| pub fn new_from_secs(secs: u64) -> IggyDuration { |
| IggyDuration { |
| duration: Duration::from_secs(secs), |
| } |
| } |
| |
| pub fn as_human_time_string(&self) -> String { |
| format!("{}", format_duration(self.duration)) |
| } |
| |
| pub fn as_secs(&self) -> u32 { |
| self.duration.as_secs() as u32 |
| } |
| |
| pub fn as_secs_f64(&self) -> f64 { |
| self.duration.as_secs_f64() |
| } |
| |
| pub fn as_micros(&self) -> u64 { |
| self.duration.as_micros() as u64 |
| } |
| |
| pub fn get_duration(&self) -> Duration { |
| self.duration |
| } |
| |
| pub fn is_zero(&self) -> bool { |
| self.duration.is_zero() |
| } |
| |
| pub fn abs_diff(&self, other: IggyDuration) -> IggyDuration { |
| let diff = self.duration.abs_diff(other.duration); |
| IggyDuration { duration: diff } |
| } |
| } |
| |
| impl FromStr for IggyDuration { |
| type Err = humantime::DurationError; |
| |
| fn from_str(s: &str) -> Result<Self, Self::Err> { |
| let s = &s.to_lowercase(); |
| if s == "0" || s == "unlimited" || s == "disabled" || s == "none" { |
| Ok(IggyDuration { |
| duration: Duration::new(0, 0), |
| }) |
| } else { |
| Ok(IggyDuration { |
| duration: humantime::parse_duration(s)?, |
| }) |
| } |
| } |
| } |
| |
| impl From<Option<u64>> for IggyDuration { |
| fn from(duration_us: Option<u64>) -> Self { |
| match duration_us { |
| Some(value) => IggyDuration { |
| duration: Duration::from_micros(value), |
| }, |
| None => IggyDuration { |
| duration: Duration::new(0, 0), |
| }, |
| } |
| } |
| } |
| |
| impl From<u64> for IggyDuration { |
| fn from(value: u64) -> Self { |
| IggyDuration { |
| duration: Duration::from_micros(value), |
| } |
| } |
| } |
| |
| impl From<Duration> for IggyDuration { |
| fn from(duration: Duration) -> Self { |
| IggyDuration { duration } |
| } |
| } |
| |
| impl From<HumanDuration> for IggyDuration { |
| fn from(human_duration: HumanDuration) -> Self { |
| Self { |
| duration: human_duration.into(), |
| } |
| } |
| } |
| |
| impl From<IggyDuration> for u64 { |
| fn from(iggy_duration: IggyDuration) -> u64 { |
| iggy_duration.duration.as_micros() as u64 |
| } |
| } |
| |
| impl Default for IggyDuration { |
| fn default() -> Self { |
| IggyDuration { |
| duration: Duration::new(0, 0), |
| } |
| } |
| } |
| |
| impl Display for IggyDuration { |
| fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
| write!(f, "{}", self.as_human_time_string()) |
| } |
| } |
| |
| impl Add for IggyDuration { |
| type Output = IggyDuration; |
| |
| fn add(self, rhs: Self) -> Self::Output { |
| IggyDuration { |
| duration: self.duration + rhs.duration, |
| } |
| } |
| } |
| |
| impl Serialize for IggyDuration { |
| fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> |
| where |
| S: Serializer, |
| { |
| serializer.serialize_u64(self.as_micros()) |
| } |
| } |
| |
| struct IggyDurationVisitor; |
| |
| impl<'de> Deserialize<'de> for IggyDuration { |
| fn deserialize<D>(deserializer: D) -> Result<IggyDuration, D::Error> |
| where |
| D: Deserializer<'de>, |
| { |
| deserializer.deserialize_u64(IggyDurationVisitor) |
| } |
| } |
| |
| impl Visitor<'_> for IggyDurationVisitor { |
| type Value = IggyDuration; |
| |
| fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { |
| formatter.write_str("a duration in seconds") |
| } |
| |
| fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E> |
| where |
| E: serde::de::Error, |
| { |
| Ok(IggyDuration::new(Duration::from_micros(value))) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use std::time::Duration; |
| |
| #[test] |
| fn test_new() { |
| let duration = Duration::new(60, 0); // 60 seconds |
| let iggy_duration = IggyDuration::new(duration); |
| assert_eq!(iggy_duration.as_secs(), 60); |
| } |
| |
| #[test] |
| fn test_as_human_time_string() { |
| let duration = Duration::new(3661, 0); // 1 hour, 1 minute and 1 second |
| let iggy_duration = IggyDuration::new(duration); |
| assert_eq!(iggy_duration.as_human_time_string(), "1h 1m 1s"); |
| } |
| |
| #[test] |
| fn test_long_duration_as_human_time_string() { |
| let duration = Duration::new(36611233, 0); // 1year 1month 28days 1hour 13minutes 37seconds |
| let iggy_duration = IggyDuration::new(duration); |
| assert_eq!( |
| iggy_duration.as_human_time_string(), |
| "1year 1month 28days 1h 13m 37s" |
| ); |
| } |
| |
| #[test] |
| fn test_from_str() { |
| let iggy_duration: IggyDuration = "1h 1m 1s".parse().unwrap(); |
| assert_eq!(iggy_duration.as_secs(), 3661); |
| } |
| |
| #[test] |
| fn test_display() { |
| let duration = Duration::new(3661, 0); |
| let iggy_duration = IggyDuration::new(duration); |
| let duration_string = format!("{iggy_duration}"); |
| assert_eq!(duration_string, "1h 1m 1s"); |
| } |
| |
| #[test] |
| fn test_invalid_duration() { |
| let result: Result<IggyDuration, _> = "1 hour and 30 minutes".parse(); |
| assert!(result.is_err()); |
| } |
| |
| #[test] |
| fn test_zero_seconds_duration() { |
| let iggy_duration: IggyDuration = "0s".parse().unwrap(); |
| assert_eq!(iggy_duration.as_secs(), 0); |
| } |
| |
| #[test] |
| fn test_zero_duration() { |
| let iggy_duration: IggyDuration = "0".parse().unwrap(); |
| assert_eq!(iggy_duration.as_secs(), 0); |
| } |
| |
| #[test] |
| fn test_unlimited() { |
| let iggy_duration: IggyDuration = "unlimited".parse().unwrap(); |
| assert_eq!(iggy_duration.as_secs(), 0); |
| } |
| |
| #[test] |
| fn test_disabled() { |
| let iggy_duration: IggyDuration = "disabled".parse().unwrap(); |
| assert_eq!(iggy_duration.as_secs(), 0); |
| } |
| |
| #[test] |
| fn test_add_duration() { |
| let iggy_duration1: IggyDuration = "6s".parse().unwrap(); |
| let iggy_duration2: IggyDuration = "1m".parse().unwrap(); |
| let result: IggyDuration = iggy_duration1 + iggy_duration2; |
| assert_eq!(result.as_secs(), 66); |
| } |
| } |