mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 19:47:45 +00:00
Merge pull request #7115 from jfinkels/stat-format-mtime-precision
stat: fix precision when rendering mtime (%Y)
This commit is contained in:
commit
988cc4eae3
2 changed files with 207 additions and 40 deletions
|
@ -94,6 +94,7 @@ pub enum OutputType {
|
||||||
Unsigned(u64),
|
Unsigned(u64),
|
||||||
UnsignedHex(u64),
|
UnsignedHex(u64),
|
||||||
UnsignedOct(u32),
|
UnsignedOct(u32),
|
||||||
|
Float(f64),
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,6 +121,13 @@ impl std::str::FromStr for QuotingStyle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
enum Precision {
|
||||||
|
NotSpecified,
|
||||||
|
NoNumber,
|
||||||
|
Number(usize),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
enum Token {
|
enum Token {
|
||||||
Char(char),
|
Char(char),
|
||||||
|
@ -127,7 +135,7 @@ enum Token {
|
||||||
Directive {
|
Directive {
|
||||||
flag: Flags,
|
flag: Flags,
|
||||||
width: usize,
|
width: usize,
|
||||||
precision: Option<usize>,
|
precision: Precision,
|
||||||
format: char,
|
format: char,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -238,10 +246,10 @@ struct Stater {
|
||||||
/// * `output` - A reference to the OutputType enum containing the value to be printed.
|
/// * `output` - A reference to the OutputType enum containing the value to be printed.
|
||||||
/// * `flags` - A Flags struct containing formatting flags.
|
/// * `flags` - A Flags struct containing formatting flags.
|
||||||
/// * `width` - The width of the field for the printed output.
|
/// * `width` - The width of the field for the printed output.
|
||||||
/// * `precision` - An Option containing the precision value.
|
/// * `precision` - How many digits of precision, if any.
|
||||||
///
|
///
|
||||||
/// This function delegates the printing process to more specialized functions depending on the output type.
|
/// This function delegates the printing process to more specialized functions depending on the output type.
|
||||||
fn print_it(output: &OutputType, flags: Flags, width: usize, precision: Option<usize>) {
|
fn print_it(output: &OutputType, flags: Flags, width: usize, precision: Precision) {
|
||||||
// If the precision is given as just '.', the precision is taken to be zero.
|
// If the precision is given as just '.', the precision is taken to be zero.
|
||||||
// A negative precision is taken as if the precision were omitted.
|
// A negative precision is taken as if the precision were omitted.
|
||||||
// This gives the minimum number of digits to appear for d, i, o, u, x, and X conversions,
|
// This gives the minimum number of digits to appear for d, i, o, u, x, and X conversions,
|
||||||
|
@ -271,7 +279,7 @@ fn print_it(output: &OutputType, flags: Flags, width: usize, precision: Option<u
|
||||||
// A sign (+ or -) should always be placed before a number produced by a signed conversion.
|
// A sign (+ or -) should always be placed before a number produced by a signed conversion.
|
||||||
// By default, a sign is used only for negative numbers.
|
// By default, a sign is used only for negative numbers.
|
||||||
// A + overrides a space if both are used.
|
// A + overrides a space if both are used.
|
||||||
let padding_char = determine_padding_char(&flags, &precision);
|
let padding_char = determine_padding_char(&flags);
|
||||||
|
|
||||||
match output {
|
match output {
|
||||||
OutputType::Str(s) => print_str(s, &flags, width, precision),
|
OutputType::Str(s) => print_str(s, &flags, width, precision),
|
||||||
|
@ -283,6 +291,9 @@ fn print_it(output: &OutputType, flags: Flags, width: usize, precision: Option<u
|
||||||
OutputType::UnsignedHex(num) => {
|
OutputType::UnsignedHex(num) => {
|
||||||
print_unsigned_hex(*num, &flags, width, precision, padding_char);
|
print_unsigned_hex(*num, &flags, width, precision, padding_char);
|
||||||
}
|
}
|
||||||
|
OutputType::Float(num) => {
|
||||||
|
print_float(*num, &flags, width, precision, padding_char);
|
||||||
|
}
|
||||||
OutputType::Unknown => print!("?"),
|
OutputType::Unknown => print!("?"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -292,13 +303,12 @@ fn print_it(output: &OutputType, flags: Flags, width: usize, precision: Option<u
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `flags` - A reference to the Flags struct containing formatting flags.
|
/// * `flags` - A reference to the Flags struct containing formatting flags.
|
||||||
/// * `precision` - An Option containing the precision value.
|
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
///
|
///
|
||||||
/// * Padding - An instance of the Padding enum representing the padding character.
|
/// * Padding - An instance of the Padding enum representing the padding character.
|
||||||
fn determine_padding_char(flags: &Flags, precision: &Option<usize>) -> Padding {
|
fn determine_padding_char(flags: &Flags) -> Padding {
|
||||||
if flags.zero && !flags.left && precision.is_none() {
|
if flags.zero && !flags.left {
|
||||||
Padding::Zero
|
Padding::Zero
|
||||||
} else {
|
} else {
|
||||||
Padding::Space
|
Padding::Space
|
||||||
|
@ -312,10 +322,10 @@ fn determine_padding_char(flags: &Flags, precision: &Option<usize>) -> Padding {
|
||||||
/// * `s` - The string to be printed.
|
/// * `s` - The string to be printed.
|
||||||
/// * `flags` - A reference to the Flags struct containing formatting flags.
|
/// * `flags` - A reference to the Flags struct containing formatting flags.
|
||||||
/// * `width` - The width of the field for the printed string.
|
/// * `width` - The width of the field for the printed string.
|
||||||
/// * `precision` - An Option containing the precision value.
|
/// * `precision` - How many digits of precision, if any.
|
||||||
fn print_str(s: &str, flags: &Flags, width: usize, precision: Option<usize>) {
|
fn print_str(s: &str, flags: &Flags, width: usize, precision: Precision) {
|
||||||
let s = match precision {
|
let s = match precision {
|
||||||
Some(p) if p < s.len() => &s[..p],
|
Precision::Number(p) if p < s.len() => &s[..p],
|
||||||
_ => s,
|
_ => s,
|
||||||
};
|
};
|
||||||
pad_and_print(s, flags.left, width, Padding::Space);
|
pad_and_print(s, flags.left, width, Padding::Space);
|
||||||
|
@ -415,13 +425,13 @@ fn process_token_filesystem(t: &Token, meta: StatFs, display_name: &str) {
|
||||||
/// * `num` - The integer value to be printed.
|
/// * `num` - The integer value to be printed.
|
||||||
/// * `flags` - A reference to the Flags struct containing formatting flags.
|
/// * `flags` - A reference to the Flags struct containing formatting flags.
|
||||||
/// * `width` - The width of the field for the printed integer.
|
/// * `width` - The width of the field for the printed integer.
|
||||||
/// * `precision` - An Option containing the precision value.
|
/// * `precision` - How many digits of precision, if any.
|
||||||
/// * `padding_char` - The padding character as determined by `determine_padding_char`.
|
/// * `padding_char` - The padding character as determined by `determine_padding_char`.
|
||||||
fn print_integer(
|
fn print_integer(
|
||||||
num: i64,
|
num: i64,
|
||||||
flags: &Flags,
|
flags: &Flags,
|
||||||
width: usize,
|
width: usize,
|
||||||
precision: Option<usize>,
|
precision: Precision,
|
||||||
padding_char: Padding,
|
padding_char: Padding,
|
||||||
) {
|
) {
|
||||||
let num = num.to_string();
|
let num = num.to_string();
|
||||||
|
@ -437,13 +447,66 @@ fn print_integer(
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
};
|
};
|
||||||
let extended = format!(
|
let extended = match precision {
|
||||||
"{prefix}{arg:0>precision$}",
|
Precision::NotSpecified => format!("{prefix}{arg}"),
|
||||||
precision = precision.unwrap_or(0)
|
Precision::NoNumber => format!("{prefix}{arg}"),
|
||||||
);
|
Precision::Number(p) => format!("{prefix}{arg:0>precision$}", precision = p),
|
||||||
|
};
|
||||||
pad_and_print(&extended, flags.left, width, padding_char);
|
pad_and_print(&extended, flags.left, width, padding_char);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Truncate a float to the given number of digits after the decimal point.
|
||||||
|
fn precision_trunc(num: f64, precision: Precision) -> String {
|
||||||
|
// GNU `stat` doesn't round, it just seems to truncate to the
|
||||||
|
// given precision:
|
||||||
|
//
|
||||||
|
// $ stat -c "%.5Y" /dev/pts/ptmx
|
||||||
|
// 1736344012.76399
|
||||||
|
// $ stat -c "%.4Y" /dev/pts/ptmx
|
||||||
|
// 1736344012.7639
|
||||||
|
// $ stat -c "%.3Y" /dev/pts/ptmx
|
||||||
|
// 1736344012.763
|
||||||
|
//
|
||||||
|
// Contrast this with `printf`, which seems to round the
|
||||||
|
// numbers:
|
||||||
|
//
|
||||||
|
// $ printf "%.5f\n" 1736344012.76399
|
||||||
|
// 1736344012.76399
|
||||||
|
// $ printf "%.4f\n" 1736344012.76399
|
||||||
|
// 1736344012.7640
|
||||||
|
// $ printf "%.3f\n" 1736344012.76399
|
||||||
|
// 1736344012.764
|
||||||
|
//
|
||||||
|
let num_str = num.to_string();
|
||||||
|
let n = num_str.len();
|
||||||
|
match (num_str.find('.'), precision) {
|
||||||
|
(None, Precision::NotSpecified) => num_str,
|
||||||
|
(None, Precision::NoNumber) => num_str,
|
||||||
|
(None, Precision::Number(0)) => num_str,
|
||||||
|
(None, Precision::Number(p)) => format!("{num_str}.{zeros}", zeros = "0".repeat(p)),
|
||||||
|
(Some(i), Precision::NotSpecified) => num_str[..i].to_string(),
|
||||||
|
(Some(_), Precision::NoNumber) => num_str,
|
||||||
|
(Some(i), Precision::Number(0)) => num_str[..i].to_string(),
|
||||||
|
(Some(i), Precision::Number(p)) if p < n - i => num_str[..i + 1 + p].to_string(),
|
||||||
|
(Some(i), Precision::Number(p)) => {
|
||||||
|
format!("{num_str}{zeros}", zeros = "0".repeat(p - (n - i - 1)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_float(num: f64, flags: &Flags, width: usize, precision: Precision, padding_char: Padding) {
|
||||||
|
let prefix = if flags.sign {
|
||||||
|
"+"
|
||||||
|
} else if flags.space {
|
||||||
|
" "
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
let num_str = precision_trunc(num, precision);
|
||||||
|
let extended = format!("{prefix}{num_str}");
|
||||||
|
pad_and_print(&extended, flags.left, width, padding_char)
|
||||||
|
}
|
||||||
|
|
||||||
/// Prints an unsigned integer value based on the provided flags, width, and precision.
|
/// Prints an unsigned integer value based on the provided flags, width, and precision.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
|
@ -451,13 +514,13 @@ fn print_integer(
|
||||||
/// * `num` - The unsigned integer value to be printed.
|
/// * `num` - The unsigned integer value to be printed.
|
||||||
/// * `flags` - A reference to the Flags struct containing formatting flags.
|
/// * `flags` - A reference to the Flags struct containing formatting flags.
|
||||||
/// * `width` - The width of the field for the printed unsigned integer.
|
/// * `width` - The width of the field for the printed unsigned integer.
|
||||||
/// * `precision` - An Option containing the precision value.
|
/// * `precision` - How many digits of precision, if any.
|
||||||
/// * `padding_char` - The padding character as determined by `determine_padding_char`.
|
/// * `padding_char` - The padding character as determined by `determine_padding_char`.
|
||||||
fn print_unsigned(
|
fn print_unsigned(
|
||||||
num: u64,
|
num: u64,
|
||||||
flags: &Flags,
|
flags: &Flags,
|
||||||
width: usize,
|
width: usize,
|
||||||
precision: Option<usize>,
|
precision: Precision,
|
||||||
padding_char: Padding,
|
padding_char: Padding,
|
||||||
) {
|
) {
|
||||||
let num = num.to_string();
|
let num = num.to_string();
|
||||||
|
@ -466,7 +529,11 @@ fn print_unsigned(
|
||||||
} else {
|
} else {
|
||||||
Cow::Borrowed(num.as_str())
|
Cow::Borrowed(num.as_str())
|
||||||
};
|
};
|
||||||
let s = format!("{s:0>precision$}", precision = precision.unwrap_or(0));
|
let s = match precision {
|
||||||
|
Precision::NotSpecified => s,
|
||||||
|
Precision::NoNumber => s,
|
||||||
|
Precision::Number(p) => format!("{s:0>precision$}", precision = p).into(),
|
||||||
|
};
|
||||||
pad_and_print(&s, flags.left, width, padding_char);
|
pad_and_print(&s, flags.left, width, padding_char);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,20 +544,21 @@ fn print_unsigned(
|
||||||
/// * `num` - The unsigned octal integer value to be printed.
|
/// * `num` - The unsigned octal integer value to be printed.
|
||||||
/// * `flags` - A reference to the Flags struct containing formatting flags.
|
/// * `flags` - A reference to the Flags struct containing formatting flags.
|
||||||
/// * `width` - The width of the field for the printed unsigned octal integer.
|
/// * `width` - The width of the field for the printed unsigned octal integer.
|
||||||
/// * `precision` - An Option containing the precision value.
|
/// * `precision` - How many digits of precision, if any.
|
||||||
/// * `padding_char` - The padding character as determined by `determine_padding_char`.
|
/// * `padding_char` - The padding character as determined by `determine_padding_char`.
|
||||||
fn print_unsigned_oct(
|
fn print_unsigned_oct(
|
||||||
num: u32,
|
num: u32,
|
||||||
flags: &Flags,
|
flags: &Flags,
|
||||||
width: usize,
|
width: usize,
|
||||||
precision: Option<usize>,
|
precision: Precision,
|
||||||
padding_char: Padding,
|
padding_char: Padding,
|
||||||
) {
|
) {
|
||||||
let prefix = if flags.alter { "0" } else { "" };
|
let prefix = if flags.alter { "0" } else { "" };
|
||||||
let s = format!(
|
let s = match precision {
|
||||||
"{prefix}{num:0>precision$o}",
|
Precision::NotSpecified => format!("{prefix}{num:o}"),
|
||||||
precision = precision.unwrap_or(0)
|
Precision::NoNumber => format!("{prefix}{num:o}"),
|
||||||
);
|
Precision::Number(p) => format!("{prefix}{num:0>precision$o}", precision = p),
|
||||||
|
};
|
||||||
pad_and_print(&s, flags.left, width, padding_char);
|
pad_and_print(&s, flags.left, width, padding_char);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -501,20 +569,21 @@ fn print_unsigned_oct(
|
||||||
/// * `num` - The unsigned hexadecimal integer value to be printed.
|
/// * `num` - The unsigned hexadecimal integer value to be printed.
|
||||||
/// * `flags` - A reference to the Flags struct containing formatting flags.
|
/// * `flags` - A reference to the Flags struct containing formatting flags.
|
||||||
/// * `width` - The width of the field for the printed unsigned hexadecimal integer.
|
/// * `width` - The width of the field for the printed unsigned hexadecimal integer.
|
||||||
/// * `precision` - An Option containing the precision value.
|
/// * `precision` - How many digits of precision, if any.
|
||||||
/// * `padding_char` - The padding character as determined by `determine_padding_char`.
|
/// * `padding_char` - The padding character as determined by `determine_padding_char`.
|
||||||
fn print_unsigned_hex(
|
fn print_unsigned_hex(
|
||||||
num: u64,
|
num: u64,
|
||||||
flags: &Flags,
|
flags: &Flags,
|
||||||
width: usize,
|
width: usize,
|
||||||
precision: Option<usize>,
|
precision: Precision,
|
||||||
padding_char: Padding,
|
padding_char: Padding,
|
||||||
) {
|
) {
|
||||||
let prefix = if flags.alter { "0x" } else { "" };
|
let prefix = if flags.alter { "0x" } else { "" };
|
||||||
let s = format!(
|
let s = match precision {
|
||||||
"{prefix}{num:0>precision$x}",
|
Precision::NotSpecified => format!("{prefix}{num:x}"),
|
||||||
precision = precision.unwrap_or(0)
|
Precision::NoNumber => format!("{prefix}{num:x}"),
|
||||||
);
|
Precision::Number(p) => format!("{prefix}{num:0>precision$x}", precision = p),
|
||||||
|
};
|
||||||
pad_and_print(&s, flags.left, width, padding_char);
|
pad_and_print(&s, flags.left, width, padding_char);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -530,6 +599,10 @@ impl Stater {
|
||||||
'0' => flag.zero = true,
|
'0' => flag.zero = true,
|
||||||
'-' => flag.left = true,
|
'-' => flag.left = true,
|
||||||
' ' => flag.space = true,
|
' ' => flag.space = true,
|
||||||
|
// This is not documented but the behavior seems to be
|
||||||
|
// the same as a space. For example `stat -c "%I5s" f`
|
||||||
|
// prints " 0".
|
||||||
|
'I' => flag.space = true,
|
||||||
'+' => flag.sign = true,
|
'+' => flag.sign = true,
|
||||||
'\'' => flag.group = true,
|
'\'' => flag.group = true,
|
||||||
_ => break,
|
_ => break,
|
||||||
|
@ -560,7 +633,7 @@ impl Stater {
|
||||||
Self::process_flags(chars, i, bound, &mut flag);
|
Self::process_flags(chars, i, bound, &mut flag);
|
||||||
|
|
||||||
let mut width = 0;
|
let mut width = 0;
|
||||||
let mut precision = None;
|
let mut precision = Precision::NotSpecified;
|
||||||
let mut j = *i;
|
let mut j = *i;
|
||||||
|
|
||||||
if let Some((field_width, offset)) = format_str[j..].scan_num::<usize>() {
|
if let Some((field_width, offset)) = format_str[j..].scan_num::<usize>() {
|
||||||
|
@ -585,11 +658,11 @@ impl Stater {
|
||||||
match format_str[j..].scan_num::<i32>() {
|
match format_str[j..].scan_num::<i32>() {
|
||||||
Some((value, offset)) => {
|
Some((value, offset)) => {
|
||||||
if value >= 0 {
|
if value >= 0 {
|
||||||
precision = Some(value as usize);
|
precision = Precision::Number(value as usize);
|
||||||
}
|
}
|
||||||
j += offset;
|
j += offset;
|
||||||
}
|
}
|
||||||
None => precision = Some(0),
|
None => precision = Precision::NoNumber,
|
||||||
}
|
}
|
||||||
check_bound(format_str, bound, old, j)?;
|
check_bound(format_str, bound, old, j)?;
|
||||||
}
|
}
|
||||||
|
@ -898,7 +971,24 @@ impl Stater {
|
||||||
// time of last data modification, human-readable
|
// time of last data modification, human-readable
|
||||||
'y' => OutputType::Str(pretty_time(meta.mtime(), meta.mtime_nsec())),
|
'y' => OutputType::Str(pretty_time(meta.mtime(), meta.mtime_nsec())),
|
||||||
// time of last data modification, seconds since Epoch
|
// time of last data modification, seconds since Epoch
|
||||||
'Y' => OutputType::Integer(meta.mtime()),
|
'Y' => {
|
||||||
|
let sec = meta.mtime();
|
||||||
|
let nsec = meta.mtime_nsec();
|
||||||
|
let tm =
|
||||||
|
chrono::DateTime::from_timestamp(sec, nsec as u32).unwrap_or_default();
|
||||||
|
let tm: DateTime<Local> = tm.into();
|
||||||
|
match tm.timestamp_nanos_opt() {
|
||||||
|
None => {
|
||||||
|
let micros = tm.timestamp_micros();
|
||||||
|
let secs = micros as f64 / 1_000_000.0;
|
||||||
|
OutputType::Float(secs)
|
||||||
|
}
|
||||||
|
Some(ns) => {
|
||||||
|
let secs = ns as f64 / 1_000_000_000.0;
|
||||||
|
OutputType::Float(secs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// time of last status change, human-readable
|
// time of last status change, human-readable
|
||||||
'z' => OutputType::Str(pretty_time(meta.ctime(), meta.ctime_nsec())),
|
'z' => OutputType::Str(pretty_time(meta.ctime(), meta.ctime_nsec())),
|
||||||
// time of last status change, seconds since Epoch
|
// time of last status change, seconds since Epoch
|
||||||
|
@ -1107,7 +1197,7 @@ fn pretty_time(sec: i64, nsec: i64) -> String {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{group_num, Flags, ScanUtil, Stater, Token};
|
use super::{group_num, precision_trunc, Flags, Precision, ScanUtil, Stater, Token};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_scanners() {
|
fn test_scanners() {
|
||||||
|
@ -1155,7 +1245,7 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
width: 10,
|
width: 10,
|
||||||
precision: Some(2),
|
precision: Precision::Number(2),
|
||||||
format: 'a',
|
format: 'a',
|
||||||
},
|
},
|
||||||
Token::Char('c'),
|
Token::Char('c'),
|
||||||
|
@ -1166,7 +1256,7 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
width: 5,
|
width: 5,
|
||||||
precision: Some(0),
|
precision: Precision::NoNumber,
|
||||||
format: 'w',
|
format: 'w',
|
||||||
},
|
},
|
||||||
Token::Char('\n'),
|
Token::Char('\n'),
|
||||||
|
@ -1186,7 +1276,7 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
width: 15,
|
width: 15,
|
||||||
precision: None,
|
precision: Precision::NotSpecified,
|
||||||
format: 'a',
|
format: 'a',
|
||||||
},
|
},
|
||||||
Token::Byte(b'\t'),
|
Token::Byte(b'\t'),
|
||||||
|
@ -1205,7 +1295,7 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
width: 20,
|
width: 20,
|
||||||
precision: None,
|
precision: Precision::NotSpecified,
|
||||||
format: 'w',
|
format: 'w',
|
||||||
},
|
},
|
||||||
Token::Byte(b'\x12'),
|
Token::Byte(b'\x12'),
|
||||||
|
@ -1216,4 +1306,16 @@ mod tests {
|
||||||
];
|
];
|
||||||
assert_eq!(&expected, &Stater::generate_tokens(s, true).unwrap());
|
assert_eq!(&expected, &Stater::generate_tokens(s, true).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_precision_trunc() {
|
||||||
|
assert_eq!(precision_trunc(123.456, Precision::NotSpecified), "123");
|
||||||
|
assert_eq!(precision_trunc(123.456, Precision::NoNumber), "123.456");
|
||||||
|
assert_eq!(precision_trunc(123.456, Precision::Number(0)), "123");
|
||||||
|
assert_eq!(precision_trunc(123.456, Precision::Number(1)), "123.4");
|
||||||
|
assert_eq!(precision_trunc(123.456, Precision::Number(2)), "123.45");
|
||||||
|
assert_eq!(precision_trunc(123.456, Precision::Number(3)), "123.456");
|
||||||
|
assert_eq!(precision_trunc(123.456, Precision::Number(4)), "123.4560");
|
||||||
|
assert_eq!(precision_trunc(123.456, Precision::Number(5)), "123.45600");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,9 +184,74 @@ fn test_char() {
|
||||||
];
|
];
|
||||||
let ts = TestScenario::new(util_name!());
|
let ts = TestScenario::new(util_name!());
|
||||||
let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str();
|
let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str();
|
||||||
|
eprintln!("{expected_stdout}");
|
||||||
ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout);
|
ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[test]
|
||||||
|
fn test_printf_mtime_precision() {
|
||||||
|
// TODO Higher precision numbers (`%.3Y`, `%.4Y`, etc.) are
|
||||||
|
// formatted correctly, but we are not precise enough when we do
|
||||||
|
// some `mtime` computations, so we get `.7640` instead of
|
||||||
|
// `.7639`. This can be fixed by being more careful when
|
||||||
|
// transforming the number from `Metadata::mtime_nsec()` to the form
|
||||||
|
// used in rendering.
|
||||||
|
let args = ["-c", "%.0Y %.1Y %.2Y", "/dev/pts/ptmx"];
|
||||||
|
let ts = TestScenario::new(util_name!());
|
||||||
|
let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str();
|
||||||
|
eprintln!("{expected_stdout}");
|
||||||
|
ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "touch")]
|
||||||
|
#[test]
|
||||||
|
fn test_timestamp_format() {
|
||||||
|
let ts = TestScenario::new(util_name!());
|
||||||
|
|
||||||
|
// Create a file with a specific timestamp for testing
|
||||||
|
ts.ccmd("touch")
|
||||||
|
.args(&["-d", "1970-01-01 18:43:33.023456789", "k"])
|
||||||
|
.succeeds()
|
||||||
|
.no_stderr();
|
||||||
|
|
||||||
|
let test_cases = vec![
|
||||||
|
// Basic timestamp formats
|
||||||
|
("%Y", "67413"),
|
||||||
|
("%.Y", "67413.023456789"),
|
||||||
|
("%.1Y", "67413.0"),
|
||||||
|
("%.3Y", "67413.023"),
|
||||||
|
("%.6Y", "67413.023456"),
|
||||||
|
("%.9Y", "67413.023456789"),
|
||||||
|
// Width and padding tests
|
||||||
|
("%13.6Y", " 67413.023456"),
|
||||||
|
("%013.6Y", "067413.023456"),
|
||||||
|
("%-13.6Y", "67413.023456 "),
|
||||||
|
// Longer width/precision combinations
|
||||||
|
("%18.10Y", " 67413.0234567890"),
|
||||||
|
("%I18.10Y", " 67413.0234567890"),
|
||||||
|
("%018.10Y", "0067413.0234567890"),
|
||||||
|
("%-18.10Y", "67413.0234567890 "),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (format_str, expected) in test_cases {
|
||||||
|
let result = ts
|
||||||
|
.ucmd()
|
||||||
|
.args(&["-c", format_str, "k"])
|
||||||
|
.succeeds()
|
||||||
|
.stdout_move_str();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
format!("{expected}\n"),
|
||||||
|
"Format '{}' failed.\nExpected: '{}'\nGot: '{}'",
|
||||||
|
format_str,
|
||||||
|
expected,
|
||||||
|
result,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple"))]
|
#[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple"))]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_date() {
|
fn test_date() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue