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

Issue:
Nicer (alternative?) `Debug` implementations
Here's one simple suggestion that may increase user-friendliness of the crate by a huge margin:

- Switch `Debug` implementations (e.g. for `Date` or `OffsetDateTime`) to user-friendly ones

Current behaviour if you ever debug-print a date or a datetime (you'll have to scroll right...):
```
date: Date { year: 2019, ordinal: 89 },
time: OffsetDateTime { local_datetime: PrimitiveDateTime { date: Date { year: 2019, ordinal: 89 }, time: Time { hour: 0, minute: 0, second: 0, nanosecond: 0 } }, offset: UtcOffset { hours: 0, minutes: 0, seconds: 0 } } },
```
Proposed behaviour:
```
date: 2019-03-30,
time: 2019-03-30 0:00:00.0 +00:00:00,
```

The main point, really, is user (or rather, dev) friendliness, to aid in developing crates that depend on `time`. As a hypothetical example, you're using this crate for parsing some structs from some APIs and you want to quickly derive Debug on those to see what you've just parsed, you'll have a hard time (especially with dates).

A few notes and some explanation:

- Whichever formatting is chosen, it has to be lossless, especially for datetimes (that is, if there are non-zero nanoseconds there, they cannot be truncated).
- `Date { year: 2019, ordinal: 89 }` is information for computers, not for human beings. You won't be able to tell what date it is without doing a mental (or computer-aided) computation first.
- `OffsetDateTime`s are absolutely humongous when debug-printed and will likely dwarf most structs; their readable representation is a tiny string that humans can immediately parse though.
- But `Debug` has to represent internals since it's meant for Debug purposes? Not really - same as `Vec`'s `Debug` implementation doesn't print its capacity and the pointer, it just outputs whatever likely is the most useful information to the user. If you need to print capacity and the pointer, in very rare cases, you can always do so manually.
- Why not implement your own `Debug`? You can. But sometimes you have 20 other fields in your structs, and you'd have to manually implement `Debug` just to be able to see your dates and times in a readable fashion.
- You can always wrap `Date` in a newtype? You can. But then have to do it everywhere you use it, you'll have to implement derefs and conversions and possible end up with a bunch of newtype wrappers, and all for the sake of being able to just print it?
- But it already implements `Display`? It does. But more often than not your structs and their fields won't, so you won't be able to derive it even with helper crates like derive_more.
- Performance-wise, it doesn't really matter. It's pretty-formatting, after all.
- If the current debug-representation is *really* needed, one could add a newtype wrapper for each struct which will be accessible as e.g. `struct.debug()`. The chances are, it will never get used, except by the devs of this crate themselves. An alternative option, if needed for dev purposes, is having a feature flag, disabled by default, for auto-derived debug (current behaviour); but then again, would it be used at all?

I could try and help out with this if needed (although it should be pretty trivial implementation-wise).

Thanks and cheers 🖖 


Discussion / hints:
Thank you for the detailed and thorough issue!

I'll think about it. Ultimately a debug representation is meant for debugging, and this includes debugging the time crate. Some of the debug outputs are not even representative of the actual internal representation, such as `Date` (`Date` is internally stored as a bitpacked `u32`).

With regard to the comparison to `Vec`, it also matters how the value is semantically thought about. Aside from `OffsetDateTime` (which uses `PrimitiveDateTime` internally), this is pretty much already the case. For what it's worth `OffsetDateTime` will have a different output come October, as that's when the internals for `OffsetDateTime` and `PrimitiveDateTime` will be merged.

`Date` could be changed to be a year-month-day debug output, but that would have some cost associated with it (namely calculating the month and day). That's not the end of the world, as the debug output shouldn't be used in places where performance matters anyways.

Fix patch:
diff --git a/src/date.rs b/src/date.rs
index 0d96976cb..263468e7b 100644
--- a/src/date.rs
+++ b/src/date.rs
@@ -41,15 +41,6 @@ pub struct Date {
     value: i32,
 }
 
-impl fmt::Debug for Date {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
-        f.debug_struct("Date")
-            .field("year", &self.year())
-            .field("ordinal", &self.ordinal())
-            .finish()
-    }
-}
-
 impl Date {
     /// The minimum valid `Date`.
     ///
@@ -983,6 +974,12 @@ impl fmt::Display for Date {
         }
     }
 }
+
+impl fmt::Debug for Date {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+        fmt::Display::fmt(self, f)
+    }
+}
 // endregion formatting & parsing
 
 // region: trait impls
