Skip to content

Commit 542e476

Browse files
authored
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
1 parent e2f5dcb commit 542e476

File tree

3 files changed

+108
-3
lines changed

3 files changed

+108
-3
lines changed

src/uu/date/locales/en-US.ftl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ date-help-iso-8601 = output date/time in ISO 8601 format.
8282
'hours', 'minutes', 'seconds', or 'ns'
8383
for date and time to the indicated precision.
8484
Example: 2006-08-14T02:34:56-06:00
85+
date-help-resolution = output the available resolution of timestamps
86+
Example: 0.000000001
8587
date-help-rfc-email = output date and time in RFC 5322 format.
8688
Example: Mon, 14 Aug 2006 02:34:56 -0600
8789
date-help-rfc-3339 = output date/time in RFC 3339 format.

src/uu/date/src/date.rs

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
55

6-
// spell-checker:ignore strtime ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes
6+
// spell-checker:ignore strtime ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes getres
77

88
use clap::{Arg, ArgAction, Command};
99
use jiff::fmt::strtime;
1010
use jiff::tz::TimeZone;
1111
use jiff::{Timestamp, Zoned};
1212
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))]
13-
use libc::{CLOCK_REALTIME, clock_settime, timespec};
13+
use libc::clock_settime;
14+
#[cfg(all(unix, not(target_os = "redox")))]
15+
use libc::{CLOCK_REALTIME, clock_getres, timespec};
1416
use std::fs::File;
1517
use std::io::{BufRead, BufReader};
1618
use std::path::PathBuf;
@@ -35,6 +37,7 @@ const OPT_FORMAT: &str = "format";
3537
const OPT_FILE: &str = "file";
3638
const OPT_DEBUG: &str = "debug";
3739
const OPT_ISO_8601: &str = "iso-8601";
40+
const OPT_RESOLUTION: &str = "resolution";
3841
const OPT_RFC_EMAIL: &str = "rfc-email";
3942
const OPT_RFC_822: &str = "rfc-822";
4043
const OPT_RFC_2822: &str = "rfc-2822";
@@ -57,6 +60,7 @@ enum Format {
5760
Iso8601(Iso8601Format),
5861
Rfc5322,
5962
Rfc3339(Rfc3339Format),
63+
Resolution,
6064
Custom(String),
6165
Default,
6266
}
@@ -68,6 +72,7 @@ enum DateSource {
6872
FileMtime(PathBuf),
6973
Stdin,
7074
Human(String),
75+
Resolution,
7176
}
7277

