mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 19:47:45 +00:00
date -d supports - 1 year ago, 2 second, etc
This commit is contained in:
parent
6f0e4ece9d
commit
825d240ef0
4 changed files with 47 additions and 3 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2538,7 +2538,9 @@ version = "0.0.18"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
"humantime_to_duration",
|
||||||
"libc",
|
"libc",
|
||||||
|
"time",
|
||||||
"uucore",
|
"uucore",
|
||||||
"windows-sys 0.45.0",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
# spell-checker:ignore humantime
|
||||||
[package]
|
[package]
|
||||||
name = "uu_date"
|
name = "uu_date"
|
||||||
version = "0.0.18"
|
version = "0.0.18"
|
||||||
|
@ -16,8 +17,11 @@ path = "src/date.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = { workspace=true }
|
chrono = { workspace=true }
|
||||||
|
#/ TODO: check if we can avoid chrono+time
|
||||||
|
time = { workspace=true }
|
||||||
clap = { workspace=true }
|
clap = { workspace=true }
|
||||||
uucore = { workspace=true }
|
uucore = { workspace=true }
|
||||||
|
humantime_to_duration = { workspace=true }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
libc = { workspace=true }
|
libc = { workspace=true }
|
||||||
|
|
|
@ -6,10 +6,10 @@
|
||||||
// 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.
|
||||||
|
|
||||||
// spell-checker:ignore (chrono) Datelike Timelike ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes
|
// spell-checker:ignore (chrono) Datelike Timelike ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes humantime
|
||||||
|
|
||||||
use chrono::format::{Item, StrftimeItems};
|
use chrono::format::{Item, StrftimeItems};
|
||||||
use chrono::{DateTime, FixedOffset, Local, Offset, Utc};
|
use chrono::{DateTime, Duration as ChronoDuration, FixedOffset, Local, Offset, Utc};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use chrono::{Datelike, Timelike};
|
use chrono::{Datelike, Timelike};
|
||||||
use clap::{crate_version, Arg, ArgAction, Command};
|
use clap::{crate_version, Arg, ArgAction, Command};
|
||||||
|
@ -18,6 +18,7 @@ use libc::{clock_settime, timespec, CLOCK_REALTIME};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{BufRead, BufReader};
|
use std::io::{BufRead, BufReader};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use time::Duration;
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
#[cfg(not(any(target_os = "redox")))]
|
#[cfg(not(any(target_os = "redox")))]
|
||||||
use uucore::error::FromIo;
|
use uucore::error::FromIo;
|
||||||
|
@ -96,6 +97,7 @@ enum DateSource {
|
||||||
Now,
|
Now,
|
||||||
Custom(String),
|
Custom(String),
|
||||||
File(PathBuf),
|
File(PathBuf),
|
||||||
|
Human(Duration),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Iso8601Format {
|
enum Iso8601Format {
|
||||||
|
@ -168,7 +170,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let date_source = if let Some(date) = matches.get_one::<String>(OPT_DATE) {
|
let date_source = if let Some(date) = matches.get_one::<String>(OPT_DATE) {
|
||||||
DateSource::Custom(date.into())
|
if let Ok(duration) = humantime_to_duration::from_str(date.as_str()) {
|
||||||
|
DateSource::Human(duration)
|
||||||
|
} else {
|
||||||
|
DateSource::Custom(date.into())
|
||||||
|
}
|
||||||
} else if let Some(file) = matches.get_one::<String>(OPT_FILE) {
|
} else if let Some(file) = matches.get_one::<String>(OPT_FILE) {
|
||||||
DateSource::File(file.into())
|
DateSource::File(file.into())
|
||||||
} else {
|
} else {
|
||||||
|
@ -219,6 +225,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let iter = std::iter::once(date);
|
let iter = std::iter::once(date);
|
||||||
Box::new(iter)
|
Box::new(iter)
|
||||||
}
|
}
|
||||||
|
DateSource::Human(ref input) => {
|
||||||
|
// Get the current DateTime<FixedOffset> and convert the input time::Duration to chrono::Duration
|
||||||
|
// for things like "1 year ago"
|
||||||
|
let current_time = DateTime::<FixedOffset>::from(Local::now());
|
||||||
|
let input_chrono = ChronoDuration::seconds(input.as_seconds_f32() as i64)
|
||||||
|
+ ChronoDuration::nanoseconds(input.subsec_nanoseconds() as i64);
|
||||||
|
let iter = std::iter::once(Ok(current_time + input_chrono));
|
||||||
|
Box::new(iter)
|
||||||
|
}
|
||||||
DateSource::File(ref path) => {
|
DateSource::File(ref path) => {
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
return Err(USimpleError::new(
|
return Err(USimpleError::new(
|
||||||
|
|
|
@ -334,6 +334,29 @@ fn test_invalid_format_string() {
|
||||||
assert!(result.stderr_str().starts_with("date: invalid format "));
|
assert!(result.stderr_str().starts_with("date: invalid format "));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_date_string_human() {
|
||||||
|
let date_formats = vec![
|
||||||
|
"1 year ago",
|
||||||
|
"1 year",
|
||||||
|
"2 months ago",
|
||||||
|
"15 days ago",
|
||||||
|
"1 week ago",
|
||||||
|
"5 hours ago",
|
||||||
|
"30 minutes ago",
|
||||||
|
"10 seconds",
|
||||||
|
];
|
||||||
|
let re = Regex::new(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}\n$").unwrap();
|
||||||
|
for date_format in date_formats {
|
||||||
|
new_ucmd!()
|
||||||
|
.arg("-d")
|
||||||
|
.arg(date_format)
|
||||||
|
.arg("+%Y-%m-%d %S:%M")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_matches(&re);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_invalid_date_string() {
|
fn test_invalid_date_string() {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue