Skip to content

Commit

Permalink
Add warning against combining multiple Datelike::with_*
Browse files Browse the repository at this point in the history
  • Loading branch information
pitdicker committed Jul 24, 2023
1 parent 4741d2c commit b14ce4c
Showing 1 changed file with 80 additions and 0 deletions.
80 changes: 80 additions & 0 deletions src/traits.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,32 @@
use crate::{IsoWeek, Weekday};

/// The common set of methods for date component.
///
/// Methods such as [`year`], [`month`], [`day`] and [`weekday`] can be used to get basic
/// information about the date.
///
/// The `with-*` methods can change the date.
///
/// # Warning
///
/// The `with-*` methods can be convenient to change a single component of a date. But use them
/// with some care. Examples to watch out for:
/// - [`with_year`] changes the year component of a year-month-day value. Don't use this method if
/// you want the ordinal to stay the same after changing the year, of if you want the week and
/// weekday values to stay the same.
/// - Don't combine two `with_*` methods to change two components of the date. For example to
/// change both the year and month components of a date. This could fail because an intermediate
/// value does not exist, while the final date would be valid.
///
/// For more complex changes to a date, it is best to use the methods on [`NaiveDate`] to create a
/// new value instead of altering an existing date.
///
/// [`year`]: Datelike::year
/// [`month`]: Datelike::month
/// [`day`]: Datelike::day
/// [`weekday`]: Datelike::weekday
/// [`with_year`]: Datelike::with_year
/// [`NaiveDate`]: crate::NaiveDate
pub trait Datelike: Sized {
/// Returns the year number in the [calendar date](./naive/struct.NaiveDate.html#calendar-date).
fn year(&self) -> i32;
Expand Down Expand Up @@ -55,6 +81,10 @@ pub trait Datelike: Sized {

/// Makes a new value with the year number changed, while keeping the same month and day.
///
/// This method assumes you want to work on the date as a year-month-day value. Don't use it if
/// you want the ordinal to stay the same after changing the year, of if you want the week and
/// weekday values to stay the same.
///
/// # Errors
///
/// Returns `None` when:
Expand All @@ -65,6 +95,25 @@ pub trait Datelike: Sized {
///
/// [`NaiveDate`]: crate::NaiveDate
/// [`DateTime<Tz>`]: crate::DateTime
///
/// # Examples
///
/// ```
/// use chrono::{NaiveDate, Datelike};
///
/// assert_eq!(
/// NaiveDate::from_ymd_opt(2020, 5, 13).unwrap().with_year(2023).unwrap(),
/// NaiveDate::from_ymd_opt(2023, 5, 13).unwrap()
/// );
/// // Resulting date 2023-02-29 does not exist:
/// assert!(NaiveDate::from_ymd_opt(2020, 2, 29).unwrap().with_year(2023).is_none());
///
/// // Don't use `with_year` if you want the ordinal date to stay the same:
/// assert_ne!(
/// NaiveDate::from_yo_opt(2020, 100).unwrap().with_year(2023).unwrap(),
/// NaiveDate::from_yo_opt(2023, 100).unwrap() // result is 2023-101
/// );
/// ```
fn with_year(&self, year: i32) -> Option<Self>;

/// Makes a new value with the month number (starting from 1) changed.
Expand All @@ -78,6 +127,37 @@ pub trait Datelike: Sized {
/// - The value for `month` is out of range.
///
/// [`DateTime<Tz>`]: crate::DateTime
///
/// # Examples
///
/// ```
/// use chrono::{NaiveDate, Datelike};
///
/// assert_eq!(
/// NaiveDate::from_ymd_opt(2023, 5, 12).unwrap().with_month(9).unwrap(),
/// NaiveDate::from_ymd_opt(2023, 9, 12).unwrap()
/// );
/// // Resulting date 2023-09-31 does not exist:
/// assert!(NaiveDate::from_ymd_opt(2023, 5, 31).unwrap().with_month(9).is_none());
/// ```
///
/// Don't combine multiple `Datelike::with_*` methods. The intermediate value may not exist.
/// ```
/// use chrono::{NaiveDate, Datelike};
///
/// fn with_year_month(date: NaiveDate, year: i32, month: u32) -> Option<NaiveDate> {
/// date.with_year(year)?.with_month(month)
/// }
/// let d = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap();
/// assert!(with_year_month(d, 2019, 1).is_none()); // fails because of invalid intermediate value
///
/// // Correct version:
/// fn with_year_month_fixed(date: NaiveDate, year: i32, month: u32) -> Option<NaiveDate> {
/// NaiveDate::from_ymd_opt(year, month, date.day())
/// }
/// let d = NaiveDate::from_ymd_opt(2020, 2, 29).unwrap();
/// assert_eq!(with_year_month_fixed(d, 2019, 1), NaiveDate::from_ymd_opt(2019, 1, 29));
/// ```
fn with_month(&self, month: u32) -> Option<Self>;

/// Makes a new value with the month number (starting from 0) changed.
Expand Down

0 comments on commit b14ce4c

Please sign in to comment.