Skip to content

Commit 3371f17

Browse files
committed
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 dependency to uu_touch for the required type conversions - Ensures tests/touch/relative passes in CI The parse_datetime_at_date() function still exists in v0.13, but now takes jiff::Zoned instead of chrono::DateTime as the reference time parameter.
1 parent 4340913 commit 3371f17

File tree

3 files changed

+22
-6
lines changed

3 files changed

+22
-6
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/uu/touch/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ path = "src/touch.rs"
2222
filetime = { workspace = true }
2323
clap = { workspace = true }
2424
chrono = { workspace = true }
25+
jiff = { workspace = true }
2526
parse_datetime = { workspace = true }
2627
thiserror = { workspace = true }
2728
uucore = { workspace = true, features = ["libc", "parser"] }

src/uu/touch/src/touch.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use chrono::{
1515
use clap::builder::{PossibleValue, ValueParser};
1616
use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command};
1717
use filetime::{FileTime, set_file_times, set_symlink_file_times};
18+
use jiff::{Timestamp, Zoned};
1819
use std::borrow::Cow;
1920
use std::ffi::OsString;
2021
use std::fs::{self, File};
@@ -588,7 +589,7 @@ fn stat(path: &Path, follow: bool) -> std::io::Result<(FileTime, FileTime)> {
588589
))
589590
}
590591

591-
fn parse_date(_ref_time: DateTime<Local>, s: &str) -> Result<FileTime, TouchError> {
592+
fn parse_date(ref_time: DateTime<Local>, s: &str) -> Result<FileTime, TouchError> {
592593
// This isn't actually compatible with GNU touch, but there doesn't seem to
593594
// be any simple specification for what format this parameter allows and I'm
594595
// not about to implement GNU parse_datetime.
@@ -638,14 +639,27 @@ fn parse_date(_ref_time: DateTime<Local>, s: &str) -> Result<FileTime, TouchErro
638639
}
639640

640641
// **parse_datetime 0.13 API change:**
641-
// Previously (0.11): parse_datetime_at_date(chrono) → chrono::DateTime
642-
// Now (0.13): parse_datetime() → jiff::Zoned
642+
// Previously (0.11): parse_datetime_at_date(chrono::DateTime) → chrono::DateTime
643+
// Now (0.13): parse_datetime_at_date(jiff::Zoned) → jiff::Zoned
643644
//
644645
// Since touch still uses chrono types internally, we convert:
645-
// jiff::Zoned → Unix timestamp → chrono::DateTime
646+
// chrono::DateTime → jiff::Zoned → parse_datetime_at_date → jiff::Zoned → chrono::DateTime
646647
//
647-
// TODO: Consider migrating touch to jiff to eliminate this conversion
648-
if let Ok(zoned) = parse_datetime::parse_datetime(s) {
648+
// The use of parse_datetime_at_date (not parse_datetime) is critical for deterministic
649+
// behavior with relative dates like "yesterday" or "2 days ago", which must be
650+
// calculated relative to ref_time, not the current system time.
651+
652+
// Convert chrono DateTime to jiff Zoned for parse_datetime_at_date
653+
let ref_zoned = {
654+
let ts = Timestamp::new(
655+
ref_time.timestamp(),
656+
ref_time.timestamp_subsec_nanos() as i32,
657+
)
658+
.map_err(|_| TouchError::InvalidDateFormat(s.to_owned()))?;
659+
Zoned::new(ts, jiff::tz::TimeZone::system())
660+
};
661+
662+
if let Ok(zoned) = parse_datetime::parse_datetime_at_date(ref_zoned, s) {
649663
let timestamp = zoned.timestamp();
650664
let dt =
651665
DateTime::from_timestamp(timestamp.as_second(), timestamp.subsec_nanosecond() as u32)

0 commit comments

Comments
 (0)