Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/uu/date/src/format_modifiers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,7 @@ struct ParsedSpec<'a> {
/// Flag characters from `[_0^#+-]`.
flags: &'a str,
/// Explicit width, if present. `None` means no width was specified.
/// A value that overflows `usize` is represented as `Some(usize::MAX)` so
/// the downstream allocation check surfaces it as `FieldWidthTooLarge`.
/// Width overflows are handled naturally by usize parsing.
width: Option<usize>,
/// The specifier itself, including any leading colons (e.g. `Y`, `:z`, `::z`).
spec: &'a str,
Expand Down Expand Up @@ -411,6 +410,16 @@ fn apply_modifiers(value: &str, parsed: &ParsedSpec<'_>) -> Result<String, Forma

// If no_pad flag is active, suppress all padding and return
if no_pad {
if specifier.ends_with('N') {
if let Some(w) = width {
// Truncate from the right by keeping the leftmost characters,
// using char iteration to avoid slicing at a non-UTF-8 boundary.
let truncated: String = result.chars().take(w).collect();
// Return truncated nanoseconds without trimming trailing zeros (GNU behavior)
return Ok(truncated);
}
Comment on lines 411 to +420
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new no_pad handling truncates result whenever a width is present, and returns early without strip_default_padding. This breaks existing documented/tested behavior that %-5d ignores width and still strips default padding (see tests in this module). Additionally, &result[..end] truncates from the left and can panic on non-ASCII output (not guaranteed to be on a UTF-8 char boundary). This truncation logic should be limited to the %N specifier (if required), should preserve existing no-pad behavior for other specifiers, and should implement the intended direction (right-truncation per PR description) safely.

Suggested change
// If no_pad flag is active, suppress all padding and return
if no_pad {
if let Some(w) = width {
let abs_w = w.abs() as usize;
let end = abs_w.min(result.len());
let truncated = &result[..end];
// Return truncated nanoseconds without trimming trailing zeros (GNU behavior)
return Ok(truncated.to_string());
}
// If no_pad flag is active, suppress all padding and return.
// Width-based truncation only applies to %N; other specifiers keep the
// documented behavior of ignoring width and stripping default padding.
if no_pad {
if specifier == "N" {
if let Some(w) = width {
let abs_w = w.abs() as usize;
// Truncate from the right by keeping the leftmost characters,
// using char iteration to avoid slicing at a non-UTF-8 boundary.
let truncated: String = result.chars().take(abs_w).collect();
// Return truncated nanoseconds without trimming trailing zeros (GNU behavior)
return Ok(truncated);
}
}

Copilot uses AI. Check for mistakes.
}
// For all other specifiers (and %N without width), just strip padding
return Ok(strip_default_padding(&result));
}

Expand Down Expand Up @@ -988,6 +997,7 @@ mod tests {

for (input, expected) in cases {
let actual = parse_format_spec(input).map(|p| (p.flags, p.width, p.spec, p.len));

assert_eq!(actual, *expected, "input = {input:?}");
}
}
Expand Down
Loading