Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@

## Changes

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

## Other

Expand Down
182 changes: 90 additions & 92 deletions src/filter/time.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use jiff::{civil::DateTime, tz::TimeZone, Span, Timestamp, Zoned};

use std::time::SystemTime;
use std::time::{Duration, SystemTime, UNIX_EPOCH};

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

#[cfg(not(test))]
fn now() -> Zoned {
Zoned::now()
}

#[cfg(test)]
thread_local! {
static TESTTIME: std::cell::RefCell<Option<Zoned>> = None.into();
}

/// This allows us to set a specific time when running tests
#[cfg(test)]
fn now() -> Zoned {
TESTTIME.with_borrow(|reftime| reftime.as_ref().cloned().unwrap_or_else(Zoned::now))
}

impl TimeFilter {
fn from_str(ref_time: &SystemTime, s: &str) -> Option<SystemTime> {
s.parse::<Span>()
.and_then(|duration| {
Zoned::try_from(*ref_time).and_then(|zdt| zdt.checked_sub(duration))
})
.ok()
.or_else(|| {
let local_tz = TimeZone::system();
s.parse::<Timestamp>()
.map(|ts| ts.to_zoned(TimeZone::UTC))
.ok()
.or_else(|| {
s.parse::<DateTime>()
.map(|dt| local_tz.to_ambiguous_zoned(dt))
.and_then(|zdt| zdt.later())
.ok()
})
.or_else(|| {
let timestamp_secs = s.strip_prefix('@')?.parse().ok()?;
Timestamp::from_second(timestamp_secs)
.map(|ts| ts.to_zoned(TimeZone::UTC))
.ok()
})
})
.map(SystemTime::from)
fn from_str(s: &str) -> Option<SystemTime> {
if let Ok(span) = s.parse::<Span>() {
let datetime = now().checked_sub(span).ok()?;
Some(datetime.into())
} else if let Ok(timestamp) = s.parse::<Timestamp>() {
Some(timestamp.into())
} else if let Ok(datetime) = s.parse::<DateTime>() {
Some(
TimeZone::system()
.to_ambiguous_zoned(datetime)
.later()
.ok()?
.into(),
)
} else {
let timestamp_secs: u64 = s.strip_prefix('@')?.parse().ok()?;
Some(UNIX_EPOCH + Duration::from_secs(timestamp_secs))
}
}

pub fn before(ref_time: &SystemTime, s: &str) -> Option<TimeFilter> {
TimeFilter::from_str(ref_time, s).map(TimeFilter::Before)
pub fn before(s: &str) -> Option<TimeFilter> {
TimeFilter::from_str(s).map(TimeFilter::Before)
}

pub fn after(ref_time: &SystemTime, s: &str) -> Option<TimeFilter> {
TimeFilter::from_str(ref_time, s).map(TimeFilter::After)
pub fn after(s: &str) -> Option<TimeFilter> {
TimeFilter::from_str(s).map(TimeFilter::After)
}

pub fn applies_to(&self, t: &SystemTime) -> bool {
Expand All @@ -58,107 +67,96 @@ mod tests {
use super::*;
use std::time::Duration;

fn set_test_time(time: Zoned) -> SystemTime {
Comment thread
tavianator marked this conversation as resolved.
Outdated
TESTTIME.with_borrow_mut(|t| *t = Some(time.clone()));
time.into()
}

#[test]
fn is_time_filter_applicable() {
let local_tz = TimeZone::system();
let ref_time = local_tz
.to_ambiguous_zoned("2010-10-10 10:10:10".parse::<DateTime>().unwrap())
.later()
.unwrap()
.into();
let ref_time = set_test_time(
local_tz
.to_ambiguous_zoned("2010-10-10 10:10:10".parse::<DateTime>().unwrap())
.later()
.unwrap(),
);

assert!(TimeFilter::after(&ref_time, "1min")
.unwrap()
.applies_to(&ref_time));
assert!(!TimeFilter::before(&ref_time, "1min")
.unwrap()
.applies_to(&ref_time));
assert!(TimeFilter::after("1min").unwrap().applies_to(&ref_time));
assert!(!TimeFilter::before("1min").unwrap().applies_to(&ref_time));

let t1m_ago = ref_time - Duration::from_secs(60);
assert!(!TimeFilter::after(&ref_time, "30sec")
.unwrap()
.applies_to(&t1m_ago));
assert!(TimeFilter::after(&ref_time, "2min")
.unwrap()
.applies_to(&t1m_ago));
assert!(!TimeFilter::after("30sec").unwrap().applies_to(&t1m_ago));
assert!(TimeFilter::after("2min").unwrap().applies_to(&t1m_ago));

assert!(TimeFilter::before(&ref_time, "30sec")
.unwrap()
.applies_to(&t1m_ago));
assert!(!TimeFilter::before(&ref_time, "2min")
.unwrap()
.applies_to(&t1m_ago));
assert!(TimeFilter::before("30sec").unwrap().applies_to(&t1m_ago));
assert!(!TimeFilter::before("2min").unwrap().applies_to(&t1m_ago));

let t10s_before = "2010-10-10 10:10:00";
assert!(!TimeFilter::before(&ref_time, t10s_before)
assert!(!TimeFilter::before(t10s_before)
.unwrap()
.applies_to(&ref_time));
assert!(TimeFilter::before(&ref_time, t10s_before)
assert!(TimeFilter::before(t10s_before)
.unwrap()
.applies_to(&t1m_ago));

assert!(TimeFilter::after(&ref_time, t10s_before)
assert!(TimeFilter::after(t10s_before)
.unwrap()
.applies_to(&ref_time));
assert!(!TimeFilter::after(&ref_time, t10s_before)
.unwrap()
.applies_to(&t1m_ago));
assert!(!TimeFilter::after(t10s_before).unwrap().applies_to(&t1m_ago));

let same_day = "2010-10-10";
assert!(!TimeFilter::before(&ref_time, same_day)
.unwrap()
.applies_to(&ref_time));
assert!(!TimeFilter::before(&ref_time, same_day)
.unwrap()
.applies_to(&t1m_ago));

assert!(TimeFilter::after(&ref_time, same_day)
.unwrap()
.applies_to(&ref_time));
assert!(TimeFilter::after(&ref_time, same_day)
.unwrap()
.applies_to(&t1m_ago));

let ref_time = "2010-10-10T10:10:10+00:00"
.parse::<Timestamp>()
.unwrap()
.into();
assert!(!TimeFilter::before(same_day).unwrap().applies_to(&ref_time));
assert!(!TimeFilter::before(same_day).unwrap().applies_to(&t1m_ago));

assert!(TimeFilter::after(same_day).unwrap().applies_to(&ref_time));
assert!(TimeFilter::after(same_day).unwrap().applies_to(&t1m_ago));

let ref_time = set_test_time(
"2010-10-10T10:10:10+00:00"
.parse::<Timestamp>()
.unwrap()
.to_zoned(local_tz.clone()),
);
let t1m_ago = ref_time - Duration::from_secs(60);
let t10s_before = "2010-10-10T10:10:00+00:00";
assert!(!TimeFilter::before(&ref_time, t10s_before)
assert!(!TimeFilter::before(t10s_before)
.unwrap()
.applies_to(&ref_time));
assert!(TimeFilter::before(&ref_time, t10s_before)
assert!(TimeFilter::before(t10s_before)
.unwrap()
.applies_to(&t1m_ago));

assert!(TimeFilter::after(&ref_time, t10s_before)
assert!(TimeFilter::after(t10s_before)
.unwrap()
.applies_to(&ref_time));
assert!(!TimeFilter::after(&ref_time, t10s_before)
.unwrap()
.applies_to(&t1m_ago));
assert!(!TimeFilter::after(t10s_before).unwrap().applies_to(&t1m_ago));

let ref_timestamp = 1707723412u64; // Mon Feb 12 07:36:52 UTC 2024
let ref_time = "2024-02-12T07:36:52+00:00"
.parse::<Timestamp>()
.unwrap()
.into();
let ref_time = set_test_time(
"2024-02-12T07:36:52+00:00"
.parse::<Timestamp>()
.unwrap()
.to_zoned(local_tz),
);
let t1m_ago = ref_time - Duration::from_secs(60);
let t1s_later = ref_time + Duration::from_secs(1);
// Timestamp only supported via '@' prefix
assert!(TimeFilter::before(&ref_time, &ref_timestamp.to_string()).is_none());
assert!(TimeFilter::before(&ref_time, &format!("@{ref_timestamp}"))
assert!(TimeFilter::before(&ref_timestamp.to_string()).is_none());
assert!(TimeFilter::before(&format!("@{ref_timestamp}"))
.unwrap()
.applies_to(&t1m_ago));
assert!(!TimeFilter::before(&ref_time, &format!("@{ref_timestamp}"))
assert!(!TimeFilter::before(&format!("@{ref_timestamp}"))
.unwrap()
.applies_to(&t1s_later));
assert!(!TimeFilter::after(&ref_time, &format!("@{ref_timestamp}"))
assert!(!TimeFilter::after(&format!("@{ref_timestamp}"))
.unwrap()
.applies_to(&t1m_ago));
assert!(TimeFilter::after(&ref_time, &format!("@{ref_timestamp}"))
assert!(TimeFilter::after(&format!("@{ref_timestamp}"))
.unwrap()
.applies_to(&t1s_later));

// Stop using manually set times
TESTTIME.with_borrow_mut(|t| *t = None);
}
}
6 changes: 2 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ use std::env;
use std::io::IsTerminal;
use std::path::Path;
use std::sync::Arc;
use std::time;

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

fn extract_time_constraints(opts: &Opts) -> Result<Vec<TimeFilter>> {
let now = time::SystemTime::now();
let mut time_constraints: Vec<TimeFilter> = Vec::new();
if let Some(ref t) = opts.changed_within {
if let Some(f) = TimeFilter::after(&now, t) {
if let Some(f) = TimeFilter::after(t) {
time_constraints.push(f);
} else {
return Err(anyhow!(
Expand All @@ -442,7 +440,7 @@ fn extract_time_constraints(opts: &Opts) -> Result<Vec<TimeFilter>> {
}
}
if let Some(ref t) = opts.changed_before {
if let Some(f) = TimeFilter::before(&now, t) {
if let Some(f) = TimeFilter::before(t) {
time_constraints.push(f);
} else {
return Err(anyhow!(
Expand Down