1mod pretty_duration_lib;
12use std::time::Duration;
13
14use crate::pretty_duration_lib::PrettyDurationOptionsWithDefault;
16
17pub use crate::pretty_duration_lib::{
19 DurationBins, PrettyDurationLabels, PrettyDurationOptions, PrettyDurationOutputFormat,
20};
21const COMPACT_DEFAULT: PrettyDurationLabels = PrettyDurationLabels {
22 year: "y",
23 month: "mon",
24 day: "d",
25 hour: "h",
26 minute: "m",
27 second: "s",
28 millisecond: "ms",
29};
30const EXPANDED_SINGULAR_DEFAULT: PrettyDurationLabels = PrettyDurationLabels {
31 year: "year",
32 month: "month",
33 day: "day",
34 hour: "hour",
35 minute: "minute",
36 second: "second",
37 millisecond: "millisecond",
38};
39const EXPANDED_PLURAL_DEFAULT: PrettyDurationLabels = PrettyDurationLabels {
40 year: "years",
41 month: "months",
42 day: "days",
43 hour: "hours",
44 minute: "minutes",
45 second: "seconds",
46 millisecond: "milliseconds",
47};
48
49pub fn pretty_duration(duration: &Duration, options: Option<PrettyDurationOptions>) -> String {
92 let options_with_default = set_default_options(options);
93 let ms = duration.as_millis();
94 let duration_by_bin = extract_bins(&ms);
95
96 let mut result: Vec<String> = Vec::new();
97 let is_full_word = matches!(
98 options_with_default.output_format,
99 PrettyDurationOutputFormat::Expanded
100 );
101 try_adding(
102 &mut result,
103 duration_by_bin.years.to_string(),
104 &get_unit(
105 options_with_default.singular_labels.year,
106 options_with_default.plural_labels.year,
107 duration_by_bin.years > 1,
108 ),
109 is_full_word,
110 );
111 try_adding(
112 &mut result,
113 duration_by_bin.months.to_string(),
114 &get_unit(
115 options_with_default.singular_labels.month,
116 options_with_default.plural_labels.month,
117 duration_by_bin.months > 1,
118 ),
119 is_full_word,
120 );
121 try_adding(
122 &mut result,
123 duration_by_bin.days.to_string(),
124 &get_unit(
125 options_with_default.singular_labels.day,
126 options_with_default.plural_labels.day,
127 duration_by_bin.days > 1,
128 ),
129 is_full_word,
130 );
131 try_adding(
132 &mut result,
133 duration_by_bin.hours.to_string(),
134 &get_unit(
135 options_with_default.singular_labels.hour,
136 options_with_default.plural_labels.hour,
137 duration_by_bin.hours > 1,
138 ),
139 is_full_word,
140 );
141 try_adding(
142 &mut result,
143 duration_by_bin.minutes.to_string(),
144 &get_unit(
145 options_with_default.singular_labels.minute,
146 options_with_default.plural_labels.minute,
147 duration_by_bin.minutes > 1,
148 ),
149 is_full_word,
150 );
151 try_adding(
152 &mut result,
153 duration_by_bin.seconds.to_string(),
154 &get_unit(
155 options_with_default.singular_labels.second,
156 options_with_default.plural_labels.second,
157 duration_by_bin.seconds > 1,
158 ),
159 is_full_word,
160 );
161 try_adding(
162 &mut result,
163 duration_by_bin.milliseconds.to_string(),
164 &get_unit(
165 options_with_default.singular_labels.millisecond,
166 options_with_default.plural_labels.millisecond,
167 duration_by_bin.milliseconds > 1,
168 ),
169 is_full_word,
170 );
171
172 if result.len() == 0 {
173 let separator = match is_full_word {
174 true => " ",
175 false => "",
176 };
177 return format!(
178 "{}{}{}",
179 "0", separator, options_with_default.singular_labels.millisecond
180 );
181 }
182 return result.join(" ");
183}
184
185fn try_adding(result: &mut Vec<String>, value: String, unit: &str, is_full_word: bool) -> () {
186 let separator = match is_full_word {
187 true => " ",
188 false => "",
189 };
190 if value != "0" {
191 result.push(value + separator + unit);
192 }
193}
194
195fn get_unit(singular_string: &str, plural_string: &str, is_plural: bool) -> String {
196 return match is_plural {
197 true => plural_string.to_string(),
198 false => singular_string.to_string(),
199 };
200}
201
202fn set_default_options(
203 user_options: Option<PrettyDurationOptions>,
204) -> PrettyDurationOptionsWithDefault {
205 let user_options2 = user_options.unwrap_or_else(|| PrettyDurationOptions {
207 output_format: None,
208 singular_labels: None,
209 plural_labels: None,
210 });
211
212 let output_format = user_options2
213 .output_format
214 .unwrap_or_else(|| PrettyDurationOutputFormat::Compact);
215
216 let default_options = PrettyDurationOptionsWithDefault {
218 output_format,
219 singular_labels: user_options2
220 .singular_labels
221 .unwrap_or_else(|| match output_format {
222 PrettyDurationOutputFormat::Compact => COMPACT_DEFAULT,
223 PrettyDurationOutputFormat::Expanded => EXPANDED_SINGULAR_DEFAULT,
224 PrettyDurationOutputFormat::Colon => EXPANDED_SINGULAR_DEFAULT,
225 }),
226 plural_labels: user_options2
227 .plural_labels
228 .unwrap_or_else(|| match output_format {
229 PrettyDurationOutputFormat::Compact => COMPACT_DEFAULT,
230 PrettyDurationOutputFormat::Expanded => EXPANDED_PLURAL_DEFAULT,
231 PrettyDurationOutputFormat::Colon => EXPANDED_PLURAL_DEFAULT,
232 }),
233 };
234
235 return default_options;
237}
238
239fn extract_bins(ms: &u128) -> DurationBins {
241 return DurationBins {
242 years: (ms / 31556926000) as u16,
243 months: (ms / 2629800000) as u8,
244 days: (ms / 86400000) as u8,
245 hours: ((ms / 3600000) % 24) as u8,
246 minutes: ((ms / 60000) % 60) as u8,
247 seconds: ((ms / 1000) % 60) as u8,
248 milliseconds: ((ms) % 1000) as u16,
249 };
250}
251
252#[cfg(test)]
253mod test_get_unit {
254 use super::*;
255
256 #[test]
257 fn test_get_unit_left_side() {
258 let result = get_unit("unit1", "unit2", true);
259 assert_eq!(result, "unit2")
260 }
261
262 #[test]
263 fn test_get_unit_right_side() {
264 let result = get_unit("unit1", "unit2", false);
265 assert_eq!(result, "unit1")
266 }
267}
268#[cfg(test)]
269mod test_extract_bins {
270 use super::*;
271
272 #[test]
273 fn test_extract_bins_huge() {
274 let result = extract_bins(&31556956789);
275 assert_eq!(result.years, 1, "Year mismatch");
276 assert_eq!(result.months, 11, "Month mismatch");
277 assert_eq!(result.days, 109, "Day mismatch");
278 assert_eq!(result.hours, 5, "Hour mismatch");
279 assert_eq!(result.minutes, 49, "Minute mismatch");
280 assert_eq!(result.seconds, 16, "Second mismatch");
281 assert_eq!(result.milliseconds, 789, "Millisecond mismatch");
282 }
283 #[test]
284 fn test_extract_bins_zero() {
285 let result = extract_bins(&0);
286 assert_eq!(result.years, 0, "Year mismatch");
287 assert_eq!(result.months, 0, "Month mismatch");
288 assert_eq!(result.days, 0, "Day mismatch");
289 assert_eq!(result.hours, 0, "Hour mismatch");
290 assert_eq!(result.minutes, 0, "Minute mismatch");
291 assert_eq!(result.seconds, 0, "Second mismatch");
292 assert_eq!(result.milliseconds, 0, "Millisecond mismatch");
293 }
294}