Skip to content

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

Description

@SichenLiang

^ 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 b7519db, 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, b7519db 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"));
    }
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions