mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-30 12:37:49 +00:00
Partial implemantion of date.
This commit is contained in:
parent
48931adffa
commit
41d1dfaf44
5 changed files with 379 additions and 0 deletions
|
@ -99,6 +99,7 @@ redox = [
|
||||||
"comm",
|
"comm",
|
||||||
"cp",
|
"cp",
|
||||||
"cut",
|
"cut",
|
||||||
|
"date",
|
||||||
"dircolors",
|
"dircolors",
|
||||||
"dirname",
|
"dirname",
|
||||||
"echo",
|
"echo",
|
||||||
|
@ -157,6 +158,7 @@ cksum = { optional=true, path="src/cksum" }
|
||||||
comm = { optional=true, path="src/comm" }
|
comm = { optional=true, path="src/comm" }
|
||||||
cp = { optional=true, path="src/cp" }
|
cp = { optional=true, path="src/cp" }
|
||||||
cut = { optional=true, path="src/cut" }
|
cut = { optional=true, path="src/cut" }
|
||||||
|
date = { optional=true, path="src/date" }
|
||||||
dircolors= { optional=true, path="src/dircolors" }
|
dircolors= { optional=true, path="src/dircolors" }
|
||||||
dirname = { optional=true, path="src/dirname" }
|
dirname = { optional=true, path="src/dirname" }
|
||||||
du = { optional=true, path="src/du" }
|
du = { optional=true, path="src/du" }
|
||||||
|
|
17
src/date/Cargo.toml
Normal file
17
src/date/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "date"
|
||||||
|
version = "0.0.1"
|
||||||
|
authors = []
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "uu_date"
|
||||||
|
path = "date.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
chrono = "*"
|
||||||
|
clap = "*"
|
||||||
|
uucore = { path="../uucore" }
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "date"
|
||||||
|
path = "main.rs"
|
283
src/date/date.rs
Normal file
283
src/date/date.rs
Normal file
|
@ -0,0 +1,283 @@
|
||||||
|
#![crate_name = "uu_date"]
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the uutils coreutils package.
|
||||||
|
*
|
||||||
|
* (c) Anthony Deschamps <anthony.j.deschamps@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
extern crate chrono;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate clap;
|
||||||
|
extern crate uucore;
|
||||||
|
|
||||||
|
use chrono::{DateTime, FixedOffset, Offset, Local};
|
||||||
|
use chrono::offset::utc::UTC;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{BufRead, BufReader};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
// Options
|
||||||
|
const DATE: &'static str = "date";
|
||||||
|
const HOURS: &'static str = "hours";
|
||||||
|
const MINUTES: &'static str = "minutes";
|
||||||
|
const SECONDS: &'static str = "seconds";
|
||||||
|
const NS: &'static str = "ns";
|
||||||
|
|
||||||
|
// Help strings
|
||||||
|
|
||||||
|
static ISO_8601_HELP_STRING: &'static str = "output date/time in ISO 8601 format.
|
||||||
|
FMT='date' for date only (the default),
|
||||||
|
'hours', 'minutes', 'seconds', or 'ns'
|
||||||
|
for date and time to the indicated precision.
|
||||||
|
Example: 2006-08-14T02:34:56-06:00";
|
||||||
|
|
||||||
|
static RFC_2822_HELP_STRING: &'static str = "output date and time in RFC 2822 format.
|
||||||
|
Example: Mon, 14 Aug 2006 02:34:56 -0600";
|
||||||
|
|
||||||
|
static RFC_3339_HELP_STRING: &'static str = "output date/time in RFC 3339 format.
|
||||||
|
FMT='date', 'seconds', or 'ns'
|
||||||
|
for date and time to the indicated precision.
|
||||||
|
Example: 2006-08-14 02:34:56-06:00";
|
||||||
|
|
||||||
|
/// Settings for this program, parsed from the command line
|
||||||
|
struct Settings {
|
||||||
|
utc: bool,
|
||||||
|
format: Format,
|
||||||
|
date_source: DateSource,
|
||||||
|
set_to: Option<DateTime<FixedOffset>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Various ways of displaying the date
|
||||||
|
enum Format {
|
||||||
|
Iso8601(Iso8601Format),
|
||||||
|
Rfc2822,
|
||||||
|
Rfc3339(Rfc3339Format),
|
||||||
|
Custom(String),
|
||||||
|
Default,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Various places that dates can come from
|
||||||
|
enum DateSource {
|
||||||
|
Now,
|
||||||
|
Custom(String),
|
||||||
|
File(PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Iso8601Format {
|
||||||
|
Date,
|
||||||
|
Hours,
|
||||||
|
Minutes,
|
||||||
|
Seconds,
|
||||||
|
Ns,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a str> for Iso8601Format {
|
||||||
|
fn from(s: &str) -> Self {
|
||||||
|
match s {
|
||||||
|
HOURS => Iso8601Format::Hours,
|
||||||
|
MINUTES => Iso8601Format::Minutes,
|
||||||
|
SECONDS => Iso8601Format::Seconds,
|
||||||
|
NS => Iso8601Format::Ns,
|
||||||
|
DATE => Iso8601Format::Date,
|
||||||
|
// Should be caught by clap
|
||||||
|
_ => panic!("Invalid format: {}", s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Rfc3339Format {
|
||||||
|
Date,
|
||||||
|
Seconds,
|
||||||
|
Ns,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a str> for Rfc3339Format {
|
||||||
|
fn from(s: &str) -> Self {
|
||||||
|
match s {
|
||||||
|
DATE => Rfc3339Format::Date,
|
||||||
|
SECONDS => Rfc3339Format::Seconds,
|
||||||
|
NS => Rfc3339Format::Ns,
|
||||||
|
// Should be caught by clap
|
||||||
|
_ => panic!("Invalid format: {}", s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
|
|
||||||
|
let settings = parse_cli(args);
|
||||||
|
|
||||||
|
if let Some(_time) = settings.set_to {
|
||||||
|
unimplemented!();
|
||||||
|
// Probably need to use this syscall:
|
||||||
|
// https://doc.rust-lang.org/libc/i686-unknown-linux-gnu/libc/fn.clock_settime.html
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Declare a file here because it needs to outlive the `dates` iterator.
|
||||||
|
let file: File;
|
||||||
|
|
||||||
|
// Get the current time, either in the local time zone or UTC.
|
||||||
|
let now: DateTime<FixedOffset> = match settings.utc {
|
||||||
|
true => {
|
||||||
|
let now = UTC::now();
|
||||||
|
now.with_timezone(&now.offset().fix())
|
||||||
|
}
|
||||||
|
false => {
|
||||||
|
let now = Local::now();
|
||||||
|
now.with_timezone(now.offset())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Parse a `String` into a `DateTime`.
|
||||||
|
/// If it fails, return a tuple of the `String` along with its `ParseError`.
|
||||||
|
fn parse_date(s: String)
|
||||||
|
-> Result<DateTime<FixedOffset>, (String, chrono::format::ParseError)> {
|
||||||
|
// TODO: The GNU date command can parse a wide variety of inputs.
|
||||||
|
s.parse().map_err(|e| (s, e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over all dates - whether it's a single date or a file.
|
||||||
|
let dates: Box<Iterator<Item = _>> = match settings.date_source {
|
||||||
|
DateSource::Custom(ref input) => {
|
||||||
|
let date = parse_date(input.clone());
|
||||||
|
let iter = std::iter::once(date);
|
||||||
|
Box::new(iter)
|
||||||
|
}
|
||||||
|
DateSource::File(ref path) => {
|
||||||
|
file = File::open(path).unwrap();
|
||||||
|
let lines = BufReader::new(file).lines();
|
||||||
|
let iter = lines.filter_map(Result::ok).map(parse_date);
|
||||||
|
Box::new(iter)
|
||||||
|
}
|
||||||
|
DateSource::Now => {
|
||||||
|
let iter = std::iter::once(Ok(now));
|
||||||
|
Box::new(iter)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let format_string = make_format_string(&settings);
|
||||||
|
|
||||||
|
// Format all the dates
|
||||||
|
for date in dates {
|
||||||
|
match date {
|
||||||
|
Ok(date) => {
|
||||||
|
let formatted = date.format(format_string);
|
||||||
|
println!("{}", formatted);
|
||||||
|
}
|
||||||
|
Err((input, _err)) => {
|
||||||
|
println!("date: invalid date '{}'", input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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| match s.starts_with("+") {
|
||||||
|
true => Ok(()),
|
||||||
|
false => 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: format,
|
||||||
|
date_source: date_source,
|
||||||
|
// TODO: Handle this option:
|
||||||
|
set_to: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Return the appropriate format string for the given settings.
|
||||||
|
fn make_format_string(settings: &Settings) -> &str {
|
||||||
|
match settings.format {
|
||||||
|
Format::Iso8601(ref fmt) => {
|
||||||
|
match fmt {
|
||||||
|
&Iso8601Format::Date => "%F",
|
||||||
|
&Iso8601Format::Hours => "%FT%H%:z",
|
||||||
|
&Iso8601Format::Minutes => "%FT%H:%M%:z",
|
||||||
|
&Iso8601Format::Seconds => "%FT%T%:z",
|
||||||
|
&Iso8601Format::Ns => "%FT%T,%f%:z",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Format::Rfc2822 => "%a, %d %h %Y %T %z",
|
||||||
|
Format::Rfc3339(ref fmt) => {
|
||||||
|
match fmt {
|
||||||
|
&Rfc3339Format::Date => "%F",
|
||||||
|
&Rfc3339Format::Seconds => "%F %T%:z",
|
||||||
|
&Rfc3339Format::Ns => "%F %T.%f%:z",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Format::Custom(ref fmt) => fmt,
|
||||||
|
Format::Default => "%c",
|
||||||
|
}
|
||||||
|
}
|
5
src/date/main.rs
Normal file
5
src/date/main.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
extern crate uu_echo;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
std::process::exit(uu_date::uumain(std::env::args().collect()));
|
||||||
|
}
|
72
src/date/usage.txt
Normal file
72
src/date/usage.txt
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
FORMAT controls the output. Interpreted sequences are:
|
||||||
|
|
||||||
|
%% a literal %
|
||||||
|
%a locale's abbreviated weekday name (e.g., Sun)
|
||||||
|
%A locale's full weekday name (e.g., Sunday)
|
||||||
|
%b locale's abbreviated month name (e.g., Jan)
|
||||||
|
%B locale's full month name (e.g., January)
|
||||||
|
%c locale's date and time (e.g., Thu Mar 3 23:05:25 2005)
|
||||||
|
%C century; like %Y, except omit last two digits (e.g., 20)
|
||||||
|
%d day of month (e.g., 01)
|
||||||
|
%D date; same as %m/%d/%y
|
||||||
|
%e day of month, space padded; same as %_d
|
||||||
|
%F full date; same as %Y-%m-%d
|
||||||
|
%g last two digits of year of ISO week number (see %G)
|
||||||
|
%G year of ISO week number (see %V); normally useful only with %V
|
||||||
|
%h same as %b
|
||||||
|
%H hour (00..23)
|
||||||
|
%I hour (01..12)
|
||||||
|
%j day of year (001..366)
|
||||||
|
%k hour, space padded ( 0..23); same as %_H
|
||||||
|
%l hour, space padded ( 1..12); same as %_I
|
||||||
|
%m month (01..12)
|
||||||
|
%M minute (00..59)
|
||||||
|
%n a newline
|
||||||
|
%N nanoseconds (000000000..999999999)
|
||||||
|
%p locale's equivalent of either AM or PM; blank if not known
|
||||||
|
%P like %p, but lower case
|
||||||
|
%q quarter of year (1..4)
|
||||||
|
%r locale's 12-hour clock time (e.g., 11:11:04 PM)
|
||||||
|
%R 24-hour hour and minute; same as %H:%M
|
||||||
|
%s seconds since 1970-01-01 00:00:00 UTC
|
||||||
|
%S second (00..60)
|
||||||
|
%t a tab
|
||||||
|
%T time; same as %H:%M:%S
|
||||||
|
%u day of week (1..7); 1 is Monday
|
||||||
|
%U week number of year, with Sunday as first day of week (00..53)
|
||||||
|
%V ISO week number, with Monday as first day of week (01..53)
|
||||||
|
%w day of week (0..6); 0 is Sunday
|
||||||
|
%W week number of year, with Monday as first day of week (00..53)
|
||||||
|
%x locale's date representation (e.g., 12/31/99)
|
||||||
|
%X locale's time representation (e.g., 23:13:48)
|
||||||
|
%y last two digits of year (00..99)
|
||||||
|
%Y year
|
||||||
|
%z +hhmm numeric time zone (e.g., -0400)
|
||||||
|
%:z +hh:mm numeric time zone (e.g., -04:00)
|
||||||
|
%::z +hh:mm:ss numeric time zone (e.g., -04:00:00)
|
||||||
|
%:::z numeric time zone with : to necessary precision (e.g., -04, +05:30)
|
||||||
|
%Z alphabetic time zone abbreviation (e.g., EDT)
|
||||||
|
|
||||||
|
By default, date pads numeric fields with zeroes.
|
||||||
|
The following optional flags may follow '%':
|
||||||
|
|
||||||
|
- (hyphen) do not pad the field
|
||||||
|
_ (underscore) pad with spaces
|
||||||
|
0 (zero) pad with zeros
|
||||||
|
^ use upper case if possible
|
||||||
|
# use opposite case if possible
|
||||||
|
|
||||||
|
After any flags comes an optional field width, as a decimal number;
|
||||||
|
then an optional modifier, which is either
|
||||||
|
E to use the locale's alternate representations if available, or
|
||||||
|
O to use the locale's alternate numeric symbols if available.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Convert seconds since the epoch (1970-01-01 UTC) to a date
|
||||||
|
$ date --date='@2147483647'
|
||||||
|
|
||||||
|
Show the time on the west coast of the US (use tzselect(1) to find TZ)
|
||||||
|
$ TZ='America/Los_Angeles' date
|
||||||
|
|
||||||
|
Show the local time for 9AM next Friday on the west coast of the US
|
||||||
|
$ date --date='TZ="America/Los_Angeles" 09:00 next Fri'
|
Loading…
Add table
Add a link
Reference in a new issue