-
Notifications
You must be signed in to change notification settings - Fork 533
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
Make most constructors fallible (relates #815) #817
Conversation
f3625b5
to
42236f0
Compare
Here's a brief guide to how the new API's are used: Note that most (if not all) Methods on pub trait TimeZone: Sized + Clone {
type Offset: Offset;
fn ymd(&self, year: i32, month: u32, day: u32) -> Result<LocalResult<Date<Self>>, Error>;
fn yo(&self, year: i32, ordinal: u32) -> Result<LocalResult<Date<Self>>, Error>;
fn isoywd(&self, year: i32, week: u32, weekday: Weekday) -> Result<LocalResult<Date<Self>>, Error>;
fn timestamp(&self, secs: i64, nsecs: u32) -> Result<DateTime<Self>, Error>;
fn timestamp_millis(&self, millis: i64) -> Result<DateTime<Self>, Error>;
fn timestamp_nanos(&self, nanos: i64) -> Result<DateTime<Self>, Error>;
fn datetime_from_str(&self, s: &str, fmt: &str) -> ParseResult<DateTime<Self>>;
fn from_offset(offset: &Self::Offset) -> Self;
fn offset_from_local_date(&self, local: &NaiveDate) -> Result<LocalResult<Self::Offset>, Error>;
fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> Result<LocalResult<Self::Offset>, Error>;
fn from_local_date(&self, local: &NaiveDate) -> Result<LocalResult<Date<Self>>, Error>;
fn from_local_datetime(&self, local: &NaiveDateTime) -> Result<LocalResult<DateTime<Self>>, Error>;
fn offset_from_utc_date(&self, utc: &NaiveDate) -> Result<Self::Offset, Error>;
fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> Result<Self::Offset, Error>;
fn from_utc_date(&self, utc: &NaiveDate) -> Result<Date<Self>, Error>;
fn from_utc_datetime(&self, utc: &NaiveDateTime) -> Result<DateTime<Self>, Error>;
} To cover some of the shortfall, the following trait is defined which is implemented for pub trait FixedTimeZone: TimeZone {
fn offset_from_utc_date_fixed(&self, utc: &NaiveDate) -> Self::Offset;
fn offset_from_utc_datetime_fixed(&self, utc: &NaiveDateTime) -> Self::Offset;
fn from_utc_date_fixed(&self, utc: &NaiveDate) -> Date<Self>;
fn from_utc_datetime_fixed(&self, utc: &NaiveDateTime) -> DateTime<Self>;
} With this impl<Tz: TimeZone> Date<Tz> {
pub fn with_fixed_timezone<Tz2: FixedTimeZone>(&self, tz: &Tz2) -> Date<Tz2>;
}
impl<Tz: TimeZone> DateTime<Tz> {
pub fn with_fixed_timezone<Tz2: FixedTimeZone>(&self, tz: &Tz2) -> DateTime<Tz2>;
} Note that it would be possible to evolve The following changes have been done to pub trait Datelike: Sized {
fn with_year(&self, year: i32) -> Result<Self, Error>;
fn with_month(&self, month: u32) -> Result<Self, Error>;
fn with_month0(&self, month0: u32) -> Result<Self, Error>;
fn with_day(&self, day: u32) -> Result<Self, Error>;
fn with_day0(&self, day0: u32) -> Result<Self, Error>;
fn with_ordinal(&self, ordinal: u32) -> Result<Self, Error>;
fn with_ordinal0(&self, ordinal0: u32) -> Result<Self, Error>;
} The following changes have been done to to pub trait TimeLike: Sized {
fn with_hour(&self, hour: u32) -> Result<Self, Error>;
fn with_minute(&self, min: u32) -> Result<Self, Error>;
fn with_second(&self, sec: u32) -> Result<Self, Error>;
fn with_nanosecond(&self, nano: u32) -> Result<Self, Error>;
} The following methods have been changed on impl<Tz: TimeZone> DateTime<Tz> {
pub fn with_timezone<Tz2: TimeZone>(&self, tz: &Tz2) -> Result<DateTime<Tz2>, Error>;
} The following methods have been changed on impl<Tz: TimeZone> Date<Tz> {
pub fn and_time(&self, time: NaiveTime) -> Result<DateTime<Tz>, Error>;
pub fn and_hms(&self, hour: u32, min: u32, sec: u32) -> Result<DateTime<Tz>, Error>;
pub fn and_hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> Result<DateTime<Tz>, Error>;
pub fn and_hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> Result<DateTime<Tz>, Error>;
pub fn and_hms_nano(&self, hour: u32, min: u32, sec: u32, nano: u32) -> Result<DateTime<Tz>, Error>;
pub fn succ(&self) -> Result<Date<Tz>, Error>;
pub fn pred(&self) -> Result<Date<Tz>, Error>;
pub fn with_timezone<Tz2: TimeZone>(&self, tz: &Tz2) -> Result<Date<Tz2>, Error>;
} The following methods have been changed/added on impl<T> LocalResult<T> {
pub fn single(self) -> Result<T, Error>;
pub fn earliest(self) -> T;
pub fn latest(self) -> T;
pub fn try_map<U, E, F: FnMut(T) -> Result<U, E>>(self, mut f: F) -> Result<LocalResult<U>, E>;
} Changes to impl FixedOffset {
pub fn east(secs: i32) -> Result<FixedOffset, Error>;
pub fn west(secs: i32) -> Result<FixedOffset, Error>;
} |
Potentially we could include the error as an associated type (potentially with default) in the trait, such that implementors can specify |
Here are some thoughts/suggestions so far (haven't dug into the code yet):
|
Are you suggesting this applies to all methods that have been made fallible or just the ones associated with @esheppa This also relates to your suggestion since you wanted a generic error type. All though I'm not super happy with
Many things could be considered out of range, so what specific standard do you have in mind to apply here? I'm tentative towards considering invalid dates to be out of range (even though they are defined to be outside EDIT: Also want to emphasize that I have no opinions on naming, so I'll adopt whatever naming changes are being suggested. |
Also since this is a big and involved PR, if you feel like you can quickly just make the necessary changes to merge this don't feel obliged to block on me. So feel free to just make the changes and merge. |
Sorry, I have way too much on my plate in terms of maintenance (as well as moving to a new place with my family) so I'm all too happy having you take care of the code changes here. But, maybe to make this easier to digest, it might be worth splitting it up in chunks, roughly along the lines that you presented in your comment? I think many of these changes are pretty independent so it might be nice to iron out some of the non-controversial stuff first and work from there. |
Don't think this is easily achievable. Most of the PR essentially flowed from "what if I do the changes to An alternative approach could be that you don't necessarily have to reach the perfect end result in this PR, but can work on changing it to what you want for release in subsequent ones. Which is easy enough to apply to changes like naming and moving things around. Anyway, I'll do any concrete changes you're requesting to the best of my ability. |
Ok, unless I'm missing something I've made the concrete changes which are no longer being discussed. The remaining considerations are: @esheppa Proposes a change to @djc Proposed to consider using Some markdown reflow is still present, but hopefully it's only in the odd API documentation and shouldn't be too much of a bother. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reviewed the date
module. It looks like several unrelated changes have been made which I'd like to have reverted in order to keep this PR as a focused change, making it easier to review only the changes necessary to making everything fallible. As such, any other changes to the code style/structure should be avoided.
@@ -70,6 +69,11 @@ pub const MIN_DATE: Date<Utc> = Date::<Utc>::MIN_UTC; | |||
pub const MAX_DATE: Date<Utc> = Date::<Utc>::MAX_UTC; | |||
|
|||
impl<Tz: TimeZone> Date<Tz> { | |||
/// The minimum possible `Date`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like an unrelated change.
pub fn and_hms(&self, hour: u32, min: u32, sec: u32) -> DateTime<Tz> { | ||
self.and_hms_opt(hour, min, sec).expect("invalid time") | ||
pub fn and_time(&self, time: NaiveTime) -> Result<DateTime<Tz>, Error> { | ||
let dt = self.naive_local().and_time(time); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please keep the localdt
variable name here to avoid unrelated changes.
pub fn and_hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> DateTime<Tz> { | ||
self.and_hms_milli_opt(hour, min, sec, milli).expect("invalid time") | ||
pub fn and_hms(&self, hour: u32, min: u32, sec: u32) -> Result<DateTime<Tz>, Error> { | ||
let time = NaiveTime::from_hms(hour, min, sec)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would prefer to avoid the intermediate time
binding here, similar to the previous structure of and_hms_opt()
.
pub fn and_hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> DateTime<Tz> { | ||
self.and_hms_micro_opt(hour, min, sec, micro).expect("invalid time") | ||
) -> Result<DateTime<Tz>, Error> { | ||
let time = NaiveTime::from_hms_milli(hour, min, sec, milli)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And here.
let date = self.date.checked_add_signed(rhs)?; | ||
Some(Date { date, offset: self.offset }) | ||
Some(Self { date, offset: self.offset }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although I am in favor of the Date<Tz>
-> Self
change, I'd prefer to move that to a separate PR to limit the scope in this PR.
So this might be why it's better for a maintainer do these kind of large changes. I'm sorry, but I don't have the time to put in work to undo stylistic changes that slipped through due to the size of this change, so unless there's a good reason (e.g. there's an obvious non-improvement) I can't do that for you. I understand if that doesn't work for you so feel free to close the PR and maybe this can be done at a later time. I'll hold off resolving the conflict for now until we've sorted this out. |
I'm sorry that you feel this is an unreasonable request. I understand that it may take some time and it is not fun work to do (but then the same is true for making the changes in this PR in the first place, to large extent). Personally I feel that it is generally reasonable to reduce time spent by the maintainer (that is, by making changes optimally easy to review) at the cost of requiring contributors to put in more time, given that maintainers are required to spend much more time, and their time is (within this particular context) "more valuable". (I am aware that you also maintain a whole bunch of projects -- although maybe you aren't as strict with contributors, so there's a different balance?) I don't maintain chrono because I find the job particularly fun -- I do it because it needs to be done, and at the time I was the only person willing to put in the time and energy. I hope you're willing to reconsider so that I can spend my time elsewhere (for example, by making similar changes in 0.4.x to deprecate non- |
Easiest way to get rid of the unwanted stylistic changes is is by restoring the index and adding only the wanted changes:
This would take a while, but it's a matter of picking y/n for each change. Do remember to keep authorship information in the new commit. |
The request to undo |
Unfortunately many of the changes do not neatly align to diff hunk boundaries, so it's not as easy as this. That said, if you want to contribute, that would be great! |
FWIW, I would prefer |
All right. I'll close for now and re-open if I ever have the time to redo the PR so it conforms to expectation. If anyone else wants to pick it up, feel free. |
- Creates a global Error enum - Breaks backwards compatiblility mainly because of promoting fallable functions (chronotope#263) - Some tests still fall - Not all doctests are fixed - to_naive_datetime_with_offset function is broken and needs fixing - serde related stuff is not checked properly This is a rebase of chronotope#817 onto the 0.5 main branch. Main differences: - Unify three different error structs - Removed ErrorKind - Adapted a lot of unit tests - Removed some commits from presumably unrelated branches (chronotope#829) or mainlined commits (chronotope#271) Co-authored-by: John-John Tedro <udoprog@tedro.se>
most of this message is copied from this comment
This is just a suggestion for changes to accomplish what's being proposed in #815. Consider it heavily WIP and any naming is preliminary.
I've opted for a single
ChronoError
type which has a privatekind
field that determines the message presented to the user (but is not public). I initially started with one error type per kind of error, but eventually noticed that it would make propagating errors overly hard like how constructing a DateTime either raises an error because the date or the time was invalid.I've opted to make loading the local timezone fallible instead of panicking. I think this is motivated because technically the underlying platform might perform checks which might fail all though what these look like exactly is a bit unclear. These kind of errors are represented through
ChronoErrorKind::SystemError(std::io::Error)
.This has the side effect of making a number of TimeZone methods fallible - which is unfortunate because a number of timezones can easily be interacted with without erroring (
Utc
andFixedOffset
). To mitigate this I've introduced a separateFixedTimeZone
trait that extends the TimeZone trait which has a number of non-fallible methods. Note that this could also in theory have a suite of methods which do not produceLocalResult
values, because the time zone used is never ambiguous.FixedOffset::{east, west}
now returns an error if the provided values is outside of a 24-hour offset. This might legitimately be a case that we are OK with panicking for instead.Making things fallible has the effect of clearly marking the standard trait implementations which might panic, these are:
From<T>
implementations which interacts with the Local timezone, because this doesn't implement FixedTimeZone. Where appropriate these have been moved to be `TryFrom<Error = ChronoError> implementations.The last big change is that
LocalResult::None
has been removed in favor of handling the absent case as an error in theunix
implementation. This has the nice side effect thatLocalResult::earlest
andLocalResult::latest
are neither fallible nor needs to return anOption<T>
.