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

Add SubSecondRound trait #217

Merged
merged 5 commits into from
Mar 6, 2018
Merged

Add SubSecondRound trait #217

merged 5 commits into from
Mar 6, 2018

Conversation

dekellum
Copy link
Contributor

Explicit rounding/truncation of subseconds is a feature offered in the baked in time libraries of other languages like ruby and go. Rounding can be used to decrease the error variance when
displaying/serializing/persisting to lower precision. Truncation is the (implicit) default behavior in Chrono display formatting. Either can be used to guarantee equality (e.g. for testing) when round-tripping through a lower precision format, like PostgreSQL timestamps.

As in this implementation, its pretty easy to add support for this in chrono via an extension trait, implemented for Timelike + Add + Sub. I don't see much downside to implementing this way vs baking it directly in to the Timelike structs?

Copy link
Contributor

@quodlibetor quodlibetor left a comment

Choose a reason for hiding this comment

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

This seems like a nice addition, I've got a couple mostly style and naming requests, though.

src/round.rs Outdated
let span = span_for_digits(digits);
let rem = self.nanosecond() % span;
if rem > 0 {
let rev = span - rem;
Copy link
Contributor

Choose a reason for hiding this comment

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

what is rev meant to signify? "Should we reverse this computation"? Could you think of a way to clarify this variable to make this calculation a bit more obvious? Honestly I expected there to be a comparison to zero in here somewhere on my first read through.

Maybe?

let round_up = (span - rem) <= rem;
if round_up {
    ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In 74ec7aa I've renamed variables to delta_down and delta_up. Logicwise, I believe this is optimal, since delta_up is used for the up branch.

src/round.rs Outdated
pub trait SubSecondRound {
/// Return a copy rounded to the specified number of subsecond digits. With
/// 9 or more digits, self is returned unmodified. Halfway values are
/// rounded up (away from zero).
Copy link
Contributor

Choose a reason for hiding this comment

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

could you put in a doctest that includes

dt = Utc.ymd(2018, 1, 11).and_hms_milli(154);
assert_eq!(dt.subsecond_round(2).nanosecond(), 150_000_000);
assert_eq!(dt.subsecond_round(1).nanosecond(), 200_000_000);

The point being to include a small number of digits that are easily understandable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added.

src/round.rs Outdated
/// Return a copy rounded to the specified number of subsecond digits. With
/// 9 or more digits, self is returned unmodified. Halfway values are
/// rounded up (away from zero).
fn round(self, digits: u16) -> Self;
Copy link
Contributor

Choose a reason for hiding this comment

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

could you rename this to subsecond_round.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Assuming your reason is to allow possibly other types of rounding in the future, I renamed to round_subsecs and trunc_subsecs The semi-abbreviated "subsecs" is used elsewhere in chrono, is a little shorter, and if other round_* later appear, they will sort together.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yup, that was why, and I was torn between the order of the words, so your choice is fine.

In this case could you rename the trait to SubsecRound, though?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Renamed to SubsecRound.

src/round.rs Outdated

/// Return a copy truncated to the specified number of subsecond
/// digits. With 9 or more digits, self is returned unmodified.
fn trunc(self, digits: u16) -> Self;
Copy link
Contributor

Choose a reason for hiding this comment

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

could you rename this to subsecond_trunc

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Per above, renamed to trunc_subsecs

src/round.rs Outdated
fn round(self, digits: u16) -> Self;

/// Return a copy truncated to the specified number of subsecond
/// digits. With 9 or more digits, self is returned unmodified.
Copy link
Contributor

Choose a reason for hiding this comment

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

could you add a similar doctest for trunc? Same numbers as for round would be ideal.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added

src/lib.rs Outdated
@@ -418,6 +419,7 @@ pub mod prelude {
#[doc(no_inline)] pub use {NaiveDate, NaiveTime, NaiveDateTime};
#[doc(no_inline)] pub use Date;
#[doc(no_inline)] pub use {DateTime, SecondsFormat};
#[doc(no_inline)] pub use {SubSecondRound};
Copy link
Contributor

Choose a reason for hiding this comment

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

There shouldn't be curlies around the SubSecondRound trait, since it's a single item.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

src/round.rs Outdated
/// Return a copy rounded to the specified number of subsecond digits. With
/// 9 or more digits, self is returned unmodified. Halfway values are
/// rounded up (away from zero).
fn round(self, digits: u16) -> Self;
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe subsecond_round and subsecond_trunc should both take &self, since they don't mutate self and TimeLike is not Copy.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

All internal implementations of TimeLike are Copy I believe. I don't think there is any way to avoid a copy in the implementation or otherwise make it more efficient with this. It might actually thwart compiler optimizations? Presumably that is why the Add and Sub implementations are also not by reference? I can be made to work if necessary however. See da4c19b using Clone, or 14504b2 using Copy. Let me know if you still think either of these are an improvement, and I'll hope to better understand the rationale.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm yeah the tests that you have demonstrate that they work as though they're copy. I wasn't concerned so much about efficiency as I was about a clear and easy-to-use API. So, specifically, if we weren't copy then the following two lines would fail with a use-after-move error:

dt.round_subsec(1);
dt.round_subsec(1);

But you have a bunch of tests that do that. I thought that it wouldn't work because e.g. DateTime<TimeZone> doesn't derive(Copy) so I'm not sure how consistency is being maintained right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The lack of derive surprised me as well, but its actually implemented manually at datetime.rs#L437 and even tested. Note that use-after-move, if lacking Copy, also applies to chrono's Add and Sub:

let future = dt + Duration::nanoseconds(1);
let past = dt - Duration::nanoseconds(1);

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, ha, yup. Okay I'll give this one more once-over and probably merge tonight. Thanks!

@quodlibetor quodlibetor merged commit c9609ea into chronotope:master Mar 6, 2018
@quodlibetor
Copy link
Contributor

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants