Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/uu/date/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,11 @@ date-error-format-modifier-width-too-large = format modifier width '{$width}' is
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 '+'
date-error-invalid-choice = invalid argument '{$arg}' for '--{$option}'
Valid arguments are:
{$choices}
Try 'date --help' for more information.
date-error-ambiguous-choice = ambiguous argument '{$arg}' for '--{$option}'
Valid arguments are:
{$choices}
Try 'date --help' for more information.
8 changes: 8 additions & 0 deletions src/uu/date/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,11 @@ date-error-format-modifier-width-too-large = la largeur du modificateur de forma
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 '+'.
date-error-invalid-choice = argument '{$arg}' invalide pour '--{$option}'
Arguments valides :
{$choices}
Essayez 'date --help' pour plus d'informations.
date-error-ambiguous-choice = argument '{$arg}' ambigu pour '--{$option}'
Arguments valides :
{$choices}
Essayez 'date --help' pour plus d'informations.
56 changes: 40 additions & 16 deletions src/uu/date/src/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ use uucore::{format_usage, show};
#[cfg(windows)]
use windows_sys::Win32::{Foundation::SYSTEMTIME, System::SystemInformation::SetSystemTime};

use uucore::parser::shortcut_value_parser::ShortcutValueParser;

// Options
const DATE: &str = "date";
const HOURS: &str = "hours";
Expand Down Expand Up @@ -125,7 +123,7 @@ impl From<&str> for Iso8601Format {
SECONDS => Self::Seconds,
NS => Self::Ns,
DATE => Self::Date,
// Note: This is caught by clap via `possible_values`
// Note: only reached with a name `resolve_choice` already validated.
_ => unreachable!(),
}
}
Expand All @@ -143,12 +141,45 @@ impl From<&str> for Rfc3339Format {
DATE => Self::Date,
SECONDS => Self::Seconds,
NS => Self::Ns,
// Should be caught by clap
_ => panic!("Invalid format: {s}"),
// Note: only reached with a name `resolve_choice` already validated.
_ => unreachable!(),
}
}
}

/// The choices `--iso-8601`'s value accepts, in the order GNU lists them.
const ISO_8601_CHOICES: &[&str] = &[HOURS, MINUTES, DATE, SECONDS, NS];

/// The choices `--rfc-3339`'s value accepts, in the order GNU lists them.
const RFC_3339_CHOICES: &[&str] = &[DATE, SECONDS, NS];

/// The choice `value` names among `choices`, accepting any unambiguous
/// abbreviation the way GNU does. `option` is the long name to report the
/// error against if it names none, or more than one.
fn resolve_choice<'a>(value: &str, option: &'static str, choices: &[&'a str]) -> UResult<&'a str> {
let list = || {
choices
.iter()
.map(|name| format!(" - '{name}'"))
.collect::<Vec<_>>()
.join("\n")
};
let mut named = choices.iter().filter(|name| name.starts_with(value));
match (named.next(), named.next()) {
// No choice abbreviates another, so a single match is the answer
// whether or not it is the whole word.
(Some(name), None) if !value.is_empty() => Ok(*name),
(Some(_), Some(_)) => Err(USimpleError::new(
1,
translate!("date-error-ambiguous-choice", "arg" => value.to_string(), "option" => option, "choices" => list()),
)),
_ => Err(USimpleError::new(
1,
translate!("date-error-invalid-choice", "arg" => value.to_string(), "option" => option, "choices" => list()),
)),
}
}

/// Indicates whether parsing a military timezone causes the date to remain the same, roll back to the previous day, or
/// advance to the next day.
/// This can occur when applying a military timezone with an optional hour offset crosses midnight
Expand Down Expand Up @@ -348,16 +379,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
Format::Custom(fmt)
} else if let Some(fmt) = matches
.get_many::<String>(OPT_ISO_8601)
.map(|mut iter| iter.next().unwrap_or(&DATE.to_string()).as_str().into())
.map(|mut iter| iter.next().map_or(DATE, String::as_str))
{
Format::Iso8601(fmt)
Format::Iso8601(resolve_choice(fmt, OPT_ISO_8601, ISO_8601_CHOICES)?.into())
} else if matches.get_flag(OPT_RFC_EMAIL) {
Format::Rfc5322
} else if let Some(fmt) = matches
.get_one::<String>(OPT_RFC_3339)
.map(|s| s.as_str().into())
{
Format::Rfc3339(fmt)
} else if let Some(fmt) = matches.get_one::<String>(OPT_RFC_3339) {
Format::Rfc3339(resolve_choice(fmt, OPT_RFC_3339, RFC_3339_CHOICES)?.into())
} else if matches.get_flag(OPT_RESOLUTION) {
Format::Resolution
} else {
Expand Down Expand Up @@ -657,9 +685,6 @@ pub fn uu_app() -> Command {
.short('I')
.long(OPT_ISO_8601)
.value_name("FMT")
.value_parser(ShortcutValueParser::new([
DATE, HOURS, MINUTES, SECONDS, NS,
]))
.num_args(0..=1)
.default_missing_value(OPT_DATE)
.help(translate!("date-help-iso-8601")),
Expand All @@ -686,7 +711,6 @@ pub fn uu_app() -> Command {
Arg::new(OPT_RFC_3339)
.long(OPT_RFC_3339)
.value_name("FMT")
.value_parser(ShortcutValueParser::new([DATE, SECONDS, NS]))
.help(translate!("date-help-rfc-3339")),
)
.arg(
Expand Down
49 changes: 49 additions & 0 deletions tests/by-util/test_date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,55 @@ fn test_date_rfc_3339_invalid_arg() {
}
}

#[test]
fn test_date_iso_8601_invalid_arg_message() {
new_ucmd!()
.arg("--iso-8601=foo")
.fails()
.stderr_is(concat!(
"date: invalid argument 'foo' for '--iso-8601'\n",
"Valid arguments are:\n",
" - 'hours'\n",
" - 'minutes'\n",
" - 'date'\n",
" - 'seconds'\n",
" - 'ns'\n",
"Try 'date --help' for more information.\n",
));
}

#[test]
fn test_date_iso_8601_ambiguous_arg_message() {
new_ucmd!()
.arg("--iso-8601=")
.fails()
.stderr_is(concat!(
"date: ambiguous argument '' for '--iso-8601'\n",
"Valid arguments are:\n",
" - 'hours'\n",
" - 'minutes'\n",
" - 'date'\n",
" - 'seconds'\n",
" - 'ns'\n",
"Try 'date --help' for more information.\n",
));
}

#[test]
fn test_date_rfc_3339_invalid_arg_message() {
new_ucmd!()
.arg("--rfc-3339=foo")
.fails()
.stderr_is(concat!(
"date: invalid argument 'foo' for '--rfc-3339'\n",
"Valid arguments are:\n",
" - 'date'\n",
" - 'seconds'\n",
" - 'ns'\n",
"Try 'date --help' for more information.\n",
));
}

#[test]
fn test_date_rfc_8601_default() {
let re = Regex::new(r"^\d{4}-\d{2}-\d{2}\n$").unwrap();
Expand Down
Loading