5

I want to parse this string: 1595879159050208. Here's what I tried.

use chrono::prelude::*;

fn main() {
    let utc: DateTime<Utc> = Utc::now();
    let format = "%s%6f";
    let string = utc.format(format).to_string();
    println!("{}", string); // 1595879159050208
    let parsed = DateTime::parse_from_str(&string, format);
    println!("{:?}", parsed); // Err(ParseError(TooShort))
}

Why doesn't this work?

Chrono 0.4
rustc 1.41.0
askaroni
  • 913
  • 5
  • 10
  • 1
    [How to parse a date with milliseconds?](https://stackoverflow.com/q/31126366/155423); [How to convert Unix time / time since the epoch to standard date and time?](https://stackoverflow.com/q/42572107/155423) – Shepmaster Jul 27 '20 at 19:54
  • Thanks for the links. Any idea why this one doesn't work? – askaroni Jul 27 '20 at 19:58
  • sure thats micro and not nano? – Alex Jul 27 '20 at 19:59
  • 1
    @Alex I think it's 10 digits of seconds and 6 digits after the decimal, which should be "micro". – askaroni Jul 27 '20 at 20:00

1 Answers1

6

You can see in the parsing function documentation that the function is greedy while obeying the intrinsic parsing width, which means that when "matching" your format, it will try to consume the most characters it can left to right, respecting a maximum value. As a unix timestamp (%s) doesn't have a maximum number of characters, you can see in the source that the maximum number of characters allowed is usize::MAX (which is at least 65535). In this example, the whole string (1595879159050208) is consumed as the timestamp Tue May 12 02:50:08 50573367 UTC (veeeeeeery far in the future), and the remaining digits (%6f) cannot be found.

We can test this by adding a space to your format: "%s %6f". In this case, a new error shows up (ParseError(NotEnough)) which is better explained by using DateTime::parse_from_str docs:

Note that this method requires a timezone in the string. See NaiveDateTime::parse_from_str for a version that does not require a timezone in the to-be-parsed str.

So using NaiveDateTime:

use chrono::prelude::*;

fn main() {
    let utc: DateTime<Utc> = Utc::now();
    let format = "%s %6f";
    let string = utc.format(format).to_string();
    println!("{}", string); // 1595902520 811515
    let parsed = NaiveDateTime::parse_from_str(&string, format);
    println!("{:?}", parsed); // Ok(2020-07-28T02:15:20.811515)
}

Unfortunately, you want to parse the original string, with no spaces. For that I think you can:

  • Parse the string to an i64 as µs using the standard library and calculate the number of seconds / nanoseconds from there, using NaiveDateTime::from_timestamp
  • Slice the string and parse seconds / microseconds separately
  • If you don't care about sub-second precision, pass a slice of the original string to parse_from_str, formatting with just %s

Using the second option:

use chrono::prelude::*;

fn main() {
    let utc: DateTime<Utc> = Utc::now();
    let format = "%s%6f";
    let string = utc.format(format).to_string();
    println!("{}", string); // 1595904294646258

    let secs = string[..string.len() - 6].parse();
    let microsecs = string[string.len() - 6..].parse::<u32>();
    let parsed = match (secs, microsecs) {
        (Ok(s), Ok(us)) => NaiveDateTime::from_timestamp_opt(s, us * 1000),
        _ => None,
    };
    println!("{:?}", parsed); // Some(2020-07-28T02:44:54.646258)
}
pheki
  • 454
  • 3
  • 6