From 70da84930d7298bb35f0f063bf23459d0c63bb10 Mon Sep 17 00:00:00 2001 From: Rafael de Conde Date: Sat, 26 Nov 2022 19:44:53 -0300 Subject: [PATCH 1/2] add Years to mod tree & exploring a simple impl I started a simple implementation that I intend to add proper add checks once the problem is actually solved. --- src/lib.rs | 5 +++ src/years.rs | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 src/years.rs diff --git a/src/lib.rs b/src/lib.rs index 861ee10593..91d57e46e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -492,6 +492,11 @@ pub use weekday::{ParseWeekdayError, Weekday}; mod month; pub use month::{Month, Months, ParseMonthError}; +// I'm not entirely sure too this should be here, because months +// was implemented at this level and Days at naive level +// I believe here might be better since it should help avoid coupling +mod years; // add pub uses + mod traits; pub use traits::{Datelike, Timelike}; diff --git a/src/years.rs b/src/years.rs new file mode 100644 index 0000000000..3bebecb239 --- /dev/null +++ b/src/years.rs @@ -0,0 +1,115 @@ +// I usually develop with this clippy flags +// but I can remove once the this module is done +#![warn(clippy::all, +/*#![warn(*/clippy::pedantic, + clippy::perf, + clippy::nursery, + // clippy::cargo, + clippy::unwrap_used, + clippy::expect_used)] + +use num_integer::Integer; + +use super::{datetime::DateTime, NaiveDate, NaiveDateTime}; +use crate::traits::Datelike; +use crate::TimeZone; + +struct Years(u32); // I believe the fields of the struct should be private + +impl Years { + fn new(num_years: u32) -> Years { + Self(num_years) + } +} + +// would to the same for all the others +impl DateTime { + pub fn checked_add_years(self, num_years: Years) -> Option> {} + + pub fn checked_sub_years(self, num_years: Years) -> Option> {} +} + +impl NaiveDateTime { + pub fn checked_add_years(self, num_years: Years) -> Option {} + + pub fn checked_sub_years(self, num_years: Years) -> Option {} +} + +// think about a better logic to use `checked_add` +// currently trying to finish a simple implementation +// is it ok to assum it'll only receive positive numbers? I think so +fn get_current_or_next_leap_year(year: u32) -> u32 { + if !year.is_even() { + year += 1; + } + while !is_leap_year(year) { + year += 1; + } + year +} + +// almost surely there is a more precise way to implement this +fn is_leap_year(year: i32) -> bool { + if (year % 4) == 0 { + if (year % 100) != 0 { + true + } else { + // if divisible by 100 but also divisible by 400 than is leap year + (year % 400) == 0 + } + } else { + false + } +} + +impl NaiveDate { + pub fn checked_add_years(self, years: Years) -> Option { + if years.0 == 0 { + return Some(self); + } + let current_year = self.year(); + let current_month = self.month(); + let next_leap_year = get_current_or_next_leap_year(current_year); + let total_amount_of_days; + if years.0 < (next_leap_year - current_year) { + total_amount_of_days = years.0 * 365; + //use this to transform a years addition into the proper addition of days + // not sure this is the best implementation + // but seems robust since it would rely on the top level implementation + } + } + + pub fn checked_sub_years(self, num_years: Years) -> Option {} +} + +// impl Add for NaiveDate {} +// +// impl Add for NaiveDateTime {} +// +// impl Add for DateTime {} +// +// impl Sub For NaiveDate {} +// +// impl Sub for NaiveDateTime {} +// +// impl Sub For DateTime {} + +#[cfg(test)] +mod tests { + use super::is_leap_year; + + #[test] + fn not_leap_year() { + assert!(is_leap_year(2022) == false) + } + + #[test] + fn leap_year() { + assert!(is_leap_year(2020) == true) + } + + #[test] + fn leap_year_mod_400() { + assert!(is_leap_year(2400) == true); + } +} From b6167de7b55e25bfd1c626cdcdb96ab08f8486e3 Mon Sep 17 00:00:00 2001 From: Rafael de Conde Date: Sat, 17 Dec 2022 19:39:17 -0300 Subject: [PATCH 2/2] finished impl, changed code position & clean up --- src/date.rs | 12 ++++ src/datetime/mod.rs | 12 ++++ src/lib.rs | 1 + src/naive/date.rs | 35 ++++++++++++ src/naive/datetime/mod.rs | 12 ++++ src/years.rs | 117 ++++++++++---------------------------- 6 files changed, 102 insertions(+), 87 deletions(-) diff --git a/src/date.rs b/src/date.rs index bad4bfbb8a..177a17365e 100644 --- a/src/date.rs +++ b/src/date.rs @@ -23,6 +23,8 @@ use crate::oldtime::Duration as OldDuration; use crate::DateTime; use crate::{Datelike, Weekday}; +use crate::Years; + /// ISO 8601 calendar date with time zone. /// /// You almost certainly want to be using a [`NaiveDate`] instead of this type. @@ -368,6 +370,16 @@ where ) -> DelayedFormat> { self.format_localized_with_items(StrftimeItems::new_with_locale(fmt, locale), locale) } + + pub fn checked_add_years(mut self, num_years: Years) -> Option> { + self.date = self.date.checked_add_years(num_years)?; + Some(self) + } + + pub fn checked_sub_years(mut self, num_years: Years) -> Option> { + self.date = self.date.checked_sub_years(num_years)?; + Some(self) + } } impl Datelike for Date { diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 05d0458e84..a7d8ea2d94 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -35,6 +35,8 @@ use crate::Date; use crate::Months; use crate::{Datelike, Timelike, Weekday}; +use crate::Years; + #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; @@ -743,6 +745,16 @@ where ) -> DelayedFormat> { self.format_localized_with_items(StrftimeItems::new_with_locale(fmt, locale), locale) } + + pub fn checked_add_years(mut self, num_years: Years) -> Option> { + self.datetime = self.datetime.checked_add_years(num_years)?; + Some(self) + } + + pub fn checked_sub_years(mut self, num_years: Years) -> Option> { + self.datetime = self.datetime.checked_sub_years(num_years)?; + Some(self) + } } impl Datelike for DateTime { diff --git a/src/lib.rs b/src/lib.rs index 91d57e46e0..37fc94d57b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -495,6 +495,7 @@ pub use month::{Month, Months, ParseMonthError}; // I'm not entirely sure too this should be here, because months // was implemented at this level and Days at naive level // I believe here might be better since it should help avoid coupling +pub use years::Years; mod years; // add pub uses mod traits; diff --git a/src/naive/date.rs b/src/naive/date.rs index 3734460713..b445ad994c 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -27,6 +27,8 @@ use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; use crate::oldtime::Duration as OldDuration; use crate::{Datelike, Duration, Weekday}; +use crate::Years; + use super::internals::{self, DateImpl, Mdf, Of, YearFlags}; use super::isoweek; @@ -1248,6 +1250,23 @@ impl NaiveDate { NaiveWeek { date: *self, start } } + // I think self here is a copy, but I still have to make sure it's + pub fn checked_add_years(self, years: Years) -> Option { + if years.0 == 0 { + return Some(self); + } + let num_years = i32::try_from(years.0).ok()?; + self.with_year(self.year() + num_years) + } + + pub fn checked_sub_years(self, years: Years) -> Option { + if years.0 == 0 { + return Some(self); + } + let num_years = i32::try_from(years.0).ok()?; + self.with_year(self.year() - num_years) + } + /// The minimum possible `NaiveDate` (January 1, 262145 BCE). pub const MIN: NaiveDate = NaiveDate { ymdf: (MIN_YEAR << 13) | (1 << 4) | 0o07 /*FE*/ }; /// The maximum possible `NaiveDate` (December 31, 262143 CE). @@ -1687,6 +1706,22 @@ impl Sub for NaiveDate { } } +impl Add for NaiveDate { + type Output = NaiveDate; + + fn add(self, years: Years) -> Self::Output { + self.checked_add_years(years).unwrap() + } +} + +impl Sub for NaiveDate { + type Output = NaiveDate; + + fn sub(self, years: Years) -> Self::Output { + self.checked_sub_years(years).unwrap() + } +} + impl Add for NaiveDate { type Output = NaiveDate; diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index c34813f10b..b2ec613966 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -24,6 +24,8 @@ use crate::oldtime::Duration as OldDuration; use crate::{DateTime, Datelike, LocalResult, Months, TimeZone, Timelike, Weekday}; use core::cmp::Ordering; +use crate::Years; + #[cfg(feature = "rustc-serialize")] pub(super) mod rustc_serialize; @@ -927,6 +929,16 @@ impl NaiveDateTime { } } } + + pub fn checked_add_years(mut self, num_years: Years) -> Option { + self.date = self.date.checked_add_years(num_years)?; + Some(self) + } + + pub fn checked_sub_years(mut self, num_years: Years) -> Option { + self.date = self.date.checked_sub_years(num_years)?; + Some(self) + } } impl Datelike for NaiveDateTime { diff --git a/src/years.rs b/src/years.rs index 3bebecb239..f16755f615 100644 --- a/src/years.rs +++ b/src/years.rs @@ -8,108 +8,51 @@ clippy::unwrap_used, clippy::expect_used)] -use num_integer::Integer; - -use super::{datetime::DateTime, NaiveDate, NaiveDateTime}; -use crate::traits::Datelike; -use crate::TimeZone; - -struct Years(u32); // I believe the fields of the struct should be private +/// TODO +#[derive(Debug)] +pub struct Years(pub(crate) u32); // I believe the fields of the struct should be private impl Years { - fn new(num_years: u32) -> Years { + /// TODO + pub fn new(num_years: u32) -> Years { Self(num_years) } } -// would to the same for all the others -impl DateTime { - pub fn checked_add_years(self, num_years: Years) -> Option> {} - - pub fn checked_sub_years(self, num_years: Years) -> Option> {} -} - -impl NaiveDateTime { - pub fn checked_add_years(self, num_years: Years) -> Option {} - - pub fn checked_sub_years(self, num_years: Years) -> Option {} -} - -// think about a better logic to use `checked_add` -// currently trying to finish a simple implementation -// is it ok to assum it'll only receive positive numbers? I think so -fn get_current_or_next_leap_year(year: u32) -> u32 { - if !year.is_even() { - year += 1; - } - while !is_leap_year(year) { - year += 1; - } - year -} - -// almost surely there is a more precise way to implement this -fn is_leap_year(year: i32) -> bool { - if (year % 4) == 0 { - if (year % 100) != 0 { - true - } else { - // if divisible by 100 but also divisible by 400 than is leap year - (year % 400) == 0 - } - } else { - false - } -} - -impl NaiveDate { - pub fn checked_add_years(self, years: Years) -> Option { - if years.0 == 0 { - return Some(self); - } - let current_year = self.year(); - let current_month = self.month(); - let next_leap_year = get_current_or_next_leap_year(current_year); - let total_amount_of_days; - if years.0 < (next_leap_year - current_year) { - total_amount_of_days = years.0 * 365; - //use this to transform a years addition into the proper addition of days - // not sure this is the best implementation - // but seems robust since it would rely on the top level implementation - } - } - - pub fn checked_sub_years(self, num_years: Years) -> Option {} -} - -// impl Add for NaiveDate {} -// -// impl Add for NaiveDateTime {} -// -// impl Add for DateTime {} -// -// impl Sub For NaiveDate {} -// -// impl Sub for NaiveDateTime {} -// -// impl Sub For DateTime {} - #[cfg(test)] mod tests { - use super::is_leap_year; + use crate::NaiveDate; + use crate::Years; + + #[test] + fn checked_adding_years() { + assert_eq!( + NaiveDate::from_ymd_opt(2023, 2, 28).unwrap(), + NaiveDate::from_ymd_opt(2020, 2, 28).unwrap().checked_add_years(Years::new(3)).unwrap() + ); + } #[test] - fn not_leap_year() { - assert!(is_leap_year(2022) == false) + fn using_add_operator() { + assert_eq!( + NaiveDate::from_ymd_opt(2023, 2, 28).unwrap(), + NaiveDate::from_ymd_opt(2020, 2, 28).unwrap() + Years::new(3) + ); } #[test] - fn leap_year() { - assert!(is_leap_year(2020) == true) + fn checked_subtracting_years() { + assert_eq!( + NaiveDate::from_ymd_opt(2017, 2, 28).unwrap(), + NaiveDate::from_ymd_opt(2020, 2, 28).unwrap().checked_sub_years(Years::new(3)).unwrap() + ); } #[test] - fn leap_year_mod_400() { - assert!(is_leap_year(2400) == true); + fn using_sub_operator() { + assert_eq!( + NaiveDate::from_ymd_opt(2017, 2, 28).unwrap(), + NaiveDate::from_ymd_opt(2020, 2, 28).unwrap() - Years::new(3) + ); } }