mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
refactor: use jiff for safer time features
This commit is contained in:
Generated
+1
-4
@@ -537,7 +537,6 @@ name = "coreutils"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"chrono",
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"clap_mangen",
|
||||
@@ -4011,7 +4010,6 @@ dependencies = [
|
||||
name = "uu_touch"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"filetime",
|
||||
"fluent",
|
||||
@@ -4122,9 +4120,9 @@ dependencies = [
|
||||
name = "uu_uptime"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"fluent",
|
||||
"jiff",
|
||||
"thiserror 2.0.17",
|
||||
"utmp-classic",
|
||||
"uucore",
|
||||
@@ -4204,7 +4202,6 @@ dependencies = [
|
||||
"blake2b_simd",
|
||||
"blake3",
|
||||
"bstr",
|
||||
"chrono",
|
||||
"clap",
|
||||
"codspeed-divan-compat",
|
||||
"crc-fast",
|
||||
|
||||
+1
-11
@@ -311,11 +311,6 @@ binary-heap-plus = "0.5.0"
|
||||
bstr = "1.9.1"
|
||||
bytecount = "0.6.8"
|
||||
byteorder = "1.5.0"
|
||||
chrono = { version = "0.4.41", default-features = false, features = [
|
||||
"std",
|
||||
"alloc",
|
||||
"clock",
|
||||
] }
|
||||
clap = { version = "4.5", features = ["wrap_help", "cargo", "color"] }
|
||||
clap_complete = "4.4"
|
||||
clap_mangen = "0.2"
|
||||
@@ -341,11 +336,7 @@ icu_locale = "2.0.0"
|
||||
icu_provider = "2.0.0"
|
||||
indicatif = "0.18.0"
|
||||
itertools = "0.14.0"
|
||||
jiff = { version = "0.2.10", default-features = false, features = [
|
||||
"std",
|
||||
"alloc",
|
||||
"tz-system",
|
||||
] }
|
||||
jiff = "0.2.18"
|
||||
libc = "0.2.172"
|
||||
linux-raw-sys = "0.12"
|
||||
lscolors = { version = "0.21.0", default-features = false, features = [
|
||||
@@ -541,7 +532,6 @@ yes = { optional = true, version = "0.5.0", package = "uu_yes", path = "src/uu/y
|
||||
#pin_cc = { version="1.0.61, < 1.0.62", package="cc" } ## cc v1.0.62 has compiler errors for MinRustV v1.32.0, requires 1.34 (for `std::str::split_ascii_whitespace()`)
|
||||
|
||||
[dev-dependencies]
|
||||
chrono.workspace = true
|
||||
ctor.workspace = true
|
||||
filetime.workspace = true
|
||||
glob.workspace = true
|
||||
|
||||
Generated
+11
-11
@@ -53,7 +53,7 @@ version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -64,7 +64,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -504,7 +504,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -824,9 +824,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.16"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35"
|
||||
checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50"
|
||||
dependencies = [
|
||||
"jiff-static",
|
||||
"jiff-tzdb-platform",
|
||||
@@ -834,14 +834,14 @@ dependencies = [
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde_core",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.16"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69"
|
||||
checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1281,7 +1281,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1447,7 +1447,7 @@ dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1902,7 +1902,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -21,8 +21,7 @@ path = "src/touch.rs"
|
||||
[dependencies]
|
||||
filetime = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
jiff = "0.2.15"
|
||||
jiff = { workspace = true }
|
||||
parse_datetime = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
uucore = { workspace = true, features = ["libc", "parser"] }
|
||||
|
||||
@@ -16,7 +16,7 @@ pub enum TouchError {
|
||||
#[error("{}", translate!("touch-error-unable-to-parse-date", "date" => .0.clone()))]
|
||||
InvalidDateFormat(String),
|
||||
|
||||
/// The source time couldn't be converted to a [`chrono::DateTime`]
|
||||
/// The source time couldn't be converted to a [`jiff::Zoned`]
|
||||
#[error("{}", translate!("touch-error-invalid-filetime", "time" => .0))]
|
||||
InvalidFiletime(FileTime),
|
||||
|
||||
|
||||
+61
-91
@@ -3,24 +3,24 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (ToDO) filetime datetime lpszfilepath mktime DATETIME datelike timelike UTIME
|
||||
// spell-checker:ignore (ToDO) datelike datetime filetime lpszfilepath mktime strtime timelike utime
|
||||
// spell-checker:ignore (FORMATS) MMDDhhmm YYYYMMDDHHMM YYMMDDHHMM YYYYMMDDHHMMS
|
||||
|
||||
pub mod error;
|
||||
|
||||
use chrono::{
|
||||
DateTime, Datelike, Duration, Local, LocalResult, NaiveDate, NaiveDateTime, NaiveTime,
|
||||
TimeZone, Timelike,
|
||||
};
|
||||
use clap::builder::{PossibleValue, ValueParser};
|
||||
use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command};
|
||||
use filetime::{FileTime, set_file_times, set_symlink_file_times};
|
||||
use jiff::{Timestamp, Zoned};
|
||||
use jiff::civil::Time;
|
||||
use jiff::fmt::strtime;
|
||||
use jiff::tz::TimeZone;
|
||||
use jiff::{Timestamp, ToSpan, Zoned};
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fs::{self, File};
|
||||
use std::io::{Error, ErrorKind};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::SystemTime;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{FromIo, UResult, USimpleError};
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -125,16 +125,13 @@ mod format {
|
||||
pub(crate) const YYYYMMDDHHMM_OFFSET: &str = "%Y-%m-%d %H:%M %z";
|
||||
}
|
||||
|
||||
/// Convert a [`DateTime`] with a TZ offset into a [`FileTime`]
|
||||
///
|
||||
/// The [`DateTime`] is converted into a unix timestamp from which the [`FileTime`] is
|
||||
/// constructed.
|
||||
fn datetime_to_filetime<T: TimeZone>(dt: &DateTime<T>) -> FileTime {
|
||||
FileTime::from_unix_time(dt.timestamp(), dt.timestamp_subsec_nanos())
|
||||
fn timestamp_to_filetime(ts: Timestamp) -> FileTime {
|
||||
FileTime::from_system_time(SystemTime::from(ts))
|
||||
}
|
||||
|
||||
fn filetime_to_datetime(ft: &FileTime) -> Option<DateTime<Local>> {
|
||||
Some(DateTime::from_timestamp(ft.unix_seconds(), ft.nanoseconds())?.into())
|
||||
fn filetime_to_zoned(ft: &FileTime) -> Option<Zoned> {
|
||||
let ts = Timestamp::new(ft.unix_seconds(), ft.nanoseconds() as i32).ok()?;
|
||||
Some(Zoned::new(ts, TimeZone::system()))
|
||||
}
|
||||
|
||||
/// Whether all characters in the string are digits.
|
||||
@@ -385,12 +382,12 @@ pub fn touch(files: &[InputFile], opts: &Options) -> Result<(), TouchError> {
|
||||
if opts.date.is_none() {
|
||||
now = FileTime::from_unix_time(0, libc::UTIME_NOW as u32);
|
||||
} else {
|
||||
now = datetime_to_filetime(&Local::now());
|
||||
now = timestamp_to_filetime(Timestamp::now());
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
now = datetime_to_filetime(&Local::now());
|
||||
now = timestamp_to_filetime(Timestamp::now());
|
||||
}
|
||||
(now, now)
|
||||
}
|
||||
@@ -400,11 +397,11 @@ pub fn touch(files: &[InputFile], opts: &Options) -> Result<(), TouchError> {
|
||||
let (atime, mtime) = if let Some(date) = &opts.date {
|
||||
(
|
||||
parse_date(
|
||||
filetime_to_datetime(&atime).ok_or_else(|| TouchError::InvalidFiletime(atime))?,
|
||||
filetime_to_zoned(&atime).ok_or_else(|| TouchError::InvalidFiletime(atime))?,
|
||||
date,
|
||||
)?,
|
||||
parse_date(
|
||||
filetime_to_datetime(&mtime).ok_or_else(|| TouchError::InvalidFiletime(mtime))?,
|
||||
filetime_to_zoned(&mtime).ok_or_else(|| TouchError::InvalidFiletime(mtime))?,
|
||||
date,
|
||||
)?,
|
||||
)
|
||||
@@ -610,7 +607,7 @@ fn stat(path: &Path, follow: bool) -> std::io::Result<(FileTime, FileTime)> {
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_date(ref_time: DateTime<Local>, s: &str) -> Result<FileTime, TouchError> {
|
||||
fn parse_date(ref_zoned: Zoned, s: &str) -> Result<FileTime, TouchError> {
|
||||
// This isn't actually compatible with GNU touch, but there doesn't seem to
|
||||
// be any simple specification for what format this parameter allows and I'm
|
||||
// not about to implement GNU parse_datetime.
|
||||
@@ -625,8 +622,11 @@ fn parse_date(ref_time: DateTime<Local>, s: &str) -> Result<FileTime, TouchError
|
||||
// Tue Dec 3 ...
|
||||
// ("%c", POSIX_LOCALE_FORMAT),
|
||||
//
|
||||
if let Ok(parsed) = NaiveDateTime::parse_from_str(s, format::POSIX_LOCALE) {
|
||||
return Ok(datetime_to_filetime(&parsed.and_utc()));
|
||||
if let Ok(parsed) = strtime::parse(format::POSIX_LOCALE, s)
|
||||
.and_then(|tm| tm.to_datetime())
|
||||
.and_then(|dt| TimeZone::UTC.to_zoned(dt))
|
||||
{
|
||||
return Ok(timestamp_to_filetime(parsed.timestamp()));
|
||||
}
|
||||
|
||||
// Also support other formats found in the GNU tests like
|
||||
@@ -638,18 +638,26 @@ fn parse_date(ref_time: DateTime<Local>, s: &str) -> Result<FileTime, TouchError
|
||||
format::YYYY_MM_DD_HH_MM,
|
||||
format::YYYYMMDDHHMM_OFFSET,
|
||||
] {
|
||||
if let Ok(parsed) = NaiveDateTime::parse_from_str(s, fmt) {
|
||||
return Ok(datetime_to_filetime(&parsed.and_utc()));
|
||||
if let Ok(parsed) = strtime::parse(fmt, s)
|
||||
.and_then(|tm| tm.to_datetime())
|
||||
.and_then(|dt| TimeZone::UTC.to_zoned(dt))
|
||||
{
|
||||
return Ok(timestamp_to_filetime(parsed.timestamp()));
|
||||
}
|
||||
}
|
||||
|
||||
// "Equivalent to %Y-%m-%d (the ISO 8601 date format). (C99)"
|
||||
// ("%F", ISO_8601_FORMAT),
|
||||
if let Ok(parsed_date) = NaiveDate::parse_from_str(s, format::ISO_8601) {
|
||||
let parsed = Local
|
||||
.from_local_datetime(&parsed_date.and_time(NaiveTime::MIN))
|
||||
.unwrap();
|
||||
return Ok(datetime_to_filetime(&parsed));
|
||||
if let Ok(filetime) = strtime::parse(format::ISO_8601, s)
|
||||
.and_then(|tm| tm.to_date())
|
||||
.and_then(|date| {
|
||||
TimeZone::system()
|
||||
.to_ambiguous_zoned(date.to_datetime(Time::midnight()))
|
||||
.unambiguous()
|
||||
})
|
||||
.map(|zdt| timestamp_to_filetime(zdt.timestamp()))
|
||||
{
|
||||
return Ok(filetime);
|
||||
}
|
||||
|
||||
// "@%s" is "The number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC). (TZ) (Calculated from mktime(tm).)"
|
||||
@@ -659,42 +667,8 @@ fn parse_date(ref_time: DateTime<Local>, s: &str) -> Result<FileTime, TouchError
|
||||
}
|
||||
}
|
||||
|
||||
// **parse_datetime 0.13 API change:**
|
||||
// The parse_datetime crate was updated from 0.11 to 0.13 in commit 2a69918ca to fix
|
||||
// issue #8754 (large second values like "12345.123456789 seconds ago" failing).
|
||||
// This introduced a breaking API change in parse_datetime_at_date:
|
||||
//
|
||||
// Previously (0.11): parse_datetime_at_date(chrono::DateTime) → chrono::DateTime
|
||||
// Now (0.13): parse_datetime_at_date(jiff::Zoned) → jiff::Zoned
|
||||
//
|
||||
// Commit 4340913c4 initially adapted to this by switching from parse_datetime_at_date
|
||||
// to parse_datetime, which broke deterministic relative date parsing (the ref_time
|
||||
// parameter was no longer used, causing tests/touch/relative to fail in CI).
|
||||
//
|
||||
// This implementation restores parse_datetime_at_date usage with proper conversions:
|
||||
// chrono::DateTime → jiff::Zoned → parse_datetime_at_date → jiff::Zoned → chrono::DateTime
|
||||
//
|
||||
// The use of parse_datetime_at_date (not parse_datetime) is critical for deterministic
|
||||
// behavior with relative dates like "yesterday" or "2 days ago", which must be
|
||||
// calculated relative to ref_time, not the current system time.
|
||||
|
||||
// Convert chrono DateTime to jiff Zoned for parse_datetime_at_date
|
||||
let ref_zoned = {
|
||||
let ts = Timestamp::new(
|
||||
ref_time.timestamp(),
|
||||
ref_time.timestamp_subsec_nanos() as i32,
|
||||
)
|
||||
.map_err(|_| TouchError::InvalidDateFormat(s.to_owned()))?;
|
||||
Zoned::new(ts, jiff::tz::TimeZone::system())
|
||||
};
|
||||
|
||||
if let Ok(zoned) = parse_datetime::parse_datetime_at_date(ref_zoned, s) {
|
||||
let timestamp = zoned.timestamp();
|
||||
let dt =
|
||||
DateTime::from_timestamp(timestamp.as_second(), timestamp.subsec_nanosecond() as u32)
|
||||
.map(|dt| dt.with_timezone(&Local))
|
||||
.ok_or_else(|| TouchError::InvalidDateFormat(s.to_owned()))?;
|
||||
return Ok(datetime_to_filetime(&dt));
|
||||
return Ok(timestamp_to_filetime(zoned.timestamp()));
|
||||
}
|
||||
|
||||
Err(TouchError::InvalidDateFormat(s.to_owned()))
|
||||
@@ -730,7 +704,7 @@ fn prepend_century(s: &str) -> UResult<String> {
|
||||
fn parse_timestamp(s: &str) -> UResult<FileTime> {
|
||||
use format::*;
|
||||
|
||||
let current_year = || Local::now().year();
|
||||
let current_year = || Timestamp::now().to_zoned(TimeZone::system()).year();
|
||||
|
||||
let (format, ts) = match s.chars().count() {
|
||||
15 => (YYYYMMDDHHMM_DOT_SS, s.to_owned()),
|
||||
@@ -748,41 +722,37 @@ fn parse_timestamp(s: &str) -> UResult<FileTime> {
|
||||
}
|
||||
};
|
||||
|
||||
let local = NaiveDateTime::parse_from_str(&ts, format).map_err(|_| {
|
||||
USimpleError::new(
|
||||
1,
|
||||
translate!("touch-error-invalid-date-ts-format", "date" => ts.quote()),
|
||||
)
|
||||
})?;
|
||||
let LocalResult::Single(mut local) = Local.from_local_datetime(&local) else {
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
translate!("touch-error-invalid-date-ts-format", "date" => ts.quote()),
|
||||
));
|
||||
};
|
||||
let mut dt = strtime::parse(format, &ts)
|
||||
.and_then(|parsed| parsed.to_datetime())
|
||||
.map_err(|_| {
|
||||
USimpleError::new(
|
||||
1,
|
||||
translate!("touch-error-invalid-date-ts-format", "date" => ts.quote()),
|
||||
)
|
||||
})?;
|
||||
|
||||
// Chrono caps seconds at 59, but 60 is valid. It might be a leap second
|
||||
// Jiff caps seconds at 59, but 60 is valid. It might be a leap second
|
||||
// or wrap to the next minute. But that doesn't really matter, because we
|
||||
// only care about the timestamp anyway.
|
||||
// Tested in gnu/tests/touch/60-seconds
|
||||
if local.second() == 59 && ts.ends_with(".60") {
|
||||
local += Duration::try_seconds(1).unwrap();
|
||||
if dt.second() == 59 && ts.ends_with(".60") {
|
||||
dt += 1.second();
|
||||
}
|
||||
|
||||
// Due to daylight saving time switch, local time can jump from 1:59 AM to
|
||||
// 3:00 AM, in which case any time between 2:00 AM and 2:59 AM is not
|
||||
// valid. If we are within this jump, chrono takes the offset from before
|
||||
// the jump. If we then jump forward an hour, we get the new corrected
|
||||
// offset. Jumping back will then now correctly take the jump into account.
|
||||
let local2 = local + Duration::try_hours(1).unwrap() - Duration::try_hours(1).unwrap();
|
||||
if local.hour() != local2.hour() {
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
translate!("touch-error-invalid-date-format", "date" => s.quote()),
|
||||
));
|
||||
}
|
||||
// 3:00 AM, in which case any time between 2:00 AM and 2:59 AM is not valid.
|
||||
// Jiff's `to_ambiguous_zoned(...).unambiguous()` handles this case.
|
||||
let local = TimeZone::system()
|
||||
.to_ambiguous_zoned(dt)
|
||||
.unambiguous()
|
||||
.map_err(|_| {
|
||||
USimpleError::new(
|
||||
1,
|
||||
translate!("touch-error-invalid-date-ts-format", "date" => ts.quote()),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(datetime_to_filetime(&local))
|
||||
Ok(timestamp_to_filetime(local.timestamp()))
|
||||
}
|
||||
|
||||
// TODO: this may be a good candidate to put in fsext.rs
|
||||
|
||||
@@ -23,11 +23,11 @@ feat_systemd_logind = ["uucore/feat_systemd_logind"]
|
||||
path = "src/uptime.rs"
|
||||
|
||||
[dependencies]
|
||||
chrono = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
uucore = { workspace = true, features = ["libc", "utmpx", "uptime"] }
|
||||
fluent = { workspace = true }
|
||||
jiff = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "openbsd")'.dependencies]
|
||||
utmp-classic = { workspace = true }
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
|
||||
// spell-checker:ignore getloadavg behaviour loadavg uptime upsecs updays upmins uphours boottime nusers utmpxname gettime clockid couldnt
|
||||
|
||||
use chrono::{Local, TimeZone, Utc};
|
||||
use jiff::tz::TimeZone;
|
||||
use jiff::{Timestamp, ToSpan};
|
||||
#[cfg(unix)]
|
||||
use std::ffi::OsString;
|
||||
use std::io;
|
||||
@@ -196,10 +197,8 @@ fn uptime_since() -> UResult<()> {
|
||||
#[cfg(any(windows, target_os = "openbsd"))]
|
||||
let uptime = get_uptime(None)?;
|
||||
|
||||
let since_date = Local
|
||||
.timestamp_opt(Utc::now().timestamp() - uptime, 0)
|
||||
.unwrap();
|
||||
println!("{}", since_date.format("%Y-%m-%d %H:%M:%S"));
|
||||
let since_date = (Timestamp::now() - uptime.seconds()).to_zoned(TimeZone::system());
|
||||
println!("{}", since_date.strftime("%Y-%m-%d %H:%M:%S"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ path = "src/lib/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
bstr = { workspace = true, optional = true }
|
||||
chrono = { workspace = true, optional = true }
|
||||
clap = { workspace = true }
|
||||
uucore_procs = { workspace = true }
|
||||
unit-prefix = { workspace = true, optional = true }
|
||||
@@ -185,5 +184,5 @@ version-cmp = []
|
||||
wide = []
|
||||
tty = []
|
||||
time = ["jiff"]
|
||||
uptime = ["chrono", "libc", "windows-sys", "utmpx", "utmp-classic"]
|
||||
uptime = ["jiff", "libc", "windows-sys", "utmpx", "utmp-classic"]
|
||||
benchmark = ["divan", "tempfile"]
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
|
||||
use crate::error::{UError, UResult};
|
||||
use crate::translate;
|
||||
use chrono::Local;
|
||||
use jiff::Timestamp;
|
||||
use jiff::tz::TimeZone;
|
||||
use libc::time_t;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -38,7 +39,10 @@ impl UError for UptimeError {
|
||||
|
||||
/// Returns the formatted time string, e.g. "12:34:56"
|
||||
pub fn get_formatted_time() -> String {
|
||||
Local::now().time().format("%H:%M:%S").to_string()
|
||||
Timestamp::now()
|
||||
.to_zoned(TimeZone::system())
|
||||
.strftime("%H:%M:%S")
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Safely get macOS boot time using sysctl command
|
||||
@@ -187,7 +191,7 @@ pub fn get_uptime(boot_time: Option<time_t>) -> UResult<i64> {
|
||||
};
|
||||
|
||||
if let Some(t) = derived_boot_time {
|
||||
let now = Local::now().timestamp();
|
||||
let now = Timestamp::now().as_second();
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
let boottime: i64 = t;
|
||||
#[cfg(not(target_pointer_width = "64"))]
|
||||
@@ -470,7 +474,7 @@ mod tests {
|
||||
assert!(boot_time > 946684800, "Boot time should be after year 2000");
|
||||
|
||||
// Boot time should be before current time
|
||||
let now = chrono::Local::now().timestamp();
|
||||
let now = Timestamp::now().as_second();
|
||||
assert!(
|
||||
(boot_time as i64) < now,
|
||||
"Boot time should be before current time"
|
||||
|
||||
+28
-26
@@ -5,7 +5,10 @@
|
||||
//
|
||||
// spell-checker: ignore: AEDT AEST EEST NZDT NZST Kolkata Iseconds
|
||||
|
||||
use chrono::{DateTime, Datelike, Duration, NaiveTime, Utc}; // spell-checker:disable-line
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use jiff::tz::TimeZone;
|
||||
use jiff::{Timestamp, ToSpan};
|
||||
use regex::Regex;
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
use uucore::process::geteuid;
|
||||
@@ -485,7 +488,7 @@ fn test_invalid_format_string() {
|
||||
#[test]
|
||||
fn test_capitalized_numeric_time_zone() {
|
||||
// %z +hhmm numeric time zone (e.g., -0400)
|
||||
// # is supposed to capitalize, which makes little sense here, but chrono crashes
|
||||
// # is supposed to capitalize, which makes little sense here, but keep coverage
|
||||
// on such format so it's good to test.
|
||||
let re = Regex::new(r"^[+-]\d{4,4}\n$").unwrap();
|
||||
new_ucmd!().arg("+%#z").succeeds().stdout_matches(&re);
|
||||
@@ -528,10 +531,10 @@ fn test_date_string_human() {
|
||||
#[test]
|
||||
fn test_negative_offset() {
|
||||
let data_formats = vec![
|
||||
("-1 hour", Duration::hours(1)),
|
||||
("-1 hours", Duration::hours(1)),
|
||||
("-1 day", Duration::days(1)),
|
||||
("-2 weeks", Duration::weeks(2)),
|
||||
("-1 hour", 1.hours()),
|
||||
("-1 hours", 1.hours()),
|
||||
("-1 day", 24.hours()),
|
||||
("-2 weeks", (14 * 24).hours()),
|
||||
];
|
||||
for (date_format, offset) in data_formats {
|
||||
new_ucmd!()
|
||||
@@ -540,11 +543,10 @@ fn test_negative_offset() {
|
||||
.arg("--rfc-3339=seconds")
|
||||
.succeeds()
|
||||
.stdout_str_check(|out| {
|
||||
let date = DateTime::parse_from_rfc3339(out.trim()).unwrap();
|
||||
|
||||
let date = out.trim().parse::<Timestamp>().unwrap();
|
||||
// Is the resulting date roughly what is expected?
|
||||
let expected_date = Utc::now() - offset;
|
||||
(date.to_utc() - expected_date).abs() < Duration::minutes(10)
|
||||
let expected_date = Timestamp::now() - offset;
|
||||
(date - expected_date).abs().compare(10.minutes()).unwrap() == Ordering::Less
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -552,14 +554,15 @@ fn test_negative_offset() {
|
||||
#[test]
|
||||
fn test_relative_weekdays() {
|
||||
// Truncate time component to midnight
|
||||
let today = Utc::now().with_time(NaiveTime::MIN).unwrap();
|
||||
let today = Timestamp::now().to_zoned(TimeZone::UTC).date();
|
||||
// Loop through each day of the week, starting with today
|
||||
for offset in 0..7 {
|
||||
for direction in ["last", "this", "next"] {
|
||||
let weekday = (today + Duration::days(offset))
|
||||
.weekday()
|
||||
.to_string()
|
||||
.to_lowercase();
|
||||
let weekday = today
|
||||
.checked_add(offset.days())
|
||||
.unwrap()
|
||||
.strftime("%a")
|
||||
.to_string();
|
||||
new_ucmd!()
|
||||
.arg("-d")
|
||||
.arg(format!("{direction} {weekday}"))
|
||||
@@ -567,14 +570,15 @@ fn test_relative_weekdays() {
|
||||
.arg("--utc")
|
||||
.succeeds()
|
||||
.stdout_str_check(|out| {
|
||||
let result = DateTime::parse_from_rfc3339(out.trim()).unwrap().to_utc();
|
||||
let result = out.trim().parse::<Timestamp>().unwrap();
|
||||
let expected = match (direction, offset) {
|
||||
("last", _) => today - Duration::days(7 - offset),
|
||||
("last", _) => today.checked_sub((7 - offset).days()).unwrap(),
|
||||
("this", 0) => today,
|
||||
("next", 0) => today + Duration::days(7),
|
||||
_ => today + Duration::days(offset),
|
||||
("next", 0) => today.checked_add(7.days()).unwrap(),
|
||||
_ => today.checked_add(offset.days()).unwrap(),
|
||||
};
|
||||
result == expected
|
||||
let expected_ts = expected.to_zoned(TimeZone::UTC).unwrap().timestamp();
|
||||
result == expected_ts
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -862,7 +866,7 @@ fn test_date_resolution_no_combine() {
|
||||
fn test_date_numeric_d_basic_utc() {
|
||||
// Verify GNU-compatible pure-digit parsing for -d STRING under UTC
|
||||
// 0/00 -> today at 00:00; 7/07 -> today at 07:00; 0700 -> today at 07:00
|
||||
let today = Utc::now().date_naive();
|
||||
let today = Timestamp::now().to_zoned(TimeZone::UTC).date();
|
||||
let yyyy = today.year();
|
||||
let mm = today.month();
|
||||
let dd = today.day();
|
||||
@@ -1134,9 +1138,7 @@ fn test_date_military_timezone_with_offset_variations() {
|
||||
|
||||
#[test]
|
||||
fn test_date_military_timezone_with_offset_and_date() {
|
||||
use chrono::{Duration, Utc};
|
||||
|
||||
let today = Utc::now().date_naive();
|
||||
let today = Timestamp::now().to_zoned(TimeZone::UTC).date();
|
||||
|
||||
let test_cases = vec![
|
||||
("m", -1), // M = UTC+12
|
||||
@@ -1158,9 +1160,9 @@ fn test_date_military_timezone_with_offset_and_date() {
|
||||
];
|
||||
|
||||
for (input, day_delta) in test_cases {
|
||||
let expected_date = today.checked_add_signed(Duration::days(day_delta)).unwrap();
|
||||
let expected_date = today.checked_add(day_delta.days()).unwrap();
|
||||
|
||||
let expected = format!("{}\n", expected_date.format("%F"));
|
||||
let expected = format!("{}\n", expected_date.strftime("%F"));
|
||||
|
||||
new_ucmd!()
|
||||
.env("TZ", "UTC")
|
||||
|
||||
+14
-14
@@ -4,7 +4,7 @@
|
||||
// file that was distributed with this source code.
|
||||
// spell-checker:ignore (ToDO) Sdivide
|
||||
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use jiff::{Timestamp, ToSpan};
|
||||
use regex::Regex;
|
||||
use std::fs::metadata;
|
||||
use uutests::new_ucmd;
|
||||
@@ -17,8 +17,8 @@ fn file_last_modified_time_format(ucmd: &UCommand, path: &str, format: &str) ->
|
||||
metadata(tmp_dir_path)
|
||||
.and_then(|meta| meta.modified())
|
||||
.map(|mtime| {
|
||||
let dt: DateTime<Utc> = mtime.into();
|
||||
dt.format(format).to_string()
|
||||
let dt: Timestamp = mtime.try_into().unwrap();
|
||||
dt.strftime(format).to_string()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
@@ -27,19 +27,19 @@ fn file_last_modified_time(ucmd: &UCommand, path: &str) -> String {
|
||||
file_last_modified_time_format(ucmd, path, DATE_TIME_FORMAT_DEFAULT)
|
||||
}
|
||||
|
||||
fn all_minutes(from: DateTime<Utc>, to: DateTime<Utc>) -> Vec<String> {
|
||||
let to = to + Duration::try_minutes(1).unwrap();
|
||||
fn all_minutes(from: Timestamp, to: Timestamp) -> Vec<String> {
|
||||
let to = to + 1.minute();
|
||||
let mut vec = vec![];
|
||||
let mut current = from;
|
||||
while current < to {
|
||||
vec.push(current.format(DATE_TIME_FORMAT_DEFAULT).to_string());
|
||||
current += Duration::try_minutes(1).unwrap();
|
||||
vec.push(current.strftime(DATE_TIME_FORMAT_DEFAULT).to_string());
|
||||
current += 1.minute();
|
||||
}
|
||||
vec
|
||||
}
|
||||
|
||||
fn valid_last_modified_template_vars(from: DateTime<Utc>) -> Vec<Vec<(String, String)>> {
|
||||
all_minutes(from, Utc::now())
|
||||
fn valid_last_modified_template_vars(from: Timestamp) -> Vec<Vec<(String, String)>> {
|
||||
all_minutes(from, Timestamp::now())
|
||||
.into_iter()
|
||||
.map(|time| vec![("{last_modified_time}".to_string(), time)])
|
||||
.collect()
|
||||
@@ -264,7 +264,7 @@ fn test_with_suppress_error_option() {
|
||||
fn test_with_stdin() {
|
||||
let expected_file_path = "stdin.log.expected";
|
||||
let mut scenario = new_ucmd!();
|
||||
let start = Utc::now();
|
||||
let start = Timestamp::now();
|
||||
scenario
|
||||
.pipe_in_fixture("stdin.log")
|
||||
.args(&["--pages=1:2", "-n", "-"])
|
||||
@@ -327,7 +327,7 @@ fn test_with_mpr() {
|
||||
let expected_test_file_path = "mpr.log.expected";
|
||||
let expected_test_file_path1 = "mpr1.log.expected";
|
||||
let expected_test_file_path2 = "mpr2.log.expected";
|
||||
let start = Utc::now();
|
||||
let start = Timestamp::now();
|
||||
new_ucmd!()
|
||||
.args(&["--pages=1:2", "-m", "-n", test_file_path, test_file_path1])
|
||||
.succeeds()
|
||||
@@ -336,7 +336,7 @@ fn test_with_mpr() {
|
||||
&valid_last_modified_template_vars(start),
|
||||
);
|
||||
|
||||
let start = Utc::now();
|
||||
let start = Timestamp::now();
|
||||
new_ucmd!()
|
||||
.args(&["--pages=2:4", "-m", "-n", test_file_path, test_file_path1])
|
||||
.succeeds()
|
||||
@@ -345,7 +345,7 @@ fn test_with_mpr() {
|
||||
&valid_last_modified_template_vars(start),
|
||||
);
|
||||
|
||||
let start = Utc::now();
|
||||
let start = Timestamp::now();
|
||||
new_ucmd!()
|
||||
.args(&[
|
||||
"--pages=1:2",
|
||||
@@ -530,7 +530,7 @@ fn test_with_join_lines_option() {
|
||||
let test_file_2 = "test.log";
|
||||
let expected_file_path = "joined.log.expected";
|
||||
let mut scenario = new_ucmd!();
|
||||
let start = Utc::now();
|
||||
let start = Timestamp::now();
|
||||
scenario
|
||||
.args(&["+1:2", "-J", "-m", test_file_1, test_file_2])
|
||||
.succeeds()
|
||||
|
||||
Reference in New Issue
Block a user