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

Tracking issue: Converting the API to return Results #1469

Open
pitdicker opened this issue Feb 28, 2024 · 19 comments
Open

Tracking issue: Converting the API to return Results #1469

pitdicker opened this issue Feb 28, 2024 · 19 comments

Comments

@pitdicker
Copy link
Collaborator

pitdicker commented Feb 28, 2024

A list to keep score. Not yet complete, and only lists the public API.

Currently at 61/153.

NaiveDate

NaiveTime

NaiveDateTime

NaiveWeek

  • NaiveWeek::first_day (?)
  • NaiveWeek::last_day (?)
  • NaiveWeek::days (?)

FixedOffset

TimeDelta

DateTime

TimeZone

  • TimeZone::offset_from_local_datetime
  • TimeZone::offset_from_utc_datetime
  • TimeZone::from_local_datetime
  • TimeZone::from_utc_datetime
  • TimeZone::timestamp
  • TimeZone::timestamp_micros
  • TimeZone::timestamp_millis
  • TimeZone::timestamp_nanos
  • TimeZone::with_ymd_and_hms

LocalResult

  • LocalResult::single
  • LocalResult::earliest
  • LocalResult::latest

Parsed

StrftimeItems

  • StrftimeItems::parse
  • StrftimeItems::parse_to_owned

Other

  • format::parse
  • format::parse_and_remainder

FromStr implementations

  • NaiveDate
  • NaiveTime
  • NaiveDateTime
  • DateTime<FixedOffset>
  • DateTime<Utc>
  • DateTime<Local>
  • FixedOffset
  • Weekday
  • Month

TryFrom implementations

@djc
Copy link
Contributor

djc commented Feb 28, 2024

For bonus points, make this more like a dependency tree?

@pitdicker
Copy link
Collaborator Author

That would be cool to have, but it is more a dependency graph than a tree.

@djc
Copy link
Contributor

djc commented Feb 28, 2024

Those are kind of the same thing IMO -- unless you expect there are a bunch of cycles in there?

@pitdicker
Copy link
Collaborator Author

I'll add a 🌞 after items when I think they are ready to convert without deep dependencies.

@pitdicker
Copy link
Collaborator Author

@Zomtir What would you like to work on next, if any? Then I'll stay clear.

@Zomtir
Copy link
Contributor

Zomtir commented Mar 12, 2024

Unfortunately I am occupied for the next three weeks. I will rebase the checked_(add/sub)_days once I am home and have to take a break then.

@pitdicker
Copy link
Collaborator Author

Thank you for everything until now 👍.

@Zomtir
Copy link
Contributor

Zomtir commented Mar 12, 2024

Well... I lied. The succ/pred caught my eye and I could not resist. Uno mas: #1513

@ISibboI
Copy link

ISibboI commented Mar 14, 2024

Please also all the try_... APIs. Returning Option makes it impossible to use the ? operator properly, as the returned None is not connected to the chrono crate at all.

Also, please have the Err value include the erroneous input value for the try_... functions. Otherwise, functional code style becomes annoying.

Example

Currently, the try_... functions can only be used as follows:

let duration_seconds = input_function();
let duration = Duration::try_seconds(duration_seconds).ok_or(Error::TimeRangeError(duration_seconds))?;

Ideally however, it would work as follows:

impl From<ChronoTimeRangeError> for Error {
    fn from(ChronoTimeRangeError {value}: ChronoTimeRangeError) {
        Error::TimeRangeError(value)
    }
}

fn foo() -> Result<(), Error> {
    let duration = Duration::try_seconds(input_function())?;
}

@pitdicker
Copy link
Collaborator Author

This issue is for the next major version of chrono, 0.5. All deprecated methods are removed, and the _opt and try_ methods are renamed. @ISibboI In this case you may be interested in #1515 which was just merged.

@pitdicker
Copy link
Collaborator Author

Sorry, I was on mobile and just saw this part of your comment:

