1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-27 11:07:44 +00:00

fix(date): fix the date

Date didn't work out of the box.
Refactor it a bit to match other programs
This commit is contained in:
Sylvestre Ledru 2020-05-01 23:58:35 +02:00 committed by Roy Ivy III
parent ecfb42ba6c
commit 850bd9c32d

View file

@ -4,16 +4,20 @@
* This file is part of the uutils coreutils package. * This file is part of the uutils coreutils package.
* *
* (c) Anthony Deschamps <anthony.j.deschamps@gmail.com> * (c) Anthony Deschamps <anthony.j.deschamps@gmail.com>
* (c) Sylvestre Ledru <sylvestre@debian.org>
* *
* For the full copyright and license information, please view the LICENSE * For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
extern crate chrono; extern crate chrono;
#[macro_use]
extern crate clap; extern crate clap;
#[macro_use]
extern crate uucore; extern crate uucore;
use clap::{App, Arg};
use chrono::offset::Utc; use chrono::offset::Utc;
use chrono::{DateTime, FixedOffset, Local, Offset}; use chrono::{DateTime, FixedOffset, Local, Offset};
use std::fs::File; use std::fs::File;
@ -25,8 +29,27 @@ const DATE: &str = "date";
const HOURS: &str = "hours"; const HOURS: &str = "hours";
const MINUTES: &str = "minutes"; const MINUTES: &str = "minutes";
const SECONDS: &str = "seconds"; const SECONDS: &str = "seconds";
const HOUR: &str = "hour";
const MINUTE: &str = "minute";
const SECOND: &str = "second";
const NS: &str = "ns"; const NS: &str = "ns";
const NAME: &str = "date";
const VERSION: &str = env!("CARGO_PKG_VERSION");
const ABOUT: &str = "print or set the system date and time";
const OPT_DATE: &str = "date";
const OPT_FORMAT: &str = "format";
const OPT_FILE: &str = "file";
const OPT_DEBUG: &str = "debug";
const OPT_ISO_8601: &str = "iso-8601";
const OPT_RFC_EMAIL: &str = "rfc-email";
const OPT_RFC_3339: &str = "rfc-3339";
const OPT_SET: &str = "set";
const OPT_REFERENCE: &str = "reference";
const OPT_UNIVERSAL: &str = "universal";
const OPT_UNIVERSAL_2: &str = "utc";
// Help strings // Help strings
static ISO_8601_HELP_STRING: &str = "output date/time in ISO 8601 format. static ISO_8601_HELP_STRING: &str = "output date/time in ISO 8601 format.
@ -35,7 +58,7 @@ static ISO_8601_HELP_STRING: &str = "output date/time in ISO 8601 format.
for date and time to the indicated precision. for date and time to the indicated precision.
Example: 2006-08-14T02:34:56-06:00"; Example: 2006-08-14T02:34:56-06:00";
static RFC_2822_HELP_STRING: &str = "output date and time in RFC 2822 format. static RFC_5322_HELP_STRING: &str = "output date and time in RFC 5322 format.
Example: Mon, 14 Aug 2006 02:34:56 -0600"; Example: Mon, 14 Aug 2006 02:34:56 -0600";
static RFC_3339_HELP_STRING: &str = "output date/time in RFC 3339 format. static RFC_3339_HELP_STRING: &str = "output date/time in RFC 3339 format.
@ -54,7 +77,7 @@ struct Settings {
/// Various ways of displaying the date /// Various ways of displaying the date
enum Format { enum Format {
Iso8601(Iso8601Format), Iso8601(Iso8601Format),
Rfc2822, Rfc5322,
Rfc3339(Rfc3339Format), Rfc3339(Rfc3339Format),
Custom(String), Custom(String),
Default, Default,
@ -78,9 +101,9 @@ enum Iso8601Format {
impl<'a> From<&'a str> for Iso8601Format { impl<'a> From<&'a str> for Iso8601Format {
fn from(s: &str) -> Self { fn from(s: &str) -> Self {
match s { match s {
HOURS => Iso8601Format::Hours, HOURS | HOUR => Iso8601Format::Hours,
MINUTES => Iso8601Format::Minutes, MINUTES | MINUTE => Iso8601Format::Minutes,
SECONDS => Iso8601Format::Seconds, SECONDS | SECOND => Iso8601Format::Seconds,
NS => Iso8601Format::Ns, NS => Iso8601Format::Ns,
DATE => Iso8601Format::Date, DATE => Iso8601Format::Date,
// Should be caught by clap // Should be caught by clap
@ -99,7 +122,7 @@ impl<'a> From<&'a str> for Rfc3339Format {
fn from(s: &str) -> Self { fn from(s: &str) -> Self {
match s { match s {
DATE => Rfc3339Format::Date, DATE => Rfc3339Format::Date,
SECONDS => Rfc3339Format::Seconds, SECONDS | SECOND => Rfc3339Format::Seconds,
NS => Rfc3339Format::Ns, NS => Rfc3339Format::Ns,
// Should be caught by clap // Should be caught by clap
_ => panic!("Invalid format: {}", s), _ => panic!("Invalid format: {}", s),
@ -108,7 +131,108 @@ impl<'a> From<&'a str> for Rfc3339Format {
} }
pub fn uumain(args: Vec<String>) -> i32 { pub fn uumain(args: Vec<String>) -> i32 {
let settings = parse_cli(args); let syntax = format!(
"{0} [OPTION]... [+FORMAT]...
{0} [OPTION]... [MMDDhhmm[[CC]YY][.ss]]",
NAME
);
let matches = App::new(executable!())
.version(VERSION)
.about(ABOUT)
.usage(&syntax[..])
.arg(
Arg::with_name(OPT_DATE)
.short("d")
.long(OPT_DATE)
.takes_value(true)
.help("display time described by STRING, not 'now'"),
)
.arg(
Arg::with_name(OPT_FILE)
.short("f")
.long(OPT_FILE)
.takes_value(true)
.help("like --date; once for each line of DATEFILE"),
)
.arg(
Arg::with_name(OPT_ISO_8601)
.short("I")
.long(OPT_ISO_8601)
.takes_value(true)
.help(ISO_8601_HELP_STRING),
)
.arg(
Arg::with_name(OPT_RFC_EMAIL)
.short("R")
.long(OPT_RFC_EMAIL)
.help(RFC_5322_HELP_STRING),
)
.arg(
Arg::with_name(OPT_RFC_3339)
.long(OPT_RFC_3339)
.takes_value(true)
.help(RFC_3339_HELP_STRING),
)
.arg(
Arg::with_name(OPT_DEBUG)
.long(OPT_DEBUG)
.help("annotate the parsed date, and warn about questionable usage to stderr"),
)
.arg(
Arg::with_name(OPT_REFERENCE)
.short("r")
.long(OPT_REFERENCE)
.takes_value(true)
.help("display the last modification time of FILE"),
)
.arg(
Arg::with_name(OPT_SET)
.short("s")
.long(OPT_SET)
.takes_value(true)
.help("set time described by STRING"),
)
.arg(
Arg::with_name(OPT_UNIVERSAL)
.short("u")
.long(OPT_UNIVERSAL)
.alias(OPT_UNIVERSAL_2)
.help("print or set Coordinated Universal Time (UTC)"),
)
.arg(Arg::with_name(OPT_FORMAT).multiple(true))
.get_matches_from(&args);
let format = if let Some(form) = matches.value_of(OPT_FORMAT) {
let form = form[1..].into();
Format::Custom(form)
} else if let Some(fmt) = matches
.values_of(OPT_ISO_8601)
.map(|mut iter| iter.next().unwrap_or(DATE).into())
{
Format::Iso8601(fmt)
} else if matches.is_present(OPT_RFC_EMAIL) {
Format::Rfc5322
} else if let Some(fmt) = matches.value_of(OPT_RFC_3339).map(Into::into) {
Format::Rfc3339(fmt)
} else {
Format::Default
};
let date_source = if let Some(date) = matches.value_of(OPT_DATE) {
DateSource::Custom(date.into())
} else if let Some(file) = matches.value_of(OPT_FILE) {
DateSource::File(file.into())
} else {
DateSource::Now
};
let settings = Settings {
utc: matches.is_present(OPT_UNIVERSAL),
format,
date_source,
// TODO: Handle this option:
set_to: None,
};
if let Some(_time) = settings.set_to { if let Some(_time) = settings.set_to {
unimplemented!(); unimplemented!();
@ -174,81 +298,6 @@ pub fn uumain(args: Vec<String>) -> i32 {
0 0
} }
/// Handle command line arguments.
fn parse_cli(args: Vec<String>) -> Settings {
let matches = clap_app!(
date =>
(@group dates =>
(@arg date: -d --date [STRING]
"display time described by STRING, not 'now'")
(@arg file: -f --file [DATEFILE]
"like --date; once for each line of DATEFILE"))
(@group format =>
(@arg iso_8601: -I --("iso-8601") <FMT>
possible_value[date hours minutes seconds ns]
#{0, 1}
ISO_8601_HELP_STRING)
(@arg rfc_2822: -R --("rfc-2822")
RFC_2822_HELP_STRING)
(@arg rfc_3339: --("rfc-3339") <FMT>
possible_value[date seconds ns]
RFC_3339_HELP_STRING)
(@arg custom_format: +takes_value {
|s| if s.starts_with('+') {
Ok(())
} else {
Err(String::from("Date formats must start with a '+' character"))
}
}))
(@arg debug: --debug
"annotate the parsed date, and warn about questionable usage to stderr")
(@arg reference: -r --reference [FILE]
"display the last modification time of FILE")
(@arg set: -s --set [STRING]
"set time described by STRING")
(@arg utc: -u --utc --universal
"print or set Coordinated Universal Time (UTC)"))
// TODO: Decide whether this is appropriate.
// The GNU date command has an explanation of all formatting options,
// but the `chrono` crate has a few differences (most notably, the %Z option)
// (after_help: include_str!("usage.txt")))
.get_matches_from(args);
let format = if let Some(form) = matches.value_of("custom_format") {
let form = form[1..].into();
Format::Custom(form)
} else if let Some(fmt) = matches
.values_of("iso_8601")
.map(|mut iter| iter.next().unwrap_or(DATE).into())
{
Format::Iso8601(fmt)
} else if matches.is_present("rfc_2822") {
Format::Rfc2822
} else if let Some(fmt) = matches.value_of("rfc_3339").map(Into::into) {
Format::Rfc3339(fmt)
} else {
Format::Default
};
let date_source = if let Some(date) = matches.value_of("date") {
DateSource::Custom(date.into())
} else if let Some(file) = matches.value_of("file") {
DateSource::File(file.into())
} else {
DateSource::Now
};
Settings {
utc: matches.is_present("utc"),
format,
date_source,
// TODO: Handle this option:
set_to: None,
}
}
/// Return the appropriate format string for the given settings. /// Return the appropriate format string for the given settings.
fn make_format_string(settings: &Settings) -> &str { fn make_format_string(settings: &Settings) -> &str {
match settings.format { match settings.format {
@ -259,7 +308,7 @@ fn make_format_string(settings: &Settings) -> &str {
Iso8601Format::Seconds => "%FT%T%:z", Iso8601Format::Seconds => "%FT%T%:z",
Iso8601Format::Ns => "%FT%T,%f%:z", Iso8601Format::Ns => "%FT%T,%f%:z",
}, },
Format::Rfc2822 => "%a, %d %h %Y %T %z", Format::Rfc5322 => "%a, %d %h %Y %T %z",
Format::Rfc3339(ref fmt) => match *fmt { Format::Rfc3339(ref fmt) => match *fmt {
Rfc3339Format::Date => "%F", Rfc3339Format::Date => "%F",
Rfc3339Format::Seconds => "%F %T%:z", Rfc3339Format::Seconds => "%F %T%:z",