Skip to content
Open
Show file tree
Hide file tree
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
47 changes: 46 additions & 1 deletion src/uu/date/src/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,50 @@
out
}

/// Drop the `-` and `_` pad flags before `%D`, which GNU treats as atomic but
/// jiff expands into `%m/%d/%y`, where they do apply.
fn strip_modifiers_on_composite(fmt: &str) -> String {
if !fmt.contains('%') {
return fmt.to_string();
}

let mut out = String::with_capacity(fmt.len());
let mut chars = fmt.chars().peekable();
while let Some(c) = chars.next() {
if c != '%' {
out.push(c);
continue;
}
// Skip `%%` literally.
if chars.peek() == Some(&'%') {
chars.next();
out.push_str("%%");
continue;
}
// Look ahead: optional pad flags (`-`/`_`), other modifiers, then `%D`.
let mut ahead = chars.clone();
while ahead
.peek()
.is_some_and(|&m| m == '-' || m == '_' || "_0^#+".contains(m) || m.is_ascii_digit())
{
ahead.next();
}
if ahead.peek() == Some(&'D') {
out.push('%');
// Consume only the pad flags (`-`/`_`); keep width and other flags.
while chars.peek().is_some_and(|&m| m == '-' || m == '_') {
chars.next();
}
if let Some(specifier) = chars.next() {
out.push(specifier);
}
} else {
out.push('%');
}
}
out
}

/// Remove the `O` strftime modifier from `fmt`.
///
/// In the C locale `%O` requests alternative numeric symbols that do not
Expand Down Expand Up @@ -886,7 +930,7 @@
}

fn format_date_with_locale_aware_months(
date: &Zoned,

Check warning on line 933 in src/uu/date/src/date.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

WARNING: `cspell`: Unknown word 'euctw' (file:'src/uu/date/src/date.rs', line:933)
format_string: &str,
config: &Config<PosixCustom>,
#[cfg(feature = "i18n-datetime")] skip_localization: bool,
Expand All @@ -908,7 +952,8 @@
// negative infinity (e.g. `@-1.5` → `-2`, not `-1`). Every other field jiff
// produces already agrees with GNU, so only `%s` needs correcting; rewrite it
// to the floored epoch second before jiff sees the format string.
let fmt_owned = strip_o_modifier(&substitute_epoch_seconds(fmt, date));
let fmt_owned =
strip_modifiers_on_composite(&strip_o_modifier(&substitute_epoch_seconds(fmt, date)));
let fmt = fmt_owned.as_str();

// Check if format string has GNU modifiers (width/flags) and format if present
Expand Down
10 changes: 10 additions & 0 deletions src/uu/date/src/format_modifiers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,10 @@

// If no_pad flag is active, suppress all padding and return
if no_pad {
// GNU keeps the internal zero padding when the flag is applied to %F.
if specifier == "F" {
return Ok(result);
}
return Ok(strip_default_padding(&result));
}

Expand Down Expand Up @@ -456,6 +460,12 @@
}
}

// jiff leaves a single-digit hour unpadded in %T; GNU keeps it padded
// before applying any other flags or width.

Check warning on line 464 in src/uu/date/src/format_modifiers.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

WARNING: `cspell`: Unknown word 'unpadded' (file:'src/uu/date/src/format_modifiers.rs', line:464)
if specifier == "T" && result.len() == 7 {
result.insert(0, '0');
}

// Apply force sign for numeric values
// GNU behavior: + only adds sign if:
// 1. An explicit width is provided, OR
Expand Down
72 changes: 70 additions & 2 deletions tests/by-util/test_date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2051,10 +2051,8 @@ fn test_date_strftime_n_width_and_flags() {
}

#[test]
#[ignore = "https://github.com/uutils/coreutils/issues/11657 — GNU date treats composite strftime specifiers (%D, %F, %T, ...) as atomic; flags like `-` should not propagate to sub-fields."]
fn test_date_strftime_flag_on_composite() {
// GNU `%-D` keeps `06/15/24` (flag ignored on composite).
// uutils applies `-` to inner `%m`, producing `6/15/24`.
new_ucmd!()
.env("LC_ALL", "C")
.env("TZ", "UTC")
Expand All @@ -2063,6 +2061,76 @@ fn test_date_strftime_flag_on_composite() {
.arg("+%-D")
.succeeds()
.stdout_is("06/15/24\n");

// GNU applies the modifier to the whole %F expansion, including its year.
new_ucmd!()
.env("LC_ALL", "C")
.env("TZ", "UTC")
.arg("-d")
.arg("0001-01-01")
.arg("+%-F")
.succeeds()
.stdout_is("0001-01-01\n");

// GNU pads the whole %T expansion to the requested width.
new_ucmd!()
.env("LC_ALL", "C")
.env("TZ", "UTC")
.arg("-d")
.arg("2024-06-15 07:08:09")
.arg("+%_10T")
.succeeds()
.stdout_is(" 07:08:09\n");

// `%_D` is stripped the same way as `%-D`.
new_ucmd!()
.env("LC_ALL", "C")
.env("TZ", "UTC")
.arg("-d")
.arg("2024-06-15")
.arg("+%_D")
.succeeds()
.stdout_is("06/15/24\n");

// Non-composite specifiers keep their modifiers: `%-d` drops the padding of
// the day, `%_m` pads the month with a space.
new_ucmd!()
.env("LC_ALL", "C")
.env("TZ", "UTC")
.arg("-d")
.arg("2024-06-05")
.arg("+%-d %_m")
.succeeds()
.stdout_is("5 6\n");

// An enormous width must still be rejected rather than silently truncated.
new_ucmd!()
.env("LC_ALL", "C")
.env("TZ", "UTC")
.arg("-d")
.arg("2024-06-15")
.arg("+%18446744073709551615c")
.fails();

// `%%` is a literal, so the `-D` after it is not a composite specifier.
new_ucmd!()
.env("LC_ALL", "C")
.env("TZ", "UTC")
.arg("-d")
.arg("2024-06-15")
.arg("+%%-%D")
.succeeds()
.stdout_is("%-06/15/24\n");

// The rewrite applies mid-string too, not just at the start.
new_ucmd!()
.env("LC_ALL", "C")
.env("TZ", "UTC")
.arg("-d")
.arg("2024-06-15")
.arg("+a%-Db")
.succeeds()
.stdout_is("a06/15/24b\n");
}

#[test]
Expand Down
Loading