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 DurationPart
s into a Duration
. We do that by passing a mutable reference to a Duration
along with the each of the DurationPart
s 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)); } } #}