From 7632acfc902e04e3adb4e674b33345bc6f0f253c Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Mon, 3 Mar 2025 16:52:53 +0100 Subject: [PATCH] Fix touch -t with 2 digit years when YY > 68 When using `touch -t` with a 2 digit year, the year is interpreted as a relative year to 2000. When the year is 68 or less, it should be interpreted as 20xx. When the year is 69 or more, it should be interpreted as 19xx. This is the behavior of GNU `touch`. fixes gh-7280 Arguably 2 digits years should be deprecated as we are already closer to 2069, than 1969. --- src/uu/touch/src/touch.rs | 30 +++++++++++++++++++++++++--- tests/by-util/test_touch.rs | 39 +++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 047313e64..eb6154f2b 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -640,6 +640,30 @@ fn parse_date(ref_time: DateTime, s: &str) -> Result UResult { + let first_two_digits = s[..2] + .parse::() + .map_err(|_| USimpleError::new(1, format!("invalid date ts format {}", s.quote())))?; + Ok(format!( + "{}{s}", + if first_two_digits > 68 { 19 } else { 20 } + )) +} + +/// Parses a timestamp string into a FileTime. +/// +/// This function attempts to parse a string into a FileTime +/// As expected by gnu touch -t : `[[cc]yy]mmddhhmm[.ss]` +/// +/// Note that If the year is specified with only two digits, +/// then cc is 20 for years in the range 0 … 68, and 19 for years in 69 … 99. +/// in order to be compatible with GNU `touch`. fn parse_timestamp(s: &str) -> UResult { use format::*; @@ -648,9 +672,9 @@ fn parse_timestamp(s: &str) -> UResult { let (format, ts) = match s.chars().count() { 15 => (YYYYMMDDHHMM_DOT_SS, s.to_owned()), 12 => (YYYYMMDDHHMM, s.to_owned()), - // If we don't add "20", we have insufficient information to parse - 13 => (YYYYMMDDHHMM_DOT_SS, format!("20{s}")), - 10 => (YYYYMMDDHHMM, format!("20{s}")), + // If we don't add "19" or "20", we have insufficient information to parse + 13 => (YYYYMMDDHHMM_DOT_SS, prepend_century(s)?), + 10 => (YYYYMMDDHHMM, prepend_century(s)?), 11 => (YYYYMMDDHHMM_DOT_SS, format!("{}{}", current_year(), s)), 8 => (YYYYMMDDHHMM, format!("{}{}", current_year(), s)), _ => { diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index 91298ff9e..98004bb71 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -118,6 +118,45 @@ fn test_touch_set_mdhms_time() { assert_eq!(mtime.unix_seconds() - start_of_year.unix_seconds(), 45296); } +#[test] +fn test_touch_2_digit_years_68() { + // 68 and before are 20xx + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_set_two_digit_68_time"; + + ucmd.args(&["-t", "6801010000", file]) + .succeeds() + .no_output(); + + assert!(at.file_exists(file)); + + // January 1, 2068, 00:00:00 + let expected = FileTime::from_unix_time(3_092_601_600, 0); + let (atime, mtime) = get_file_times(&at, file); + assert_eq!(atime, mtime); + assert_eq!(atime, expected); + assert_eq!(mtime, expected); +} + +#[test] +fn test_touch_2_digit_years_69() { + // 69 and after are 19xx + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_set_two_digit_69_time"; + + ucmd.args(&["-t", "6901010000", file]) + .succeeds() + .no_output(); + + assert!(at.file_exists(file)); + // January 1, 1969, 00:00:00 + let expected = FileTime::from_unix_time(-31_536_000, 0); + let (atime, mtime) = get_file_times(&at, file); + assert_eq!(atime, mtime); + assert_eq!(atime, expected); + assert_eq!(mtime, expected); +} + #[test] fn test_touch_set_ymdhm_time() { let (at, mut ucmd) = at_and_ucmd!();