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

Deprecate panicking constructors of TimeDelta #1450

Merged
merged 13 commits into from Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
60 changes: 31 additions & 29 deletions src/datetime/tests.rs
Expand Up @@ -53,7 +53,7 @@ impl TimeZone for DstTester {
DstTester::TO_WINTER_MONTH_DAY.1,
)
.unwrap()
.and_time(DstTester::transition_start_local() - TimeDelta::hours(1));
.and_time(DstTester::transition_start_local() - TimeDelta::try_hours(1).unwrap());

let local_to_summer_transition_start = NaiveDate::from_ymd_opt(
local.year(),
Expand All @@ -69,7 +69,7 @@ impl TimeZone for DstTester {
DstTester::TO_SUMMER_MONTH_DAY.1,
)
.unwrap()
.and_time(DstTester::transition_start_local() + TimeDelta::hours(1));
.and_time(DstTester::transition_start_local() + TimeDelta::try_hours(1).unwrap());

if *local < local_to_winter_transition_end || *local >= local_to_summer_transition_end {
LocalResult::Single(DstTester::summer_offset())
Expand Down Expand Up @@ -301,13 +301,13 @@ fn test_nanosecond_range() {
// Just beyond range
let maximum = "2262-04-11T23:47:16.854775804UTC";
let parsed: DateTime<Utc> = maximum.parse().unwrap();
let beyond_max = parsed + TimeDelta::milliseconds(300);
let beyond_max = parsed + TimeDelta::try_milliseconds(300).unwrap();
assert!(beyond_max.timestamp_nanos_opt().is_none());

// Far beyond range
let maximum = "2262-04-11T23:47:16.854775804UTC";
let parsed: DateTime<Utc> = maximum.parse().unwrap();
let beyond_max = parsed + TimeDelta::days(365);
let beyond_max = parsed + Days::new(365);
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, maybe we should resurrect your work on CalendarDuration?

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'd like to. Part 1 is ready for review 😄. Maybe wait a few more weeks until we are good underway with the Result stuff?

assert!(beyond_max.timestamp_nanos_opt().is_none());
}

Expand Down Expand Up @@ -580,12 +580,12 @@ fn test_datetime_offset() {
let dt = Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap();
assert_eq!(dt, edt.with_ymd_and_hms(2014, 5, 6, 3, 8, 9).unwrap());
assert_eq!(
dt + TimeDelta::seconds(3600 + 60 + 1),
dt + TimeDelta::try_seconds(3600 + 60 + 1).unwrap(),
Utc.with_ymd_and_hms(2014, 5, 6, 8, 9, 10).unwrap()
);
assert_eq!(
dt.signed_duration_since(edt.with_ymd_and_hms(2014, 5, 6, 10, 11, 12).unwrap()),
TimeDelta::seconds(-7 * 3600 - 3 * 60 - 3)
TimeDelta::try_seconds(-7 * 3600 - 3 * 60 - 3).unwrap()
);

assert_eq!(*Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap().offset(), Utc);
Expand Down Expand Up @@ -1455,20 +1455,16 @@ fn test_datetime_before_windows_api_limits() {
#[test]
#[cfg(feature = "clock")]
fn test_years_elapsed() {
const WEEKS_PER_YEAR: f32 = 52.1775;

// This is always at least one year because 1 year = 52.1775 weeks.
let one_year_ago =
Utc::now().date_naive() - TimeDelta::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64);
// A bit more than 2 years.
let two_year_ago =
Utc::now().date_naive() - TimeDelta::weeks((WEEKS_PER_YEAR * 2.5).ceil() as i64);
// A bit more than 1 year
let one_year_ago = Utc::now().date_naive() - Days::new(400);
// A bit more than 2 years
let two_year_ago = Utc::now().date_naive() - Days::new(750);

assert_eq!(Utc::now().date_naive().years_since(one_year_ago), Some(1));
assert_eq!(Utc::now().date_naive().years_since(two_year_ago), Some(2));

// If the given DateTime is later than now, the function will always return 0.
let future = Utc::now().date_naive() + TimeDelta::weeks(12);
let future = Utc::now().date_naive() + Days(100);
assert_eq!(Utc::now().date_naive().years_since(future), None);
}

Expand All @@ -1478,20 +1474,20 @@ fn test_datetime_add_assign() {
let datetime = naivedatetime.and_utc();
let mut datetime_add = datetime;

datetime_add += TimeDelta::seconds(60);
assert_eq!(datetime_add, datetime + TimeDelta::seconds(60));
datetime_add += TimeDelta::try_seconds(60).unwrap();
assert_eq!(datetime_add, datetime + TimeDelta::try_seconds(60).unwrap());

let timezone = FixedOffset::east_opt(60 * 60).unwrap();
let datetime = datetime.with_timezone(&timezone);
let datetime_add = datetime_add.with_timezone(&timezone);

assert_eq!(datetime_add, datetime + TimeDelta::seconds(60));
assert_eq!(datetime_add, datetime + TimeDelta::try_seconds(60).unwrap());

let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap();
let datetime = datetime.with_timezone(&timezone);
let datetime_add = datetime_add.with_timezone(&timezone);

assert_eq!(datetime_add, datetime + TimeDelta::seconds(60));
assert_eq!(datetime_add, datetime + TimeDelta::try_seconds(60).unwrap());
}

#[test]
Expand All @@ -1504,8 +1500,8 @@ fn test_datetime_add_assign_local() {

// ensure we cross a DST transition
for i in 1..=365 {
datetime_add += TimeDelta::days(1);
assert_eq!(datetime_add, datetime + TimeDelta::days(i))
datetime_add += TimeDelta::try_days(1).unwrap();
assert_eq!(datetime_add, datetime + TimeDelta::try_days(i).unwrap())
}
}

Expand All @@ -1515,20 +1511,20 @@ fn test_datetime_sub_assign() {
let datetime = naivedatetime.and_utc();
let mut datetime_sub = datetime;

datetime_sub -= TimeDelta::minutes(90);
assert_eq!(datetime_sub, datetime - TimeDelta::minutes(90));
datetime_sub -= TimeDelta::try_minutes(90).unwrap();
assert_eq!(datetime_sub, datetime - TimeDelta::try_minutes(90).unwrap());

let timezone = FixedOffset::east_opt(60 * 60).unwrap();
let datetime = datetime.with_timezone(&timezone);
let datetime_sub = datetime_sub.with_timezone(&timezone);

assert_eq!(datetime_sub, datetime - TimeDelta::minutes(90));
assert_eq!(datetime_sub, datetime - TimeDelta::try_minutes(90).unwrap());

let timezone = FixedOffset::west_opt(2 * 60 * 60).unwrap();
let datetime = datetime.with_timezone(&timezone);
let datetime_sub = datetime_sub.with_timezone(&timezone);

assert_eq!(datetime_sub, datetime - TimeDelta::minutes(90));
assert_eq!(datetime_sub, datetime - TimeDelta::try_minutes(90).unwrap());
}

#[test]
Expand Down Expand Up @@ -1611,7 +1607,10 @@ fn test_min_max_setters() {
assert_eq!(beyond_min.with_ordinal0(beyond_min.ordinal0()), Some(beyond_min));
assert_eq!(beyond_min.with_ordinal0(200), None);
assert_eq!(beyond_min.with_hour(beyond_min.hour()), Some(beyond_min));
assert_eq!(beyond_min.with_hour(23), beyond_min.checked_add_signed(TimeDelta::hours(1)));
assert_eq!(
beyond_min.with_hour(23),
beyond_min.checked_add_signed(TimeDelta::try_hours(1).unwrap())
);
assert_eq!(beyond_min.with_hour(5), None);
assert_eq!(beyond_min.with_minute(0), Some(beyond_min));
assert_eq!(beyond_min.with_second(0), Some(beyond_min));
Expand All @@ -1632,7 +1631,10 @@ fn test_min_max_setters() {
assert_eq!(beyond_max.with_ordinal0(beyond_max.ordinal0()), Some(beyond_max));
assert_eq!(beyond_max.with_ordinal0(200), None);
assert_eq!(beyond_max.with_hour(beyond_max.hour()), Some(beyond_max));
assert_eq!(beyond_max.with_hour(0), beyond_max.checked_sub_signed(TimeDelta::hours(1)));
assert_eq!(
beyond_max.with_hour(0),
beyond_max.checked_sub_signed(TimeDelta::try_hours(1).unwrap())
);
assert_eq!(beyond_max.with_hour(5), None);
assert_eq!(beyond_max.with_minute(beyond_max.minute()), Some(beyond_max));
assert_eq!(beyond_max.with_second(beyond_max.second()), Some(beyond_max));
Expand Down Expand Up @@ -1713,8 +1715,8 @@ fn test_datetime_sub_assign_local() {

// ensure we cross a DST transition
for i in 1..=365 {
datetime_sub -= TimeDelta::days(1);
assert_eq!(datetime_sub, datetime - TimeDelta::days(i))
datetime_sub -= TimeDelta::try_days(1).unwrap();
assert_eq!(datetime_sub, datetime - TimeDelta::try_days(i).unwrap())
}
}

Expand Down
100 changes: 35 additions & 65 deletions src/format/parsed.rs
Expand Up @@ -694,71 +694,15 @@ impl Parsed {
(verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date)
}

(
Some(year),
_,
&Parsed { week_from_sun: Some(week_from_sun), weekday: Some(weekday), .. },
) => {
(Some(year), _, &Parsed { week_from_sun: Some(week), weekday: Some(weekday), .. }) => {
// year, week (starting at 1st Sunday), day of the week
let newyear = NaiveDate::from_yo_opt(year, 1).ok_or(OUT_OF_RANGE)?;
let firstweek = match newyear.weekday() {
Weekday::Sun => 0,
Weekday::Mon => 6,
Weekday::Tue => 5,
Weekday::Wed => 4,
Weekday::Thu => 3,
Weekday::Fri => 2,
Weekday::Sat => 1,
};

// `firstweek+1`-th day of January is the beginning of the week 1.
if week_from_sun > 53 {
return Err(OUT_OF_RANGE);
} // can it overflow?
let ndays = firstweek
+ (week_from_sun as i32 - 1) * 7
+ weekday.num_days_from_sunday() as i32;
let date = newyear
.checked_add_signed(TimeDelta::days(i64::from(ndays)))
.ok_or(OUT_OF_RANGE)?;
if date.year() != year {
return Err(OUT_OF_RANGE);
} // early exit for correct error

let date = resolve_week_date(year, week, weekday, Weekday::Sun)?;
(verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date)
}

(
Some(year),
_,
&Parsed { week_from_mon: Some(week_from_mon), weekday: Some(weekday), .. },
) => {
(Some(year), _, &Parsed { week_from_mon: Some(week), weekday: Some(weekday), .. }) => {
// year, week (starting at 1st Monday), day of the week
let newyear = NaiveDate::from_yo_opt(year, 1).ok_or(OUT_OF_RANGE)?;
let firstweek = match newyear.weekday() {
Weekday::Sun => 1,
Weekday::Mon => 0,
Weekday::Tue => 6,
Weekday::Wed => 5,
Weekday::Thu => 4,
Weekday::Fri => 3,
Weekday::Sat => 2,
};

// `firstweek+1`-th day of January is the beginning of the week 1.
if week_from_mon > 53 {
return Err(OUT_OF_RANGE);
} // can it overflow?
let ndays = firstweek
+ (week_from_mon as i32 - 1) * 7
+ weekday.num_days_from_monday() as i32;
let date = newyear
.checked_add_signed(TimeDelta::days(i64::from(ndays)))
.ok_or(OUT_OF_RANGE)?;
if date.year() != year {
return Err(OUT_OF_RANGE);
} // early exit for correct error

let date = resolve_week_date(year, week, weekday, Weekday::Mon)?;
(verify_ymd(date) && verify_isoweekdate(date) && verify_ordinal(date), date)
}

Expand Down Expand Up @@ -895,7 +839,7 @@ impl Parsed {
59 => {}
// `datetime` is known to be off by one second.
0 => {
datetime -= TimeDelta::seconds(1);
datetime -= TimeDelta::try_seconds(1).unwrap();
}
// otherwise it is impossible.
_ => return Err(IMPOSSIBLE),
Expand Down Expand Up @@ -1038,6 +982,32 @@ impl Parsed {
}
}

/// Create a `NaiveDate` when given a year, week, weekday, and the definition at which day of the
/// week a week starts.
///
/// Returns `IMPOSSIBLE` if `week` is `0` or `53` and the `weekday` falls outside the year.
fn resolve_week_date(
year: i32,
week: u32,
weekday: Weekday,
week_start_day: Weekday,
) -> ParseResult<NaiveDate> {
if week > 53 {
return Err(OUT_OF_RANGE);
}

let first_day_of_year = NaiveDate::from_yo_opt(year, 1).ok_or(OUT_OF_RANGE)?;
// Ordinal of the day at which week 1 starts.
let first_week_start = 1 + week_start_day.num_days_from(first_day_of_year.weekday()) as i32;
// Number of the `weekday`, which is 0 for the first day of the week.
let weekday = weekday.num_days_from(week_start_day) as i32;
let ordinal = first_week_start + (week as i32 - 1) * 7 + weekday;
if ordinal <= 0 {
return Err(IMPOSSIBLE);
}
first_day_of_year.with_ordinal(ordinal as u32).ok_or(IMPOSSIBLE)
}

#[cfg(test)]
mod tests {
use super::super::{IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE};
Expand Down Expand Up @@ -1216,8 +1186,8 @@ mod tests {
assert_eq!(parse!(year: 2000, week_from_mon: 0), Err(NOT_ENOUGH));
assert_eq!(parse!(year: 2000, week_from_sun: 0), Err(NOT_ENOUGH));
assert_eq!(parse!(year: 2000, weekday: Sun), Err(NOT_ENOUGH));
assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Fri), Err(OUT_OF_RANGE));
assert_eq!(parse!(year: 2000, week_from_sun: 0, weekday: Fri), Err(OUT_OF_RANGE));
assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Fri), Err(IMPOSSIBLE));
assert_eq!(parse!(year: 2000, week_from_sun: 0, weekday: Fri), Err(IMPOSSIBLE));
assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Sat), ymd(2000, 1, 1));
assert_eq!(parse!(year: 2000, week_from_sun: 0, weekday: Sat), ymd(2000, 1, 1));
assert_eq!(parse!(year: 2000, week_from_mon: 0, weekday: Sun), ymd(2000, 1, 2));
Expand All @@ -1231,9 +1201,9 @@ mod tests {
assert_eq!(parse!(year: 2000, week_from_mon: 2, weekday: Mon), ymd(2000, 1, 10));
assert_eq!(parse!(year: 2000, week_from_sun: 52, weekday: Sat), ymd(2000, 12, 30));
assert_eq!(parse!(year: 2000, week_from_sun: 53, weekday: Sun), ymd(2000, 12, 31));
assert_eq!(parse!(year: 2000, week_from_sun: 53, weekday: Mon), Err(OUT_OF_RANGE));
assert_eq!(parse!(year: 2000, week_from_sun: 53, weekday: Mon), Err(IMPOSSIBLE));
assert_eq!(parse!(year: 2000, week_from_sun: 0xffffffff, weekday: Mon), Err(OUT_OF_RANGE));
assert_eq!(parse!(year: 2006, week_from_sun: 0, weekday: Sat), Err(OUT_OF_RANGE));
assert_eq!(parse!(year: 2006, week_from_sun: 0, weekday: Sat), Err(IMPOSSIBLE));
assert_eq!(parse!(year: 2006, week_from_sun: 1, weekday: Sun), ymd(2006, 1, 1));

// weekdates: conflicting inputs
Expand Down
16 changes: 6 additions & 10 deletions src/lib.rs
Expand Up @@ -257,16 +257,12 @@
//! // arithmetic operations
//! let dt1 = Utc.with_ymd_and_hms(2014, 11, 14, 8, 9, 10).unwrap();
//! let dt2 = Utc.with_ymd_and_hms(2014, 11, 14, 10, 9, 8).unwrap();
//! assert_eq!(dt1.signed_duration_since(dt2), TimeDelta::seconds(-2 * 3600 + 2));
//! assert_eq!(dt2.signed_duration_since(dt1), TimeDelta::seconds(2 * 3600 - 2));
//! assert_eq!(
//! Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() + TimeDelta::seconds(1_000_000_000),
//! Utc.with_ymd_and_hms(2001, 9, 9, 1, 46, 40).unwrap()
//! );
//! assert_eq!(
//! Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() - TimeDelta::seconds(1_000_000_000),
//! Utc.with_ymd_and_hms(1938, 4, 24, 22, 13, 20).unwrap()
//! );
//! assert_eq!(dt1.signed_duration_since(dt2), TimeDelta::try_seconds(-2 * 3600 + 2).unwrap());
//! assert_eq!(dt2.signed_duration_since(dt1), TimeDelta::try_seconds(2 * 3600 - 2).unwrap());
//! assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() + TimeDelta::try_seconds(1_000_000_000).unwrap(),
//! Utc.with_ymd_and_hms(2001, 9, 9, 1, 46, 40).unwrap());
//! assert_eq!(Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap() - TimeDelta::try_seconds(1_000_000_000).unwrap(),
//! Utc.with_ymd_and_hms(1938, 4, 24, 22, 13, 20).unwrap());
//! ```
//!
//! ### Formatting and Parsing
Expand Down