humanize_duration/
lib.rs

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	/// Given the truncate type, it should return the corresponding implemented Unit
27	fn get(&self, truncate: Truncate) -> Box<dyn Unit>;
28
29	// This method is responsible to format the duration parts into the final string
30	fn format(&self, f: &mut std::fmt::Formatter<'_>, parts: DurationParts, truncate: Truncate) -> core::fmt::Result;
31
32	/// default format implementation
33	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
101/// Duration part formatter Unit.
102/// Each unit is responsible to format a specific part of the duration
103pub 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}