1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 12:07:46 +00:00

uucore: parser: parse_time: Handle infinity and nan

There were some missing corner cases when handling infinity and
nan:
 - inf/infinity can be capitalized
 - nan must always be rejected, even if a suffix is provided

Also, return Duration::MAX with infinite values, just for consistency
(num.fract() returns 0 for infinity so technically we were just
short of that).

Add unit tests too.

Fixes some of #7475.
This commit is contained in:
Nicolas Boichat 2025-04-04 10:42:31 +02:00
parent b860ce8553
commit 94a26e170e

View file

@ -3,7 +3,7 @@
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore (vars) NANOS numstr // spell-checker:ignore (vars) NANOS numstr infinityh INFD nans nanh
//! Parsing a duration from a string. //! Parsing a duration from a string.
//! //!
//! Use the [`from_str`] function to parse a [`Duration`] from a string. //! Use the [`from_str`] function to parse a [`Duration`] from a string.
@ -58,22 +58,23 @@ pub fn from_str(string: &str) -> Result<Duration, String> {
'h' => (slice, 60 * 60), 'h' => (slice, 60 * 60),
'd' => (slice, 60 * 60 * 24), 'd' => (slice, 60 * 60 * 24),
val if !val.is_alphabetic() => (string, 1), val if !val.is_alphabetic() => (string, 1),
_ => { _ => match string.to_ascii_lowercase().as_str() {
if string == "inf" || string == "infinity" { "inf" | "infinity" => ("inf", 1),
("inf", 1) _ => return Err(format!("invalid time interval {}", string.quote())),
} else { },
return Err(format!("invalid time interval {}", string.quote()));
}
}
}; };
let num = numstr let num = numstr
.parse::<f64>() .parse::<f64>()
.map_err(|e| format!("invalid time interval {}: {}", string.quote(), e))?; .map_err(|e| format!("invalid time interval {}: {}", string.quote(), e))?;
if num < 0. { if num < 0. || num.is_nan() {
return Err(format!("invalid time interval {}", string.quote())); return Err(format!("invalid time interval {}", string.quote()));
} }
if num.is_infinite() {
return Ok(Duration::MAX);
}
const NANOS_PER_SEC: u32 = 1_000_000_000; const NANOS_PER_SEC: u32 = 1_000_000_000;
let whole_secs = num.trunc(); let whole_secs = num.trunc();
let nanos = (num.fract() * (NANOS_PER_SEC as f64)).trunc(); let nanos = (num.fract() * (NANOS_PER_SEC as f64)).trunc();
@ -127,6 +128,24 @@ mod tests {
assert!(from_str("-1").is_err()); assert!(from_str("-1").is_err());
} }
#[test]
fn test_infinity() {
assert_eq!(from_str("inf"), Ok(Duration::MAX));
assert_eq!(from_str("infinity"), Ok(Duration::MAX));
assert_eq!(from_str("infinityh"), Ok(Duration::MAX));
assert_eq!(from_str("INF"), Ok(Duration::MAX));
assert_eq!(from_str("INFs"), Ok(Duration::MAX));
}
#[test]
fn test_nan() {
assert!(from_str("nan").is_err());
assert!(from_str("nans").is_err());
assert!(from_str("-nanh").is_err());
assert!(from_str("NAN").is_err());
assert!(from_str("-NAN").is_err());
}
/// Test that capital letters are not allowed in suffixes. /// Test that capital letters are not allowed in suffixes.
#[test] #[test]
fn test_no_capital_letters() { fn test_no_capital_letters() {
@ -134,5 +153,6 @@ mod tests {
assert!(from_str("1M").is_err()); assert!(from_str("1M").is_err());
assert!(from_str("1H").is_err()); assert!(from_str("1H").is_err());
assert!(from_str("1D").is_err()); assert!(from_str("1D").is_err());
assert!(from_str("INFD").is_err());
} }
} }