touch: fix parse_datetime 0.13 API usage for deterministic relative dates

The previous implementation incorrectly used parse_datetime() instead of
parse_datetime_at_date(), causing relative date strings like 'yesterday' or
'2 days ago' to be calculated from the current system time instead of the
caller-specified reference time.

This fix:
- Uses parse_datetime_at_date() with proper chrono->jiff->chrono conversions
- Restores deterministic behavior for relative date parsing
- Adds jiff 0.2.15 as direct dependency (not workspace) to avoid feature conflicts
- Ensures tests/touch/relative passes in CI

Note on jiff dependency:
parse_datetime 0.13 depends on jiff ^0.2.15 with specific features. Adding jiff
via workspace dependency causes feature unification conflicts across platforms.
Using a direct dependency with version 0.2.15 resolves this while maintaining
compatibility with parse_datetime's requirements.
This commit is contained in:
naoNao89 2025-10-15 09:21:09 +07:00 committed by Sylvestre Ledru
parent f4b66479f0
commit f764be8ddf
3 changed files with 34 additions and 10 deletions

7
Cargo.lock generated
View file

@ -508,7 +508,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@ -1074,7 +1074,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
dependencies = [
"libc",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@ -1667,7 +1667,7 @@ dependencies = [
"portable-atomic",
"portable-atomic-util",
"serde",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@ -4127,6 +4127,7 @@ dependencies = [
"clap",
"filetime",
"fluent",
"jiff",
"parse_datetime",
"thiserror 2.0.17",
"uucore",

View file

@ -22,6 +22,7 @@ path = "src/touch.rs"
filetime = { workspace = true }
clap = { workspace = true }
chrono = { workspace = true }
jiff = "0.2.15"
parse_datetime = { workspace = true }
thiserror = { workspace = true }
uucore = { workspace = true, features = ["libc", "parser"] }

View file

@ -15,6 +15,7 @@ use chrono::{
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 std::borrow::Cow;
use std::ffi::OsString;
use std::fs::{self, File};
@ -588,7 +589,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_time: DateTime<Local>, 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.
@ -638,14 +639,35 @@ fn parse_date(_ref_time: DateTime<Local>, s: &str) -> Result<FileTime, TouchErro
}
// **parse_datetime 0.13 API change:**
// Previously (0.11): parse_datetime_at_date(chrono) → chrono::DateTime
// Now (0.13): parse_datetime() → jiff::Zoned
// 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:
//
// Since touch still uses chrono types internally, we convert:
// jiff::Zoned → Unix timestamp → chrono::DateTime
// Previously (0.11): parse_datetime_at_date(chrono::DateTime) → chrono::DateTime
// Now (0.13): parse_datetime_at_date(jiff::Zoned) → jiff::Zoned
//
// TODO: Consider migrating touch to jiff to eliminate this conversion
if let Ok(zoned) = parse_datetime::parse_datetime(s) {
// 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)