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

Issue:
Parse error on datetime with long fractional value
The following seems to fail to parse:

```toml
bestdayever = 1987-07-05T17:45:00.123456789012345Z
```

```
TOML parse error at line 2, column 34
  |
  2 | bestdayever = 1987-07-05T17:45:00.123456789012345Z
  |                                  ^
  Unexpected `.`
Expected `#`
```

It works with just 9 digits.  This is part of the toml-rs testsuite.

I'm curious if there is some reason to have the limitation?  toml-rs will just truncate to the precision it allows.  If there is a good reason to reject it, I would expect the error message to be a little better. Though I think it is reasonable to just accept it with a small precision loss.

Using v0.10.0.



Discussion / hints:
Thanks for reporting this! I'll try to get to this today, tomorrow at the latest.
The [spec](https://toml.io/en/v1.0.0#offset-date-time) says:
> Millisecond precision is required. Further precision of fractional seconds is implementation-specific. If the value contains greater precision than the implementation can support, the additional precision must be truncated, not rounded.


so it sounds like a bug. We might consider option 1 from #188 (reverting #187).
I'd be hesitant to add back in `chrono` for everyone.  Most people probably do not touch dates, adding an unused dependency.  Also it seems like there is uncertainty around `time` and `chrono` last I looked.

Fix patch:
diff --git a/src/parser/datetime.rs b/src/parser/datetime.rs
index 6d5eb44a..97ac395f 100644
--- a/src/parser/datetime.rs
+++ b/src/parser/datetime.rs
@@ -163,16 +163,23 @@ parse!(time_second() -> u8, {
 
 // time-secfrac   = "." 1*DIGIT
 parse!(time_secfrac() -> u32, {
+    static SCALE: [u32; 10] =
+        [0, 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1];
     byte(b'.').and(take_while1(|c: u8| c.is_ascii_digit())).and_then::<_, _, CustomError>(|(_, repr): (u8, &[u8])| {
-        let repr = unsafe { from_utf8_unchecked(repr, "`is_ascii_digit` filters out on-ASCII") };
+        let mut repr = unsafe { from_utf8_unchecked(repr, "`is_ascii_digit` filters out on-ASCII") };
+        let max_digits = SCALE.len() - 1;
+        if max_digits < repr.len() {
+            // Millisecond precision is required. Further precision of fractional seconds is
+            // implementation-specific. If the value contains greater precision than the
+            // implementation can support, the additional precision must be truncated, not rounded.
+            repr = &repr[0..max_digits];
+        }
 
         let v = repr.parse::<u32>().map_err(|_| CustomError::OutOfRange)?;
-        let consumed = repr.len();
+        let num_digits = repr.len();
 
         // scale the number accordingly.
-        static SCALE: [u32; 10] =
-            [0, 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1];
-        let scale = SCALE.get(consumed).ok_or(CustomError::OutOfRange)?;
+        let scale = SCALE.get(num_digits).ok_or(CustomError::OutOfRange)?;
         let v = v.checked_mul(*scale).ok_or(CustomError::OutOfRange)?;
         Ok(v)
     })
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index c70c88cf..c5640d2b 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -288,7 +288,7 @@ mod tests {
         parsed_eq!(
             parsed,
             "I\'m a string. \"You can quote me\". Name\tJosé\nLocation\tSF. \u{2070E}"
-        )
+        );
     }
 
     #[test]
@@ -419,6 +419,12 @@ trimmed in raw strings.
         }
     }
 
+    #[test]
+    fn time_fraction_truncated() {
+        let input = "1987-07-05T17:45:00.123456789012345Z";
+        parsed_date_time_eq!(input, is_datetime);
+    }
+
     #[test]
     fn trivia() {
         let inputs = [


Test patch: