From aa2011e87b2c433db6b845a282c41bf582a1a9eb Mon Sep 17 00:00:00 2001 From: harshasiddartha <147021873+harshasiddartha@users.noreply.github.com> Date: Fri, 4 Sep 2026 00:22:11 +0530 Subject: [PATCH] date: apply the ^ and # case flags per specifier GNU applies the case flags inside each conversion, not to the whole rendered string, so `#` reaches only the specifiers that emit a name and there it wins over `^`. uutils instead let `^` cancel `#` and applied both to every specifier, so `%#c` printed `SAT JUN 15 13:05:03 2024` instead of `Sat Jun 15 13:05:03 2024`, `%#r` lower-cased the `PM`, `%^P` printed `PM`, and `%^#p` and `%^#Z` printed `PM` and `UTC` rather than `pm` and `utc`. Honor `#` only for `%a %A %b %B %h %p %P %Z`, let it take precedence over `^`, and keep `%P` lower case whatever the flags ask for. Single flags and `%^#B`/`%^#A` already matched GNU and are unchanged. --- src/uu/date/src/format_modifiers.rs | 36 +++++++++++++++++--- tests/by-util/test_date.rs | 52 ++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/src/uu/date/src/format_modifiers.rs b/src/uu/date/src/format_modifiers.rs index 35e1d8d9982..cbdf4914ac4 100644 --- a/src/uu/date/src/format_modifiers.rs +++ b/src/uu/date/src/format_modifiers.rs @@ -19,7 +19,9 @@ //! - `_`: Pad with spaces instead of zeros //! - `0`: Pad with zeros (default for numeric fields) //! - `^`: Convert to uppercase -//! - `#`: Use opposite case (uppercase becomes lowercase and vice versa) +//! - `#`: Use opposite case (uppercase becomes lowercase and vice versa). +//! Only the specifiers that emit a name (`%a %A %b %B %h %p %P %Z`) honor it, +//! and there it takes precedence over `^` //! - `+`: Force display of sign (+ for positive, - for negative) //! //! ### Width @@ -265,6 +267,24 @@ fn is_text_specifier(specifier: &str) -> bool { ) } +/// Returns true if GNU's conversion for `specifier` honors the `#` +/// (opposite case) flag. +/// +/// Only the conversions that emit a locale name or abbreviation look at `#`. +/// Composite specifiers such as `%c` and `%r` are expanded recursively without +/// it, so `%#c` and `%#r` print like `%c` and `%r`, while `^` still reaches +/// into that expansion. +/// +/// This deliberately duplicates the list in `is_text_specifier` rather than +/// calling it: that one classifies specifiers for padding, and the two sets +/// only happen to coincide today. +fn honors_opposite_case(specifier: &str) -> bool { + matches!( + specifier.chars().last(), + Some('A' | 'a' | 'B' | 'b' | 'h' | 'Z' | 'p' | 'P') + ) +} + /// Returns true if the specifier defaults to space padding. /// This includes text specifiers and numeric specifiers like %e and %k /// that use blank-padding by default in GNU date. @@ -391,10 +411,8 @@ fn apply_modifiers(value: &str, parsed: &ParsedSpec<'_>) -> Result { uppercase = true; - swap_case = false; // ^ overrides # } - '#' if !uppercase => { - // Only apply # if ^ hasn't been set + '#' => { swap_case = true; } '+' => { @@ -406,7 +424,15 @@ fn apply_modifiers(value: &str, parsed: &ParsedSpec<'_>) -> Result