Repository: BurntSushi/jiff (language: rust)

Issue:
Suprising behaviour: setting `.with().nanosecond(0)` doesn't set the whole subsecond part to 0, must use `.subsec_nanosecond()`
Hi,

This is not an issue per se, but just a surprising (and non-intuitive to me) behavior that led to a bug in one of my projects. I was expecting that `.with().nanosecond(0)` on a `Zoned` will reset its whole sub-second part to `0` (akin to the `chrono`'s `.with_nanosecond()`). Turns out it only resets the nano part of the sub-second. I.e. `0.123456789` will become `0.123456`. For the expected behavior one should use `with().subsec_nanosecond()`.

Knowing that, I checked the docs more carefully, and I only found mention of this in the `DateTimeWith` method descriptions (relationship between `subsec_nanosecond()` and separate sub-second methods). But it never clearly mentions that setting the sub-second part it will only set that part of the sub-second without resetting other parts.

I think it should be addressed, maybe with more prominent documentation mention or to make it so that `.with().microsecond(100)` changes `0.123456789` to `0.000100000` instead of `0.123100789`.

Thank you for you work on the crate!


Discussion / hints:
I think the behavior here is correct, and a different behavior would actually end up being more surprising. Having `microsecond(100)` _also_ set the nanosecond component to `0` I think would be pretty bad juju and totally inconsistent with how other units are set. It would be like having `hour(17)` setting minutes to `0`.

The special part of fractional seconds is just that you can access the entire sub-second component in units of nanoseconds, and that corresponds to the "subsec" APIs. This is actually something that is influenced by std, where accessing the sub-second part of a `Duration` is done via [`Duration::subsec_nanos`](https://doc.rust-lang.org/std/time/struct.Duration.html#method.subsec_nanos).

Chrono's API is a bit weird in that they _only_ offer the entire sub-second value in units of nanoseconds, but don't offer milliseconds, microseconds or nanoseconds as distinct components (like is done for hours, minutes and seconds). So you wind up with a weird inconsistency where some units are individual components (hours) and other units subsume smaller units. Of course, the sub-second representation is useful sometimes, so it's good to have. But I think it's also good to distinguish it with a different name.

> Knowing that, I checked the docs more carefully, and I only found mention of this in the DateTimeWith method descriptions (relationship between subsec_nanosecond() and separate sub-second methods). But it never clearly mentions that setting the sub-second part it will only set that part of the sub-second without resetting other parts.

I think the docs can be more explicit about this.
Thanks a lot for the explanation! In my mental model milli-, micro- and nanoseconds didn't exist as separate discreet time parts, but all are the same fractional part of the second expressed with different power of 10.

Should I close the issue or would you like to keep it as a kind of todo item (if there are plans to act on this)?
Definitely keep it open. To track an improvement to the docs. :-)

I'll add docs to the millisecond, microsecond, nanosecond and subsec_nanosecond methods clarifying their behavior a bit.

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 93f8e05..9ea5a11 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,16 @@
 # CHANGELOG
 
+0.2.2 (TBD)
+===========
+TODO
+
+Bug fixes:
+
+* [#261](https://github.com/BurntSushi/jiff/issues/261):
+Improve the documentation for `ZonedWith::nanosecond` and
+`ZonedWith::subsec_nanosecond`.
+
+
 0.2.1 (2025-02-16)
 ==================
 This release includes a massive number of optimizations that significantly
diff --git a/src/civil/datetime.rs b/src/civil/datetime.rs
index f805748..58227bd 100644
--- a/src/civil/datetime.rs
+++ b/src/civil/datetime.rs
@@ -4183,6 +4183,11 @@ impl DateTimeWith {
     ///
     /// This overrides any previous millisecond settings.
     ///
+    /// Note that this only sets the millisecond component. It does
+    /// not change the microsecond or nanosecond components. To set
+    /// the fractional second component to nanosecond precision, use
+    /// [`DateTimeWith::subsec_nanosecond`].
+    ///
     /// # Errors
     ///
     /// This returns an error when [`DateTimeWith::build`] is called if the
@@ -4217,6 +4222,11 @@ impl DateTimeWith {
     ///
     /// This overrides any previous microsecond settings.
     ///
+    /// Note that this only sets the microsecond component. It does
+    /// not change the millisecond or nanosecond components. To set
+    /// the fractional second component to nanosecond precision, use
+    /// [`DateTimeWith::subsec_nanosecond`].
+    ///
     /// # Errors
     ///
     /// This returns an error when [`DateTimeWith::build`] is called if the
@@ -4251,6 +4261,11 @@ impl DateTimeWith {
     ///
     /// This overrides any previous nanosecond settings.
     ///
+    /// Note that this only sets the nanosecond component. It does
+    /// not change the millisecond or microsecond components. To set
+    /// the fractional second component to nanosecond precision, use
+    /// [`DateTimeWith::subsec_nanosecond`].
+    ///
     /// # Errors
     ///
     /// This returns an error when [`DateTimeWith::build`] is called if the
@@ -4286,6 +4301,12 @@ impl DateTimeWith {
     ///
     /// This overrides any previous subsecond nanosecond settings.
     ///
+    /// Note that this sets the entire fractional second component to
+    /// nanosecond precision, and overrides any individual millisecond,
+    /// microsecond or nanosecond settings. To set individual components,
+    /// use [`DateTimeWith::millisecond`], [`DateTimeWith::microsecond`] or
+    /// [`DateTimeWith::nanosecond`].
+    ///
     /// # Errors
     ///
     /// This returns an error when [`DateTimeWith::build`] is called if the
diff --git a/src/civil/time.rs b/src/civil/time.rs
index 772f592..c6fbdef 100644
--- a/src/civil/time.rs
+++ b/src/civil/time.rs
@@ -3093,6 +3093,11 @@ impl TimeWith {
     ///
     /// This overrides any previous millisecond settings.
     ///
+    /// Note that this only sets the millisecond component. It does
+    /// not change the microsecond or nanosecond components. To set
+    /// the fractional second component to nanosecond precision, use
+    /// [`TimeWith::subsec_nanosecond`].
+    ///
     /// # Errors
     ///
     /// This returns an error when [`TimeWith::build`] is called if the given
@@ -3123,6 +3128,11 @@ impl TimeWith {
     ///
     /// This overrides any previous microsecond settings.
     ///
+    /// Note that this only sets the microsecond component. It does
+    /// not change the millisecond or nanosecond components. To set
+    /// the fractional second component to nanosecond precision, use
+    /// [`TimeWith::subsec_nanosecond`].
+    ///
     /// # Errors
     ///
     /// This returns an error when [`TimeWith::build`] is called if the given
@@ -3153,6 +3163,11 @@ impl TimeWith {
     ///
     /// This overrides any previous nanosecond settings.
     ///
+    /// Note that this only sets the nanosecond component. It does
+    /// not change the millisecond or microsecond components. To set
+    /// the fractional second component to nanosecond precision, use
+    /// [`TimeWith::subsec_nanosecond`].
+    ///
     /// # Errors
     ///
     /// This returns an error when [`TimeWith::build`] is called if the given
@@ -3184,6 +3199,12 @@ impl TimeWith {
     ///
     /// This overrides any previous subsecond nanosecond settings.
     ///
+    /// Note that this sets the entire fractional second component to
+    /// nanosecond precision, and overrides any individual millisecond,
+    /// microsecond or nanosecond settings. To set individual components,
+    /// use [`TimeWith::millisecond`], [`TimeWith::microsecond`] or
+    /// [`TimeWith::nanosecond`].
+    ///
     /// # Errors
     ///
     /// This returns an error when [`TimeWith::build`] is called if the given
diff --git a/src/zoned.rs b/src/zoned.rs
index a6f6cff..f2e30d6 100644
--- a/src/zoned.rs
+++ b/src/zoned.rs
@@ -4887,6 +4887,11 @@ impl ZonedWith {
     ///
     /// This overrides any previous millisecond settings.
     ///
+    /// Note that this only sets the millisecond component. It does
+    /// not change the microsecond or nanosecond components. To set
+    /// the fractional second component to nanosecond precision, use
+    /// [`ZonedWith::subsec_nanosecond`].
+    ///
     /// # Errors
     ///
     /// This returns an error when [`ZonedWith::build`] is called if the
@@ -4921,6 +4926,11 @@ impl ZonedWith {
     ///
     /// This overrides any previous microsecond settings.
     ///
+    /// Note that this only sets the microsecond component. It does
+    /// not change the millisecond or nanosecond components. To set
+    /// the fractional second component to nanosecond precision, use
+    /// [`ZonedWith::subsec_nanosecond`].
+    ///
     /// # Errors
     ///
     /// This returns an error when [`ZonedWith::build`] is called if the
@@ -4955,6 +4965,11 @@ impl ZonedWith {
     ///
     /// This overrides any previous nanosecond settings.
     ///
+    /// Note that this only sets the nanosecond component. It does
+    /// not change the millisecond or microsecond components. To set
+    /// the fractional second component to nanosecond precision, use
+    /// [`ZonedWith::subsec_nanosecond`].
+    ///
     /// # Errors
     ///
     /// This returns an error when [`ZonedWith::build`] is called if the
@@ -4990,6 +5005,12 @@ impl ZonedWith {
     ///
     /// This overrides any previous subsecond nanosecond settings.
     ///
+    /// Note that this sets the entire fractional second component to
+    /// nanosecond precision, and overrides any individual millisecond,
+    /// microsecond or nanosecond settings. To set individual components,
+    /// use [`ZonedWith::millisecond`], [`ZonedWith::microsecond`] or
+    /// [`ZonedWith::nanosecond`].
+    ///
     /// # Errors
     ///
     /// This returns an error when [`ZonedWith::build`] is called if the


Test patch: