chrono icon indicating copy to clipboard operation
chrono copied to clipboard

Tracking issue: Converting the API to return `Result`s

Open pitdicker opened this issue 1 year ago • 19 comments

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

Currently at 61/153.

NaiveDate

  • [x] NaiveDate::from_ymd: #1444
  • [x] NaiveDate::from_yo: #1460
  • [x] NaiveDate::from_isoywd: #1460
  • [x] NaiveDate::from_num_days_from_ce: #1470
  • [x] NaiveDate::from_weekday_of_month: #1512
  • [ ] NaiveDate::parse_from_str
  • [ ] NaiveDate::parse_and_remainder
  • [x] NaiveDate::and_hms: #1410
  • [x] NaiveDate::and_hms_milli: #1410
  • [x] NaiveDate::and_hms_micro: #1410
  • [x] NaiveDate::and_hms_nano: #1410
  • [x] NaiveDate::succ: #1513
  • [x] NaiveDate::pred: #1513
  • [x] NaiveDate::checked_add_days: #1475
  • [x] NaiveDate::checked_sub_days: #1475
  • [x] NaiveDate::checked_add_months: #1537
  • [x] NaiveDate::checked_sub_months: #1537
  • [x] NaiveDate::checked_add_signed: #1514
  • [x] NaiveDate::checked_sub_signed: #1514
  • [x] NaiveDate::with_year: #1466
  • [x] NaiveDate::with_month: #1509
  • [x] NaiveDate::with_day: #1509
  • [x] NaiveDate::with_ordinal: #1531

NaiveTime

  • [x] NaiveTime::from_hms: #1410
  • [x] NaiveTime::from_hms_micro: #1410
  • [x] NaiveTime::from_hms_milli: #1410
  • [x] NaiveTime::from_hms_nano: #1410
  • [x] NaiveTime::from_num_seconds_from_midnight: #1410
  • [ ] NaiveTime::parse_from_str
  • [ ] NaiveTime::parse_and_remainder
  • [x] NaiveTime::with_hour: #1498
  • [x] NaiveTime::with_minute: #1498
  • [x] NaiveTime::with_second: #1498
  • [x] NaiveTime::with_nanosecond: #1498

NaiveDateTime

  • [ ] NaiveDateTime::and_local_timezone (to LocalResult)
  • [ ] NaiveDateTime::parse_and_remainder
  • [ ] NaiveDateTime::parse_from_str
  • [x] NaiveDateTime::checked_add_days: #1475
  • [x] NaiveDateTime::checked_sub_days: #1475
  • [x] NaiveDateTime::checked_add_months: #1537
  • [x] NaiveDateTime::checked_sub_months: #1537
  • [x] NaiveDateTime::checked_add_signed: #1514
  • [x] NaiveDateTime::checked_sub_signed: #1514
  • [x] NaiveDateTime::checked_add_offset: #1514
  • [x] NaiveDateTime::checked_sub_offset: #1514
  • [x] NaiveDateTime::with_year: #1466
  • [x] NaiveDateTime::with_month: #1509
  • [x] NaiveDateTime::with_day: #1509
  • [x] NaiveDateTime::with_ordinal: #1531
  • [x] NaiveDateTime::with_hour: #1498
  • [x] NaiveDateTime::with_minute: #1498
  • [x] NaiveDateTime::with_second: #1498
  • [x] NaiveDateTime::with_nanosecond: #1498

NaiveWeek

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

FixedOffset

  • [x] FixedOffset::east: #1468
  • [x] FixedOffset::west: #1468

TimeDelta

  • [x] TimeDelta::new: #1538
  • [x] TimeDelta::milliseconds: #1538
  • [x] TimeDelta::num_microseconds: #1538
  • [x] TimeDelta::num_nanoseconds: #1538
  • [ ] TimeDelta::checked_add: ?
  • [ ] TimeDelta::checked_sub: ?
  • [x] TimeDelta::from_std: #1538
  • [x] TimeDelta::to_std: #1538

DateTime

  • [ ] DateTime::checked_add_days
  • [ ] DateTime::checked_add_months
  • [ ] DateTime::checked_add_signed
  • [ ] DateTime::checked_sub_days
  • [ ] DateTime::checked_sub_months
  • [ ] DateTime::checked_sub_signed
  • [ ] DateTime::date_naive (?)
  • [x] DateTime<Utc>::from_timestamp: #1495
  • [x] DateTime<Utc>::from_timestamp_millis: #1495
  • [x] DateTime<Utc>::from_timestamp_micros: #1495
  • [ ] DateTime::naive_local (?)
  • [ ] DateTime<FixedOffset>::parse_from_rfc2822
  • [ ] DateTime<FixedOffset>::parse_from_rfc3339
  • [ ] DateTime<FixedOffset>::parse_from_str
  • [ ] DateTime<FixedOffset>::parse_and_remainder
  • [x] DateTime::timestamp_nanos: #1495
  • [ ] DateTime::to_rfc2822
  • [ ] DateTime::to_rfc3339
  • [ ] DateTime::with_timezone
  • [ ] DateTime::with_year
  • [ ] DateTime::with_month
  • [ ] DateTime::with_day
  • [ ] DateTime::with_ordinal
  • [ ] DateTime::with_time
  • [ ] DateTime::with_hour
  • [ ] DateTime::with_minute
  • [ ] DateTime::with_second
  • [x] DateTime::with_nanosecond: #1520

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

  • [ ] Parsed::set_ampm: #1511
  • [ ] Parsed::set_day: #1511
  • [ ] Parsed::set_hour: #1511
  • [ ] Parsed::set_hour12: #1511
  • [ ] Parsed::set_isoweek: #1511
  • [ ] Parsed::set_isoyear: #1511
  • [ ] Parsed::set_isoyear_div_100: #1511
  • [ ] Parsed::set_isoyear_mod_100: #1511
  • [ ] Parsed::set_minute: #1511
  • [ ] Parsed::set_month: #1511
  • [ ] Parsed::set_nanosecond: #1511
  • [ ] Parsed::set_offset: #1511
  • [ ] Parsed::set_ordinal: #1511
  • [ ] Parsed::set_second: #1511
  • [ ] Parsed::set_timestamp: #1511
  • [ ] Parsed::set_week_from_mon: #1511
  • [ ] Parsed::set_week_from_sun: #1511
  • [ ] Parsed::set_weekday: #1511
  • [ ] Parsed::set_year: #1511
  • [ ] Parsed::set_year_div_100: #1511
  • [ ] Parsed::set_year_mod_100: #1511
  • [ ] Parsed::to_datetime
  • [ ] Parsed::to_datetime_with_timezone
  • [ ] Parsed::to_fixed_offset
  • [ ] Parsed::to_naive_date
  • [ ] Parsed::to_naive_datetime_with_offset
  • [ ] Parsed::to_naive_time

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

  • [ ] TryFrom<u8> for Weekday: :sun_with_face:
  • [ ] TryFrom<u8> for Month: :sun_with_face:
  • [x] TryFrom<SystemTime> for DateTime<Utc>: #1524
  • [ ] TryFrom<SystemTime> for DateTime<Local>
  • [x] TryFrom<DateTime<Tz>> for SystemTime: #1524
  • [ ] From<DateTime<Utc>> for DateTime<Local>
  • [ ] From<DateTime<FixedOffset>> for DateTime<Local>

pitdicker avatar Feb 28 '24 14:02 pitdicker

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

djc avatar Feb 28 '24 14:02 djc

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

pitdicker avatar Feb 28 '24 14:02 pitdicker

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

djc avatar Feb 28 '24 14:02 djc

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

pitdicker avatar Mar 01 '24 17:03 pitdicker

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

pitdicker avatar Mar 12 '24 14:03 pitdicker

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.

Zomtir avatar Mar 12 '24 14:03 Zomtir

Thank you for everything until now :+1:.

pitdicker avatar Mar 12 '24 15:03 pitdicker

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

Zomtir avatar Mar 12 '24 20:03 Zomtir

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())?;
}

ISibboI avatar Mar 14 '24 15:03 ISibboI

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 avatar Mar 14 '24 16:03 pitdicker

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.

pitdicker avatar Mar 14 '24 19:03 pitdicker

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

Zomtir avatar Apr 06 '24 08:04 Zomtir

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?

pitdicker avatar Apr 06 '24 08:04 pitdicker

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().

Zomtir avatar Apr 06 '24 08:04 Zomtir

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.

pitdicker avatar Apr 06 '24 08:04 pitdicker

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.

Zomtir avatar Apr 06 '24 08:04 Zomtir

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

pitdicker avatar Apr 06 '24 09:04 pitdicker

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.

Zomtir avatar Apr 06 '24 09:04 Zomtir

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)?

pitdicker avatar Apr 06 '24 10:04 pitdicker