diff --git a/src/offset_date_time.rs b/src/offset_date_time.rs
index 598203a8a..ad68fc111 100644
--- a/src/offset_date_time.rs
+++ b/src/offset_date_time.rs
@@ -27,7 +27,7 @@ const UNIX_EPOCH_JULIAN_DAY: i32 = Date::__from_ordinal_date_unchecked(1970, 1).
 /// A [`PrimitiveDateTime`] with a [`UtcOffset`].
 ///
 /// All comparisons are performed using the UTC time.
-#[derive(Debug, Clone, Copy, Eq)]
+#[derive(Clone, Copy, Eq)]
 pub struct OffsetDateTime {
     /// The [`PrimitiveDateTime`], which is _always_ in the stored offset.
     pub(crate) local_datetime: PrimitiveDateTime,
@@ -1132,6 +1132,12 @@ impl fmt::Display for OffsetDateTime {
         write!(f, "{} {} {}", self.date(), self.time(), self.offset)
     }
 }
+
+impl fmt::Debug for OffsetDateTime {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt::Display::fmt(self, f)
+    }
+}
 // endregion formatting & parsing
 
 // region: trait impls
diff --git a/src/primitive_date_time.rs b/src/primitive_date_time.rs
index 798d089dd..5ea54c3b5 100644
--- a/src/primitive_date_time.rs
+++ b/src/primitive_date_time.rs
@@ -13,7 +13,7 @@ use crate::parsing::Parsable;
 use crate::{error, util, Date, Duration, Month, OffsetDateTime, Time, UtcOffset, Weekday};
 
 /// Combined date and time.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
+#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
 pub struct PrimitiveDateTime {
     #[allow(clippy::missing_docs_in_private_items)]
     pub(crate) date: Date,
@@ -821,6 +821,12 @@ impl fmt::Display for PrimitiveDateTime {
         write!(f, "{} {}", self.date, self.time)
     }
 }
+
+impl fmt::Debug for PrimitiveDateTime {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt::Display::fmt(self, f)
+    }
+}
 // endregion formatting & parsing
 
 // region: trait impls
diff --git a/src/time.rs b/src/time.rs
index aae1f2ae5..b0a918e49 100644
--- a/src/time.rs
+++ b/src/time.rs
@@ -42,17 +42,6 @@ pub struct Time {
     padding: Padding,
 }
 
-impl fmt::Debug for Time {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct("Time")
-            .field("hour", &self.hour)
-            .field("minute", &self.minute)
-            .field("second", &self.second)
-            .field("nanosecond", &self.nanosecond)
-            .finish()
-    }
-}
-
 impl Time {
     /// Create a `Time` that is exactly midnight.
     ///
@@ -656,6 +645,12 @@ impl fmt::Display for Time {
         )
     }
 }
+
+impl fmt::Debug for Time {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt::Display::fmt(self, f)
+    }
+}
 // endregion formatting & parsing
 
 // region: trait impls
diff --git a/src/utc_offset.rs b/src/utc_offset.rs
index 5f3b72f9b..73384e762 100644
--- a/src/utc_offset.rs
+++ b/src/utc_offset.rs
@@ -20,7 +20,7 @@ use crate::OffsetDateTime;
 /// This struct can store values up to ±23:59:59. If you need support outside this range, please
 /// file an issue with your use case.
 // All three components _must_ have the same sign.
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
+#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
 pub struct UtcOffset {
     #[allow(clippy::missing_docs_in_private_items)]
     hours: i8,
@@ -326,6 +326,12 @@ impl fmt::Display for UtcOffset {
         )
     }
 }
+
+impl fmt::Debug for UtcOffset {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt::Display::fmt(self, f)
+    }
+}
 // endregion formatting & parsing
 
 impl Neg for UtcOffset {


Test patch:
diff --git a/tests/integration/date.rs b/tests/integration/date.rs
index b1cd3289c..ed564fadc 100644
--- a/tests/integration/date.rs
+++ b/tests/integration/date.rs
@@ -8,10 +8,7 @@ use time::{util, Date, Duration, Month, Weekday};
 
 #[test]
 fn debug() {
-    assert_eq!(
-        format!("{:?}", date!(2020 - 02 - 03)),
-        "Date { year: 2020, ordinal: 34 }"
-    );
+    assert_eq!(format!("{:?}", date!(2020 - 02 - 03)), "2020-02-03");
 }
 
 #[test]