use super::{Duration, Unit};
use crate::{HifitimeError, ParsingError};
use core::str::FromStr;
const UNITS: &[(&str, usize)] = &[
("d", 0),
("days", 0),
("day", 0),
("h", 1),
("hours", 1),
("hour", 1),
("hr", 1),
("min", 2),
("mins", 2),
("minute", 2),
("minutes", 2),
("s", 3),
("second", 3),
("seconds", 3),
("sec", 3),
("ms", 4),
("millisecond", 4),
("milliseconds", 4),
("μs", 5),
("us", 5),
("microsecond", 5),
("microseconds", 5),
("ns", 6),
("nanosecond", 6),
("nanoseconds", 6),
];
impl FromStr for Duration {
type Err = HifitimeError;
fn from_str(s_in: &str) -> Result<Self, Self::Err> {
let s = s_in.trim();
if s.is_empty() {
return Err(HifitimeError::Parse {
source: ParsingError::NothingToParse,
details: "input string is empty",
});
}
let (sign, maybe_offset, skip) = if s.chars().nth(0).unwrap() == '-' {
(-1, true, 1)
} else if s.chars().nth(0).unwrap() == '+' {
(1, true, 0)
} else {
(1, false, 0)
};
if maybe_offset {
if let Ok(duration) = parse_offset(s) {
if sign == -1 {
return Ok(-duration);
} else {
return Ok(duration);
}
}
}
let duration = parse_duration(&s[skip..])?;
if sign == -1 {
Ok(-duration)
} else {
Ok(duration)
}
}
}
fn cmp_chars_to_str(s: &str, start_idx: usize, cmp_str: &str) -> bool {
let cmp_bytes = cmp_str.as_bytes();
let s_bytes = s.as_bytes();
if start_idx + cmp_bytes.len() > s_bytes.len() {
return false; }
&s_bytes[start_idx..start_idx + cmp_bytes.len()] == cmp_bytes
}
fn parse_duration(s: &str) -> Result<Duration, HifitimeError> {
let mut decomposed = [0.0_f64; 7];
let mut prev_idx = 0;
let mut seeking_number = true;
let mut latest_value = 0.0;
let mut prev_char_was_space = false;
for (idx, char) in s.char_indices() {
if char == ' ' {
if seeking_number {
if !prev_char_was_space {
if prev_idx == idx {
return Err(HifitimeError::Parse {
source: ParsingError::UnknownOrMissingUnit,
details: "expect a unit after a numeric",
});
}
match lexical_core::parse(&s.as_bytes()[prev_idx..idx]) {
Ok(val) => latest_value = val,
Err(_) => {
return Err(HifitimeError::Parse {
source: ParsingError::ValueError,
details: "could not parse what precedes the space",
});
}
}
seeking_number = false;
}
} else {
let end_idx = if let Some((inner_idx, _)) = s[idx..].char_indices().next() {
idx + inner_idx
} else {
idx
};
let start_idx = prev_idx;
let mut found_unit = false;
for &(unit_str, pos) in UNITS {
if cmp_chars_to_str(s, start_idx, unit_str) {
decomposed[pos] = latest_value;
seeking_number = true;
prev_idx = end_idx;
found_unit = true;
break;
}
}
if !found_unit {
return Err(HifitimeError::Parse {
source: ParsingError::UnknownOrMissingUnit,
details: "unknown unit",
});
}
}
prev_char_was_space = true;
} else {
if prev_char_was_space {
prev_idx = idx;
}
prev_char_was_space = false;
}
}
if !seeking_number {
let start_idx = prev_idx;
let mut found_unit = false;
for &(unit_str, pos) in UNITS {
if cmp_chars_to_str(s, start_idx, unit_str) {
decomposed[pos] = latest_value;
found_unit = true;
break;
}
}
if !found_unit {
return Err(HifitimeError::Parse {
source: ParsingError::UnknownOrMissingUnit,
details: "unknown unit",
});
}
} else if prev_idx < s.len() {
return Err(HifitimeError::Parse {
source: ParsingError::UnknownOrMissingUnit,
details: "expect a unit after the last numeric",
});
}
Ok(Duration::compose_f64(
1,
decomposed[0],
decomposed[1],
decomposed[2],
decomposed[3],
decomposed[4],
decomposed[5],
decomposed[6],
))
}
fn parse_offset(s: &str) -> Result<Duration, HifitimeError> {
let indexes: (usize, usize, usize) = (1, 3, 5);
let colon = if s.len() == 3 || s.len() == 5 || s.len() == 7 {
0
} else if s.len() == 4 || s.len() == 6 || s.len() == 9 {
1
} else {
return Err(HifitimeError::Parse {
source: ParsingError::InvalidTimezone,
details: "invalid timezone format [+/-]HH:MM",
});
};
let hours: i64 = match lexical_core::parse(&s.as_bytes()[indexes.0..indexes.1]) {
Ok(val) => val,
Err(err) => {
return Err(HifitimeError::Parse {
source: ParsingError::Lexical { err },
details: "invalid hours",
})
}
};
let mut minutes: i64 = 0;
let mut seconds: i64 = 0;
match s.get(indexes.1 + colon..indexes.2 + colon) {
None => {
}
Some(subs) => {
match lexical_core::parse(subs.as_bytes()) {
Ok(val) => minutes = val,
Err(_) => {
return Err(HifitimeError::Parse {
source: ParsingError::ValueError,
details: "invalid minute",
})
}
}
match s.get(indexes.2 + 2 * colon..) {
None => {
}
Some(subs) if !subs.is_empty() => {
match lexical_core::parse(subs.as_bytes()) {
Ok(val) => seconds = val,
Err(_) => {
return Err(HifitimeError::Parse {
source: ParsingError::ValueError,
details: "invalid seconds",
})
}
}
}
_ => {}
}
}
}
Ok(hours * Unit::Hour + minutes * Unit::Minute + seconds * Unit::Second)
}