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

Issue:
ISO 8601 parsing from string sometimes is off by 1 nanosecond
Sometimes timestamps parsed from string are off by 1 nanosecond from the string text:
```rust
use time::{format_description::well_known::iso8601::Iso8601, macros::datetime, OffsetDateTime};

fn main() {
    let timestamp = "2022-07-22T12:52:50.349409+00:00";
    let parsed = OffsetDateTime::parse(timestamp, &Iso8601::DEFAULT).unwrap();
    // panic: `left` is `nanosecond: 349408999`, `right` is `nanosecond: 349409000`
    assert_eq!(parsed, datetime!(2022-07-22 12:52:50.349409000 UTC));
}
```

Better reproducer: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=31030fbca526a129ba26e1528bbe163a



Discussion / hints:

Fix patch:
diff --git a/src/parsing/iso8601.rs b/src/parsing/iso8601.rs
index fb2880cc1..1956b4dbf 100644
--- a/src/parsing/iso8601.rs
+++ b/src/parsing/iso8601.rs
@@ -203,7 +203,7 @@ impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
             let (input, second, subsecond) = match float(input) {
                 Some(ParsedItem(input, (second, None))) => (input, second, 0),
                 Some(ParsedItem(input, (second, Some(fractional_part)))) => {
-                    (input, second, (fractional_part * 1_000_000_000.) as _)
+                    (input, second, round(fractional_part * 1_000_000_000.) as _)
                 }
                 None if extended_kind.is_extended() => {
                     return Err(error::Parse::ParseFromDescription(InvalidComponent(
@@ -286,3 +286,37 @@ impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> {
         }
     }
 }
+
+/// Round wrapper that uses hardware implementation if `std` is available, falling back to manual
+/// implementation for `no_std`
+fn round(value: f64) -> f64 {
+    #[cfg(feature = "std")]
+    let rounded = value.round();
+    #[cfg(not(feature = "std"))]
+    let rounded = round_impl(value);
+    rounded
+}
+
+#[cfg(not(feature = "std"))]
+#[allow(clippy::missing_docs_in_private_items)]
+// Based on num-trait for no_std feature
+#[inline]
+fn round_impl(value: f64) -> f64 {
+    #[inline]
+    fn fract(value: f64) -> f64 {
+        if value == 0. { 0.0 } else { value % 1. }
+    }
+
+    let one = 1.;
+    let h = 0.5;
+    let f = fract(value);
+    if f.is_nan() || f == 0. {
+        value
+    } else if value > 0. {
+        if f < h { value - f } else { value - f + one }
+    } else if -f < h {
+        value - f
+    } else {
+        value - f - one
+    }
+}


Test patch:
diff --git a/tests/integration/parsing.rs b/tests/integration/parsing.rs
index ec57bc29a..248e745a7 100644
--- a/tests/integration/parsing.rs
+++ b/tests/integration/parsing.rs
@@ -559,6 +559,10 @@ fn iso_8601() {
         UtcOffset::parse("+0304", &Iso8601::DEFAULT),
         Ok(offset!(+03:04))
     );
+    assert_eq!(
+        PrimitiveDateTime::parse("2022-07-22T12:52:50.349409", &Iso8601::DEFAULT),
+        Ok(datetime!(2022-07-22 12:52:50.349409000))
+    );
 }
 
 #[test]