time-rs / time

The most used Rust library for date and time handling.
https://time-rs.github.io
Apache License 2.0
1.06k stars 261 forks source link

OffsetDateTime lacks an obvious constructor #640

Closed jcdyer closed 6 months ago

jcdyer commented 6 months ago

As evidenced by posts like this: https://www.reddit.com/r/rust/comments/18i8y39/learning_rust_my_experience_so_far_has_been_mixed/, as well as my own experience converting code from chrono to time, the lack of an obvious OffsetDateTime::new makes it more difficult to figure out how to work with the time crate.

Some current workarounds:

Using macros. This is how the docs tend to show creating an OffsetDateTime, but it only works if you have a literal datetime to plug in. You can't use it with a list of integers representing different components, which is how you would want to, say, convert from a datetime gotten from another library or language.

Constructing a PrimitiveDateTime, and adding an offset to it. This is an unsatisfying learning experience, because the user is trying to create an OffsetDateTime, so they aren't expecting the constructor to live on a different type.

PrimitiveDateTime::new(
    Date::from_calendar_date(2023, Month::December, 14)?, 
    Time::from_hms(12, 30, 59)?
).assume_utc()

Create an arbitrary OffsetDateTime, and replace all the components. This also doesn't feel right, because you have to construct the wrong datetime to get to the right one, and you involve a syscall where it isn't necessary. Also, you can get funky results but no error if you forget a component. For instance, if you forget the nanoseconds, you could end up with two values that you expect to be equal that are not.

let dt1 = OffsetDateTime::now_utc()
    .replace_date(date)
    .replace_time(time)
    .replace_offset(offset);
let dt2 = OffsetDateTime::now_utc()
    .replace_year(year)?
    .replace_month(month)?
    .replace_day(day)?
    .replace_hour(hour)?
    .replace_minute(minute)?
    .replace_second(second)?
    .replace_nanosecond(nano)?
    .replace_offset(offset);

I propose adding a constructor:

fn new(date: Date, time: Time, offset: UtcOffset) -> OffsetDateTime {
     PrimitiveDateTime::new(date, time).assume_offset(offset)
}

and perhaps also a shortcut for utc, since that seems useful and common in time's api:

fn new_utc(date: Date, time: Time) -> OffsetDateTime {
     PrimitiveDateTime::new(date, time).assume_utc()
}

This still requires the user to know how to create a Date and a Time, but at least the references to those types are present in the new documentation, which is where they would expect it, and example code on the OffsetDateTime constuctors could show how to create them without requiring the user to navigate to the other types.