mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
date: Implement setting the date on Unix & Windows (#1798)
* date: implement set date for unix and windows Parsing the date string is not fully implemented yet, as in it relies on the internals of chrono - things like "Mon, 14 Aug 2006 02:34:56 -0600" do not work, nor does "2006-08-14 02:34:56" (no TZ / local time). This is no different to using the "--date" option however, and will get fixed when `parse_date` is a bit smarter. Only supports unix and Windows platforms for now.
This commit is contained in:
parent
fcccc2a973
commit
5ec87dc70a
4 changed files with 157 additions and 19 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1475,8 +1475,10 @@ version = "0.0.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
"chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"uucore 0.0.7",
|
"uucore 0.0.7",
|
||||||
"uucore_procs 0.0.5",
|
"uucore_procs 0.0.5",
|
||||||
|
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -20,6 +20,12 @@ clap = "2.33"
|
||||||
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
|
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
|
||||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
libc = "0.2"
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
winapi = { version = "0.3", features = ["minwinbase", "sysinfoapi", "minwindef"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "date"
|
name = "date"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
|
@ -12,13 +12,20 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate uucore;
|
extern crate uucore;
|
||||||
|
|
||||||
|
use chrono::{DateTime, FixedOffset, Local, Offset, Utc};
|
||||||
|
#[cfg(windows)]
|
||||||
|
use chrono::{Datelike, Timelike};
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
|
#[cfg(all(unix, not(target_os = "macos")))]
|
||||||
use chrono::offset::Utc;
|
use libc::{clock_settime, timespec, CLOCK_REALTIME};
|
||||||
use chrono::{DateTime, FixedOffset, Local, Offset};
|
|
||||||
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;
|
||||||
|
#[cfg(windows)]
|
||||||
|
use winapi::{
|
||||||
|
shared::minwindef::WORD,
|
||||||
|
um::{minwinbase::SYSTEMTIME, sysinfoapi::SetSystemTime},
|
||||||
|
};
|
||||||
|
|
||||||
// Options
|
// Options
|
||||||
const DATE: &str = "date";
|
const DATE: &str = "date";
|
||||||
|
@ -62,6 +69,11 @@ static RFC_3339_HELP_STRING: &str = "output date/time in RFC 3339 format.
|
||||||
for date and time to the indicated precision.
|
for date and time to the indicated precision.
|
||||||
Example: 2006-08-14 02:34:56-06:00";
|
Example: 2006-08-14 02:34:56-06:00";
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
static OPT_SET_HELP_STRING: &str = "set time described by STRING";
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
static OPT_SET_HELP_STRING: &str = "set time described by STRING (not available on mac yet)";
|
||||||
|
|
||||||
/// Settings for this program, parsed from the command line
|
/// Settings for this program, parsed from the command line
|
||||||
struct Settings {
|
struct Settings {
|
||||||
utc: bool,
|
utc: bool,
|
||||||
|
@ -186,7 +198,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
.short("s")
|
.short("s")
|
||||||
.long(OPT_SET)
|
.long(OPT_SET)
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.help("set time described by STRING"),
|
.help(OPT_SET_HELP_STRING),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_UNIVERSAL)
|
Arg::with_name(OPT_UNIVERSAL)
|
||||||
|
@ -222,18 +234,31 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
DateSource::Now
|
DateSource::Now
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let set_to = match matches.value_of(OPT_SET).map(parse_date) {
|
||||||
|
None => None,
|
||||||
|
Some(Err((input, _err))) => {
|
||||||
|
eprintln!("date: invalid date '{}'", input);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
Some(Ok(date)) => Some(date),
|
||||||
|
};
|
||||||
|
|
||||||
let settings = Settings {
|
let settings = Settings {
|
||||||
utc: matches.is_present(OPT_UNIVERSAL),
|
utc: matches.is_present(OPT_UNIVERSAL),
|
||||||
format,
|
format,
|
||||||
date_source,
|
date_source,
|
||||||
// TODO: Handle this option:
|
set_to,
|
||||||
set_to: None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(_time) = settings.set_to {
|
if let Some(date) = settings.set_to {
|
||||||
unimplemented!();
|
// All set time functions expect UTC datetimes.
|
||||||
// Probably need to use this syscall:
|
let date: DateTime<Utc> = if settings.utc {
|
||||||
// https://doc.rust-lang.org/libc/i686-unknown-linux-gnu/libc/fn.clock_settime.html
|
date.with_timezone(&Utc)
|
||||||
|
} else {
|
||||||
|
date.into()
|
||||||
|
};
|
||||||
|
|
||||||
|
return set_system_datetime(date);
|
||||||
} else {
|
} else {
|
||||||
// Declare a file here because it needs to outlive the `dates` iterator.
|
// Declare a file here because it needs to outlive the `dates` iterator.
|
||||||
let file: File;
|
let file: File;
|
||||||
|
@ -247,15 +272,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
now.with_timezone(now.offset())
|
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.
|
// Iterate over all dates - whether it's a single date or a file.
|
||||||
let dates: Box<dyn Iterator<Item = _>> = match settings.date_source {
|
let dates: Box<dyn Iterator<Item = _>> = match settings.date_source {
|
||||||
DateSource::Custom(ref input) => {
|
DateSource::Custom(ref input) => {
|
||||||
|
@ -314,3 +330,76 @@ fn make_format_string(settings: &Settings) -> &str {
|
||||||
Format::Default => "%c",
|
Format::Default => "%c",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a `String` into a `DateTime`.
|
||||||
|
/// If it fails, return a tuple of the `String` along with its `ParseError`.
|
||||||
|
fn parse_date<S: AsRef<str> + Clone>(
|
||||||
|
s: S,
|
||||||
|
) -> Result<DateTime<FixedOffset>, (String, chrono::format::ParseError)> {
|
||||||
|
// TODO: The GNU date command can parse a wide variety of inputs.
|
||||||
|
s.as_ref().parse().map_err(|e| (s.as_ref().into(), e))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(unix, windows)))]
|
||||||
|
fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
|
||||||
|
unimplemented!("setting date not implemented (unsupported target)");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
|
||||||
|
eprintln!("date: setting the date is not supported by macOS");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(unix, not(target_os = "macos")))]
|
||||||
|
/// System call to set date (unix).
|
||||||
|
/// See here for more:
|
||||||
|
/// https://doc.rust-lang.org/libc/i686-unknown-linux-gnu/libc/fn.clock_settime.html
|
||||||
|
/// https://linux.die.net/man/3/clock_settime
|
||||||
|
/// https://www.gnu.org/software/libc/manual/html_node/Time-Types.html
|
||||||
|
fn set_system_datetime(date: DateTime<Utc>) -> i32 {
|
||||||
|
let timespec = timespec {
|
||||||
|
tv_sec: date.timestamp() as _,
|
||||||
|
tv_nsec: date.timestamp_subsec_nanos() as _,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = unsafe { clock_settime(CLOCK_REALTIME, ×pec) };
|
||||||
|
|
||||||
|
if result != 0 {
|
||||||
|
let error = std::io::Error::last_os_error();
|
||||||
|
eprintln!("date: cannot set date: {}", error);
|
||||||
|
error.raw_os_error().unwrap()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
/// System call to set date (Windows).
|
||||||
|
/// See here for more:
|
||||||
|
/// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-setsystemtime
|
||||||
|
/// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-systemtime
|
||||||
|
fn set_system_datetime(date: DateTime<Utc>) -> i32 {
|
||||||
|
let system_time = SYSTEMTIME {
|
||||||
|
wYear: date.year() as WORD,
|
||||||
|
wMonth: date.month() as WORD,
|
||||||
|
// Ignored
|
||||||
|
wDayOfWeek: 0,
|
||||||
|
wDay: date.day() as WORD,
|
||||||
|
wHour: date.hour() as WORD,
|
||||||
|
wMinute: date.minute() as WORD,
|
||||||
|
wSecond: date.second() as WORD,
|
||||||
|
// TODO: be careful of leap seconds - valid range is [0, 999] - how to handle?
|
||||||
|
wMilliseconds: ((date.nanosecond() / 1_000_000) % 1000) as WORD,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = unsafe { SetSystemTime(&system_time) };
|
||||||
|
|
||||||
|
if result == 0 {
|
||||||
|
let error = std::io::Error::last_os_error();
|
||||||
|
eprintln!("date: cannot set date: {}", error);
|
||||||
|
error.raw_os_error().unwrap()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ extern crate regex;
|
||||||
|
|
||||||
use self::regex::Regex;
|
use self::regex::Regex;
|
||||||
use crate::common::util::*;
|
use crate::common::util::*;
|
||||||
|
#[cfg(all(unix, not(target_os = "macos")))]
|
||||||
|
use rust_users::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_date_email() {
|
fn test_date_email() {
|
||||||
|
@ -131,3 +133,42 @@ fn test_date_format_full_day() {
|
||||||
let re = Regex::new(r"\S+ \d{4}-\d{2}-\d{2}").unwrap();
|
let re = Regex::new(r"\S+ \d{4}-\d{2}-\d{2}").unwrap();
|
||||||
assert!(re.is_match(&result.stdout.trim()));
|
assert!(re.is_match(&result.stdout.trim()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(all(unix, not(target_os = "macos")))]
|
||||||
|
fn test_date_set_valid() {
|
||||||
|
if get_effective_uid() == 0 {
|
||||||
|
let (_, mut ucmd) = at_and_ucmd!();
|
||||||
|
let result = ucmd.arg("--set").arg("2020-03-12 13:30:00+08:00").succeeds();
|
||||||
|
result.no_stdout().no_stderr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(any(windows, all(unix, not(target_os = "macos"))))]
|
||||||
|
fn test_date_set_invalid() {
|
||||||
|
let (_, mut ucmd) = at_and_ucmd!();
|
||||||
|
let result = ucmd.arg("--set").arg("123abcd").fails();
|
||||||
|
let result = result.no_stdout();
|
||||||
|
assert!(result.stderr.starts_with("date: invalid date "));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(all(unix, not(target_os = "macos")))]
|
||||||
|
fn test_date_set_permissions_error() {
|
||||||
|
if !(get_effective_uid() == 0 || is_wsl()) {
|
||||||
|
let (_, mut ucmd) = at_and_ucmd!();
|
||||||
|
let result = ucmd.arg("--set").arg("2020-03-11 21:45:00+08:00").fails();
|
||||||
|
let result = result.no_stdout();
|
||||||
|
assert!(result.stderr.starts_with("date: cannot set date: "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn test_date_set_mac_unavailable() {
|
||||||
|
let (_, mut ucmd) = at_and_ucmd!();
|
||||||
|
let result = ucmd.arg("--set").arg("2020-03-11 21:45:00+08:00").fails();
|
||||||
|
let result = result.no_stdout();
|
||||||
|
assert!(result.stderr.starts_with("date: setting the date is not supported by macOS"));
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue