mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-29 12:07:46 +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",
|
||||
"cp",
|
||||
"cut",
|
||||
"date",
|
||||
"dircolors",
|
||||
"dirname",
|
||||
"echo",
|
||||
|
@ -157,6 +158,7 @@ cksum = { optional=true, path="src/cksum" }
|
|||
comm = { optional=true, path="src/comm" }
|
||||
cp = { optional=true, path="src/cp" }
|
||||
cut = { optional=true, path="src/cut" }
|
||||
date = { optional=true, path="src/date" }
|
||||
dircolors= { optional=true, path="src/dircolors" }
|
||||
dirname = { optional=true, path="src/dirname" }
|
||||
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