Skip to content

Commit 450e7cf

Browse files
authored
date: use PosixCustom formatting for GNU-compatible output (uutils#10245)
1 parent f7b5f8f commit 450e7cf

File tree

2 files changed

+41
-16
lines changed

2 files changed

+41
-16
lines changed

src/uu/date/src/date.rs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
mod locale;
99

1010
use clap::{Arg, ArgAction, Command};
11-
use jiff::fmt::strtime;
11+
use jiff::fmt::strtime::{self, BrokenDownTime, Config, PosixCustom};
1212
use jiff::tz::{TimeZone, TimeZoneDatabase};
1313
use jiff::{Timestamp, Zoned};
1414
use std::collections::HashMap;
@@ -431,21 +431,23 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
431431
let mut stdout = BufWriter::new(std::io::stdout().lock());
432432

433433
// Format all the dates
434+
let config = Config::new().custom(PosixCustom::new()).lenient(true);
434435
for date in dates {
435436
match date {
436-
// TODO: Switch to lenient formatting.
437-
Ok(date) => match strtime::format(format_string, &date) {
438-
Ok(s) => writeln!(stdout, "{s}").map_err(|e| {
439-
USimpleError::new(1, translate!("date-error-write", "error" => e))
440-
})?,
441-
Err(e) => {
442-
let _ = stdout.flush();
443-
return Err(USimpleError::new(
444-
1,
445-
translate!("date-error-invalid-format", "format" => format_string, "error" => e),
446-
));
437+
Ok(date) => {
438+
match BrokenDownTime::from(&date).to_string_with_config(&config, format_string) {
439+
Ok(s) => writeln!(stdout, "{s}").map_err(|e| {
440+
USimpleError::new(1, translate!("date-error-write", "error" => e))
441+
})?,
442+
Err(e) => {
443+
let _ = stdout.flush();
444+
return Err(USimpleError::new(
445+
1,
446+
translate!("date-error-invalid-format", "format" => format_string, "error" => e),
447+
));
448+
}
447449
}
448-
},
450+
}
449451
Err((input, _err)) => {
450452
let _ = stdout.flush();
451453
show!(USimpleError::new(

tests/by-util/test_date.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -480,9 +480,8 @@ fn test_date_set_valid_4() {
480480

481481
#[test]
482482
fn test_invalid_format_string() {
483-
let result = new_ucmd!().arg("+%!").fails();
484-
result.no_stdout();
485-
assert!(result.stderr_str().starts_with("date: invalid format "));
483+
// With lenient mode, invalid format sequences are output literally (like GNU date)
484+
new_ucmd!().arg("+%!").succeeds().stdout_is("%!\n");
486485
}
487486

488487
#[test]
@@ -1446,3 +1445,27 @@ fn test_date_locale_fr_french() {
14461445
"Output should include timezone information, got: {stdout}"
14471446
);
14481447
}
1448+
1449+
#[test]
1450+
fn test_date_posix_format_specifiers() {
1451+
let cases = [
1452+
// %r: 12-hour time with zero-padded hour (08:17:48 AM, not 8:17:48 AM)
1453+
("%r", "08:17:48 AM"),
1454+
// %x: locale date in MM/DD/YY format
1455+
("%x", "01/19/97"),
1456+
// %X: locale time in HH:MM:SS format
1457+
("%X", "08:17:48"),
1458+
// %:8z: invalid format (width between : and z) should output literally (lenient mode)
1459+
("%:8z", "%:8z"),
1460+
];
1461+
1462+
for (format, expected) in cases {
1463+
new_ucmd!()
1464+
.env("TZ", "UTC")
1465+
.arg("-d")
1466+
.arg("1997-01-19 08:17:48")
1467+
.arg(format!("+{format}"))
1468+
.succeeds()
1469+
.stdout_is(format!("{expected}\n"));
1470+
}
1471+
}

0 commit comments

Comments
 (0)