Skip to content

Commit

Permalink
revset: create TimePattern type
Browse files Browse the repository at this point in the history
Creates a TimePattern type that can be created by parsing a string in any
format supported by the chrono-english crate, including:

- 2024-03-25
- 2024-03-25T00:00:00
- 2024-03-25T00:00:00-08:00
- 2 weeks ago
- 5 minutes ago
- yesterday
- yesterday 5pm
- yesterday 10:30
- yesterday 15:30
- tomorrow

A `kind` can be specified to indicate whether the pattern should match dates at
or after (`after`) or strictly before (`before`) the given instant.

chrono-english supports US and UK dialects to disambiguate mm/dd/yy from
dd/mm/yy, but for now we default to US. This should probably be a config
setting.
  • Loading branch information
jennings committed Jul 3, 2024
1 parent ca4eb60 commit 098513f
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 0 deletions.
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ chrono = { version = "0.4.38", default-features = false, features = [
"std",
"clock",
] }
chrono-english = { version = "0.1.7" }
config = { version = "0.13.4", default-features = false, features = ["toml"] }
criterion = "0.5.1"
crossterm = { version = "0.27", default-features = false }
Expand Down
1 change: 1 addition & 0 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ backoff = { workspace = true }
blake2 = { workspace = true }
bytes = { workspace = true }
chrono = { workspace = true }
chrono-english = { workspace = true }
config = { workspace = true }
digest = { workspace = true }
either = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ pub mod stacked_table;
pub mod store;
pub mod str_util;
pub mod submodule_store;
pub mod time_pattern;
pub mod transaction;
pub mod tree;
pub mod tree_builder;
Expand Down
137 changes: 137 additions & 0 deletions lib/src/time_pattern.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Copyright 2024 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Provides a TimeExpression type that represents a range of time.
use chrono::{DateTime, TimeZone};
use chrono_english::{parse_date_string, DateError, Dialect};
use thiserror::Error;

use crate::backend::Timestamp;

/// Error occurred during time pattern parsing.
#[derive(Debug, Error)]
pub enum TimePatternParseError {
/// Unknown pattern kind is specified.
#[error(r#"Invalid time pattern kind "{0}:""#)]
InvalidKind(String),
/// Failed to parse input UI path.
#[error(transparent)]
ParseError(#[from] DateError),
}

/// Represents an range of dates and times that revisions may be matched
/// against.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum TimePattern {
/// Represents all times at or after the given instant in time.
AtOrAfter(Timestamp),
/// Represents all times before, but not including, the given instant in
/// time.
Before(Timestamp),
}

impl TimePattern {
/// Parses a string into a TimePattern.
pub fn from_str_kind<Tz: TimeZone>(
s: &str,
kind: Option<&str>,
now: DateTime<Tz>,
) -> Result<TimePattern, TimePatternParseError>
where
Tz::Offset: Copy,
{
let d =
parse_date_string(s, now, Dialect::Us).map_err(TimePatternParseError::ParseError)?;
let ts = Timestamp::from_datetime(d.to_utc().fixed_offset());
match kind {
None => Ok(TimePattern::AtOrAfter(ts)),
Some("after") => Ok(TimePattern::AtOrAfter(ts)),
Some("before") => Ok(TimePattern::Before(ts)),
Some(kind) => Err(TimePatternParseError::InvalidKind(kind.to_owned())),
}
}
}

#[cfg(test)]
mod tests {
use chrono::DateTime;

use super::*;

fn test_equal<Tz: TimeZone>(now: DateTime<Tz>, expression: &str, should_equal_time: &str)
where
Tz::Offset: Copy,
{
let expression = TimePattern::from_str_kind(expression, Some("after"), now).unwrap();
assert_eq!(
expression,
TimePattern::AtOrAfter(Timestamp::from_datetime(
DateTime::parse_from_rfc3339(should_equal_time)
.unwrap()
.to_utc()
.fixed_offset()
))
);
}

#[test]
fn test_time_pattern_parses_dates_without_times_as_the_date_at_local_midnight() {
let now = DateTime::parse_from_rfc3339("2024-01-01T00:00:00-08:00").unwrap();
test_equal(now, "2023-03-25", "2023-03-25T08:00:00Z");
test_equal(now, "3/25/2023", "2023-03-25T08:00:00Z");
test_equal(now, "3/25/23", "2023-03-25T08:00:00Z");
}

#[test]
fn test_time_pattern_parses_dates_with_times_without_specifying_an_offset() {
let now = DateTime::parse_from_rfc3339("2024-01-01T00:00:00-08:00").unwrap();
test_equal(now, "2023-03-25T00:00:00", "2023-03-25T08:00:00Z");
test_equal(now, "2023-03-25 00:00:00", "2023-03-25T08:00:00Z");
}

#[test]
fn test_time_pattern_parses_dates_with_a_specified_offset() {
let now = DateTime::parse_from_rfc3339("2024-01-01T00:00:00-08:00").unwrap();
test_equal(
now,
"2023-03-25T00:00:00-05:00",
"2023-03-25T00:00:00-05:00",
);
}

#[test]
fn test_time_pattern_parses_dates_with_the_z_offset() {
let now = DateTime::parse_from_rfc3339("2024-01-01T00:00:00-08:00").unwrap();
test_equal(now, "2023-03-25T00:00:00Z", "2023-03-25T00:00:00Z");
}

#[test]
fn test_time_pattern_parses_relative_durations() {
let now = DateTime::parse_from_rfc3339("2024-01-01T00:00:00-08:00").unwrap();
test_equal(now, "2 hours ago", "2024-01-01T06:00:00Z");
test_equal(now, "5 minutes", "2024-01-01T08:05:00Z");
test_equal(now, "1 week ago", "2023-12-25T08:00:00Z");
test_equal(now, "yesterday", "2023-12-31T08:00:00Z");
test_equal(now, "tomorrow", "2024-01-02T08:00:00Z");
}

#[test]
fn test_time_pattern_parses_relative_dates_with_times() {
let now = DateTime::parse_from_rfc3339("2024-01-01T08:00:00-08:00").unwrap();
test_equal(now, "yesterday 5pm", "2024-01-01T01:00:00Z");
test_equal(now, "yesterday 10am", "2023-12-31T18:00:00Z");
test_equal(now, "yesterday 10:30", "2023-12-31T18:30:00Z");
}
}

0 comments on commit 098513f

Please sign in to comment.