Skip to content

Commit 2b0f609

Browse files
authored
Merge pull request #1692 from tmccombs/jiff-more
Some small improvements to time filter code after switch to jiff
2 parents 803d1fd + f370301 commit 2b0f609

3 files changed

Lines changed: 117 additions & 97 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010

1111
## Changes
1212

13-
- Replace `humantime` crate and `chrono` crate with `jiff` crate, see #1690 (@sorairolake)
13+
- Replace `humantime` crate and `chrono` crate with `jiff` crate, see #1690 (@sorairolake). This has some small changes to the
14+
way dates given to options such `--changed-within` and `--changed-before` including:
15+
- 'M' no longer means "month", as that could be confusing with minutes. Use "mo", "mos", "month" or "months" instead.
16+
- month and year now account for variability in the calander rather than being a hard-coded number of seconds. That is probably
17+
what you would expect, but it is a slight change in behavior.
1418

1519
## Other
1620

src/filter/time.rs

Lines changed: 110 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use jiff::{civil::DateTime, tz::TimeZone, Span, Timestamp, Zoned};
22

3-
use std::time::SystemTime;
3+
use std::time::{Duration, SystemTime, UNIX_EPOCH};
44

55
/// Filter based on time ranges.
66
#[derive(Debug, PartialEq, Eq)]
@@ -9,40 +9,49 @@ pub enum TimeFilter {
99
After(SystemTime),
1010
}
1111

