1lazy_static::lazy_static! {
68 static ref FANCY_FORMAT: regex::Regex = regex::Regex::new(r#"([0-9]+)([a-zA-Z]{1,2})\s*"#).unwrap();
69}
70
71#[cfg(feature = "serde")]
72use serde::{de::Visitor, Deserialize, Serialize};
73#[cfg(feature = "serde")]
74use std::marker::PhantomData;
75use std::time::Duration;
76
77pub trait AsFancyDuration<T>
80where
81 Self: Sized,
82 T: AsTimes + Clone,
83{
84 fn fancy_duration(&self) -> FancyDuration<T>;
87}
88
89pub trait ParseFancyDuration<T>
93where
94 Self: Sized,
95 T: AsTimes + Clone,
96{
97 fn parse_fancy_duration(s: String) -> Result<Self, anyhow::Error>;
100}
101
102impl ParseFancyDuration<Duration> for Duration {
103 fn parse_fancy_duration(s: String) -> Result<Self, anyhow::Error> {
104 Ok(FancyDuration::<Duration>::parse(&s)?.duration())
105 }
106}
107
108impl AsFancyDuration<Duration> for Duration {
109 fn fancy_duration(&self) -> FancyDuration<Duration> {
110 FancyDuration::new(self.clone())
111 }
112}
113
114impl<D> std::str::FromStr for FancyDuration<D>
115where
116 D: AsTimes + Clone,
117{
118 type Err = anyhow::Error;
119
120 fn from_str(s: &str) -> Result<Self, Self::Err> {
121 Self::parse(s)
122 }
123}
124
125#[cfg(feature = "time")]
126impl ParseFancyDuration<time::Duration> for time::Duration {
127 fn parse_fancy_duration(s: String) -> Result<Self, anyhow::Error> {
128 Ok(FancyDuration::<time::Duration>::parse(&s)?.duration())
129 }
130}
131
132#[cfg(feature = "time")]
133impl AsFancyDuration<time::Duration> for time::Duration {
134 fn fancy_duration(&self) -> FancyDuration<time::Duration> {
135 FancyDuration::new(self.clone())
136 }
137}
138
139#[cfg(feature = "chrono")]
140impl ParseFancyDuration<chrono::Duration> for chrono::Duration {
141 fn parse_fancy_duration(s: String) -> Result<Self, anyhow::Error> {
142 Ok(FancyDuration::<chrono::Duration>::parse(&s)?.duration())
143 }
144}
145
146#[cfg(feature = "chrono")]
147impl AsFancyDuration<chrono::Duration> for chrono::Duration {
148 fn fancy_duration(&self) -> FancyDuration<chrono::Duration> {
149 FancyDuration::new(self.clone())
150 }
151}
152
153pub trait AsTimes: Sized {
156 fn as_times(&self) -> (u64, u64);
160 fn parse_to_duration(s: &str) -> Result<Self, anyhow::Error>;
163 fn from_times(&self, s: u64, ns: u64) -> Self;
165}
166
167impl AsTimes for Duration {
168 fn as_times(&self) -> (u64, u64) {
169 let secs = self.as_secs();
170 let nanos = self.as_nanos();
171
172 (
173 secs,
174 (nanos - (nanos / 1e9 as u128) * 1e9 as u128)
175 .try_into()
176 .unwrap(),
177 )
178 }
179
180 fn parse_to_duration(s: &str) -> Result<Self, anyhow::Error> {
181 let ns = FancyDuration::<Duration>::parse_to_ns(s)?;
182 Ok(Duration::new(ns.0, ns.1.try_into()?))
183 }
184
185 fn from_times(&self, s: u64, ns: u64) -> Self {
186 Duration::new(s, ns.try_into().unwrap())
187 }
188}
189
190#[cfg(feature = "chrono")]
191impl AsTimes for chrono::Duration {
192 fn as_times(&self) -> (u64, u64) {
193 let secs = self.num_seconds();
194 let nanos = self.num_nanoseconds().unwrap();
195
196 (
197 secs.try_into().unwrap(),
198 (nanos - (nanos / 1e9 as i64) * 1e9 as i64)
199 .try_into()
200 .unwrap(),
201 )
202 }
203
204 fn parse_to_duration(s: &str) -> Result<Self, anyhow::Error> {
205 let ns = FancyDuration::<chrono::Duration>::parse_to_ns(s)?;
206
207 Ok(
208 chrono::TimeDelta::try_seconds(ns.0.try_into()?).unwrap_or_default()
209 + chrono::Duration::nanoseconds(ns.1.try_into()?),
210 )
211 }
212
213 fn from_times(&self, s: u64, ns: u64) -> Self {
214 chrono::TimeDelta::try_seconds(s.try_into().unwrap()).unwrap_or_default()
215 + chrono::Duration::nanoseconds(ns.try_into().unwrap())
216 }
217}
218
219#[cfg(feature = "time")]
220impl AsTimes for time::Duration {
221 fn as_times(&self) -> (u64, u64) {
222 (
223 self.as_seconds_f64() as u64,
224 self.subsec_nanoseconds() as u64,
225 )
226 }
227
228 fn parse_to_duration(s: &str) -> Result<Self, anyhow::Error> {
229 let ns = FancyDuration::<Duration>::parse_to_ns(s)?;
230 Ok(time::Duration::new(ns.0.try_into()?, ns.1.try_into()?))
231 }
232
233 fn from_times(&self, s: u64, ns: u64) -> Self {
234 time::Duration::new(s.try_into().unwrap(), ns.try_into().unwrap())
235 }
236}
237
238#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
239pub enum DurationPart {
240 Years,
241 Months,
242 Weeks,
243 Days,
244 Hours,
245 Minutes,
246 Seconds,
247 Milliseconds,
248 Microseconds,
249 Nanoseconds,
250}
251
252#[derive(Debug, Clone)]
253pub(crate) struct DurationBreakdown {
254 pub(crate) years: u64,
255 pub(crate) months: u64,
256 pub(crate) weeks: u64,
257 pub(crate) days: u64,
258 pub(crate) hours: u64,
259 pub(crate) minutes: u64,
260 pub(crate) seconds: u64,
261 pub(crate) milliseconds: u64,
262 pub(crate) microseconds: u64,
263 pub(crate) nanoseconds: u64,
264}
265
266const YEAR: u64 = 12 * 30 * 24 * 60 * 60;
267const MONTH: u64 = 30 * 24 * 60 * 60;
268const WEEK: u64 = 7 * 24 * 60 * 60;
269const DAY: u64 = 24 * 60 * 60;
270const HOUR: u64 = 60 * 60;
271const MINUTE: u64 = 60;
272
273impl DurationBreakdown {
274 pub(crate) fn new(mut s: u64, mut ns: u64) -> Self {
275 let years = s / YEAR;
276 s -= years * YEAR;
277 let months = s / MONTH;
278 s -= months * MONTH;
279 let weeks = s / WEEK;
280 s -= weeks * WEEK;
281 let days = s / DAY;
282 s -= days * DAY;
283 let hours = s / HOUR;
284 s -= hours * HOUR;
285 let minutes = s / MINUTE;
286 s -= minutes * MINUTE;
287
288 let ms = ns / 1e6 as u64;
289 ns -= ms * 1e6 as u64;
290 let us = ns / 1e3 as u64;
291 ns -= us * 1e3 as u64;
292
293 Self {
294 years,
295 months,
296 weeks,
297 days,
298 hours,
299 minutes,
300 seconds: s,
301 milliseconds: ms,
302 microseconds: us,
303 nanoseconds: ns,
304 }
305 }
306
307 pub(crate) fn truncate(&self, mut limit: usize) -> Self {
308 let mut obj = self.clone();
309 let mut limit_started = false;
310
311 for val in [
312 &mut obj.years,
313 &mut obj.months,
314 &mut obj.weeks,
315 &mut obj.days,
316 &mut obj.hours,
317 &mut obj.minutes,
318 &mut obj.seconds,
319 &mut obj.milliseconds,
320 &mut obj.microseconds,
321 &mut obj.nanoseconds,
322 ] {
323 if limit_started || *val > 0 {
324 limit_started = true;
325
326 if limit == 0 {
327 *val = 0
328 }
329
330 if limit != 0 {
331 limit -= 1;
332 }
333 }
334 }
335
336 obj
337 }
338
339 pub fn filter(&self, filter: &[DurationPart]) -> Self {
340 let mut obj = self.clone();
341
342 let all = &[
343 DurationPart::Years,
344 DurationPart::Months,
345 DurationPart::Weeks,
346 DurationPart::Days,
347 DurationPart::Hours,
348 DurationPart::Minutes,
349 DurationPart::Seconds,
350 DurationPart::Milliseconds,
351 DurationPart::Microseconds,
352 DurationPart::Nanoseconds,
353 ];
354
355 for part in all {
356 if !filter.contains(part) {
357 match part {
358 DurationPart::Years => obj.years = 0,
359 DurationPart::Months => obj.months = 0,
360 DurationPart::Weeks => obj.weeks = 0,
361 DurationPart::Days => obj.days = 0,
362 DurationPart::Hours => obj.hours = 0,
363 DurationPart::Minutes => obj.minutes = 0,
364 DurationPart::Seconds => obj.seconds = 0,
365 DurationPart::Milliseconds => obj.milliseconds = 0,
366 DurationPart::Microseconds => obj.microseconds = 0,
367 DurationPart::Nanoseconds => obj.nanoseconds = 0,
368 }
369 }
370 }
371
372 obj
373 }
374
375 pub fn as_times(&self) -> (u64, u64) {
376 let mut s = 0;
377 let mut ns = 0;
378
379 s += self.years * 12 * 30 * 24 * 60 * 60
380 + self.months * 30 * 24 * 60 * 60
381 + self.weeks * 7 * 24 * 60 * 60
382 + self.days * 24 * 60 * 60
383 + self.hours * 60 * 60
384 + self.minutes * 60
385 + self.seconds;
386 ns += self.milliseconds * 1e6 as u64 + self.microseconds * 1e3 as u64 + self.nanoseconds;
387
388 (s, ns)
389 }
390}
391
392#[derive(Clone, Debug, PartialEq)]
440pub struct FancyDuration<D: AsTimes + Clone>(pub D);
441
442impl<D> FancyDuration<D>
443where
444 D: AsTimes + Clone,
445{
446 pub fn new(d: D) -> Self {
451 Self(d)
452 }
453
454 pub fn duration(&self) -> D
456 where
457 D: Clone,
458 {
459 self.0.clone()
460 }
461
462 pub fn filter(&self, filter: &[DurationPart]) -> Self {
465 let mut obj = self.clone();
466 let times = self.0.as_times();
467 let filtered = DurationBreakdown::new(times.0, times.1)
468 .filter(filter)
469 .as_times();
470 obj.0 = self.0.from_times(filtered.0, filtered.1);
471 obj
472 }
473
474 pub fn truncate(&self, limit: usize) -> Self {
480 let mut obj = self.clone();
481 let times = self.0.as_times();
482 let truncated = DurationBreakdown::new(times.0, times.1)
483 .truncate(limit)
484 .as_times();
485 obj.0 = self.0.from_times(truncated.0, truncated.1);
486 obj
487 }
488
489 pub fn parse(s: &str) -> Result<Self, anyhow::Error> {
492 Ok(FancyDuration::new(D::parse_to_duration(s)?))
493 }
494
495 pub fn format(&self) -> String {
498 self.format_internal(true)
499 }
500
501 pub fn format_compact(&self) -> String {
504 self.format_internal(false)
505 }
506
507 fn format_internal(&self, pad: bool) -> String {
508 let times = self.0.as_times();
509
510 if times.0 == 0 && times.1 == 0 {
511 return "0".to_string();
512 }
513
514 let breakdown = DurationBreakdown::new(times.0, times.1);
515
516 let mut s = String::new();
517
518 let spad = if pad { " " } else { "" };
519
520 if breakdown.years > 0 {
521 s += &format!("{}y{}", breakdown.years, spad)
522 }
523
524 if breakdown.months > 0 {
525 s += &format!("{}m{}", breakdown.months, spad)
526 }
527
528 if breakdown.weeks > 0 {
529 s += &format!("{}w{}", breakdown.weeks, spad)
530 }
531
532 if breakdown.days > 0 {
533 s += &format!("{}d{}", breakdown.days, spad)
534 }
535
536 if breakdown.hours > 0 {
537 s += &format!("{}h{}", breakdown.hours, spad)
538 }
539
540 if breakdown.minutes > 0 {
541 s += &format!("{}m{}", breakdown.minutes, spad)
542 }
543
544 if breakdown.seconds > 0 {
545 s += &format!("{}s{}", breakdown.seconds, spad)
546 }
547
548 if breakdown.milliseconds > 0 {
549 s += &format!("{}ms{}", breakdown.milliseconds, spad)
550 }
551
552 if breakdown.microseconds > 0 {
553 s += &format!("{}us{}", breakdown.microseconds, spad)
554 }
555
556 if breakdown.nanoseconds > 0 {
557 s += &format!("{}ns{}", breakdown.nanoseconds, spad)
558 }
559
560 if pad {
561 s.truncate(s.len() - 1);
562 }
563
564 s
565 }
566
567 pub fn parse_to_ns(s: &str) -> Result<(u64, u64), anyhow::Error> {
571 let mut subseconds: u64 = 0;
572 let mut seconds: u64 = 0;
573 let mut past_minutes = false;
574
575 let mut list: Vec<(&str, &str)> = Vec::new();
576
577 for item in FANCY_FORMAT.captures_iter(s) {
578 list.push((item.get(1).unwrap().as_str(), item.get(2).unwrap().as_str()));
579 }
580
581 for (value, suffix) in list.iter().rev() {
582 match *suffix {
583 "ns" => {
584 let result: u64 = value.parse()?;
585 subseconds += result;
586 }
587 "ms" => {
588 let result: u64 = value.parse()?;
589 subseconds += result * 1e6 as u64;
590 }
591 "us" => {
592 let result: u64 = value.parse()?;
593 subseconds += result * 1e3 as u64;
594 }
595 "s" => {
596 let result: u64 = value.parse()?;
597 seconds += result;
598 }
599 "m" => {
600 let result: u64 = value.parse()?;
601 seconds += if past_minutes {
602 result * 60 * 60 * 24 * 30
603 } else {
604 past_minutes = true;
605 result * 60
606 }
607 }
608 "h" => {
609 past_minutes = true;
610 let result: u64 = value.parse()?;
611 seconds += result * 60 * 60
612 }
613 "d" => {
614 past_minutes = true;
615 let result: u64 = value.parse()?;
616 seconds += result * 60 * 60 * 24
617 }
618 "w" => {
619 past_minutes = true;
620 let result: u64 = value.parse()?;
621 seconds += result * 60 * 60 * 24 * 7
622 }
623 "y" => {
624 past_minutes = true;
625 let result: u64 = value.parse()?;
626 seconds += result * 12 * 30 * 60 * 60 * 24
627 }
628 _ => {}
629 }
630 }
631
632 Ok((seconds, subseconds))
633 }
634}
635
636impl<D> std::fmt::Display for FancyDuration<D>
637where
638 D: AsTimes + Clone,
639{
640 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
641 f.write_str(&self.format())
642 }
643}
644
645#[cfg(feature = "serde")]
646impl<D> Serialize for FancyDuration<D>
647where
648 D: AsTimes + Clone,
649{
650 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
651 where
652 S: serde::Serializer,
653 {
654 serializer.serialize_str(&self.to_string())
655 }
656}
657
658#[cfg(feature = "serde")]
659struct FancyDurationVisitor<D: AsTimes>(PhantomData<D>);
660
661#[cfg(feature = "serde")]
662impl<D> Visitor<'_> for FancyDurationVisitor<D>
663where
664 D: AsTimes + Clone,
665{
666 type Value = FancyDuration<D>;
667
668 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
669 formatter.write_str("expecting a duration in 'fancy' format")
670 }
671
672 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
673 where
674 E: serde::de::Error,
675 {
676 match FancyDuration::parse(v) {
677 Ok(res) => Ok(res),
678 Err(e) => Err(serde::de::Error::custom(e)),
679 }
680 }
681}
682
683#[cfg(feature = "serde")]
684impl<'de, T> Deserialize<'de> for FancyDuration<T>
685where
686 T: AsTimes + Clone,
687{
688 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
689 where
690 D: serde::Deserializer<'de>,
691 {
692 deserializer.deserialize_str(FancyDurationVisitor(PhantomData::default()))
693 }
694}
695
696#[cfg(test)]
697mod tests {
698 use std::time::Duration;
699
700 use crate::FancyDuration;
701
702 #[test]
703 fn test_fancy_duration_call() {
704 use super::{AsFancyDuration, ParseFancyDuration};
705
706 assert_eq!(Duration::new(0, 600).fancy_duration().to_string(), "600ns");
707 #[cfg(feature = "time")]
708 assert_eq!(
709 time::Duration::new(0, 600).fancy_duration().to_string(),
710 "600ns"
711 );
712 #[cfg(feature = "chrono")]
713 assert_eq!(
714 chrono::Duration::nanoseconds(600)
715 .fancy_duration()
716 .to_string(),
717 "600ns"
718 );
719 assert_eq!(
720 Duration::parse_fancy_duration("600ns".to_string()).unwrap(),
721 Duration::new(0, 600)
722 );
723 #[cfg(feature = "time")]
724 assert_eq!(
725 time::Duration::parse_fancy_duration("600ns".to_string()).unwrap(),
726 time::Duration::new(0, 600)
727 );
728 #[cfg(feature = "chrono")]
729 assert_eq!(
730 chrono::Duration::parse_fancy_duration("600ns".to_string()).unwrap(),
731 chrono::Duration::nanoseconds(600)
732 );
733 }
734
735 #[test]
736 fn test_duration_to_string() {
737 assert_eq!(FancyDuration(Duration::new(0, 600)).to_string(), "600ns");
738 assert_eq!(FancyDuration(Duration::new(0, 600000)).to_string(), "600us");
739 assert_eq!(
740 FancyDuration(Duration::new(0, 600000000)).to_string(),
741 "600ms"
742 );
743 assert_eq!(FancyDuration(Duration::new(600, 0)).to_string(), "10m");
744 assert_eq!(FancyDuration(Duration::new(120, 0)).to_string(), "2m");
745 assert_eq!(FancyDuration(Duration::new(185, 0)).to_string(), "3m 5s");
746 assert_eq!(
747 FancyDuration(Duration::new(24 * 60 * 60, 0)).to_string(),
748 "1d"
749 );
750 assert_eq!(FancyDuration(Duration::new(324, 0)).to_string(), "5m 24s");
751 assert_eq!(
752 FancyDuration(Duration::new(24 * 60 * 60 + 324, 0)).to_string(),
753 "1d 5m 24s"
754 );
755 assert_eq!(
756 FancyDuration(Duration::new(27 * 24 * 60 * 60 + 324, 0)).to_string(),
757 "3w 6d 5m 24s"
758 );
759 assert_eq!(
760 FancyDuration(Duration::new(99 * 24 * 60 * 60 + 324, 0)).to_string(),
761 "3m 1w 2d 5m 24s"
762 );
763
764 assert_eq!(
765 FancyDuration(Duration::new(12 * 30 * 24 * 60 * 60 + 10 * 24 * 60 * 60, 0)).to_string(),
766 "1y 1w 3d"
767 );
768
769 assert_eq!(
770 FancyDuration(Duration::new(12 * 30 * 24 * 60 * 60 + 10 * 24 * 60 * 60, 0))
771 .format_compact(),
772 "1y1w3d"
773 );
774
775 assert_eq!(
776 FancyDuration(Duration::new(324, 0)).format_compact(),
777 "5m24s"
778 );
779 assert_eq!(
780 FancyDuration(Duration::new(24 * 60 * 60 + 324, 0)).format_compact(),
781 "1d5m24s"
782 );
783 assert_eq!(
784 FancyDuration(Duration::new(27 * 24 * 60 * 60 + 324, 0)).format_compact(),
785 "3w6d5m24s"
786 );
787 assert_eq!(
788 FancyDuration(Duration::new(99 * 24 * 60 * 60 + 324, 0)).format_compact(),
789 "3m1w2d5m24s"
790 );
791 }
792
793 #[test]
794 #[cfg(feature = "time")]
795 fn test_time_duration_to_string() {
796 assert_eq!(
797 FancyDuration(time::Duration::new(0, 600)).to_string(),
798 "600ns"
799 );
800 assert_eq!(
801 FancyDuration(time::Duration::new(0, 600000)).to_string(),
802 "600us"
803 );
804 assert_eq!(
805 FancyDuration(time::Duration::new(0, 600000000)).to_string(),
806 "600ms"
807 );
808 assert_eq!(
809 FancyDuration(time::Duration::new(600, 0)).to_string(),
810 "10m"
811 );
812 assert_eq!(FancyDuration(time::Duration::new(120, 0)).to_string(), "2m");
813 assert_eq!(
814 FancyDuration(time::Duration::new(185, 0)).to_string(),
815 "3m 5s"
816 );
817 assert_eq!(
818 FancyDuration(time::Duration::new(24 * 60 * 60, 0)).to_string(),
819 "1d"
820 );
821 assert_eq!(
822 FancyDuration(time::Duration::new(324, 0)).to_string(),
823 "5m 24s"
824 );
825 assert_eq!(
826 FancyDuration(time::Duration::new(24 * 60 * 60 + 324, 0)).to_string(),
827 "1d 5m 24s"
828 );
829 assert_eq!(
830 FancyDuration(time::Duration::new(27 * 24 * 60 * 60 + 324, 0)).to_string(),
831 "3w 6d 5m 24s"
832 );
833 assert_eq!(
834 FancyDuration(time::Duration::new(99 * 24 * 60 * 60 + 324, 0)).to_string(),
835 "3m 1w 2d 5m 24s"
836 );
837
838 assert_eq!(
839 FancyDuration(time::Duration::new(
840 12 * 30 * 24 * 60 * 60 + 10 * 24 * 60 * 60,
841 0
842 ))
843 .to_string(),
844 "1y 1w 3d"
845 );
846
847 assert_eq!(
848 FancyDuration(time::Duration::new(
849 12 * 30 * 24 * 60 * 60 + 10 * 24 * 60 * 60,
850 0
851 ))
852 .format_compact(),
853 "1y1w3d"
854 );
855
856 assert_eq!(
857 FancyDuration(time::Duration::new(24 * 60 * 60 + 324, 0)).format_compact(),
858 "1d5m24s"
859 );
860 assert_eq!(
861 FancyDuration(time::Duration::new(27 * 24 * 60 * 60 + 324, 0)).format_compact(),
862 "3w6d5m24s"
863 );
864 assert_eq!(
865 FancyDuration(time::Duration::new(99 * 24 * 60 * 60 + 324, 0)).format_compact(),
866 "3m1w2d5m24s"
867 );
868 }
869
870 #[test]
871 #[cfg(feature = "chrono")]
872 fn test_chrono_duration_to_string() {
873 assert_eq!(
874 FancyDuration(chrono::Duration::nanoseconds(600)).to_string(),
875 "600ns"
876 );
877 assert_eq!(
878 FancyDuration(chrono::Duration::microseconds(600)).to_string(),
879 "600us"
880 );
881 assert_eq!(
882 FancyDuration(chrono::TimeDelta::try_milliseconds(600).unwrap_or_default()).to_string(),
883 "600ms"
884 );
885 assert_eq!(
886 FancyDuration(chrono::TimeDelta::try_seconds(600).unwrap_or_default()).to_string(),
887 "10m"
888 );
889 assert_eq!(
890 FancyDuration(chrono::TimeDelta::try_seconds(120).unwrap_or_default()).to_string(),
891 "2m"
892 );
893 assert_eq!(
894 FancyDuration(chrono::TimeDelta::try_seconds(185).unwrap_or_default()).to_string(),
895 "3m 5s"
896 );
897 assert_eq!(
898 FancyDuration(chrono::TimeDelta::try_seconds(24 * 60 * 60).unwrap_or_default())
899 .to_string(),
900 "1d"
901 );
902 assert_eq!(
903 FancyDuration(chrono::TimeDelta::try_seconds(324).unwrap_or_default()).to_string(),
904 "5m 24s"
905 );
906 assert_eq!(
907 FancyDuration(chrono::TimeDelta::try_seconds(24 * 60 * 60 + 324).unwrap_or_default())
908 .to_string(),
909 "1d 5m 24s"
910 );
911 assert_eq!(
912 FancyDuration(
913 chrono::TimeDelta::try_seconds(27 * 24 * 60 * 60 + 324).unwrap_or_default()
914 )
915 .to_string(),
916 "3w 6d 5m 24s"
917 );
918 assert_eq!(
919 FancyDuration(
920 chrono::TimeDelta::try_seconds(99 * 24 * 60 * 60 + 324).unwrap_or_default()
921 )
922 .to_string(),
923 "3m 1w 2d 5m 24s"
924 );
925
926 assert_eq!(
927 FancyDuration(
928 chrono::Duration::try_seconds(12 * 30 * 24 * 60 * 60 + 10 * 24 * 60 * 60,)
929 .unwrap_or_default()
930 )
931 .to_string(),
932 "1y 1w 3d"
933 );
934
935 assert_eq!(
936 FancyDuration(
937 chrono::TimeDelta::try_seconds(12 * 30 * 24 * 60 * 60 + 10 * 24 * 60 * 60,)
938 .unwrap_or_default()
939 )
940 .format_compact(),
941 "1y1w3d"
942 );
943 assert_eq!(
944 FancyDuration(chrono::TimeDelta::try_seconds(24 * 60 * 60 + 324).unwrap_or_default())
945 .format_compact(),
946 "1d5m24s"
947 );
948 assert_eq!(
949 FancyDuration(
950 chrono::TimeDelta::try_seconds(27 * 24 * 60 * 60 + 324).unwrap_or_default()
951 )
952 .format_compact(),
953 "3w6d5m24s"
954 );
955 assert_eq!(
956 FancyDuration(
957 chrono::TimeDelta::try_seconds(99 * 24 * 60 * 60 + 324).unwrap_or_default()
958 )
959 .format_compact(),
960 "3m1w2d5m24s"
961 );
962 }
963
964 #[test]
965 fn test_parse_filter() {
966 use super::DurationPart;
967 let duration_table = [
968 (
969 "1m 5s 10ms",
970 vec![DurationPart::Minutes, DurationPart::Milliseconds],
971 "1m 10ms",
972 ),
973 (
974 "1h 1m 30us",
975 vec![DurationPart::Minutes, DurationPart::Microseconds],
976 "1m 30us",
977 ),
978 ("1d 1h 30ns", vec![DurationPart::Days], "1d"),
979 (
980 "10s",
981 vec![DurationPart::Seconds, DurationPart::Minutes],
982 "10s",
983 ),
984 (
985 "3m 5s",
986 vec![
987 DurationPart::Hours,
988 DurationPart::Minutes,
989 DurationPart::Seconds,
990 ],
991 "3m 5s",
992 ),
993 (
994 "3m 2w 2d 10m 10s",
995 vec![
996 DurationPart::Months,
997 DurationPart::Weeks,
998 DurationPart::Days,
999 ],
1000 "3m 2w 2d",
1001 ),
1002 ];
1003
1004 for (orig_duration, filter, new_duration) in &duration_table {
1005 assert_eq!(
1006 *new_duration,
1007 FancyDuration::<Duration>::parse(orig_duration)
1008 .unwrap()
1009 .filter(&filter)
1010 .to_string()
1011 )
1012 }
1013
1014 #[cfg(feature = "time")]
1015 for (orig_duration, filter, new_duration) in &duration_table {
1016 assert_eq!(
1017 *new_duration,
1018 FancyDuration::<time::Duration>::parse(orig_duration)
1019 .unwrap()
1020 .filter(&filter)
1021 .to_string()
1022 )
1023 }
1024
1025 #[cfg(feature = "chrono")]
1026 for (orig_duration, filter, new_duration) in &duration_table {
1027 assert_eq!(
1028 *new_duration,
1029 FancyDuration::<chrono::Duration>::parse(orig_duration)
1030 .unwrap()
1031 .filter(&filter)
1032 .to_string()
1033 )
1034 }
1035 }
1036 #[test]
1037 fn test_parse_truncate() {
1038 let duration_table = [
1039 ("1m 5s 10ms", 2, "1m 5s"),
1040 ("1h 1m 30us", 3, "1h 1m"),
1041 ("1d 1h 30ns", 1, "1d"),
1042 ("10s", 3, "10s"),
1043 ("3m 5s", 2, "3m 5s"),
1044 ("3m 2w 2d 10m 10s", 3, "3m 2w 2d"),
1045 ];
1046
1047 for (orig_duration, truncate, new_duration) in &duration_table {
1048 assert_eq!(
1049 *new_duration,
1050 FancyDuration::<Duration>::parse(orig_duration)
1051 .unwrap()
1052 .truncate(*truncate)
1053 .to_string()
1054 )
1055 }
1056
1057 #[cfg(feature = "time")]
1058 for (orig_duration, truncate, new_duration) in &duration_table {
1059 assert_eq!(
1060 *new_duration,
1061 FancyDuration::<time::Duration>::parse(orig_duration)
1062 .unwrap()
1063 .truncate(*truncate)
1064 .to_string()
1065 )
1066 }
1067
1068 #[cfg(feature = "chrono")]
1069 for (orig_duration, truncate, new_duration) in &duration_table {
1070 assert_eq!(
1071 *new_duration,
1072 FancyDuration::<chrono::Duration>::parse(orig_duration)
1073 .unwrap()
1074 .truncate(*truncate)
1075 .to_string()
1076 )
1077 }
1078 }
1079
1080 #[test]
1081 fn test_parse_duration() {
1082 let duration_table = [
1083 ("1m 10ms", Duration::new(60, 10000000)),
1084 ("1h 30us", Duration::new(60 * 60, 30000)),
1085 ("1d 30ns", Duration::new(60 * 60 * 24, 30)),
1086 ("10s", Duration::new(10, 0)),
1087 ("3m 5s", Duration::new(185, 0)),
1088 ("3m 2w 2d 10m 10s", Duration::new(9159010, 0)),
1089 ];
1090
1091 let compact_duration_table = [
1092 ("10s30ns", Duration::new(10, 30)),
1093 ("3m5s", Duration::new(185, 0)),
1094 ("3m2w2d10m10s", Duration::new(9159010, 0)),
1095 ];
1096
1097 for item in duration_table {
1098 let fancy = FancyDuration::<Duration>::parse(item.0).unwrap();
1099 assert_eq!(fancy.duration(), item.1);
1100 assert_eq!(FancyDuration::new(item.1).to_string(), item.0);
1101 }
1102
1103 for item in compact_duration_table {
1104 let fancy = FancyDuration::<Duration>::parse(item.0).unwrap();
1105 assert_eq!(fancy.duration(), item.1);
1106 assert_eq!(FancyDuration::new(item.1).format_compact(), item.0);
1107 }
1108
1109 #[cfg(feature = "time")]
1110 {
1111 let time_table = [
1112 ("1m 10ms", time::Duration::new(60, 10000000)),
1113 ("1h 30us", time::Duration::new(60 * 60, 30000)),
1114 ("1d 30ns", time::Duration::new(60 * 60 * 24, 30)),
1115 ("10s", time::Duration::new(10, 0)),
1116 ("3m 5s", time::Duration::new(185, 0)),
1117 ("3m 2w 2d 10m 10s", time::Duration::new(9159010, 0)),
1118 ];
1119
1120 let compact_time_table = [
1121 ("3m5s", time::Duration::new(185, 0)),
1122 ("3m2w2d10m10s", time::Duration::new(9159010, 0)),
1123 ];
1124 for item in time_table {
1125 let fancy = FancyDuration::<time::Duration>::parse(item.0).unwrap();
1126 assert_eq!(fancy.duration(), item.1);
1127 assert_eq!(FancyDuration::new(item.1).to_string(), item.0);
1128 }
1129
1130 for item in compact_time_table {
1131 let fancy = FancyDuration::<time::Duration>::parse(item.0).unwrap();
1132 assert_eq!(fancy.duration(), item.1);
1133 assert_eq!(FancyDuration::new(item.1).format_compact(), item.0);
1134 }
1135 }
1136
1137 #[cfg(feature = "chrono")]
1138 {
1139 let chrono_table = [
1140 (
1141 "1m 10ms",
1142 chrono::TimeDelta::try_seconds(60).unwrap_or_default()
1143 + chrono::TimeDelta::try_milliseconds(10).unwrap_or_default(),
1144 ),
1145 (
1146 "1h 30us",
1147 chrono::TimeDelta::try_hours(1).unwrap_or_default()
1148 + chrono::Duration::microseconds(30),
1149 ),
1150 (
1151 "1d 30ns",
1152 chrono::TimeDelta::try_days(1).unwrap_or_default()
1153 + chrono::Duration::nanoseconds(30),
1154 ),
1155 (
1156 "10s",
1157 chrono::TimeDelta::try_seconds(10).unwrap_or_default(),
1158 ),
1159 (
1160 "3m 5s",
1161 chrono::TimeDelta::try_seconds(185).unwrap_or_default(),
1162 ),
1163 (
1164 "3m 2w 2d 10m 10s",
1165 chrono::TimeDelta::try_seconds(9159010).unwrap_or_default(),
1166 ),
1167 ];
1168
1169 let compact_chrono_table = [
1170 (
1171 "3m5s",
1172 chrono::TimeDelta::try_seconds(185).unwrap_or_default(),
1173 ),
1174 (
1175 "3m2w2d10m10s",
1176 chrono::TimeDelta::try_seconds(9159010).unwrap_or_default(),
1177 ),
1178 ];
1179 for item in chrono_table {
1180 let fancy = FancyDuration::<chrono::Duration>::parse(item.0).unwrap();
1181 assert_eq!(fancy.duration(), item.1);
1182 assert_eq!(FancyDuration::new(item.1).to_string(), item.0);
1183 }
1184
1185 for item in compact_chrono_table {
1186 let fancy = FancyDuration::<chrono::Duration>::parse(item.0).unwrap();
1187 assert_eq!(fancy.duration(), item.1);
1188 assert_eq!(FancyDuration::new(item.1).format_compact(), item.0);
1189 }
1190 }
1191 }
1192
1193 #[cfg(feature = "serde")]
1194 #[test]
1195 fn test_serde() {
1196 use serde::{Deserialize, Serialize};
1197
1198 #[derive(Serialize, Deserialize)]
1199 struct StdDuration {
1200 duration: FancyDuration<std::time::Duration>,
1201 }
1202
1203 let duration_table = [
1204 ("{\"duration\":\"10ns\"}", Duration::new(0, 10)),
1205 ("{\"duration\":\"10s\"}", Duration::new(10, 0)),
1206 ("{\"duration\":\"3m 5s\"}", Duration::new(185, 0)),
1207 (
1208 "{\"duration\":\"1y 3m 2w 2d 10m 10s\"}",
1209 Duration::new(40263010, 0),
1210 ),
1211 ];
1212
1213 for item in duration_table {
1214 let md: StdDuration = serde_json::from_str(item.0).unwrap();
1215 assert_eq!(md.duration.duration(), item.1);
1216 assert_eq!(serde_json::to_string(&md).unwrap(), item.0);
1217 }
1218
1219 #[cfg(feature = "time")]
1220 {
1221 #[derive(Serialize, Deserialize)]
1222 struct TimeDuration {
1223 duration: FancyDuration<time::Duration>,
1224 }
1225
1226 let time_table = [
1227 ("{\"duration\":\"10ns\"}", time::Duration::new(0, 10)),
1228 ("{\"duration\":\"10s\"}", time::Duration::new(10, 0)),
1229 ("{\"duration\":\"3m 5s\"}", time::Duration::new(185, 0)),
1230 (
1231 "{\"duration\":\"1y 3m 2w 2d 10m 10s\"}",
1232 time::Duration::new(40263010, 0),
1233 ),
1234 ];
1235
1236 for item in time_table {
1237 let md: TimeDuration = serde_json::from_str(item.0).unwrap();
1238 assert_eq!(md.duration.duration(), item.1);
1239 assert_eq!(serde_json::to_string(&md).unwrap(), item.0);
1240 }
1241 }
1242
1243 #[cfg(feature = "chrono")]
1244 {
1245 #[derive(Serialize, Deserialize)]
1246 struct ChronoDuration {
1247 duration: FancyDuration<chrono::Duration>,
1248 }
1249
1250 let chrono_table = [
1251 ("{\"duration\":\"10ns\"}", chrono::Duration::nanoseconds(10)),
1252 (
1253 "{\"duration\":\"10s\"}",
1254 chrono::TimeDelta::try_seconds(10).unwrap_or_default(),
1255 ),
1256 (
1257 "{\"duration\":\"3m 5s\"}",
1258 chrono::TimeDelta::try_seconds(185).unwrap_or_default(),
1259 ),
1260 (
1261 "{\"duration\":\"1y 3m 2w 2d 10m 10s\"}",
1262 chrono::TimeDelta::try_seconds(40263010).unwrap_or_default(),
1263 ),
1264 ];
1265
1266 for item in chrono_table {
1267 let md: ChronoDuration = serde_json::from_str(item.0).unwrap();
1268 assert_eq!(md.duration.duration(), item.1);
1269 assert_eq!(serde_json::to_string(&md).unwrap(), item.0);
1270 }
1271 }
1272 }
1273}