DIY

With this one, we can start from the top. Our entry point, as always, is the parse function. The first step here is to check if the first character is a P, if it isn't we can stop this is not a Duration. Next we want to split the &str into parts, the first part would be everything after P and before T, the second would be everything from T to the end of the string.

Once we split it up, we can expect there to be, at most, 2 parts so a manual calls to next on the Split iterator should be enough. We can feel confident that the first one is going to be the date part because calling split on T3H would be "" followed by "3D", so first we test that next is Some then we double check it isn't "", if both are true we can pass the first half to parse_parts with the false as the second argument.

parse_parts takes in one half of the duration and a flag to indicate if M should be a month or a minute. It first finds all of the char_indices that have one of our unit characters. We are going to need to keep track of our position in this &str and this is done with the start_idx variable. We can now loop over the indices getting a slice of the input string from the start_index to the idx of the unit character which we want to parse as an f32. Next we want to match on the unit character which should be at the idx, using the time flag to determine if M means minute or month, we create a duration part. We are going to collect all of these parts in a Vec<DurationPart> to eventually return it so we push the duration into that Vec and finally update thestart_idx to be the idx + 1. This should get us through all of the parsing, next we need to collect these DurationParts into a Duration. We do that by passing a mutable reference to a Duration along with the each of the DurationParts off to the update_duration function. This just matches on the DurationPart and updates the provided Duration accordingly. We do this for both of our expected iterator items and we are done. There is a check in here to make sure that there is at least 1 unit/value pair.


# #![allow(unused_variables)]
#fn main() {
extern crate duration;
use duration::{Duration, DurationPart};


pub fn parse(s: &str) -> Result<Duration, String> {
    if &s[0..1] != "P" {
        return Err(format!("All durations must start with a P: {:?}", s));
    }
    let s = &s[1..];
    let mut parts = s.split('T');
    let mut found_one = false;
    let mut ret = Duration::new();
    if let Some(date_part) = parts.next() {
        if date_part != "" {
            found_one = true;
            for part in parse_parts(date_part, false)? {
                update_duration(&mut ret, &part);
            }
        }
    }
    if let Some(time_part) = parts.next() {
        if time_part != "" {
            found_one = true;
            for part in parse_parts(time_part, true)? {
                update_duration(&mut ret, &part);
            }
        }
    }
    if !found_one {
        return Err(format!("duration contains no information: {:?}", s));
    }

    Ok(ret)
}

fn parse_parts(s: &str, is_time: bool) -> Result<Vec<DurationPart>, String> {
    let idxs = s.char_indices().filter_map(|(i, c)| {
        if c == 'Y'
        || c == 'M'
        || c == 'W'
        || c == 'D'
        || c == 'H'
        || c == 'M'
        || c == 'S' {
            Some(i)
        } else {
            None
        }
    });
    let mut ret = Vec::with_capacity(4);
    let mut start_idx = 0;
    for idx in idxs {
        let float: f32 = s[start_idx..idx].parse().map_err(|e| format!("{}", e))?;
        let tag = &s[idx..idx+1];
        let part = match tag {
            "Y" => DurationPart::Years(float),
            "M" => if is_time {
                DurationPart::Minutes(float)
            } else {
                DurationPart::Months(float)
            },
            "W" => DurationPart::Weeks(float),
            "D" => DurationPart::Days(float),
            "H" => DurationPart::Hours(float),
            "S" => DurationPart::Seconds(float),
            _ => return Err(format!("Invalid unit tag pair at {} in {:?}", idx, s)),
        };
        ret.push(part);
        start_idx = idx + 1;
    }
    Ok(ret)
}

fn update_duration(d: &mut Duration, part: &DurationPart) {
    match part {
        DurationPart::Years(v) => d.set_years(*v),
        DurationPart::Months(v) => d.set_months(*v),
        DurationPart::Weeks(v) => d.set_weeks(*v),
        DurationPart::Days(v) => d.set_days(*v),
        DurationPart::Hours(v) => d.set_hours(*v),
        DurationPart::Minutes(v) => d.set_minutes(*v),
        DurationPart::Seconds(v) => d.set_seconds(*v),
    }
}

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn all() {
        let d = "P1Y1M1W1DT1H1M1.1S";
        let p = parse(d).unwrap();
        assert_eq!(d, &format!("{}", p));
    }
    #[test]
    fn time_only() {
        let d = "PT1H1M1.1S";
        let p = parse(d).unwrap();
        assert_eq!(d, &format!("{}", p));
    }
    #[test]
    fn date_only() {
        let d = "P1Y1M1W1D";
        let p = parse(d).unwrap();
        assert_eq!(d, &format!("{}", p));
    }
}
#}

Are we not men?

We are demo!