uutils / uutils/coreutils

date: case flags ^ and # don't match GNU on %c %p %P %r %Z

Open
#14,351 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

U - date
Dominant language
Rust
Stars
24.1k
Forks
2k
Avg merge
1d 5h
Merged PRs (30d)
365

Description

^ and # aren't in POSIX, and the manual describes them without saying which
specifiers they apply to, so I've been treating the GNU binary as the reference.

gnudate is GNU 9.7, uu is a build of b7519db78, TZ=UTC LC_ALL=C
throughout. Same output on the 9.11 reference build.

$ gnudate -d '2024-06-15 13:05:03' +'[%#c]'   [Sat Jun 15 13:05:03 2024]
                                      uu:     [SAT JUN 15 13:05:03 2024]
$ gnudate -d '2024-06-15 13:05:03' +'[%#r]'   [01:05:03 PM]   uu: [01:05:03 pm]
$ gnudate -d '2024-06-15 13:05:03' +'[%^P]'   [pm]            uu: [PM]
$ gnudate -d '2024-06-15 13:05:03' +'[%^#p]'  [pm]            uu: [PM]
$ gnudate -d '2024-06-15 13:05:03' +'[%#^p]'  [pm]            uu: [PM]
$ gnudate -d '2024-06-15 13:05:03' +'[%^#Z]'  [utc]           uu: [UTC]
$ TZ=America/New_York gnudate -d 2024-06-15 +'[%^#Z]'  [edt]  uu: [EDT]

uutils applies ^/# to the whole rendered string and lets ^ cancel #.
GNU's are per specifier — strftime(3) says as much for # ("This flag works
only with certain conversion specifier characters, and of these, it is only
really useful with %Z") without saying which ones.

%^P doesn't need # at all — it's the same shape as #11659, with ^ in
place of # (that one was fixed in #11671).

Singles all agree: %p %^p %#p %P %#P %Z %^Z %#Z %c %^c %r %^r. So do %^#B
and %^#A on that date — JUNE and SATURDAY both ways. That's why the
combination is easy to miss. Found by sweeping flag, specifier and width
combinations against GNU.

The precedence is explicit, b7519db78 src/uu/date/src/format_modifiers.rs:392:

'^' => {
    uppercase = true;
    swap_case = false; // ^ overrides #
}
'#' if !uppercase => {
    // Only apply # if ^ hasn't been set
    swap_case = true;
}

A test that fails on main:

#[test]
fn test_date_strftime_case_flags_match_gnu() {
    for (fmt, expected) in [
        ("%#c", "Sat Jun 15 13:05:03 2024"),
        ("%#r", "01:05:03 PM"),
        ("%^P", "pm"),
        ("%^#p", "pm"),
        ("%#^p", "pm"),
        ("%^#Z", "utc"),
    ] {
        new_ucmd!()
            .env("LC_ALL", "C")
            .env("TZ", "UTC")
            .arg("-d")
            .arg("2024-06-15 13:05:03")
            .arg(format!("+{fmt}"))
            .succeeds()
            .stdout_is(format!("{expected}\n"));
    }
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading src/uu/date/src/format_modifiers.rs around line 392, then run or add the test_date_strftime_case_flags_match_gnu test from the issue. Compare the listed outputs for the case flags against GNU date; done means all cases, including flag combinations and %c, %r, %P, and %Z, produce the expected output.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
cli, testing-qa
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
84/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.