blob: f8c8de92e333619b92a14569c50ff63eb2c166c7 [file] [log] [blame]
/* 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);
}
}