12+
#[cfg(not(test))]
13+
fn now() -> Zoned {
14+
Zoned::now()
15+
}
16+
17+
#[cfg(test)]
18+
thread_local! {
19+
static TESTTIME: std::cell::RefCell<Option<Zoned>> = None.into();
20+
}
21+
22+
/// This allows us to set a specific time when running tests
23+
#[cfg(test)]
24+
fn now() -> Zoned {
25+
TESTTIME.with_borrow(|reftime| reftime.as_ref().cloned().unwrap_or_else(Zoned::now))
26+
}
27+
1228
impl TimeFilter {
13-
fn from_str(ref_time: &SystemTime, s: &str) -> Option<SystemTime> {
14-
s.parse::<Span>()
15-
.and_then(|duration| {
16-
Zoned::try_from(*ref_time).and_then(|zdt| zdt.checked_sub(duration))
17-
})
18-
.ok()
19-
.or_else(|| {
20-
let local_tz = TimeZone::system();
21-
s.parse::<Timestamp>()
22-
.map(|ts| ts.to_zoned(TimeZone::UTC))
23-
.ok()
24-
.or_else(|| {
25-
s.parse::<DateTime>()
26-
.map(|dt| local_tz.to_ambiguous_zoned(dt))
27-
.and_then(|zdt| zdt.later())
28-
.ok()
29-
})
30-
.or_else(|| {
31-
let timestamp_secs = s.strip_prefix('@')?.parse().ok()?;
32-
Timestamp::from_second(timestamp_secs)
33-
.map(|ts| ts.to_zoned(TimeZone::UTC))
34-
.ok()
35-
})
36-
})
37-
.map(SystemTime::from)
29+
fn from_str(s: &str) -> Option<SystemTime> {
30+
if let Ok(span) = s.parse::<Span>() {
31+
let datetime = now().checked_sub(span).ok()?;
32+
Some(datetime.into())
33+
} else if let Ok(timestamp) = s.parse::<Timestamp>() {
34+
Some(timestamp.into())
35+
} else if let Ok(datetime) = s.parse::<DateTime>() {
36+
Some(
37+
TimeZone::system()
38+
.to_ambiguous_zoned(datetime)
39+
.later()
40+
.ok()?
41+
.into(),
42+
)
43+
} else {
44+
let timestamp_secs: u64 = s.strip_prefix('@')?.parse().ok()?;
45+
Some(UNIX_EPOCH + Duration::from_secs(timestamp_secs))
46+
}
3847
}
3948

40-
pub fn before(ref_time: &SystemTime, s: &str) -> Option<TimeFilter> {
41-
TimeFilter::from_str(ref_time, s).map(TimeFilter::Before)
49+
pub fn before(s: &str) -> Option<TimeFilter> {
50+
TimeFilter::from_str(s).map(TimeFilter::Before)
4251
}
4352

44-
pub fn after(ref_time: &SystemTime, s: &str) -> Option<TimeFilter> {
45-
TimeFilter::from_str(ref_time, s).map(TimeFilter::After)
53+
pub fn after(s: &str) -> Option<TimeFilter> {
54+
TimeFilter::from_str(s).map(TimeFilter::After)
4655
}
4756

4857
pub fn applies_to(&self, t: &SystemTime) -> bool {
@@ -58,106 +67,115 @@ mod tests {
5867
use super::*;
5968
use std::time::Duration;
6069

70+
struct TestTime(SystemTime);
71+
72+
impl TestTime {
73+
fn new(time: Zoned) -> Self {
74+
TESTTIME.with_borrow_mut(|t| *t = Some(time.clone()));
75+
TestTime(time.into())
76+
}
77+
78+
fn set(&mut self, time: Zoned) {
79+
TESTTIME.with_borrow_mut(|t| *t = Some(time.clone()));
80+
self.0 = time.into();
81+
}
82+
83+
fn timestamp(&self) -> SystemTime {
84+
self.0
85+
}
86+
}
87+
88+
impl Drop for TestTime {
89+
fn drop(&mut self) {
90+
// Stop using manually set times
91+
TESTTIME.with_borrow_mut(|t| *t = None);
92+
}
93+
}
94+
6195
#[test]
6296
fn is_time_filter_applicable() {
6397
let local_tz = TimeZone::system();
64-
let ref_time = local_tz
65-
.to_ambiguous_zoned("2010-10-10 10:10:10".parse::<DateTime>().unwrap())
66-
.later()
67-
.unwrap()
68-
.into();
98+
let mut test_time = TestTime::new(
99+
local_tz
100+
.to_ambiguous_zoned("2010-10-10 10:10:10".parse::<DateTime>().unwrap())
101+
.later()
102+
.unwrap(),
103+
);
104+
let mut ref_time = test_time.timestamp();
69105

70-
assert!(TimeFilter::after(&ref_time, "1min")
71-
.unwrap()
72-
.applies_to(&ref_time));
73-
assert!(!TimeFilter::before(&ref_time, "1min")
74-
.unwrap()
75-
.applies_to(&ref_time));
106+
assert!(TimeFilter::after("1min").unwrap().applies_to(&ref_time));
107+
assert!(!TimeFilter::before("1min").unwrap().applies_to(&ref_time));
76108

77109
let t1m_ago = ref_time - Duration::from_secs(60);
78-
assert!(!TimeFilter::after(&ref_time, "30sec")
79-
.unwrap()
80-
.applies_to(&t1m_ago));
81-
assert!(TimeFilter::after(&ref_time, "2min")
82-
.unwrap()
83-
.applies_to(&t1m_ago));
110+
assert!(!TimeFilter::after("30sec").unwrap().applies_to(&t1m_ago));
111+
assert!(TimeFilter::after("2min").unwrap().applies_to(&t1m_ago));
84112

85-
assert!(TimeFilter::before(&ref_time, "30sec")
86-
.unwrap()
87-
.applies_to(&t1m_ago));
88-
assert!(!TimeFilter::before(&ref_time, "2min")
89-
.unwrap()
90-
.applies_to(&t1m_ago));
113+
assert!(TimeFilter::before("30sec").unwrap().applies_to(&t1m_ago));
114+
assert!(!TimeFilter::before("2min").unwrap().applies_to(&t1m_ago));
91115

92116
let t10s_before = "2010-10-10 10:10:00";
93-
assert!(!TimeFilter::before(&ref_time, t10s_before)
117+
assert!(!TimeFilter::before(t10s_before)
94118
.unwrap()
95119
.applies_to(&ref_time));
96-
assert!(TimeFilter::before(&ref_time, t10s_before)
120+
assert!(TimeFilter::before(t10s_before)
97121
.unwrap()
98122
.applies_to(&t1m_ago));
99123

100-
assert!(TimeFilter::after(&ref_time, t10s_before)
124+
assert!(TimeFilter::after(t10s_before)
101125
.unwrap()
102126
.applies_to(&ref_time));
103-
assert!(!TimeFilter::after(&ref_time, t10s_before)
104-
.unwrap()
105-
.applies_to(&t1m_ago));
127+
assert!(!TimeFilter::after(t10s_before).unwrap().applies_to(&t1m_ago));
106128

107129
let same_day = "2010-10-10";
108-
assert!(!TimeFilter::before(&ref_time, same_day)
109-
.unwrap()
110-
.applies_to(&ref_time));
111-
assert!(!TimeFilter::before(&ref_time, same_day)
112-
.unwrap()
113-
.applies_to(&t1m_ago));
114-
115-
assert!(TimeFilter::after(&ref_time, same_day)
116-
.unwrap()
117-
.applies_to(&ref_time));
118-
assert!(TimeFilter::after(&ref_time, same_day)
119-
.unwrap()
120-
.applies_to(&t1m_ago));
121-
122-
let ref_time = "2010-10-10T10:10:10+00:00"
123-
.parse::<Timestamp>()
124-
.unwrap()
125-
.into();
130+
assert!(!TimeFilter::before(same_day).unwrap().applies_to(&ref_time));
131+
assert!(!TimeFilter::before(same_day).unwrap().applies_to(&t1m_ago));
132+
133+
assert!(TimeFilter::after(same_day).unwrap().applies_to(&ref_time));
134+
assert!(TimeFilter::after(same_day).unwrap().applies_to(&t1m_ago));
135+
136+
test_time.set(
137+
"2010-10-10T10:10:10+00:00"
138+
.parse::<Timestamp>()
139+
.unwrap()
140+
.to_zoned(local_tz.clone()),
141+
);
142+
ref_time = test_time.timestamp();
126143
let t1m_ago = ref_time - Duration::from_secs(60);
127144
let t10s_before = "2010-10-10T10:10:00+00:00";
128-
assert!(!TimeFilter::before(&ref_time, t10s_before)
145+
assert!(!TimeFilter::before(t10s_before)
129146
.unwrap()
130147
.applies_to(&ref_time));
131-
assert!(TimeFilter::before(&ref_time, t10s_before)
148+
assert!(TimeFilter::before(t10s_before)
132149
.unwrap()
133150
.applies_to(&t1m_ago));
134151

135-
assert!(TimeFilter::after(&ref_time, t10s_before)
152+
assert!(TimeFilter::after(t10s_before)
136153
.unwrap()
137154
.applies_to(&ref_time));
138-
assert!(!TimeFilter::after(&ref_time, t10s_before)
139-
.unwrap()
140-
.applies_to(&t1m_ago));
155+
assert!(!TimeFilter::after(t10s_before).unwrap().applies_to(&t1m_ago));
141156

142157
let ref_timestamp = 1707723412u64; // Mon Feb 12 07:36:52 UTC 2024
143-
let ref_time = "2024-02-12T07:36:52+00:00"
144-
.parse::<Timestamp>()
145-
.unwrap()
146-
.into();
158+
test_time.set(
159+
"2024-02-12T07:36:52+00:00"
160+
.parse::<Timestamp>()
161+
.unwrap()
162+
.to_zoned(local_tz),
163+
);
164+
ref_time = test_time.timestamp();
147165
let t1m_ago = ref_time - Duration::from_secs(60);
148166
let t1s_later = ref_time + Duration::from_secs(1);
149167
// Timestamp only supported via '@' prefix
150-
assert!(TimeFilter::before(&ref_time, &ref_timestamp.to_string()).is_none());
151-
assert!(TimeFilter::before(&ref_time, &format!("@{ref_timestamp}"))
168+
assert!(TimeFilter::before(&ref_timestamp.to_string()).is_none());
169+
assert!(TimeFilter::before(&format!("@{ref_timestamp}"))
152170
.unwrap()
153171
.applies_to(&t1m_ago));
154-
assert!(!TimeFilter::before(&ref_time, &format!("@{ref_timestamp}"))
172+
assert!(!TimeFilter::before(&format!("@{ref_timestamp}"))
155173
.unwrap()
156174
.applies_to(&t1s_later));
157-
assert!(!TimeFilter::after(&ref_time, &format!("@{ref_timestamp}"))
175+
assert!(!TimeFilter::after(&format!("@{ref_timestamp}"))
158176
.unwrap()
159177
.applies_to(&t1m_ago));
160-
assert!(TimeFilter::after(&ref_time, &format!("@{ref_timestamp}"))
178+
assert!(TimeFilter::after(&format!("@{ref_timestamp}"))
161179
.unwrap()
162180
.applies_to(&t1s_later));
163181
}

src/main.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ use std::env;
1717
use std::io::IsTerminal;
1818
use std::path::Path;
1919
use std::sync::Arc;
20-
use std::time;
2120

2221
use anyhow::{anyhow, bail, Context, Result};
2322
use clap::{CommandFactory, Parser};
@@ -429,10 +428,9 @@ fn determine_ls_command(colored_output: bool) -> Result<Vec<&'static str>> {
429428
}
430429

431430
fn extract_time_constraints(opts: &Opts) -> Result<Vec<TimeFilter>> {
432-
let now = time::SystemTime::now();
433431
let mut time_constraints: Vec<TimeFilter> = Vec::new();
434432
if let Some(ref t) = opts.changed_within {
435-
if let Some(f) = TimeFilter::after(&now, t) {
433+
if let Some(f) = TimeFilter::after(t) {
436434
time_constraints.push(f);
437435
} else {
438436
return Err(anyhow!(
@@ -442,7 +440,7 @@ fn extract_time_constraints(opts: &Opts) -> Result<Vec<TimeFilter>> {
442440
}
443441
}
444442
if let Some(ref t) = opts.changed_before {
445-
if let Some(f) = TimeFilter::before(&now, t) {
443+
if let Some(f) = TimeFilter::before(t) {
446444
time_constraints.push(f);
447445
} else {
448446
return Err(anyhow!(

0 commit comments

Comments
 (0)