refactor: use jiff for safer time features

This commit is contained in:
Aaron Ang
2026-01-07 22:25:55 -08:00
parent 20a5c3afe9
commit 6a844812cf
12 changed files with 132 additions and 172 deletions
Generated
+1 -4
View File
@@ -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
View File
@@ -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
+11 -11
View File
@@ -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]]
+1 -2
View File
@@ -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"] }
+1 -1
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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 }
+4 -5
View File
@@ -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(())
}
+1 -2
View File
@@ -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"]
+8 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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()