Initializing DateTime inside a loop

I'm looping over a list of dates and I want to display the elapsed duration of each date from the first date. Without pre-unrolling the list to initialize the base_date how can I initialize it inside the loop?

If I initialize the base_date outside the loop, I have this Playground.

Output:

2022-06-20T10:00:00Z Duration { secs: -28033 }
2022-06-20T10:00:10Z Duration { secs: -28023 }
2022-06-20T10:01:00Z Duration { secs: -27973 }

But the output I'm looking for is:

2022-06-20T10:00:00Z Duration { secs: 0 }
2022-06-20T10:00:10Z Duration { secs: 10 }
2022-06-20T10:01:00Z Duration { secs: 60 }

Trying to initialize inside the loop:

for date in dates.iter() {
    let diff = match Some(date.signed_duration_since(base_time)) {
        Some(duration) => duration,
        None => {base_time = *date; Duration::seconds(0)},
    };
    println!("{:?} {:?}", date, diff)
}

I get this error:

error[E0381]: use of possibly-uninitialized variable: `base_time`

(Playground)

NOTE 1: I'm new to Rust so any tips are appreciated.

NOTE 2: Also, I've looked at two (2) other posts that are similar but somehow didn't help me :slight_smile: see Note 1:

Use Option like the 2nd post (his problem was different due to borrowing.)

    let mut base_time: Option<DateTime<Utc>> = None;
    for date in dates.iter() {
        if base_time.is_none() {
            base_time = Some(*date);
        }
        let diff = date.signed_duration_since(base_time.unwrap());
1 Like

I'd just peak the start of your list, honestly.

    let base_time = match dates.first() {
        Some(bt) => *bt,
        None => return,
    };

    for date in dates.iter() {
        let diff = date.signed_duration_since(base_time);
        println!("{:?} {:?}", date, diff)
    }

Playground.

1 Like

That's it, wrap it in an Option. Ok, it worked. Thank you.

I couldn't figure out how to unwrap inside the if statement so it doesn't need to be done on every iteration.

@quinedot I like this approach. I wonder about the performance as it seems it has to read the list again for every iteration? Maybe there is some optimization there.

I can't seem to implement this though, because my actual code uses a Filter which apparently does not have a first() method:

fn search_term(logs: Vec<Log>, term: &str) {
    let logs_with_term = logs.iter().filter(|log| log.line.contains(&term));
    let base_time = match logs_with_term.first() {
        Some(bt) => *bt,
        None => return,
    };
    for (i, log) in logs_with_term.enumerate() {
        ...

Error:

no method named `first` found for struct `Filter` in the current scope
method not found in `Filter<std::slice::Iter<'_, Log>, [closure@src/main.rs:274:45: 274:75]>`
rustc (E0599)

Ah, I see. Yes, first is a method on slices, not iterators. You could make your iterator Peekable, but it's true, the advantage isn't so great in this case.

Playground.

...although come to think of it, is that really what you want? If you want the time delta since the first log entry, you'll still want to check the first entry in the Vec, not the iterator. [1] The version in my last comment gives time deltas from the first matching log entry instead.


  1. Or written more like my first suggestion here ↩ī¸Ž

The peekable thing worked; though, yeah, not as graceful as you're first suggestion.

Maybe I can just create a slice from the Filter and call .first() on that?

I do want to calculate the time difference from the first entry of the filtered data, not the very first log entry in the file.

You can't create a slice from the Filter -- it's a generic type holding an iterator and a closure that iterates and checks the condition item by item, and not a collection of prefiltered elements.

You could iterate twice, once to find the first match and then again from the first match to the end with the filter.

Ah ok, Filter is only applied when iterated and then it's probably consumed.

Good call, for my use case iterating twice is the best bet.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.