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

Update duration rounding to new algorithms #65

Merged
merged 10 commits into from
Jul 6, 2024
Merged

Conversation

nekevss
Copy link
Member

@nekevss nekevss commented Jun 30, 2024

Currently a WIP.

This closes #19 outside of anything related to ZonedDateTime, which I'd prefer to consider separately due to the reliance on time zones.

This PR updates duration rounding to the new duration rounding algorithms.

As it's a bit of a larger PR, I thought I would create a draft in case anyone wants to take a look early.

Overall, this draft is fairly close to complete, but there are still at least a couple bugs around the actually rounding that I'm working through.

@nekevss nekevss marked this pull request as ready for review July 1, 2024 23:59
@nekevss nekevss changed the title WIP: Update duration rounding to new algorithms Update duration rounding to new algorithms Jul 1, 2024
@nekevss
Copy link
Member Author

nekevss commented Jul 2, 2024

This PR should definitely wait until after #66 is merged so it can be rebased to remove the custom traits, but it is essentially ready for review outside of that impending rebase

There are a couple things to note:

  1. This implements the new rounding methods under a round_v2 and doesn't remove the previous one just yet. It can be removed if everyone else thinks it should be in this PR, but I was sort of thinking about lagging on removing the previous methods while the rest is tested. Hopefully, it also makes the diff just a little bit easier to read.
  2. Any rounding around ZonedDateTime is not included in this, but it's hard to implement and test that without proper time zone support, so if everyone else is in agreement I'd prefer to file that under a new issue.
  3. We should probably file an issue for more unit tests around the internal nudge algorithms and normalization records.

Copy link
Member

@jedel1043 jedel1043 left a comment

Choose a reason for hiding this comment

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

Looks great! I have some suggestions on some APIs that we can improve.

src/components/duration.rs Outdated Show resolved Hide resolved
src/components/duration.rs Outdated Show resolved Hide resolved
src/components/duration/normalized.rs Outdated Show resolved Hide resolved
src/components/duration/normalized.rs Outdated Show resolved Hide resolved
src/utils.rs Outdated Show resolved Hide resolved
@nekevss nekevss force-pushed the update-duration-rounding branch from 009faf9 to e34d24b Compare July 3, 2024 23:57
@nekevss nekevss requested a review from jedel1043 July 4, 2024 22:42
src/options.rs Outdated Show resolved Hide resolved
src/components/duration.rs Outdated Show resolved Hide resolved
src/options.rs Outdated
Comment on lines 63 to 68
pub(crate) fn from_diff_settings(
options: DifferenceSettings,
operation: DifferenceOperation,
fallback_largest: TemporalUnit,
fallback_smallest: TemporalUnit,
) -> TemporalResult<(f64, Self)> {
Copy link
Member

Choose a reason for hiding this comment

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

Thought: Maybe we could return NonZeroDurationSign here? It would be more consistent to use a single type for the signs instead of having a mix of i8, f64, Sign and NonZeroDurationSign everywhere.

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated to a Sign value.

@@ -365,6 +370,69 @@ impl TimeDuration {
// ==== TimeDuration method impls ====

impl TimeDuration {
// TODO: Maybe move to `NormalizedTimeDuration`
pub fn round_v2(
Copy link
Member

Choose a reason for hiding this comment

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

Should this be replacing the original round? I think we decided to remove the original rounding functions instead of having a v2 for them.

Copy link
Member Author

@nekevss nekevss Jul 6, 2024

Choose a reason for hiding this comment

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

I went to remove it, but there's still a dependency on the original round. I was going to follow up on this in a different PR.

EDIT: Went through and cleaned it up.

@@ -30,9 +32,28 @@ impl Time {
self.iso.is_valid()
}

/// Specification equivalent to `AddTime`
pub(crate) fn add_norm(&self, norm: NormalizedTimeDuration) -> (i32, Self) {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: probably use the full name of add_normalized_time_duration. add_norm is a bit more confusing because it has a different meaning mathematically.

Comment on lines +54 to +60
#[derive(Debug, Clone, Copy)]
pub(crate) struct ResolvedRoundingOptions {
pub(crate) largest_unit: TemporalUnit,
pub(crate) smallest_unit: TemporalUnit,
pub(crate) increment: RoundingIncrement,
pub(crate) rounding_mode: TemporalRoundingMode,
}
Copy link
Member

Choose a reason for hiding this comment

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

Praise: Loving this new API! I think it really makes sense to have an options struct that resolves all the Options in RoundingOptions, instead of having to unwrap everywhere.

Comment on lines 26 to 59
#[repr(i8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum DurationSign {
Positive = 1,
Zero = 0,
Negative = -1,
}

impl From<Ordering> for DurationSign {
fn from(value: Ordering) -> Self {
match value {
Ordering::Greater => Self::Positive,
Ordering::Equal => Self::Zero,
Ordering::Less => Self::Negative,
}
}
}

#[repr(i8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum NonZeroDurationSign {
Positive = 1,
Negative = -1,
}

impl From<DurationSign> for NonZeroDurationSign {
fn from(value: DurationSign) -> Self {
if matches!(value, DurationSign::Negative) {
return Self::Negative;
}
Self::Positive
}
}

Copy link
Member

@jedel1043 jedel1043 Jul 6, 2024

Choose a reason for hiding this comment

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

After seeing how NonZeroDurationSign basically optimizes the same as DurationSign, I don't think it's worth it anymore. Having just DurationSign should be okay, IMO.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm going back and forth with DurationSign vs. Sign. 😕 Updated to Sign this time around.

Comment on lines 34 to 42
impl From<Ordering> for DurationSign {
fn from(value: Ordering) -> Self {
match value {
Ordering::Greater => Self::Positive,
Ordering::Equal => Self::Zero,
Ordering::Less => Self::Negative,
}
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Hmmm, I don't know if it makes much sense to have a conversion from these enums. Ordering is a completely different concept than DurationSign, and things like DurationSign::from(zero.cmp(&target)) don't make much sense if you think about it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, it's a bit awkward. I wasn't super in love with it. Maybe just a From<i8>

Comment on lines 180 to 182
pub(crate) fn norm(&self) -> NormalizedTimeDuration {
self.norm
}
Copy link
Member

Choose a reason for hiding this comment

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

Same suggestion of using the full name.

src/lib.rs Outdated
Comment on lines 105 to 110
pub(crate) fn as_non_zero(&self) -> Self {
if matches!(self, Self::Zero) {
return Self::Positive;
}
*self
}
Copy link
Member

Choose a reason for hiding this comment

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

If this is to multiply a duration with the sign, what about having an as_sign_multiplier function instead that returns 1i8 for zero and positive, and -1i8 for negative?

Copy link
Member Author

Choose a reason for hiding this comment

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

It is. It's specifically a nonzero variant in these rounding methods vs. the regular multiplication that occurs elsewhere. I'm fine with as_sign_multiplier though. Better than anything I'm coming up with in this instance.

Copy link
Member

@jedel1043 jedel1043 left a comment

Choose a reason for hiding this comment

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

Really impressive work! Pretty much perfect IMO :)

Copy link
Member

@jasonwilliams jasonwilliams left a comment

Choose a reason for hiding this comment

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

LGTM!
As @jedel said this looks great, big improvement

@nekevss nekevss merged commit c999bb3 into main Jul 6, 2024
5 checks passed
@jedel1043 jedel1043 deleted the update-duration-rounding branch July 6, 2024 17:32
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.

Implement Duration normalization and new algorithms
3 participants