7378
enum Iso8601Format {
@@ -136,6 +141,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
136141
.map(|s| s.as_str().into())
137142
{
138143
Format::Rfc3339(fmt)
144+
} else if matches.get_flag(OPT_RESOLUTION) {
145+
Format::Resolution
139146
} else {
140147
Format::Default
141148
};
@@ -149,6 +156,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
149156
}
150157
} else if let Some(file) = matches.get_one::<String>(OPT_REFERENCE) {
151158
DateSource::FileMtime(file.into())
159+
} else if matches.get_flag(OPT_RESOLUTION) {
160+
DateSource::Resolution
152161
} else {
153162
DateSource::Now
154163
};
@@ -230,6 +239,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
230239
let iter = std::iter::once(Ok(date));
231240
Box::new(iter)
232241
}
242+
DateSource::Resolution => {
243+
let resolution = get_clock_resolution();
244+
let date = resolution.to_zoned(TimeZone::system());
245+
let iter = std::iter::once(Ok(date));
246+
Box::new(iter)
247+
}
233248
DateSource::Now => {
234249
let iter = std::iter::once(Ok(now));
235250
Box::new(iter)
@@ -283,6 +298,7 @@ pub fn uu_app() -> Command {
283298
.long(OPT_FILE)
284299
.value_name("DATEFILE")
285300
.value_hint(clap::ValueHint::FilePath)
301+
.conflicts_with(OPT_DATE)
286302
.help(translate!("date-help-file")),
287303
)
288304
.arg(
@@ -297,6 +313,14 @@ pub fn uu_app() -> Command {
297313
.default_missing_value(OPT_DATE)
298314
.help(translate!("date-help-iso-8601")),
299315
)
316+
.arg(
317+
Arg::new(OPT_RESOLUTION)
318+
.long(OPT_RESOLUTION)
319+
.conflicts_with_all([OPT_DATE, OPT_FILE])
320+
.overrides_with(OPT_RESOLUTION)
321+
.help(translate!("date-help-resolution"))
322+
.action(ArgAction::SetTrue),
323+
)
300324
.arg(
301325
Arg::new(OPT_RFC_EMAIL)
302326
.short('R')
@@ -325,6 +349,7 @@ pub fn uu_app() -> Command {
325349
.long(OPT_REFERENCE)
326350
.value_name("FILE")
327351
.value_hint(clap::ValueHint::AnyPath)
352+
.conflicts_with_all([OPT_DATE, OPT_FILE, OPT_RESOLUTION])
328353
.help(translate!("date-help-reference")),
329354
)
330355
.arg(
@@ -374,6 +399,7 @@ fn make_format_string(settings: &Settings) -> &str {
374399
Rfc3339Format::Seconds => "%F %T%:z",
375400
Rfc3339Format::Ns => "%F %T.%N%:z",
376401
},
402+
Format::Resolution => "%s.%N",
377403
Format::Custom(ref fmt) => fmt,
378404
Format::Default => "%a %b %e %X %Z %Y",
379405
}
@@ -398,6 +424,50 @@ fn parse_date<S: AsRef<str> + Clone>(
398424
}
399425
}
400426

427+
#[cfg(not(any(unix, windows)))]
428+
fn get_clock_resolution() -> Timestamp {
429+
unimplemented!("getting clock resolution not implemented (unsupported target)");
430+
}
431+
432+
#[cfg(all(unix, not(target_os = "redox")))]
433+
fn get_clock_resolution() -> Timestamp {
434+
let mut timespec = timespec {
435+
tv_sec: 0,
436+
tv_nsec: 0,
437+
};
438+
unsafe {
439+
// SAFETY: the timespec struct lives for the full duration of this function call.
440+
//
441+
// The clock_getres function can only fail if the passed clock_id is not
442+
// a known clock. All compliant posix implementors must support
443+
// CLOCK_REALTIME, therefore this function call cannot fail on any
444+
// compliant posix implementation.
445+
//
446+
// See more here:
447+
// https://pubs.opengroup.org/onlinepubs/9799919799/functions/clock_getres.html
448+
clock_getres(CLOCK_REALTIME, &raw mut timespec);
449+
}
450+
#[allow(clippy::unnecessary_cast)] // Cast required on 32-bit platforms
451+
Timestamp::constant(timespec.tv_sec as i64, timespec.tv_nsec as i32)
452+
}
453+
454+
#[cfg(all(unix, target_os = "redox"))]
455+
fn get_clock_resolution() -> Timestamp {
456+
// Redox OS does not support the posix clock_getres function, however
457+
// internally it uses a resolution of 1ns to represent timestamps.
458+
// https://gitlab.redox-os.org/redox-os/kernel/-/blob/master/src/time.rs
459+
Timestamp::constant(0, 1)
460+
}
461+
462+
#[cfg(windows)]
463+
fn get_clock_resolution() -> Timestamp {
464+
// Windows does not expose a system call for getting the resolution of the
465+
// clock, however the FILETIME struct returned by GetSystemTimeAsFileTime,
466+
// and GetSystemTimePreciseAsFileTime has a resolution of 100ns.
467+
// https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime
468+
Timestamp::constant(0, 100)
469+
}
470+
401471
#[cfg(not(any(unix, windows)))]
402472
fn set_system_datetime(_date: Zoned) -> UResult<()> {
403473
unimplemented!("setting date not implemented (unsupported target)");

tests/by-util/test_date.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
55
//
6-
// spell-checker: ignore: AEDT AEST EEST NZDT NZST Kolkata
6+
// spell-checker: ignore: AEDT AEST EEST NZDT NZST Kolkata Iseconds
77

88
use chrono::{DateTime, Datelike, Duration, NaiveTime, Utc}; // spell-checker:disable-line
99
use regex::Regex;
@@ -745,3 +745,36 @@ fn test_date_empty_tz_time() {
745745
.succeeds()
746746
.stdout_only("Thu Jan 1 00:00:00 UTC 1970\n");
747747
}
748+
749+
#[test]
750+
fn test_date_resolution() {
751+
// Test that --resolution flag returns a floating point number by default
752+
new_ucmd!()
753+
.arg("--resolution")
754+
.succeeds()
755+
.stdout_str_check(|s| s.trim().parse::<f64>().is_ok());
756+
757+
// Test that --resolution flag can be passed twice to match gnu
758+
new_ucmd!()
759+
.arg("--resolution")
760+
.arg("--resolution")
761+
.succeeds()
762+
.stdout_str_check(|s| s.trim().parse::<f64>().is_ok());
763+
764+
// Test that can --resolution output can be formatted as a date
765+
new_ucmd!()
766+
.arg("--resolution")
767+
.arg("-Iseconds")
768+
.succeeds()
769+
.stdout_only("1970-01-01T00:00:00+00:00\n");
770+
}
771+
772+
#[test]
773+
fn test_date_resolution_no_combine() {
774+
// Test that date fails when --resolution flag is passed with date flag
775+
new_ucmd!()
776+
.arg("--resolution")
777+
.arg("-d")
778+
.arg("2025-01-01")
779+
.fails();
780+
}

0 commit comments

Comments
 (0)