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

Month::overflowing_add/sub #753

Conversation

esheppa
Copy link
Collaborator

@esheppa esheppa commented Aug 3, 2022

Add an overflowing_add/sub similar to the equivalents for NaiveTime, which are useful for NaiveDate + Months
Also change Months constructor to only accept u32 <= i32::MAX and add a try_new

@esheppa
Copy link
Collaborator Author

esheppa commented Aug 3, 2022

If this is looking like a good API, I'll add some more documentation prior to this being merged

@esheppa
Copy link
Collaborator Author

esheppa commented Aug 3, 2022

I still need to do the MIN_YEAR / MAX_YEAR checks in diff_months as well

Copy link
Member

@djc djc left a comment

Choose a reason for hiding this comment

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

In principle I'm open to the modularization of moving some of diff_months()'s internals into public API on Month, but I'm not sure this API is really natural/otherwise useful.

};
let year = self.year() + years;
Copy link
Member

Choose a reason for hiding this comment

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

No more overflow/underflow checking for the years?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Accidentally removed too much from the function. I've added it back as:

        if year < MIN_YEAR || year > MAX_YEAR {
            return None;
        }

Or do you mean the self.year() + years overflowing/underflowing the i32?

Copy link
Member

Choose a reason for hiding this comment

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

Both...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

will add the check on i32 as well

fn diff_months(self, months: Months, forwards: bool) -> Option<Self> {
// safe as .month() returns [1..12]
// can be further improved when .month() returns a Month
let month: crate::Month = num_traits::FromPrimitive::from_u32(self.month()).unwrap();
Copy link
Member

Choose a reason for hiding this comment

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

I don't see the point of using num_traits::FromPrimitive and using unwrap() if we can just use as here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I need the actual Month type here as that's what overflowing_add/sub take as the &self parameter. This will be able to be cleaned up later if/when we fix #727

Copy link
Member

Choose a reason for hiding this comment

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

FromPrimitive is just too opaque to me, IMO something lower-level is easier to reason about.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't think it is possible to go from u32 to an enumeration thought?

src/month.rs Outdated
@@ -195,8 +223,23 @@ pub struct Months(pub(crate) u32);

impl Months {
/// Construct a new `Months` from a number of months
pub fn new_opt(num: u32) -> Option<Self> {
if num <= core::i32::MAX as u32 {
Copy link
Member

Choose a reason for hiding this comment

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

I'm not really a fan of this as the constraint ends up not being expressed in the type system, so we have to lean on convention/remembering/understanding. IMO it's better just to it as u32 (which also makes the initialization API simple) and check the range in usage context.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

My thinking with this is that if the Months is invalid, its better for this to show up when creating the months rather than adding it, but admittedly most Months will be made and added as part of the same expression. Perhaps another option is taking i32 here but returning None on negative values? Or else removing new_opt and having the check be done inside overflowing_add/sub. This also forces months to have one less possible value on the sub side due to i32::MAX != abs(i32::MIN), but potentially this is acceptable as it is erring on the side of allowing a slightly smaller maximum subtraction?

Copy link
Member

Choose a reason for hiding this comment

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

But that's just the point: a Months::new(u32::MAX) is not invalid by itself -- it's conceptually perfectly valid. It's only invalid in the context of what checked_add_months()/checked_sub_months() are trying to do, as such IMO the burden of validation should fall on the context, not the type itself.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Makes sense, will move the checks there

@@ -2153,26 +2125,13 @@ mod tests {
use std::{i32, u32};

#[test]
fn diff_months() {
fn test_diff_months() {
Copy link
Member

Choose a reason for hiding this comment

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

What does the test_ prefix buy us? This seems like an unrelated change.


// Clamp original day in case new month is shorter

Copy link
Member

Choose a reason for hiding this comment

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

I'd prefer to keep the empty line here

@esheppa
Copy link
Collaborator Author

esheppa commented Aug 4, 2022

With regard to the API, I agree that it is debatable if it is generally useful. A couple of potential use cases are:

  • Users wanting to implement add_months but with their own clamping behavior on invalid days
  • Users wanting to know the month n months after another, irrespective of the year

Alternatively if you like this but don't think think the public API is useful, it could be made private?

@djc
Copy link
Member

djc commented Aug 4, 2022

I think this is only good if the public API is useful. Otherwise the indirection makes the code harder to understand and I'd prefer to keep it as I had it in my original PR. It sounds to me like we don't yet have a very convincing use case in mind for exposing this publicly, so in that case I think we should leave this for now and keep this in mind as a potential direction for future evolution.

@djc djc closed this Aug 4, 2022
@djc djc reopened this Aug 4, 2022
@djc
Copy link
Member

djc commented Aug 4, 2022

(Sorry, closed it accidentally.)

remove wayward debug

fix formatting, add better docs, improve impl, check for valid years

move months validity checks to checked_add/sub
@djc djc deleted the branch chronotope:checked-add-sub-months August 4, 2022 14:05
@djc djc closed this Aug 4, 2022
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