diff --git a/src/types.rs b/src/types.rs index ecf7d531..e95e3293 100644 --- a/src/types.rs +++ b/src/types.rs @@ -235,7 +235,7 @@ impl DateTime { /// The bounds are: /// * year: [1980, 2107] /// * month: [1, 12] - /// * day: [1, 31] + /// * day: [1, 28..=31] /// * hour: [0, 23] /// * minute: [0, 59] /// * second: [0, 58] @@ -247,6 +247,10 @@ impl DateTime { minute: u8, second: u8, ) -> Result { + fn is_leap_year(year: u16) -> bool { + (year % 4 == 0) && ((year % 25 != 0) || (year % 16 == 0)) + } + if (1980..=2107).contains(&year) && (1..=12).contains(&month) && (1..=31).contains(&day) @@ -254,6 +258,16 @@ impl DateTime { && minute <= 59 && second <= 58 { + let max_day = match month { + 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31, + 4 | 6 | 9 | 11 => 30, + 2 if is_leap_year(year) => 29, + 2 => 28, + _ => unreachable!(), + }; + if day > max_day { + return Err(DateTimeRangeError); + } Ok(DateTime { year, month, @@ -1179,6 +1193,31 @@ mod test { assert!(DateTime::from_date_and_time(2108, 12, 31, 0, 0, 0).is_err()); assert!(DateTime::from_date_and_time(2107, 13, 31, 0, 0, 0).is_err()); assert!(DateTime::from_date_and_time(2107, 12, 32, 0, 0, 0).is_err()); + + assert!(DateTime::from_date_and_time(2018, 1, 31, 0, 0, 0).is_ok()); + assert!(DateTime::from_date_and_time(2018, 2, 28, 0, 0, 0).is_ok()); + assert!(DateTime::from_date_and_time(2018, 2, 29, 0, 0, 0).is_err()); + assert!(DateTime::from_date_and_time(2018, 3, 31, 0, 0, 0).is_ok()); + assert!(DateTime::from_date_and_time(2018, 4, 30, 0, 0, 0).is_ok()); + assert!(DateTime::from_date_and_time(2018, 4, 31, 0, 0, 0).is_err()); + assert!(DateTime::from_date_and_time(2018, 5, 31, 0, 0, 0).is_ok()); + assert!(DateTime::from_date_and_time(2018, 6, 30, 0, 0, 0).is_ok()); + assert!(DateTime::from_date_and_time(2018, 6, 31, 0, 0, 0).is_err()); + assert!(DateTime::from_date_and_time(2018, 7, 31, 0, 0, 0).is_ok()); + assert!(DateTime::from_date_and_time(2018, 8, 31, 0, 0, 0).is_ok()); + assert!(DateTime::from_date_and_time(2018, 9, 30, 0, 0, 0).is_ok()); + assert!(DateTime::from_date_and_time(2018, 9, 31, 0, 0, 0).is_err()); + assert!(DateTime::from_date_and_time(2018, 10, 31, 0, 0, 0).is_ok()); + assert!(DateTime::from_date_and_time(2018, 11, 30, 0, 0, 0).is_ok()); + assert!(DateTime::from_date_and_time(2018, 11, 31, 0, 0, 0).is_err()); + assert!(DateTime::from_date_and_time(2018, 12, 31, 0, 0, 0).is_ok()); + + // leap year: divisible by 4 + assert!(DateTime::from_date_and_time(2024, 2, 29, 0, 0, 0).is_ok()); + // leap year: divisible by 100 and by 400 + assert!(DateTime::from_date_and_time(2000, 2, 29, 0, 0, 0).is_ok()); + // common year: divisible by 100 but not by 400 + assert!(DateTime::from_date_and_time(2100, 2, 29, 0, 0, 0).is_err()); } #[cfg(feature = "time")]