1use crate::unit::TimeUnit;
2use std::time::Duration;
3
4pub trait HumanFormat {
5 fn human_format(&self) -> String;
6}
7
8const ONE_SECOND_SECOND: u64 = 1;
9const ONE_MINUTE_SECOND: u64 = 60 * ONE_SECOND_SECOND;
10const ONE_HOUR_SECOND: u64 = 60 * ONE_MINUTE_SECOND;
11const ONE_DAY_SECOND: u64 = 24 * ONE_HOUR_SECOND;
12const ONE_WEEK_SECOND: u64 = 7 * ONE_DAY_SECOND;
13const ONE_MONTH_SECOND: u64 = 30 * ONE_DAY_SECOND;
14const ONE_YEAR_SECOND: u64 = 365 * ONE_DAY_SECOND;
15
16fn accrual(val: u64, unit: TimeUnit, format: &mut String) {
17 if val > 0 {
18 if !format.is_empty() {
19 format.push(' ');
20 }
21 format.push_str(&format!("{}{}", val, unit));
22 }
23}
24
25fn format_inner(seconds: u64, nanos: u32) -> String {
26 if seconds == 0 && nanos == 0 {
27 return "0s".to_string();
28 }
29
30 let year = seconds / ONE_YEAR_SECOND;
31 let ydays = seconds % ONE_YEAR_SECOND;
32 let month = ydays / ONE_MONTH_SECOND;
33 let mdays = ydays % ONE_MONTH_SECOND;
34 let week = mdays / ONE_WEEK_SECOND;
35 let wdays = mdays % ONE_WEEK_SECOND;
36 let day = wdays / ONE_DAY_SECOND;
37 let day_secs = wdays % ONE_DAY_SECOND;
38 let hour = day_secs / ONE_HOUR_SECOND;
39 let minutes = day_secs % ONE_HOUR_SECOND / ONE_MINUTE_SECOND;
40 let second = day_secs % ONE_MINUTE_SECOND;
41
42 let (millis, micros, nano) = (nanos / 1_000_000, nanos / 1000 % 1000, nanos % 1000);
43
44 let mut format = String::new();
45 accrual(year, TimeUnit::Year, &mut format);
46 accrual(month, TimeUnit::Month, &mut format);
47 accrual(week, TimeUnit::Week, &mut format);
48 accrual(day, TimeUnit::Day, &mut format);
49 accrual(hour, TimeUnit::Hour, &mut format);
50 accrual(minutes, TimeUnit::Minute, &mut format);
51 accrual(second, TimeUnit::Second, &mut format);
52 accrual(millis as u64, TimeUnit::MilliSecond, &mut format);
53 accrual(micros as u64, TimeUnit::MicroSecond, &mut format);
54 accrual(nano as u64, TimeUnit::NanoSecond, &mut format);
55
56 format
57}
58
59impl HumanFormat for Duration {
60 fn human_format(&self) -> String {
61 let seconds = self.as_secs();
62 let nanos = self.subsec_nanos();
63 format_inner(seconds, nanos)
64 }
65}
66
67#[cfg(all(feature = "chrono", feature = "serde"))]
68use chrono::Duration as CDuration;
69
70#[cfg(all(feature = "time", feature = "serde"))]
71use time::Duration as TDuration;
72
73#[cfg(all(feature = "chrono", feature = "serde"))]
74impl HumanFormat for CDuration {
75 fn human_format(&self) -> String {
76 let seconds = self.num_seconds() as _;
77 let nanos = self.subsec_nanos() as _;
78 format_inner(seconds, nanos)
79 }
80}
81
82#[cfg(all(feature = "time", feature = "serde"))]
83impl HumanFormat for TDuration {
84 fn human_format(&self) -> String {
85 let seconds = self.as_seconds_f64() as _;
86 let nanos = self.subsec_nanoseconds() as _;
87 format_inner(seconds, nanos)
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94 use crate::parse;
95
96 #[test]
97 fn test_human_format() {
98 let duration = parse("0s").unwrap();
99 assert_eq!(duration.human_format(), "0s");
100
101 let duration = parse("1y 12d 3s").unwrap();
102 assert_eq!(duration.human_format(), "1y 1w 5d 3s");
103
104 let duration = parse("751d 1mon 3week 5d 2ns").unwrap();
105 assert_eq!(duration.human_format(), "2y 2mon 2w 3d 2ns");
106
107 let duration = parse(" 7h ").unwrap();
108 assert_eq!(duration.human_format(), "7h");
109
110 let duration = parse(" 7h 1s ").unwrap();
111 assert_eq!(duration.human_format(), "7h 1s");
112
113 let duration = parse(" 7h 0s ").unwrap();
114 assert_eq!(duration.human_format(), "7h");
115 }
116
117 #[cfg(all(feature = "serde", feature = "chrono"))]
118 #[test]
119 fn test_human_format_chrono() {
120 let duration = crate::parse_chrono("0s").unwrap();
121 assert_eq!(duration.human_format(), "0s");
122
123 let duration = crate::parse_chrono("1y 12d 3s").unwrap();
124 assert_eq!(duration.human_format(), "1y 1w 5d 3s");
125
126 let duration = crate::parse_chrono("751d 1mon 3week 5d 2ns").unwrap();
127 assert_eq!(duration.human_format(), "2y 2mon 2w 3d 2ns");
128
129 let duration = crate::parse_chrono(" 7h ").unwrap();
130 assert_eq!(duration.human_format(), "7h");
131
132 let duration = crate::parse_chrono(" 7h 1s ").unwrap();
133 assert_eq!(duration.human_format(), "7h 1s");
134
135 let duration = crate::parse_chrono(" 7h 0s ").unwrap();
136 assert_eq!(duration.human_format(), "7h");
137 }
138
139 #[cfg(all(feature = "serde", feature = "time"))]
140 #[test]
141 fn test_human_format_time() {
142 let duration = crate::parse_time("0s").unwrap();
143 assert_eq!(duration.human_format(), "0s");
144
145 let duration = crate::parse_time("1y 12d 3s").unwrap();
146 assert_eq!(duration.human_format(), "1y 1w 5d 3s");
147
148 let duration = crate::parse_time("751d 1mon 3week 5d 2ns").unwrap();
149 assert_eq!(duration.human_format(), "2y 2mon 2w 3d 2ns");
150
151 let duration = crate::parse_time(" 7h ").unwrap();
152 assert_eq!(duration.human_format(), "7h");
153
154 let duration = crate::parse_time(" 7h 1s ").unwrap();
155 assert_eq!(duration.human_format(), "7h 1s");
156
157 let duration = crate::parse_time(" 7h 0s ").unwrap();
158 assert_eq!(duration.human_format(), "7h");
159 }
160}