1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! Human-readable duration
//!
//! human-duration converts a [`std::time::Duration`] to a human readable string.
//!
//! Examples:
//!
//! ```
//! use human_duration::human_duration;
//!
//! let duration = std::time::Duration::new(120, 30_000_000);
//! assert_eq!(human_duration(&duration), "2m 0s 30ms");
//!
//! let duration = std::time::Duration::new(9000, 0);
//! assert_eq!(human_duration(&duration), "2h 30m 0s 0ms");
//! ```

#![forbid(unsafe_code)]
#![warn(missing_docs)]

use std::time::Duration;

static SECONDS_PER_YEAR: u64 = 31_536_000;
static SECONDS_PER_MONTH: u64 = 2_628_000;
static SECONDS_PER_DAY: u64 = 86400;
static SECONDS_PER_HOUR: u64 = 3600;
static SECONDS_PER_MINUTE: u64 = 60;

/// Takes a [`std::time::Duration`] and returns a formatted [`String`].
///
/// Examples:
/// ```
/// use human_duration::human_duration;
///
/// let duration = std::time::Duration::new(5, 0);
/// assert_eq!(human_duration(&duration), "5s 0ms");
///
/// let duration = std::time::Duration::new(125, 0);
/// assert_eq!(human_duration(&duration), "2m 5s 0ms");
///
/// let duration = std::time::Duration::new(45_000_000, 0);
/// assert_eq!(human_duration(&duration), "1y 5mon 3d 18h 0m 0s 0ms");
/// ```
pub fn human_duration(duration: &Duration) -> String {
    let seconds = duration.as_secs();
    let millis = duration.subsec_millis();

    let years = seconds / SECONDS_PER_YEAR;
    let mut remainder = seconds % SECONDS_PER_YEAR;

    let months = remainder / SECONDS_PER_MONTH;
    remainder %= SECONDS_PER_MONTH;

    let days = remainder / SECONDS_PER_DAY;
    remainder %= SECONDS_PER_DAY;

    let hours = remainder / SECONDS_PER_HOUR;
    remainder %= SECONDS_PER_HOUR;

    let minutes = remainder / SECONDS_PER_MINUTE;
    remainder %= SECONDS_PER_MINUTE;

    let mut output = Vec::with_capacity(7);

    if years > 0 {
        let f_years = format!("{years}y");
        output.push(f_years);
    }

    if !output.is_empty() || months > 0 {
        let f_months = format!("{months}mon");
        output.push(f_months);
    }

    if !output.is_empty() || days > 0 {
        let f_days = format!("{days}d");
        output.push(f_days);
    }

    if !output.is_empty() || hours > 0 {
        let f_hours = format!("{hours}h");
        output.push(f_hours);
    }

    if !output.is_empty() || minutes > 0 {
        let f_minutes = format!("{minutes}m");
        output.push(f_minutes);
    }

    if !output.is_empty() || seconds > 0 {
        let f_seconds = format!("{remainder}s");
        output.push(f_seconds);
    }

    let f_millis = format!("{millis}ms");
    output.push(f_millis);

    output.join(" ")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_human_duration() {
        let result = human_duration(&std::time::Duration::new(0, 0));
        assert_eq!(result, "0ms");

        let result = human_duration(&std::time::Duration::new(30, 5_000_000));
        assert_eq!(result, "30s 5ms");

        let result = human_duration(&std::time::Duration::new(60, 50_000_000));
        assert_eq!(result, "1m 0s 50ms");

        let result = human_duration(&std::time::Duration::new(92, 0));
        assert_eq!(result, "1m 32s 0ms");

        let result = human_duration(&std::time::Duration::new(3600, 0));
        assert_eq!(result, "1h 0m 0s 0ms");

        let result = human_duration(&std::time::Duration::new(3666, 0));
        assert_eq!(result, "1h 1m 6s 0ms");

        let result = human_duration(&std::time::Duration::new(86400, 337_000_000));
        assert_eq!(result, "1d 0h 0m 0s 337ms");

        let result = human_duration(&std::time::Duration::new(86680, 0));
        assert_eq!(result, "1d 0h 4m 40s 0ms");

        let result = human_duration(&std::time::Duration::new(2_628_000, 0));
        assert_eq!(result, "1mon 0d 0h 0m 0s 0ms");

        let result = human_duration(&std::time::Duration::new(2_828_000, 0));
        assert_eq!(result, "1mon 2d 7h 33m 20s 0ms");

        let result = human_duration(&std::time::Duration::new(31_536_000, 0));
        assert_eq!(result, "1y 0mon 0d 0h 0m 0s 0ms");

        let result = human_duration(&std::time::Duration::new(34_536_000, 0));
        assert_eq!(result, "1y 1mon 4d 7h 20m 0s 0ms");
    }
}