mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
date: implement --resolution flag (#8686)
* date: implement --resolution flag * date: add tests for --resolution flag * date: add date source option conflicts * fixup: fix type error on 32-bit platforms * fixup: fix spelling issue in test * fixup: use hardcoded resolution on redox os
This commit is contained in:
@@ -82,6 +82,8 @@ date-help-iso-8601 = output date/time in ISO 8601 format.
|
||||
'hours', 'minutes', 'seconds', or 'ns'
|
||||
for date and time to the indicated precision.
|
||||
Example: 2006-08-14T02:34:56-06:00
|
||||
date-help-resolution = output the available resolution of timestamps
|
||||
Example: 0.000000001
|
||||
date-help-rfc-email = output date and time in RFC 5322 format.
|
||||
Example: Mon, 14 Aug 2006 02:34:56 -0600
|
||||
date-help-rfc-3339 = output date/time in RFC 3339 format.
|
||||
|
||||
+72
-2
@@ -3,14 +3,16 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore strtime ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes
|
||||
// spell-checker:ignore strtime ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes getres
|
||||
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
use jiff::fmt::strtime;
|
||||
use jiff::tz::TimeZone;
|
||||
use jiff::{Timestamp, Zoned};
|
||||
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))]
|
||||
use libc::{CLOCK_REALTIME, clock_settime, timespec};
|
||||
use libc::clock_settime;
|
||||
#[cfg(all(unix, not(target_os = "redox")))]
|
||||
use libc::{CLOCK_REALTIME, clock_getres, timespec};
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::path::PathBuf;
|
||||
@@ -35,6 +37,7 @@ const OPT_FORMAT: &str = "format";
|
||||
const OPT_FILE: &str = "file";
|
||||
const OPT_DEBUG: &str = "debug";
|
||||
const OPT_ISO_8601: &str = "iso-8601";
|
||||
const OPT_RESOLUTION: &str = "resolution";
|
||||
const OPT_RFC_EMAIL: &str = "rfc-email";
|
||||
const OPT_RFC_822: &str = "rfc-822";
|
||||
const OPT_RFC_2822: &str = "rfc-2822";
|
||||
@@ -57,6 +60,7 @@ enum Format {
|
||||
Iso8601(Iso8601Format),
|
||||
Rfc5322,
|
||||
Rfc3339(Rfc3339Format),
|
||||
Resolution,
|
||||
Custom(String),
|
||||
Default,
|
||||
}
|
||||
@@ -68,6 +72,7 @@ enum DateSource {
|
||||
FileMtime(PathBuf),
|
||||
Stdin,
|
||||
Human(String),
|
||||
Resolution,
|
||||
}
|
||||
|
||||
enum Iso8601Format {
|
||||
@@ -136,6 +141,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
.map(|s| s.as_str().into())
|
||||
{
|
||||
Format::Rfc3339(fmt)
|
||||
} else if matches.get_flag(OPT_RESOLUTION) {
|
||||
Format::Resolution
|
||||
} else {
|
||||
Format::Default
|
||||
};
|
||||
@@ -149,6 +156,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
}
|
||||
} else if let Some(file) = matches.get_one::<String>(OPT_REFERENCE) {
|
||||
DateSource::FileMtime(file.into())
|
||||
} else if matches.get_flag(OPT_RESOLUTION) {
|
||||
DateSource::Resolution
|
||||
} else {
|
||||
DateSource::Now
|
||||
};
|
||||
@@ -230,6 +239,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let iter = std::iter::once(Ok(date));
|
||||
Box::new(iter)
|
||||
}
|
||||
DateSource::Resolution => {
|
||||
let resolution = get_clock_resolution();
|
||||
let date = resolution.to_zoned(TimeZone::system());
|
||||
let iter = std::iter::once(Ok(date));
|
||||
Box::new(iter)
|
||||
}
|
||||
DateSource::Now => {
|
||||
let iter = std::iter::once(Ok(now));
|
||||
Box::new(iter)
|
||||
@@ -283,6 +298,7 @@ pub fn uu_app() -> Command {
|
||||
.long(OPT_FILE)
|
||||
.value_name("DATEFILE")
|
||||
.value_hint(clap::ValueHint::FilePath)
|
||||
.conflicts_with(OPT_DATE)
|
||||
.help(translate!("date-help-file")),
|
||||
)
|
||||
.arg(
|
||||
@@ -297,6 +313,14 @@ pub fn uu_app() -> Command {
|
||||
.default_missing_value(OPT_DATE)
|
||||
.help(translate!("date-help-iso-8601")),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(OPT_RESOLUTION)
|
||||
.long(OPT_RESOLUTION)
|
||||
.conflicts_with_all([OPT_DATE, OPT_FILE])
|
||||
.overrides_with(OPT_RESOLUTION)
|
||||
.help(translate!("date-help-resolution"))
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(OPT_RFC_EMAIL)
|
||||
.short('R')
|
||||
@@ -325,6 +349,7 @@ pub fn uu_app() -> Command {
|
||||
.long(OPT_REFERENCE)
|
||||
.value_name("FILE")
|
||||
.value_hint(clap::ValueHint::AnyPath)
|
||||
.conflicts_with_all([OPT_DATE, OPT_FILE, OPT_RESOLUTION])
|
||||
.help(translate!("date-help-reference")),
|
||||
)
|
||||
.arg(
|
||||
@@ -374,6 +399,7 @@ fn make_format_string(settings: &Settings) -> &str {
|
||||
Rfc3339Format::Seconds => "%F %T%:z",
|
||||
Rfc3339Format::Ns => "%F %T.%N%:z",
|
||||
},
|
||||
Format::Resolution => "%s.%N",
|
||||
Format::Custom(ref fmt) => fmt,
|
||||
Format::Default => "%a %b %e %X %Z %Y",
|
||||
}
|
||||
@@ -398,6 +424,50 @@ fn parse_date<S: AsRef<str> + Clone>(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(unix, windows)))]
|
||||
fn get_clock_resolution() -> Timestamp {
|
||||
unimplemented!("getting clock resolution not implemented (unsupported target)");
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "redox")))]
|
||||
fn get_clock_resolution() -> Timestamp {
|
||||
let mut timespec = timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
};
|
||||
unsafe {
|
||||
// SAFETY: the timespec struct lives for the full duration of this function call.
|
||||
//
|
||||
// The clock_getres function can only fail if the passed clock_id is not
|
||||
// a known clock. All compliant posix implementors must support
|
||||
// CLOCK_REALTIME, therefore this function call cannot fail on any
|
||||
// compliant posix implementation.
|
||||
//
|
||||
// See more here:
|
||||
// https://pubs.opengroup.org/onlinepubs/9799919799/functions/clock_getres.html
|
||||
clock_getres(CLOCK_REALTIME, &raw mut timespec);
|
||||
}
|
||||
#[allow(clippy::unnecessary_cast)] // Cast required on 32-bit platforms
|
||||
Timestamp::constant(timespec.tv_sec as i64, timespec.tv_nsec as i32)
|
||||
}
|
||||
|
||||
#[cfg(all(unix, target_os = "redox"))]
|
||||
fn get_clock_resolution() -> Timestamp {
|
||||
// Redox OS does not support the posix clock_getres function, however
|
||||
// internally it uses a resolution of 1ns to represent timestamps.
|
||||
// https://gitlab.redox-os.org/redox-os/kernel/-/blob/master/src/time.rs
|
||||
Timestamp::constant(0, 1)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn get_clock_resolution() -> Timestamp {
|
||||
// Windows does not expose a system call for getting the resolution of the
|
||||
// clock, however the FILETIME struct returned by GetSystemTimeAsFileTime,
|
||||
// and GetSystemTimePreciseAsFileTime has a resolution of 100ns.
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime
|
||||
Timestamp::constant(0, 100)
|
||||
}
|
||||
|
||||
#[cfg(not(any(unix, windows)))]
|
||||
fn set_system_datetime(_date: Zoned) -> UResult<()> {
|
||||
unimplemented!("setting date not implemented (unsupported target)");
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
//
|
||||
// spell-checker: ignore: AEDT AEST EEST NZDT NZST Kolkata
|
||||
// spell-checker: ignore: AEDT AEST EEST NZDT NZST Kolkata Iseconds
|
||||
|
||||
use chrono::{DateTime, Datelike, Duration, NaiveTime, Utc}; // spell-checker:disable-line
|
||||
use regex::Regex;
|
||||
@@ -745,3 +745,36 @@ fn test_date_empty_tz_time() {
|
||||
.succeeds()
|
||||
.stdout_only("Thu Jan 1 00:00:00 UTC 1970\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_resolution() {
|
||||
// Test that --resolution flag returns a floating point number by default
|
||||
new_ucmd!()
|
||||
.arg("--resolution")
|
||||
.succeeds()
|
||||
.stdout_str_check(|s| s.trim().parse::<f64>().is_ok());
|
||||
|
||||
// Test that --resolution flag can be passed twice to match gnu
|
||||
new_ucmd!()
|
||||
.arg("--resolution")
|
||||
.arg("--resolution")
|
||||
.succeeds()
|
||||
.stdout_str_check(|s| s.trim().parse::<f64>().is_ok());
|
||||
|
||||
// Test that can --resolution output can be formatted as a date
|
||||
new_ucmd!()
|
||||
.arg("--resolution")
|
||||
.arg("-Iseconds")
|
||||
.succeeds()
|
||||
.stdout_only("1970-01-01T00:00:00+00:00\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_resolution_no_combine() {
|
||||
// Test that date fails when --resolution flag is passed with date flag
|
||||
new_ucmd!()
|
||||
.arg("--resolution")
|
||||
.arg("-d")
|
||||
.arg("2025-01-01")
|
||||
.fails();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user