Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Implements ord and partialord to timestamp #995

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
44 changes: 44 additions & 0 deletions prost-types/src/lib.rs
Expand Up @@ -395,6 +395,16 @@ impl std::hash::Hash for Timestamp {
}
}

#[cfg(feature = "std")]
impl Ord for Timestamp {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you could just #[derive(PartialOrd, Ord)] on struct Timestamp. At least your unit test succeeds.

Copy link
Author

@LevBeta LevBeta Mar 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed a potential issue in both the comparison logic when dealing with timestamps. For instance, when comparing:
Timestamp { seconds: 1, nanos: 0 }

to

Timestamp { seconds: 0, nanos: 1_000_000_000 }

The current/(and #[derive(Ord)]) implementation considers the first timestamp as greater than the second one. However, it's crucial to recognize that the second timestamp actually represents the same duration.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a normalize function in Timestamp. I believe that does what you are looking for. https://docs.rs/prost-types/latest/prost_types/struct.Timestamp.html#method.normalize

fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match self.seconds.cmp(&other.seconds) {
std::cmp::Ordering::Equal => self.nanos.cmp(&other.nanos),
ordering => ordering,
}
}
}

#[cfg(feature = "std")]
impl From<std::time::SystemTime> for Timestamp {
fn from(system_time: std::time::SystemTime) -> Timestamp {
Expand Down Expand Up @@ -907,4 +917,38 @@ mod tests {
// Must contain at least one "/" character.
assert_eq!(TypeUrl::new("google.protobuf.Duration"), None);
}

#[test]
fn check_timestamp_ord() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you could add a parameterized test. This way you could easily test many scenarios . Here is an example:

fn check_duration_normalize() {
#[rustfmt::skip] // Don't mangle the table formatting.
let cases = [
// --- Table of test cases ---
// test seconds test nanos expected seconds expected nanos
(line!(), 0, 0, 0, 0),
(line!(), 1, 1, 1, 1),
(line!(), -1, -1, -1, -1),
(line!(), 0, 999_999_999, 0, 999_999_999),
(line!(), 0, -999_999_999, 0, -999_999_999),
(line!(), 0, 1_000_000_000, 1, 0),
(line!(), 0, -1_000_000_000, -1, 0),
(line!(), 0, 1_000_000_001, 1, 1),
(line!(), 0, -1_000_000_001, -1, -1),
(line!(), -1, 1, 0, -999_999_999),
(line!(), 1, -1, 0, 999_999_999),
(line!(), -1, 1_000_000_000, 0, 0),
(line!(), 1, -1_000_000_000, 0, 0),
(line!(), i64::MIN , 0, i64::MIN , 0),
(line!(), i64::MIN + 1, 0, i64::MIN + 1, 0),
(line!(), i64::MIN , 1, i64::MIN + 1, -999_999_999),
(line!(), i64::MIN , 1_000_000_000, i64::MIN + 1, 0),
(line!(), i64::MIN , -1_000_000_000, i64::MIN , -999_999_999),
(line!(), i64::MIN + 1, -1_000_000_000, i64::MIN , 0),
(line!(), i64::MIN + 2, -1_000_000_000, i64::MIN + 1, 0),
(line!(), i64::MIN , -1_999_999_998, i64::MIN , -999_999_999),
(line!(), i64::MIN + 1, -1_999_999_998, i64::MIN , -999_999_998),
(line!(), i64::MIN + 2, -1_999_999_998, i64::MIN + 1, -999_999_998),
(line!(), i64::MIN , -1_999_999_999, i64::MIN , -999_999_999),
(line!(), i64::MIN + 1, -1_999_999_999, i64::MIN , -999_999_999),
(line!(), i64::MIN + 2, -1_999_999_999, i64::MIN + 1, -999_999_999),
(line!(), i64::MIN , -2_000_000_000, i64::MIN , -999_999_999),
(line!(), i64::MIN + 1, -2_000_000_000, i64::MIN , -999_999_999),
(line!(), i64::MIN + 2, -2_000_000_000, i64::MIN , 0),
(line!(), i64::MIN , -999_999_998, i64::MIN , -999_999_998),
(line!(), i64::MIN + 1, -999_999_998, i64::MIN + 1, -999_999_998),
(line!(), i64::MAX , 0, i64::MAX , 0),
(line!(), i64::MAX - 1, 0, i64::MAX - 1, 0),
(line!(), i64::MAX , -1, i64::MAX - 1, 999_999_999),
(line!(), i64::MAX , 1_000_000_000, i64::MAX , 999_999_999),
(line!(), i64::MAX - 1, 1_000_000_000, i64::MAX , 0),
(line!(), i64::MAX - 2, 1_000_000_000, i64::MAX - 1, 0),
(line!(), i64::MAX , 1_999_999_998, i64::MAX , 999_999_999),
(line!(), i64::MAX - 1, 1_999_999_998, i64::MAX , 999_999_998),
(line!(), i64::MAX - 2, 1_999_999_998, i64::MAX - 1, 999_999_998),
(line!(), i64::MAX , 1_999_999_999, i64::MAX , 999_999_999),
(line!(), i64::MAX - 1, 1_999_999_999, i64::MAX , 999_999_999),
(line!(), i64::MAX - 2, 1_999_999_999, i64::MAX - 1, 999_999_999),
(line!(), i64::MAX , 2_000_000_000, i64::MAX , 999_999_999),
(line!(), i64::MAX - 1, 2_000_000_000, i64::MAX , 999_999_999),
(line!(), i64::MAX - 2, 2_000_000_000, i64::MAX , 0),
(line!(), i64::MAX , 999_999_998, i64::MAX , 999_999_998),
(line!(), i64::MAX - 1, 999_999_998, i64::MAX - 1, 999_999_998),
];
for case in cases.iter() {
let mut test_duration = Duration {
seconds: case.1,
nanos: case.2,
};
test_duration.normalize();
assert_eq!(
test_duration,
Duration {
seconds: case.3,
nanos: case.4,
},
"test case on line {} doesn't match",
case.0,
);
}
}

let earlier_timestamp = Timestamp {
seconds: 10,
nanos: 500,
};
let later_timestamp = Timestamp {
seconds: 10,
nanos: 700,
};
let equal_timestamp = Timestamp {
seconds: 10,
nanos: 500,
};

assert!(earlier_timestamp < later_timestamp);
assert!(earlier_timestamp <= later_timestamp);
assert!(later_timestamp > earlier_timestamp);
assert!(later_timestamp >= earlier_timestamp);

assert_eq!(
earlier_timestamp.cmp(&later_timestamp),
std::cmp::Ordering::Less
);
assert_eq!(
later_timestamp.cmp(&earlier_timestamp),
std::cmp::Ordering::Greater
);
assert_eq!(
equal_timestamp.cmp(&earlier_timestamp),
std::cmp::Ordering::Equal
);
}
}
2 changes: 1 addition & 1 deletion prost-types/src/protobuf.rs
Expand Up @@ -2293,7 +2293,7 @@ impl NullValue {
/// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
/// the Joda Time's [`ISODateTimeFormat.dateTime()`](<http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D>) to obtain a formatter capable of generating timestamps in this format.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, PartialEq, PartialOrd,::prost::Message)]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is generated, so your changes will be overwritten. Maybe you could add this to the generator so that all types can benefit from comparisons.

I believe this is the reason the CI tests fail

pub struct Timestamp {
/// Represents seconds of UTC time since Unix epoch
/// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
Expand Down