diff --git a/src/uu/date/locales/en-US.ftl b/src/uu/date/locales/en-US.ftl index 782275fec6e..512510c1b53 100644 --- a/src/uu/date/locales/en-US.ftl +++ b/src/uu/date/locales/en-US.ftl @@ -106,3 +106,6 @@ date-error-setting-date-not-supported-redox = setting the date is not supported date-error-cannot-set-date = cannot set date date-error-extra-operand = extra operand '{$operand}' date-error-write = write error: {$error} +date-error-format-missing-plus = the argument {$arg} lacks a leading '+'; + when using an option to specify date(s), any non-option + argument must be a format string beginning with '+' diff --git a/src/uu/date/locales/fr-FR.ftl b/src/uu/date/locales/fr-FR.ftl index 15321c1fcdc..cf827246634 100644 --- a/src/uu/date/locales/fr-FR.ftl +++ b/src/uu/date/locales/fr-FR.ftl @@ -101,3 +101,6 @@ date-error-setting-date-not-supported-redox = la définition de la date n'est pa date-error-cannot-set-date = impossible de définir la date date-error-extra-operand = opérande supplémentaire '{$operand}' date-error-write = erreur d'écriture: {$error} +date-error-format-missing-plus = l'argument {$arg} ne commence pas par un signe '+'; + lorsqu'une option est utilisée pour spécifier une ou plusieurs dates, tout argument autre + qu'une option doit être une chaîne de format commençant par un signe '+'. diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 02321c0c5f4..91e72747a36 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -285,6 +285,27 @@ fn parse_military_timezone_with_offset(s: &str) -> Option<(i32, DayDelta)> { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; + let date_source = if let Some(date_os) = matches.get_one::(OPT_DATE) { + // Convert OsString to String, handling invalid UTF-8 with GNU-compatible error + let date = date_os.to_str().ok_or_else(|| { + let bytes = date_os.as_encoded_bytes(); + let escaped_str = escape_invalid_bytes(bytes); + USimpleError::new(1, format!("invalid date '{escaped_str}'")) + })?; + DateSource::Human(date.into()) + } else if let Some(file) = matches.get_one::(OPT_FILE) { + match file.as_ref() { + "-" => DateSource::Stdin, + _ => DateSource::File(file.into()), + } + } else if let Some(file) = matches.get_one::(OPT_REFERENCE) { + DateSource::FileMtime(file.into()) + } else if matches.get_flag(OPT_RESOLUTION) { + DateSource::Resolution + } else { + DateSource::Now + }; + // Check for extra operands (multiple positional arguments) if let Some(formats) = matches.get_many::(OPT_FORMAT) { let format_args: Vec<&String> = formats.collect(); @@ -298,9 +319,19 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let format = if let Some(form) = matches.get_one::(OPT_FORMAT) { if !form.starts_with('+') { + // if an optional Format String was found but the user has not provided an input date + // GNU prints an invalid date Error + if !matches!(date_source, DateSource::Human(_)) { + return Err(USimpleError::new( + 1, + translate!("date-error-invalid-date", "date" => form), + )); + } + // If the user did provide an input date with the --date flag and the Format String is + // not starting with '+' GNU prints the missing '+' error message return Err(USimpleError::new( 1, - translate!("date-error-invalid-date", "date" => form), + translate!("date-error-format-missing-plus", "arg" => form), )); } let form = form[1..].to_string(); @@ -333,27 +364,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { Zoned::now() }; - let date_source = if let Some(date_os) = matches.get_one::(OPT_DATE) { - // Convert OsString to String, handling invalid UTF-8 with GNU-compatible error - let date = date_os.to_str().ok_or_else(|| { - let bytes = date_os.as_encoded_bytes(); - let escaped_str = escape_invalid_bytes(bytes); - USimpleError::new(1, format!("invalid date '{escaped_str}'")) - })?; - DateSource::Human(date.into()) - } else if let Some(file) = matches.get_one::(OPT_FILE) { - match file.as_ref() { - "-" => DateSource::Stdin, - _ => DateSource::File(file.into()), - } - } else if let Some(file) = matches.get_one::(OPT_REFERENCE) { - DateSource::FileMtime(file.into()) - } else if matches.get_flag(OPT_RESOLUTION) { - DateSource::Resolution - } else { - DateSource::Now - }; - let set_to = match matches .get_one::(OPT_SET) .map(|s| parse_date(s, &now, DebugOptions::new(debug_mode, true))) diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index 9eade4a0e59..1471634df37 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -35,6 +35,18 @@ fn test_extra_operands() { .stderr_contains("extra operand 'extra'"); } +#[test] +fn test_bad_format_option_missing_leading_plus_after_d_flag() { + let bad_arguments = vec!["q", "a", "test", "%Y-%m-%d"]; + + for bad_argument in bad_arguments { + new_ucmd!() + .args(&["--date", "1996-01-31", bad_argument]) + .fails_with_code(1) + .stderr_contains(format!("the argument {bad_argument} lacks a leading '+';\nwhen using an option to specify date(s), any non-option\nargument must be a format string beginning with '+'"), ); + } +} + #[test] fn test_invalid_long_option() { new_ucmd!()