Duration: ensures that the smallest supported duration can be parsed and serialized

pull/545/head
Tpt 2 years ago committed by Thomas Tanon
parent acf83d4a31
commit f47306a4c5
  1. 25
      lib/oxsdatatypes/src/duration.rs
  2. 53
      lib/oxsdatatypes/src/parser.rs

@ -143,13 +143,11 @@ impl fmt::Display for Duration {
#[allow(clippy::many_single_char_names)]
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut ym = self.year_month.months;
let mut ss = self.day_time.seconds;
let ym = self.year_month.months;
let ss = self.day_time.seconds;
if ym < 0 || ss < 0.into() {
write!(f, "-")?;
ym = -ym;
ss = -ss;
}
write!(f, "P")?;
@ -163,12 +161,12 @@ impl fmt::Display for Duration {
if y != 0 {
if m == 0 {
write!(f, "{y}Y")?;
write!(f, "{}Y", y.abs())?;
} else {
write!(f, "{y}Y{m}M")?;
write!(f, "{}Y{}M", y.abs(), m.abs())?;
}
} else if m != 0 || ss == 0.into() {
write!(f, "{m}M")?;
write!(f, "{}M", m.abs())?;
}
}
@ -184,19 +182,19 @@ impl fmt::Display for Duration {
.ok_or(fmt::Error)?;
if d != 0 {
write!(f, "{d}D")?;
write!(f, "{}D", d.abs())?;
}
if h != 0 || m != 0 || s != 0.into() {
write!(f, "T")?;
if h != 0 {
write!(f, "{h}H")?;
write!(f, "{}H", h.abs())?;
}
if m != 0 {
write!(f, "{m}M")?;
write!(f, "{}M", m.abs())?;
}
if s != 0.into() {
write!(f, "{s}S")?;
write!(f, "{}S", s.abs())?;
}
}
}
@ -621,10 +619,7 @@ mod tests {
#[test]
fn from_str() -> Result<(), XsdParseError> {
let min = Duration::new(
i64::MIN + 1,
Decimal::MIN.checked_add(Decimal::STEP).unwrap(),
);
let min = Duration::new(i64::MIN, Decimal::MIN);
let max = Duration::new(i64::MAX, Decimal::MAX);
assert_eq!(YearMonthDuration::from_str("P1Y")?.to_string(), "P1Y");

@ -109,7 +109,7 @@ fn duration_parts(input: &str) -> Result<(DurationParts, &str), XsdParseError> {
const AFTER_MINUTE: u32 = 6;
const AFTER_SECOND: u32 = 7;
let (negative, input) = if let Some(left) = input.strip_prefix('-') {
let (is_negative, input) = if let Some(left) = input.strip_prefix('-') {
(true, left)
} else {
(false, input)
@ -133,7 +133,7 @@ fn duration_parts(input: &str) -> Result<(DurationParts, &str), XsdParseError> {
year_month
.unwrap_or_default()
.checked_add(
i64::from_str(number_str)?
apply_i64_neg(i64::from_str(number_str)?, is_negative)?
.checked_mul(12)
.ok_or(OVERFLOW_ERROR)?,
)
@ -145,7 +145,7 @@ fn duration_parts(input: &str) -> Result<(DurationParts, &str), XsdParseError> {
year_month = Some(
year_month
.unwrap_or_default()
.checked_add(i64::from_str(number_str)?)
.checked_add(apply_i64_neg(i64::from_str(number_str)?, is_negative)?)
.ok_or(OVERFLOW_ERROR)?,
);
state = AFTER_MONTH;
@ -160,7 +160,7 @@ fn duration_parts(input: &str) -> Result<(DurationParts, &str), XsdParseError> {
day_time
.unwrap_or_default()
.checked_add(
Decimal::from_str(number_str)?
apply_decimal_neg(Decimal::from_str(number_str)?, is_negative)?
.checked_mul(86400)
.ok_or(OVERFLOW_ERROR)?,
)
@ -178,7 +178,7 @@ fn duration_parts(input: &str) -> Result<(DurationParts, &str), XsdParseError> {
day_time
.unwrap_or_default()
.checked_add(
Decimal::from_str(number_str)?
apply_decimal_neg(Decimal::from_str(number_str)?, is_negative)?
.checked_mul(3600)
.ok_or(OVERFLOW_ERROR)?,
)
@ -196,7 +196,7 @@ fn duration_parts(input: &str) -> Result<(DurationParts, &str), XsdParseError> {
day_time
.unwrap_or_default()
.checked_add(
Decimal::from_str(number_str)?
apply_decimal_neg(Decimal::from_str(number_str)?, is_negative)?
.checked_mul(60)
.ok_or(OVERFLOW_ERROR)?,
)
@ -208,7 +208,10 @@ fn duration_parts(input: &str) -> Result<(DurationParts, &str), XsdParseError> {
day_time = Some(
day_time
.unwrap_or_default()
.checked_add(Decimal::from_str(number_str)?)
.checked_add(apply_decimal_neg(
Decimal::from_str(number_str)?,
is_negative,
)?)
.ok_or(OVERFLOW_ERROR)?,
);
state = AFTER_SECOND;
@ -226,29 +229,29 @@ fn duration_parts(input: &str) -> Result<(DurationParts, &str), XsdParseError> {
Ok((
DurationParts {
year_month: if let Some(v) = year_month {
Some(if negative {
v.checked_neg().ok_or(OVERFLOW_ERROR)?
} else {
v
})
} else {
None
},
day_time: if let Some(v) = day_time {
Some(if negative {
v.checked_neg().ok_or(OVERFLOW_ERROR)?
} else {
v
})
} else {
None
},
year_month,
day_time,
},
input,
))
}
fn apply_i64_neg(value: i64, is_negative: bool) -> Result<i64, XsdParseError> {
if is_negative {
value.checked_neg().ok_or(OVERFLOW_ERROR)
} else {
Ok(value)
}
}
fn apply_decimal_neg(value: Decimal, is_negative: bool) -> Result<Decimal, XsdParseError> {
if is_negative {
value.checked_neg().ok_or(OVERFLOW_ERROR)
} else {
Ok(value)
}
}
pub fn parse_duration(input: &str) -> Result<Duration, XsdParseError> {
let parts = ensure_complete(input, duration_parts)?;
if parts.year_month.is_none() && parts.day_time.is_none() {

Loading…
Cancel
Save