Shmoopy
Shmoopy

Reputation: 5534

How to transform `chrono::format::strftime` to `chrono::format::Item` statically?

I have a static array of chrono::format::strftime formats that my application supports. I'd like to avoid parsing them during run time, so I defined a lazy_static! block that parses them to a collection of chrono::format::Item.

However, when I try to iterate over the parsed collection, I'm getting an error:

the trait bound `&chrono::format::StrftimeItems<'_>: std::iter::Iterator` is not satisfied

Here's a short reproduction:

#[macro_use]
extern crate lazy_static;
extern crate chrono;
use chrono::DateTime;
use chrono::offset::FixedOffset;
use chrono::format::{Parsed, parse};
use chrono::format::strftime::StrftimeItems;

static FORMATS : &[&'static str] = &["%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%S%.f"];

lazy_static! {
    static ref PARSED_FORMATS : Vec<StrftimeItems<'static>> = FORMATS
        .iter()
        .map(|format| StrftimeItems::new(format))
        .collect();
}

fn parse_datetime(s: &str) -> Option<DateTime<FixedOffset>> {
    for format in PARSED_FORMATS.iter() {
        let mut parsed = Parsed::new();
        let dt = parse(&mut parsed, &s, format)
            .and_then(|_| parsed.to_datetime() );
        if dt.is_ok() {
            return dt.ok()
        }
    }
    return None
}

Attempting to de-reference format in the loop gives this error:

error[E0507]: cannot move out of borrowed content
  --> src\main.rs:21:35
   |
21 |         let dt = parse(&mut parsed, &s, *format)
   |                                         ^^^^^^^ cannot move out of borrowed content

error: aborting due to previous error

Attempting to clone format seems to work, however cloning seems redundant here and I'd like to avoid it.

Is this the right approach here? or perhaps using a macro is better option?

Upvotes: 3

Views: 688

Answers (1)

Francis Gagn&#233;
Francis Gagn&#233;

Reputation: 65657

StrftimeItems is an iterator, not an iterable container (like Vec is). When you advance an iterator, you can't rewind it. parse must receive an iterator by value, which means that you must take a StrftimeItems our of your vector (which means you can't reuse it afterwards) or clone the StrftimeItems stored in the vector. By cloning the StrftimeItems, you can produce a new iterator whose state is distinct from the original (i.e. advancing one doesn't advance the other).

I'd like to avoid parsing them during run time

However, StrftimeItems doesn't let you achieve your goal, because StrftimeItems lazily parses the format string as the iterator advances.

Instead, I would suggest that you collect the results from that iterator to a Vec<Item<'static>>.

#[macro_use]
extern crate lazy_static;
extern crate chrono;
use chrono::DateTime;
use chrono::offset::FixedOffset;
use chrono::format::{Item, Parsed, parse};
use chrono::format::strftime::StrftimeItems;

static FORMATS : &[&'static str] = &["%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%S%.f"];

lazy_static! {
    static ref PARSED_FORMATS : Vec<Vec<Item<'static>>> = FORMATS
        .iter()
        .map(|format| StrftimeItems::new(format).collect())
        .collect();
}

fn parse_datetime(s: &str) -> Option<DateTime<FixedOffset>> {
    for format in PARSED_FORMATS.iter() {
        let mut parsed = Parsed::new();
        let dt = parse(&mut parsed, &s, format.iter().cloned())
            .and_then(|_| parsed.to_datetime() );
        if dt.is_ok() {
            return dt.ok()
        }
    }
    return None
}

Now, when we call parse, we pass format.iter().cloned(). format is a Vec<Item<'static>>, iter() produces a fresh iterator over references to the Items, and cloned() adapts the iterator so that each Item is returned by value (achieved by cloning them) rather than by reference (because parse expects an iterator over Item values, not Item references).

Upvotes: 2

Related Questions