1#![doc = include_str!("../README.md")]
2
3use crate::types::DurationParts;
4
5mod impls;
6
7#[macro_use]
8pub mod macros;
9
10pub mod prelude;
11pub mod types;
12
13#[derive(Copy, Clone)]
14pub(crate) struct Duration {
15 secs: i64,
16 nanos: i32,
17}
18
19pub struct FormattedDuration {
20 duration: Duration,
21 truncate_option: Truncate,
22 formatter: Box<dyn Formatter>,
23}
24
25pub trait Formatter {
26 fn get(&self, truncate: Truncate) -> Box<dyn Unit>;
28
29 fn format(&self, f: &mut std::fmt::Formatter<'_>, parts: DurationParts, truncate: Truncate) -> core::fmt::Result;
31
32 fn format_default(&self, f: &mut std::fmt::Formatter<'_>, parts: DurationParts, truncate: Truncate) -> core::fmt::Result {
34 let ref mut started = false;
35
36 if parts.is_empty() {
37 self.get(Truncate::Second)
38 .format(f, 0, truncate == Truncate::Second, started)?;
39 return Ok(());
40 }
41
42 if parts.seconds < 0 {
43 f.write_str("-")?;
44 }
45
46 self.get(Truncate::Year)
47 .format(f, parts.years.abs() as u64, truncate == Truncate::Year, started)?;
48 if truncate == Truncate::Year {
49 return Ok(());
50 }
51
52 self.get(Truncate::Month)
53 .format(f, parts.months.abs() as u64, truncate == Truncate::Month, started)?;
54 if truncate == Truncate::Month {
55 return Ok(());
56 }
57
58 self.get(Truncate::Day)
59 .format(f, parts.days.abs() as u64, truncate == Truncate::Day, started)?;
60 if truncate == Truncate::Day {
61 return Ok(());
62 }
63
64 self.get(Truncate::Hour)
65 .format(f, parts.hours.abs() as u64, truncate == Truncate::Hour, started)?;
66 if truncate == Truncate::Hour {
67 return Ok(());
68 }
69
70 self.get(Truncate::Minute)
71 .format(f, parts.minutes.abs() as u64, truncate == Truncate::Minute, started)?;
72 if truncate == Truncate::Minute {
73 return Ok(());
74 }
75
76 self.get(Truncate::Second)
77 .format(f, parts.seconds.abs() as u64, truncate == Truncate::Second, started)?;
78 if truncate == Truncate::Second {
79 return Ok(());
80 }
81
82 self.get(Truncate::Millis)
83 .format(f, parts.millis.abs() as u64, truncate == Truncate::Millis, started)?;
84 if truncate == Truncate::Millis {
85 return Ok(());
86 }
87
88 self.get(Truncate::Micro)
89 .format(f, parts.micros.abs() as u64, truncate == Truncate::Micro, started)?;
90 if truncate == Truncate::Micro {
91 return Ok(());
92 }
93
94 self.get(Truncate::Nano)
95 .format(f, parts.nanos.abs() as u64, truncate == Truncate::Nano, started)?;
96
97 Ok(())
98 }
99}
100
101pub trait Unit {
104 fn one(&self) -> &'static str;
105 fn many(&self) -> &'static str;
106 fn format(&self, f: &mut std::fmt::Formatter<'_>, value: u64, allow_zero: bool, started: &mut bool) -> std::fmt::Result;
107}
108
109unit!(Year, "year", "years");
110unit!(Month, "month", "months");
111unit!(Day, "day", "days");
112unit!(Hour, "h");
113unit!(Minute, "m");
114unit!(Second, "s");
115unit!(Millis, "ms");
116unit!(Micro, "µs");
117unit!(Nano, "ns");
118
119#[derive(Debug, Clone, Copy, PartialOrd, PartialEq)]
120pub enum Truncate {
121 Nano,
122 Micro,
123 Millis,
124 Second,
125 Minute,
126 Hour,
127 Day,
128 Month,
129 Year,
130}
131
132#[cfg(test)]
133mod tests {
134 use core::time::Duration as StdDuration;
135
136 #[cfg(feature = "chrono")]
137 use chrono::Duration as ChronoDuration;
138 use time::Duration as TimeDuration;
139
140 use crate::prelude::DurationExt;
141 use crate::types::{DefaultFormatter, DurationParts};
142 use crate::{unit, Duration, Formatter, Truncate, Unit};
143
144 #[test]
145 fn test_nano() {
146 let duration = time::Duration::nanoseconds(131_200_001_301_021_123);
147 println!("duration: {}", duration);
148
149 let human = duration.human(Truncate::Nano);
150 println!("duration: {human}");
151 assert_eq!("4years 1month 27days 2h 36m 17s 301ms 21µs 123ns", human.to_string());
152
153 let human2 = duration.human(Truncate::Day);
154 println!("duration with days: {human2}");
155 assert_eq!("4years 1month 27days", human2.to_string());
156 }
157
158 #[test]
159 fn test_micro() {
160 let duration = time::Duration::microseconds(123);
161 println!("duration: {}", duration);
162
163 let human = duration.human(Truncate::Micro);
164 println!("duration: {:}", human);
165
166 assert_eq!("123µs", duration.to_string());
167 assert_eq!("123µs", human.to_string());
168 }
169
170 #[test]
171 fn test_millis() {
172 let duration = time::Duration::milliseconds(200_200_111);
173 println!("duration: {}", duration);
174
175 let human = duration.human_with_format(Truncate::Millis, DefaultFormatter);
176 println!("human: {}", human);
177
178 assert_eq!("2d7h36m40s111ms", duration.to_string());
179 assert_eq!("2days 7h 36m 40s 111ms", human.to_string());
180 }
181
182 #[test]
183 fn test_seconds() {
184 let duration = time::Duration::seconds(31_556_952);
185 let human = duration.human_with_format(Truncate::Second, DefaultFormatter);
186 println!("human: {}", human);
187 assert_eq!("1year", human.to_string());
188 }
189
190 #[test]
191 fn test_minutes() {
192 let duration = time::Duration::seconds(556_952);
193 let human = duration.human_with_format(Truncate::Minute, DefaultFormatter);
194 println!("human: {}", human);
195 assert_eq!("6days 10h 42m", human.to_string());
196 }
197
198 #[test]
199 fn test_hours() {
200 let duration = time::Duration::seconds(556_952);
201 let human = duration.human_with_format(Truncate::Hour, DefaultFormatter);
202 println!("human: {}", human);
203 assert_eq!("6days 10h", human.to_string());
204 }
205
206 #[test]
207 fn test_days() {
208 let duration = time::Duration::seconds(556_952);
209 let human = duration.human_with_format(Truncate::Day, DefaultFormatter);
210 println!("human: {}", human);
211 assert_eq!("6days", human.to_string());
212 }
213
214 #[test]
215 fn test_months() {
216 let duration = time::Duration::seconds(556_952);
217 let human = duration.human_with_format(Truncate::Month, DefaultFormatter);
218 println!("human: {}", human);
219 assert_eq!("0months", human.to_string());
220 }
221
222 #[test]
223 fn test_years() {
224 let duration = time::Duration::seconds(456_999_556_952);
225 println!("{duration}");
226
227 let human = duration.human_with_format(Truncate::Year, DefaultFormatter);
228 println!("human: {human}");
229 assert_eq!("14481years", human.to_string());
230 }
231
232 #[cfg(feature = "chrono")]
233 #[test]
234 fn test_chrono_duration() {
235 let duration = chrono::Duration::nanoseconds(9223372036854775807);
236 let std_duration: StdDuration = duration.to_std().unwrap();
237 let time_duration: TimeDuration = time::Duration::try_from(std_duration).unwrap();
238 println!("duration: {duration}");
239 println!("std duration: {time_duration}");
240
241 let human = duration.human(Truncate::Nano);
242 println!("human: {human}");
243
244 assert_eq!("292years 3months 9days 20h 40m 4s 854ms 775µs 807ns", human.to_string());
245 }
246
247 #[cfg(feature = "chrono")]
248 #[test]
249 fn test_convert_chrono_duration() {
250 let duration: ChronoDuration = chrono::Duration::nanoseconds(9223372036854775807);
251 let converted: Duration = duration.into();
252 let duration2: ChronoDuration = converted.into();
253 assert_eq!(duration, duration2);
254 }
255
256 #[test]
257 fn test_convert_time_duration() {
258 let duration: TimeDuration = time::Duration::nanoseconds(123_456_789);
259 let converted: Duration = duration.into();
260 let duration2: TimeDuration = converted.into();
261 assert_eq!(duration, duration2);
262 }
263
264 #[test]
265 fn test_convert_std_time_duration() {
266 let duration: StdDuration = StdDuration::new(123_456_789, 999);
267 let converted: Duration = duration.into();
268 let duration2: StdDuration = converted.into();
269 assert_eq!(duration, duration2);
270 }
271
272 #[test]
273 fn test_custom_formatter() {
274 struct MyFormatter;
275
276 unit!(MyYear, " anno", " anni");
277 unit!(MyMonth, " mese", " mesi");
278 unit!(MyDay, " giorno", " giorni");
279 unit!(MyHour, " ora", " ore");
280 unit!(MyMinute, " minuto", " minuti");
281 unit!(MySecond, " secondo", " secondi");
282 unit!(MyMillis, " millisecondo", " millisecondi");
283 unit!(MyMicro, " microsecondo", " microsecondi");
284 unit!(MyNano, " nanosecondo", " nanosecondi");
285
286 impl Formatter for MyFormatter {
287 fn get(&self, truncate: Truncate) -> Box<dyn Unit> {
288 match truncate {
289 Truncate::Nano => Box::new(MyNano),
290 Truncate::Micro => Box::new(MyMicro),
291 Truncate::Millis => Box::new(MyMillis),
292 Truncate::Second => Box::new(MySecond),
293 Truncate::Minute => Box::new(MyMinute),
294 Truncate::Hour => Box::new(MyHour),
295 Truncate::Day => Box::new(MyDay),
296 Truncate::Month => Box::new(MyMonth),
297 Truncate::Year => Box::new(MyYear),
298 }
299 }
300
301 fn format(&self, f: &mut std::fmt::Formatter<'_>, parts: DurationParts, truncate: Truncate) -> std::fmt::Result {
302 self.format_default(f, parts, truncate)
303 }
304 }
305
306 let duration = TimeDuration::nanoseconds(150_345_202_557_001);
307 let human_default = duration.human(Truncate::Nano);
308 let human = duration.human_with_format(Truncate::Nano, MyFormatter);
309
310 println!("human default: {human_default}");
311 println!("human: {human}");
312
313 assert_eq!("1day 17h 45m 45s 202ms 557µs 1ns", human_default.to_string());
314 assert_eq!(
315 "1 giorno 17 ore 45 minuti 45 secondi 202 millisecondi 557 microsecondi 1 nanosecondo",
316 human.to_string()
317 );
318 }
319}