rust 38 lines · 7 steps

Parsing and iterating date ranges in Rust

A DateRange type that validates a "start/end" string and lazily yields every day it covers.

Explained by highlit
1use chrono::{Duration, NaiveDate};
2 
3#[derive(Debug)]
4pub struct DateRange {
5 start: NaiveDate,
6 end: NaiveDate,
7}
8 
9impl DateRange {
10 pub fn parse(spec: &str) -> Result<Self, String> {
11 let (start_raw, end_raw) = spec
12 .split_once('/')
13 .ok_or_else(|| format!("missing '/' separator in range: {spec}"))?;
14 
15 let start = NaiveDate::parse_from_str(start_raw.trim(), "%Y-%m-%d")
16 .map_err(|e| format!("invalid start date {start_raw:?}: {e}"))?;
17 let end = NaiveDate::parse_from_str(end_raw.trim(), "%Y-%m-%d")
18 .map_err(|e| format!("invalid end date {end_raw:?}: {e}"))?;
19 
20 if end < start {
21 return Err(format!("end {end} precedes start {start}"));
22 }
23 
24 Ok(Self { start, end })
25 }
26 
27 pub fn days(&self) -> impl Iterator<Item = NaiveDate> {
28 let end = self.end;
29 std::iter::successors(Some(self.start), move |&current| {
30 let next = current + Duration::days(1);
31 (next <= end).then_some(next)
32 })
33 }
34 
35 pub fn len(&self) -> i64 {
36 (self.end - self.start).num_days() + 1
37 }
38}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1The `?` operator lets a parser bail early while attaching a descriptive error at each fallible step.
  2. 2`std::iter::successors` turns a seed and a step function into a lazy, allocation-free sequence.
  3. 3Capturing owned values with `move` frees an iterator from borrowing the struct that produced it.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Parsing and iterating date ranges in Rust — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code