Also, please have the Err value include the erroneous input value for the try_... functions. Otherwise, functional code style becomes annoying.

Interesting example!
I'm afraid that is going to be very difficult. We are currently converting ca. 150 methods. To make them all return the erroneous input asks for multiple error types or an error type that is generic. And we can no longer simply bubble up errors within chrono but have to map them to include the input at the outermost method.
It is too big a change for me on what is already a bit complex and interwoven work to consider.

@Zomtir
Copy link
Contributor

Zomtir commented Apr 6, 2024

I will attempt TimeDelta::checked_add and TimeDelta::checked_sub now.

@pitdicker
Copy link
Collaborator Author

Great!

Honestly we are currently out of methods that are ready to convert. Converting the parsing methods depends on #1511, and the first preparations towards converting the methods that depend on the TimeZone trait depend on #1529. As soon as those are in we can pick up work here.

I'm not sure if we want to convert TimeDelta::checked_add and TimeDelta::checked_sub. The same methods on integers return an Option. We may want to follow that for this type too. What do you think?

@Zomtir
Copy link
Contributor

Zomtir commented Apr 6, 2024

It does seem very weird to hide different errors behind None. If TimeDelta::new or to a greater extent all functions of TimeDelta returned the same error, the mapping to None would be bijective.

For integers (unit-less scalar) the overall concept might be simpler than TimeDelta, which can be converted to different units of time.

Appending .ok() also isn't too much to ask for the user in checked_add().ok().

@pitdicker
Copy link
Collaborator Author

So most methods on TimeDelta are converted for that reason.
checked_add and checked_sub only fail on overflow. I have no strong opinion, but keeping a similar signature to the corresponding methods from the standard library is something to consider.

@Zomtir
Copy link
Contributor

Zomtir commented Apr 6, 2024

Partly related, but I noticed quite a few unchecked operations in TimeDelta:

impl Mul<i32> for TimeDelta {
    type Output = TimeDelta;

    fn mul(self, rhs: i32) -> TimeDelta {
        // Multiply nanoseconds as i64, because it cannot overflow that way.
        let total_nanos = self.nanos as i64 * rhs as i64;
        let (extra_secs, nanos) = div_mod_floor_64(total_nanos, NANOS_PER_SEC as i64);
        let secs = self.secs * rhs as i64 + extra_secs;
        TimeDelta { secs, nanos: nanos as i32 }
    }
}

The downcasting from i64 to i32 will wrap on overflow, which should be the same result as directly multiplying two i32. Both are standard behaviour of integers, but I wonder if this is acceptable for TimeDelta.

@pitdicker
Copy link
Collaborator Author

Do you mean nanos as i32? The result of value % NANOS_PER_SEC should always be less than NANOS_PER_SEC.

@Zomtir
Copy link
Contributor

Zomtir commented Apr 6, 2024

Do you mean nanos as i32? The result of value % NANOS_PER_SEC should always be less than NANOS_PER_SEC.

My bad, nanos are covered. let secs = self.secs * rhs could be (2^63)*(2^31) potentially? Not that it will remotely matter, just asking if this intended.

The point I'm trying to make is that integer behaviour is not always applicable, so I don't know if checked_add should be the held to that restriction. If there was a common trait that we could implement that would make more sense.

Personally I'd find internal consistency preferable, e.g having

  • add and mul return TimeDelta
  • checked_add and checked_mul return Result<TimeDelta>

But in the greater context this is splitting hairs and we should focus on the bigger targets atm. Let me know if there is something do.

@pitdicker
Copy link
Collaborator Author

My bad, nanos are covered. let secs = self.secs * rhs could be (2^63)*(2^31) potentially? Not that it will remotely matter, just asking if this intended.

Good point! self.secs is at most something like 2^44, but that still remains an easy way to create invalid TimeDeltas and is definitely something to fix. Want to make a PR for mul? And potentially add checked_mul (against the main branch)?

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

No branches or pull requests

4 participants