Skip to content

Temporal duration compare #186

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

Merged
merged 14 commits into from
Feb 20, 2025
Merged

Conversation

sffc
Copy link
Contributor

@sffc sffc commented Jan 27, 2025

Fixes #154

Wrote this along with @lockels, @Neelzee, @sebastianjacmatt, @Magnus-Fjeldstad, @HenrikTennebekk.

Please see question regarding the return type of DateDurationDays.

Copy link
Member

@nekevss nekevss left a comment

Choose a reason for hiding this comment

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

Thanks for contributing! This is looking really great overall!


/// DateDurationDays
/// TODO(review): Question: what the correct return type?
pub fn days(&self, relative_to: &PlainDate) -> TemporalResult<i64> {
Copy link
Member

Choose a reason for hiding this comment

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

note: What the return type should be here is a good question. Based off the specification, DateDurationDays is an abstract op that is only called in Temporal.Duration.compare. Given that context, my immediate reaction would be that this method should be pub(crate) (unless there is an argument for an abstract operation being public immediatley) and return TemporalResult<i64>.

This is slightly related to a long running question, I've had about whether temporal_rs would be able to move to a Duration using integers vs. floating points. But I haven't had the time to sit down and audit the spec to confirm.

@@ -572,6 +631,7 @@ impl Duration {
self.weeks(),
self.days().checked_add(&FiniteF64::from(balanced_days))?,
)?;
// TODO: Should this be using AdjustDateDurationRecord?
Copy link
Member

Choose a reason for hiding this comment

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

Good catch! This implementation is from before some of the more recent specification changes. There's a good argument to be made that round should be updated to take advantage of the methods implemented in this PR.

That may be best scoped to another PR as that may include a a couple more changes in the code then specifically in round. I'll leave that up to you.

Some(ArithmeticOverflow::Constrain),
)?;
// 4. Let epochDays1 be ISODateToEpochDays(plainRelativeTo.[[ISODate]].[[Year]], plainRelativeTo.[[ISODate]].[[Month]] - 1, plainRelativeTo.[[ISODate]].[[Day]]).
let epoch_days_1 = iso_date_to_epoch_days(
Copy link
Member

Choose a reason for hiding this comment

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

issue: iso_date_to_epoch_days deviates from the specification here and takes an exact month vs. month - 1

This is sort of a mea culpa, I should have documented this change better (and thought that I had!). We don't have to subtract 1 from month here because the neri-schneider calculations take the month exactly vs month - 1. For reference, here are the tests.

@@ -66,6 +66,7 @@ impl NormalizedTimeDuration {

// NOTE: `days: f64` should be an integer -> `i64`.
/// Equivalent: 7.5.23 Add24HourDaysToNormalizedTimeDuration ( d, days )
/// Add24HourDaysToTimeDuration??
Copy link
Member

Choose a reason for hiding this comment

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

note: looks like the abstract op name changed in the docs. This can be updated.

///
/// `7.2.3 Temporal.Duration.compare ( one, two [ , options ] )`
#[must_use]
pub fn compare(
Copy link
Member

Choose a reason for hiding this comment

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

question: Should compare take &self for the first Duration parameter or mimic the specification and have it be a standalone function on Duration?

This is more a question of what is the best way to translate compare in Rust vs. JavaScript. In the literal sense to the specification, this is implemented exactly as it should be. But in Rust where PartialOrd exists as a trait, it's common to have one.cmp(&two) conceptually. So is it best for this to be a method similar to PartialOrd, e.g. one.compare(&two, None)??

I've tended to prefer this approach, mostly, because I think it's nice to adhere to similarly structured syntax. But I could be convinced to this current implementation if others think it's bad to overload the concept of one.cmp(&two). The most important thing to be consistent across similar methods and functions in temporal_rs for the builtins.

@@ -77,7 +77,7 @@ impl PartialDuration {
/// `Duration` is made up of a `DateDuration` and `TimeDuration` as primarily
/// defined by Abtract Operation 7.5.1-5.
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd)]
Copy link
Member

Choose a reason for hiding this comment

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

issue: Duration should not implement PartialOrd and potentially not PartialEq.

The compare method supersedes the cmp impl from PartialOrd, implementing them both could lead to confusion. PartialEq is not necessarily needed by the specification. It doesn't define an equals method on Duration, but PartialEq is incredibly useful for testing purposes.

// e. If after1 > after2, return 1𝔽.
// f. If after1 < after2, return -1𝔽.
// g. Return +0𝔽.
return Ok(after1.cmp(&after2));
Copy link
Member

Choose a reason for hiding this comment

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

note: this will probably need to be updated on a rebase / merge to main due to #175.

Copy link
Member

Choose a reason for hiding this comment

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

}

/// AdjustDateDurationRecord
pub fn adjust(
Copy link
Member

Choose a reason for hiding this comment

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

suggestion: pub(crate)

Since this is an abstract op, it would be best to keep these pub(crate) until otherwise needed.

@sffc
Copy link
Contributor Author

sffc commented Feb 5, 2025

@lockels will drive this PR, coordinating with the other authors.

@lockels lockels force-pushed the temporal-duration-compare branch from 1c80ef0 to 0909baa Compare February 14, 2025 13:27
@@ -277,6 +277,73 @@ impl Duration {
pub fn is_time_within_range(&self) -> bool {
self.time.is_within_range()
}

/// Compares self with other on a field by field basis.
fn cmp_raw(&self, other: &Self) -> Option<Ordering> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this method be moved to a different section of the code that's private with pub(crate)? This is not part of the public API, but instead a helper method for the actual implementation of compare

Copy link
Member

Choose a reason for hiding this comment

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

Yes! Good question / catch! Preferably it should live in a different block.

Copy link
Contributor

Choose a reason for hiding this comment

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

Alright:) Which block exactly is the best place to put it? Private creation methods? Or maybe outside the any of the impl blocks? None of them make sense as a natural spot to place this function to me

Copy link
Member

@jedel1043 jedel1043 Feb 14, 2025

Choose a reason for hiding this comment

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

Is this even needed? I would just inline the comparison on compare_with_provider, which makes for a simpler code:

if self.date == two.date && self.time == two.time {
    return Ok(Ordering::Equal);
}
// compare by relative

Copy link
Contributor

Choose a reason for hiding this comment

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

oh, that's true! thanks a lot, i'll remove the redundant function and just inline it instead

@@ -277,6 +277,73 @@ impl Duration {
pub fn is_time_within_range(&self) -> bool {
self.time.is_within_range()
}

/// Compares self with other on a field by field basis.
fn cmp_raw(&self, other: &Self) -> Option<Ordering> {
Copy link
Member

Choose a reason for hiding this comment

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

Yes! Good question / catch! Preferably it should live in a different block.

Copy link
Member

@nekevss nekevss left a comment

Choose a reason for hiding this comment

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

This looks fantastic! Thanks for working on this.

I do have on nit that's not necessarily blocking, and this PR needs to be updated to main, but then this should be good to merge 😄

@@ -105,4 +108,59 @@ impl DateDuration {
pub fn sign(&self) -> Sign {
super::duration_sign(&self.fields())
}

/// DateDurationDays
/// TODO(review): Question: what the correct return type?
Copy link
Member

Choose a reason for hiding this comment

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

nit: remove the TODO

I believe this was answered in the call that i64 should be sufficient.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, i forgot to remove that! thanks reminding me!

@nekevss nekevss merged commit 1566796 into boa-dev:main Feb 20, 2025
7 checks passed
@jedel1043 jedel1043 added C-enhancement New feature or request C-api Changes related to the public API labels Feb 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-api Changes related to the public API C-enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement compare method for Duration
6 participants