Skip to content

Commit 11ec510

Browse files
M Bussonniersylvestre
authored andcommitted
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.
1 parent 72299d3 commit 11ec510

File tree

2 files changed

+66
-3
lines changed

2 files changed

+66
-3
lines changed

src/uu/touch/src/touch.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,30 @@ fn parse_date(ref_time: DateTime<Local>, s: &str) -> Result<FileTime, TouchError
640640
Err(TouchError::InvalidDateFormat(s.to_owned()))
641641
}
642642

643+
/// Prepends 19 or 20 to the year if it is a 2 digit year
644+
///
645+
/// GNU `touch` behavior:
646+
///
647+
/// - 68 and before is interpreted as 20xx
648+
/// - 69 and after is interpreted as 19xx
649+
fn prepend_century(s: &str) -> UResult<String> {
650+
let first_two_digits = s[..2]
651+
.parse::<u32>()
652+
.map_err(|_| USimpleError::new(1, format!("invalid date ts format {}", s.quote())))?;
653+
Ok(format!(
654+
"{}{s}",
655+
if first_two_digits > 68 { 19 } else { 20 }
656+
))
657+
}
658+
659+
/// Parses a timestamp string into a FileTime.
660+
///
661+
/// This function attempts to parse a string into a FileTime
662+
/// As expected by gnu touch -t : `[[cc]yy]mmddhhmm[.ss]`
663+
///
664+
/// Note that If the year is specified with only two digits,
665+
/// then cc is 20 for years in the range 0 … 68, and 19 for years in 69 … 99.
666+
/// in order to be compatible with GNU `touch`.
643667
fn parse_timestamp(s: &str) -> UResult<FileTime> {
644668
use format::*;
645669

@@ -648,9 +672,9 @@ fn parse_timestamp(s: &str) -> UResult<FileTime> {
648672
let (format, ts) = match s.chars().count() {
649673
15 => (YYYYMMDDHHMM_DOT_SS, s.to_owned()),
650674
12 => (YYYYMMDDHHMM, s.to_owned()),
651-
// If we don't add "20", we have insufficient information to parse
652-
13 => (YYYYMMDDHHMM_DOT_SS, format!("20{s}")),
653-
10 => (YYYYMMDDHHMM, format!("20{s}")),
675+
// If we don't add "19" or "20", we have insufficient information to parse
676+
13 => (YYYYMMDDHHMM_DOT_SS, prepend_century(s)?),
677+
10 => (YYYYMMDDHHMM, prepend_century(s)?),
654678
11 => (YYYYMMDDHHMM_DOT_SS, format!("{}{}", current_year(), s)),
655679
8 => (YYYYMMDDHHMM, format!("{}{}", current_year(), s)),
656680
_ => {

tests/by-util/test_touch.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,45 @@ fn test_touch_set_mdhms_time() {
118118
assert_eq!(mtime.unix_seconds() - start_of_year.unix_seconds(), 45296);
119119
}
120120

121+
#[test]
122+
fn test_touch_2_digit_years_68() {
123+
// 68 and before are 20xx
124+
let (at, mut ucmd) = at_and_ucmd!();
125+
let file = "test_touch_set_two_digit_68_time";
126+
127+
ucmd.args(&["-t", "6801010000", file])
128+
.succeeds()
129+
.no_output();
130+
131+
assert!(at.file_exists(file));
132+
133+
// January 1, 2068, 00:00:00
134+
let expected = FileTime::from_unix_time(3_092_601_600, 0);
135+
let (atime, mtime) = get_file_times(&at, file);
136+
assert_eq!(atime, mtime);
137+
assert_eq!(atime, expected);
138+
assert_eq!(mtime, expected);
139+
}
140+
141+
#[test]
142+
fn test_touch_2_digit_years_69() {
143+
// 69 and after are 19xx
144+
let (at, mut ucmd) = at_and_ucmd!();
145+
let file = "test_touch_set_two_digit_69_time";
146+
147+
ucmd.args(&["-t", "6901010000", file])
148+
.succeeds()
149+
.no_output();
150+
151+
assert!(at.file_exists(file));
152+
// January 1, 1969, 00:00:00
153+
let expected = FileTime::from_unix_time(-31_536_000, 0);
154+
let (atime, mtime) = get_file_times(&at, file);
155+
assert_eq!(atime, mtime);
156+
assert_eq!(atime, expected);
157+
assert_eq!(mtime, expected);
158+
}
159+
121160
#[test]
122161
fn test_touch_set_ymdhm_time() {
123162
let (at, mut ucmd) = at_and_ucmd!();

0 commit comments

Comments
 (0)