Repository: time-rs/time (language: rust)

Issue:
Alternative, concise `Display` format for `Duration`
While the current printouts for `Duration` are _precise_, they're not entirely pleasant to read:

```rust
println!("{}", time::Duration::new(123456, 789011223))
```

```
1d10h17m36s789ms11µs223ns
```

The lack of spacing probably doesn't help, but I think the main issue is that there's just too much information. 

For applications where conciseness is preferred, I suggest supporting the precision format specifier that prints only the largest nonzero field plus the given precision as a number of fractional digits, e.g.:

```rust
println!("{:.3}", time::Duration::new(123456, 789011223));
```

```
1.429d
```


Discussion / hints:
I would accept a PR implementing this. It would need to use the [`Formatter::precision`](https://doc.rust-lang.org/stable/std/fmt/struct.Formatter.html#method.precision) method.

Fix patch:
diff --git a/src/duration.rs b/src/duration.rs
index 74e1f5c15..c71b0c3d1 100644
--- a/src/duration.rs
+++ b/src/duration.rs
@@ -748,38 +748,81 @@ impl Duration {
 // region: trait impls
 /// The format returned by this implementation is not stable and must not be relied upon.
 ///
+/// By default this produces an exact, full-precision printout of the duration.
+/// For a concise, rounded printout instead, you can use the `.N` format specifier:
+///
+/// ```
+/// # use time::Duration;
+/// #
+/// let duration = Duration::new(123456, 789011223);
+/// println!("{:.3}", duration);
+/// ```
+///
 /// For the purposes of this implementation, a day is exactly 24 hours and a minute is exactly 60
 /// seconds.
 impl fmt::Display for Duration {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        /// Format a single item.
-        macro_rules! item {
-            ($name:literal, $value:expr) => {
-                match $value {
-                    0 => Ok(()),
-                    value => value.fmt(f).and(f.write_str($name)),
-                }
-            };
+        if self.is_negative() {
+            f.write_str("-")?;
         }
 
-        if self.is_zero() {
-            return f.write_str("0s");
-        }
+        if let Some(_precision) = f.precision() {
+            // Concise, rounded representation.
 
-        let seconds = self.seconds.unsigned_abs();
-        let nanoseconds = self.nanoseconds.unsigned_abs();
+            if self.is_zero() {
+                // Write a zero value with the requested precision.
+                return (0.).fmt(f).and_then(|_| f.write_str("s"));
+            }
 
-        if self.is_negative() {
-            f.write_str("-")?;
+            /// Format the first item that produces a value greater than 1 and then break.
+            macro_rules! item {
+                ($name:literal, $value:expr) => {
+                    let value = $value;
+                    if value >= 1.0 {
+                        return value.fmt(f).and_then(|_| f.write_str($name));
+                    }
+                };
+            }
+
+            // Even if this produces a de-normal float, because we're rounding we don't really care.
+            let seconds = self.unsigned_abs().as_secs_f64();
+
+            item!("d", seconds / 86_400.);
+            item!("h", seconds / 3_600.);
+            item!("m", seconds / 60.);
+            item!("s", seconds);
+            item!("ms", seconds * 1_000.);
+            item!("µs", seconds * 1_000_000.);
+            item!("ns", seconds * 1_000_000_000.);
+        } else {
+            // Precise, but verbose representation.
+
+            if self.is_zero() {
+                return f.write_str("0s");
+            }
+
+            /// Format a single item.
+            macro_rules! item {
+                ($name:literal, $value:expr) => {
+                    match $value {
+                        0 => Ok(()),
+                        value => value.fmt(f).and_then(|_| f.write_str($name)),
+                    }
+                };
+            }
+
+            let seconds = self.seconds.unsigned_abs();
+            let nanoseconds = self.nanoseconds.unsigned_abs();
+
+            item!("d", seconds / 86_400)?;
+            item!("h", seconds / 3_600 % 24)?;
+            item!("m", seconds / 60 % 60)?;
+            item!("s", seconds % 60)?;
+            item!("ms", nanoseconds / 1_000_000)?;
+            item!("µs", nanoseconds / 1_000 % 1_000)?;
+            item!("ns", nanoseconds % 1_000)?;
         }
 
-        item!("d", seconds / 86_400)?;
-        item!("h", seconds / 3_600 % 24)?;
-        item!("m", seconds / 60 % 60)?;
-        item!("s", seconds % 60)?;
-        item!("ms", nanoseconds / 1_000_000)?;
-        item!("µs", nanoseconds / 1_000 % 1_000)?;
-        item!("ns", nanoseconds % 1_000)?;
         Ok(())
     }
 }


Test patch:
diff --git a/tests/integration/duration.rs b/tests/integration/duration.rs
index 17bdc8e3f..94d41cc9c 100644
--- a/tests/integration/duration.rs
+++ b/tests/integration/duration.rs
@@ -404,6 +404,38 @@ fn display() {
         93_784_005_006_007.nanoseconds().to_string(),
         "1d2h3m4s5ms6µs7ns"
     );
+
+    assert_eq!(format!("{:.3}", 0.seconds()), "0.000s");
+    assert_eq!(format!("{:.3}", 60.days()), "60.000d");
+    assert_eq!(format!("{:.3}", (-48).hours()), "-2.000d");
+    assert_eq!(format!("{:.3}", 48.hours()), "2.000d");
+    assert_eq!(format!("{:.3}", 1.minutes()), "1.000m");
+    assert_eq!(format!("{:.3}", 10.minutes()), "10.000m");
+    assert_eq!(format!("{:.3}", 1.seconds()), "1.000s");
+    assert_eq!(format!("{:.3}", 10.seconds()), "10.000s");
+    assert_eq!(format!("{:.3}", 1.milliseconds()), "1.000ms");
+    assert_eq!(format!("{:.3}", 10.milliseconds()), "10.000ms");
+    assert_eq!(format!("{:.3}", 100.milliseconds()), "100.000ms");
+    assert_eq!(format!("{:.3}", 1.microseconds()), "1.000µs");
+    assert_eq!(format!("{:.3}", 10.microseconds()), "10.000µs");
+    assert_eq!(format!("{:.3}", 100.microseconds()), "100.000µs");
+    assert_eq!(format!("{:.3}", 1.nanoseconds()), "1.000ns");
+    assert_eq!(format!("{:.3}", 10.nanoseconds()), "10.000ns");
+    assert_eq!(format!("{:.3}", 100.nanoseconds()), "100.000ns");
+
+    assert_eq!(format!("{:.3}", 1.days()), "1.000d");
+    assert_eq!(format!("{:.3}", 26.hours()), "1.083d");
+    assert_eq!(format!("{:.4}", 1_563.minutes()), "1.0854d");
+    assert_eq!(format!("{:.5}", 93_784.seconds()), "1.08546d");
+    assert_eq!(format!("{:.6}", 93_784_005.milliseconds()), "1.085463d");
+    assert_eq!(
+        format!("{:.9}", 93_784_005_006.microseconds()),
+        "1.085463021d"
+    );
+    assert_eq!(
+        format!("{:.12}", 93_784_005_006_007.nanoseconds()),
+        "1.085463020903d"
+    );
 }
 
 #[test]