From 3660f0608e25cf0c48561a5c89f4fa080a0eb564 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sat, 16 Sep 2017 10:47:03 +0200 Subject: Add end-of-month type/calc Adds util module --- src/error.rs | 5 ++++ src/lib.rs | 1 + src/timetype.rs | 88 ++++++++++++++++++++++++++++++++++++++++++++++++--------- src/util.rs | 36 +++++++++++++++++++++++ 4 files changed, 116 insertions(+), 14 deletions(-) create mode 100644 src/util.rs diff --git a/src/error.rs b/src/error.rs index 00fad98..4d4a0af 100644 --- a/src/error.rs +++ b/src/error.rs @@ -38,6 +38,11 @@ error_chain! { display("Argument Error: Cannot calculate end-of-year on a {:?}", tt) } + CannotCalculateEndOfMonthOn(tt: TimeType) { + description("Argument Error: Cannot calculate end-of-month") + display("Argument Error: Cannot calculate end-of-month on a {:?}", tt) + } + } } diff --git a/src/lib.rs b/src/lib.rs index 2f59f3f..624ce64 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,4 +6,5 @@ pub mod error; pub mod iter; pub mod result; pub mod timetype; +mod util; diff --git a/src/timetype.rs b/src/timetype.rs index 67c9229..961a9b3 100644 --- a/src/timetype.rs +++ b/src/timetype.rs @@ -15,6 +15,7 @@ use result::Result; use error::KairosErrorKind as KEK; use error::KairosError as KE; use error_chain::ChainedError; +use util::*; /// A Type of Time, currently based on chrono::NaiveDateTime #[derive(Debug, Clone)] @@ -32,6 +33,7 @@ pub enum TimeType { Subtraction(Box, Box), EndOfYear(Box), + EndOfMonth(Box), } impl Add for TimeType { @@ -165,6 +167,16 @@ impl TimeType { TimeType::EndOfYear(Box::new(self)) } + /// Calculate the end of the month based on the current TimeType + /// + /// # Warning + /// + /// If the current TimeType does _not_ evaluate to a `TimeType::Moment`, calculating the end of + /// the month will fail + pub fn end_of_month(self) -> TimeType { + TimeType::EndOfMonth(Box::new(self)) + } + /// Get the number of seconds, if the TimeType is not a duration type, zero is returned /// /// # Warning @@ -340,6 +352,7 @@ fn do_calculate(tt: TimeType) -> Result { TT::Addition(a, b) => add(a, b), TT::Subtraction(a, b) => sub(a, b), TT::EndOfYear(inner) => end_of_year(*inner), + TT::EndOfMonth(inner) => end_of_month(*inner), x => Ok(x) } } @@ -362,6 +375,32 @@ fn end_of_year(tt: TimeType) -> Result { els @ TT::Subtraction(_, _) => Err(KE::from_kind(KEK::CannotCalculateEndOfYearOn(els))), TT::Moment(m) => Ok(TT::moment(NaiveDate::from_ymd(m.year(), 12, 31).and_hms(0, 0, 0))), TT::EndOfYear(e) => do_calculate(*e), + TT::EndOfMonth(e) => do_calculate(*e), + } +} + +/// Evaluates the passed argument and if it is a `TT::Moment` it adjust its to the end of the month +/// else returns an error +/// +/// Calling a end-of-month on a end-of-month yields end-of-month applied only once. +fn end_of_month(tt: TimeType) -> Result { + use timetype::TimeType as TT; + + match try!(do_calculate(tt)) { + els @ TT::Seconds(_) | + els @ TT::Minutes(_) | + els @ TT::Hours(_) | + els @ TT::Days(_) | + els @ TT::Months(_) | + els @ TT::Years(_) | + els @ TT::Addition(_, _) | + els @ TT::Subtraction(_, _) => Err(KE::from_kind(KEK::CannotCalculateEndOfMonthOn(els))), + TT::Moment(m) => { + let last_day = get_num_of_days_in_month(m.year(), m.month()); + Ok(TT::moment(NaiveDate::from_ymd(m.year(), m.month(), last_day).and_hms(0, 0, 0))) + }, + TT::EndOfYear(e) => do_calculate(*e), + TT::EndOfMonth(e) => do_calculate(*e), } } @@ -407,6 +446,9 @@ fn add(a: Box, b: Box) -> Result { (TT::EndOfYear(e), other) => Err(KE::from_kind(KEK::CannotAdd(other, TT::EndOfYear(e)))), (other, TT::EndOfYear(e)) => Err(KE::from_kind(KEK::CannotAdd(other, TT::EndOfYear(e)))), + (TT::EndOfMonth(e), other) => Err(KE::from_kind(KEK::CannotAdd(other, TT::EndOfMonth(e)))), + (other, TT::EndOfMonth(e)) => Err(KE::from_kind(KEK::CannotAdd(other, TT::EndOfMonth(e)))), + others => unimplemented!(), } } @@ -423,6 +465,7 @@ fn add_to_seconds(amount: i64, tt: TimeType) -> Result { TT::Years(a) => Ok(TT::Seconds(a * 60 * 60 * 24 * 30 * 12 + amount)), TT::Moment(m) => Err(KE::from_kind(KEK::CannotAdd(TT::Seconds(amount), TT::Moment(m)))), TT::EndOfYear(e) => Err(KE::from_kind(KEK::CannotAdd(TT::Seconds(amount), TT::EndOfYear(e)))), + TT::EndOfMonth(e) => Err(KE::from_kind(KEK::CannotAdd(TT::Seconds(amount), TT::EndOfMonth(e)))), TT::Addition(b, c) => add_to_seconds(amount, try!(add(b, c))), TT::Subtraction(b, c) => add_to_seconds(amount, try!(sub(b, c))), } @@ -440,6 +483,7 @@ fn add_to_minutes(amount: i64, tt: TimeType) -> Result { TT::Years(a) => Ok(TT::Minutes(a * 60 * 24 * 30 * 12 + amount)), TT::Moment(m) => Err(KE::from_kind(KEK::CannotAdd(TT::Minutes(amount), TT::Moment(m)))), TT::EndOfYear(e) => Err(KE::from_kind(KEK::CannotAdd(TT::Minutes(amount), TT::EndOfYear(e)))), + TT::EndOfMonth(e) => Err(KE::from_kind(KEK::CannotAdd(TT::Minutes(amount), TT::EndOfMonth(e)))), TT::Addition(b, c) => add_to_minutes(amount, try!(add(b, c))), TT::Subtraction(b, c) => add_to_minutes(amount, try!(sub(b, c))), } @@ -457,6 +501,7 @@ fn add_to_hours(amount: i64, tt: TimeType) -> Result { TT::Years(a) => Ok(TT::Hours( a * 24 * 30 * 12 + amount)), TT::Moment(m) => Err(KE::from_kind(KEK::CannotAdd(TT::Hours(amount), TT::Moment(m)))), TT::EndOfYear(e) => Err(KE::from_kind(KEK::CannotAdd(TT::Hours(amount), TT::EndOfYear(e)))), + TT::EndOfMonth(e) => Err(KE::from_kind(KEK::CannotAdd(TT::Hours(amount), TT::EndOfMonth(e)))), TT::Addition(b, c) => add_to_hours(amount, try!(add(b, c))), TT::Subtraction(b, c) => add_to_hours(amount, try!(sub(b, c))), } @@ -474,6 +519,7 @@ fn add_to_days(amount: i64, tt: TimeType) -> Result { TT::Years(a) => Ok(TT::Days( a * 30 * 12 + amount)), TT::Moment(m) => Err(KE::from_kind(KEK::CannotAdd(TT::Days(amount), TT::Moment(m)))), TT::EndOfYear(e) => Err(KE::from_kind(KEK::CannotAdd(TT::Days(amount), TT::EndOfYear(e)))), + TT::EndOfMonth(e) => Err(KE::from_kind(KEK::CannotAdd(TT::Days(amount), TT::EndOfMonth(e)))), TT::Addition(b, c) => add_to_days(amount, try!(add(b, c))), TT::Subtraction(b, c) => add_to_days(amount, try!(sub(b, c))), } @@ -491,6 +537,7 @@ fn add_to_months(amount: i64, tt: TimeType) -> Result { TT::Years(a) => Ok(TT::Months( a * 12 + amount)), TT::Moment(m) => Err(KE::from_kind(KEK::CannotAdd(TT::Months(amount), TT::Moment(m)))), TT::EndOfYear(e) => Err(KE::from_kind(KEK::CannotAdd(TT::Months(amount), TT::EndOfYear(e)))), + TT::EndOfMonth(e) => Err(KE::from_kind(KEK::CannotAdd(TT::Months(amount), TT::EndOfMonth(e)))), TT::Addition(b, c) => add_to_months(amount, try!(add(b, c))), TT::Subtraction(b, c) => add_to_months(amount, try!(sub(b, c))), } @@ -508,6 +555,7 @@ fn add_to_years(amount: i64, tt: TimeType) -> Result { TT::Years(a) => Ok(TT::Years( a + amount)), TT::Moment(m) => Err(KE::from_kind(KEK::CannotAdd(TT::Years(amount), TT::Moment(m)))), TT::EndOfYear(e) => Err(KE::from_kind(KEK::CannotAdd(TT::Years(amount), TT::EndOfYear(e)))), + TT::EndOfMonth(e) => Err(KE::from_kind(KEK::CannotAdd(TT::Years(amount), TT::EndOfMonth(e)))), TT::Addition(b, c) => add_to_years(amount, try!(add(b, c))), TT::Subtraction(b, c) => add_to_years(amount, try!(sub(b, c))), } @@ -635,6 +683,7 @@ fn add_to_moment(mom: NaiveDateTime, tt: TimeType) -> Result { }, TT::Moment(m) => Err(KE::from_kind(KEK::CannotAdd(TT::Moment(mom), TT::Moment(m)))), TT::EndOfYear(e) => Err(KE::from_kind(KEK::CannotAdd(TT::Moment(mom), TT::EndOfYear(e)))), + TT::EndOfMonth(e) => Err(KE::from_kind(KEK::CannotAdd(TT::Moment(mom), TT::EndOfMonth(e)))), TT::Addition(a, b) => add_to_moment(mom, try!(add(a, b))), TT::Subtraction(a, b) => add_to_moment(mom, try!(sub(a, b))), } @@ -694,6 +743,10 @@ fn sub(a: Box, b: Box) -> Result { (TT::EndOfYear(e), other) => Err(KE::from_kind(KEK::CannotSub(other, TT::EndOfYear(e)))), (other, TT::EndOfYear(e)) => Err(KE::from_kind(KEK::CannotSub(other, TT::EndOfYear(e)))), + (TT::EndOfMonth(e), other) => Err(KE::from_kind(KEK::CannotSub(other, TT::EndOfMonth(e)))), + (other, TT::EndOfMonth(e)) => Err(KE::from_kind(KEK::CannotSub(other, TT::EndOfMonth(e)))), + + others => unimplemented!(), } } @@ -708,8 +761,9 @@ fn sub_from_seconds(amount: i64, tt: TimeType) -> Result { TT::Days(a) => Ok(TT::Seconds(amount - a * 60 * 60 * 24)), TT::Months(a) => Ok(TT::Seconds(amount - a * 60 * 60 * 24 * 30)), TT::Years(a) => Ok(TT::Seconds(amount - a * 60 * 60 * 24 * 30 * 12)), - TT::Moment(m) => Err(KE::from_kind(KEK::CannotAdd(TT::Seconds(amount), TT::Moment(m)))), - TT::EndOfYear(e) => Err(KE::from_kind(KEK::CannotAdd(TT::Seconds(amount), TT::EndOfYear(e)))), + TT::Moment(m) => Err(KE::from_kind(KEK::CannotSub(TT::Seconds(amount), TT::Moment(m)))), + TT::EndOfYear(e) => Err(KE::from_kind(KEK::CannotSub(TT::Seconds(amount), TT::EndOfYear(e)))), + TT::EndOfMonth(e) => Err(KE::from_kind(KEK::CannotSub(TT::Seconds(amount), TT::EndOfMonth(e)))), TT::Addition(b, c) => sub_from_seconds(amount, try!(add(b, c))), TT::Subtraction(b, c) => sub_from_seconds(amount, try!(sub(b, c))), } @@ -725,8 +779,9 @@ fn sub_from_minutes(amount: i64, tt: TimeType) -> Result { TT::Days(a) => Ok(TT::Minutes(amount - a * 60 * 24)), TT::Months(a) => Ok(TT::Minutes(amount - a * 60 * 24 * 30)), TT::Years(a) => Ok(TT::Minutes(amount - a * 60 * 24 * 30 * 12)), - TT::Moment(m) => Err(KE::from_kind(KEK::CannotAdd(TT::Minutes(amount), TT::Moment(m)))), - TT::EndOfYear(e) => Err(KE::from_kind(KEK::CannotAdd(TT::Minutes(amount), TT::EndOfYear(e)))), + TT::Moment(m) => Err(KE::from_kind(KEK::CannotSub(TT::Minutes(amount), TT::Moment(m)))), + TT::EndOfYear(e) => Err(KE::from_kind(KEK::CannotSub(TT::Minutes(amount), TT::EndOfYear(e)))), + TT::EndOfMonth(e) => Err(KE::from_kind(KEK::CannotSub(TT::Minutes(amount), TT::EndOfMonth(e)))), TT::Addition(b, c) => sub_from_minutes(amount, try!(add(b, c))), TT::Subtraction(b, c) => sub_from_minutes(amount, try!(sub(b, c))), } @@ -742,8 +797,9 @@ fn sub_from_hours(amount: i64, tt: TimeType) -> Result { TT::Days(a) => Ok(TT::Hours(amount - a * 24)), TT::Months(a) => Ok(TT::Hours(amount - a * 24 * 30)), TT::Years(a) => Ok(TT::Hours(amount - a * 24 * 30 * 12)), - TT::Moment(m) => Err(KE::from_kind(KEK::CannotAdd(TT::Hours(amount), TT::Moment(m)))), - TT::EndOfYear(e) => Err(KE::from_kind(KEK::CannotAdd(TT::Hours(amount), TT::EndOfYear(e)))), + TT::Moment(m) => Err(KE::from_kind(KEK::CannotSub(TT::Hours(amount), TT::Moment(m)))), + TT::EndOfYear(e) => Err(KE::from_kind(KEK::CannotSub(TT::Hours(amount), TT::EndOfYear(e)))), + TT::EndOfMonth(e) => Err(KE::from_kind(KEK::CannotSub(TT::Hours(amount), TT::EndOfMonth(e)))), TT::Addition(b, c) => sub_from_hours(amount, try!(add(b, c))), TT::Subtraction(b, c) => sub_from_hours(amount, try!(sub(b, c))), } @@ -759,8 +815,9 @@ fn sub_from_days(amount: i64, tt: TimeType) -> Result { TT::Days(a) => Ok(TT::Days(amount - a)), TT::Months(a) => Ok(TT::Days(amount - a * 30)), TT::Years(a) => Ok(TT::Days(amount - a * 30 * 12)), - TT::Moment(m) => Err(KE::from_kind(KEK::CannotAdd(TT::Days(amount), TT::Moment(m)))), - TT::EndOfYear(e) => Err(KE::from_kind(KEK::CannotAdd(TT::Days(amount), TT::EndOfYear(e)))), + TT::Moment(m) => Err(KE::from_kind(KEK::CannotSub(TT::Days(amount), TT::Moment(m)))), + TT::EndOfYear(e) => Err(KE::from_kind(KEK::CannotSub(TT::Days(amount), TT::EndOfYear(e)))), + TT::EndOfMonth(e) => Err(KE::from_kind(KEK::CannotSub(TT::Days(amount), TT::EndOfMonth(e)))), TT::Addition(b, c) => sub_from_days(amount, try!(add(b, c))), TT::Subtraction(b, c) => sub_from_days(amount, try!(sub(b, c))), } @@ -776,8 +833,9 @@ fn sub_from_months(amount: i64, tt: TimeType) -> Result { TT::Days(a) => Ok(TT::Days(amount * 30 - a)), TT::Months(a) => Ok(TT::Months(amount - a)), TT::Years(a) => Ok(TT::Months(amount - a * 12)), - TT::Moment(m) => Err(KE::from_kind(KEK::CannotAdd(TT::Months(amount), TT::Moment(m)))), - TT::EndOfYear(e) => Err(KE::from_kind(KEK::CannotAdd(TT::Months(amount), TT::EndOfYear(e)))), + TT::Moment(m) => Err(KE::from_kind(KEK::CannotSub(TT::Months(amount), TT::Moment(m)))), + TT::EndOfYear(e) => Err(KE::from_kind(KEK::CannotSub(TT::Months(amount), TT::EndOfYear(e)))), + TT::EndOfMonth(e) => Err(KE::from_kind(KEK::CannotSub(TT::Months(amount), TT::EndOfMonth(e)))), TT::Addition(b, c) => sub_from_months(amount, try!(add(b, c))), TT::Subtraction(b, c) => sub_from_months(amount, try!(sub(b, c))), } @@ -793,8 +851,9 @@ fn sub_from_years(amount: i64, tt: TimeType) -> Result { TT::Days(a) => Ok(TT::Days(amount * 12 * 30 - a)), TT::Months(a) => Ok(TT::Months(amount * 12 - a)), TT::Years(a) => Ok(TT::Years(amount - a)), - TT::Moment(m) => Err(KE::from_kind(KEK::CannotAdd(TT::Years(amount), TT::Moment(m)))), - TT::EndOfYear(e) => Err(KE::from_kind(KEK::CannotAdd(TT::Years(amount), TT::EndOfYear(e)))), + TT::Moment(m) => Err(KE::from_kind(KEK::CannotSub(TT::Years(amount), TT::Moment(m)))), + TT::EndOfYear(e) => Err(KE::from_kind(KEK::CannotSub(TT::Years(amount), TT::EndOfYear(e)))), + TT::EndOfMonth(e) => Err(KE::from_kind(KEK::CannotSub(TT::Months(amount), TT::EndOfMonth(e)))), TT::Addition(b, c) => sub_from_years(amount, try!(add(b, c))), TT::Subtraction(b, c) => sub_from_years(amount, try!(sub(b, c))), } @@ -945,8 +1004,9 @@ fn sub_from_moment(mom: NaiveDateTime, tt: TimeType) -> Result { .and_hms(h as u32, mi as u32, s as u32); Ok(TimeType::moment(tt)) }, - TT::Moment(m) => Err(KE::from_kind(KEK::CannotAdd(TT::Moment(mom), TT::Moment(m)))), - TT::EndOfYear(e) => Err(KE::from_kind(KEK::CannotAdd(TT::Moment(mom), TT::EndOfYear(e)))), + TT::Moment(m) => Err(KE::from_kind(KEK::CannotSub(TT::Moment(mom), TT::Moment(m)))), + TT::EndOfYear(e) => Err(KE::from_kind(KEK::CannotSub(TT::Moment(mom), TT::EndOfYear(e)))), + TT::EndOfMonth(e) => Err(KE::from_kind(KEK::CannotSub(TT::Moment(mom), TT::EndOfMonth(e)))), TT::Addition(a, b) => sub_from_moment(mom, try!(add(a, b))), TT::Subtraction(a, b) => sub_from_moment(mom, try!(sub(a, b))), } diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..3062c84 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,36 @@ + +#[inline] +pub fn get_num_of_days_in_month(y: i32, m: u32) -> u32 { + if m == 1 || m == 3 || m == 5 || m == 7 || m == 8 || m == 10 || m == 12 { + 31 + } else if m == 2 { + if is_leap_year(y) { + 29 + } else { + 28 + } + } else { + 30 + } +} + +#[inline] +pub fn is_leap_year(y: i32) -> bool { + (y % 4 == 0) && (y % 100 != 0 || y % 400 == 0) +} + +#[test] +fn test_is_leap_year() { + let leaps = [ 1880, 1884, 1888, 1892, 1896, 1904, 1908, 1912, 1916, 1920, 1924, 1928, 1932, + 1936, 1940, 1944, 1948, 1952, 1956, 1960, 1964, 1968, 1972, 1976, 1980, 1984, 1988, 1992, + 1996, 2000, 2004, 2008, 2012, 2020, 2024, 2028, 2032, 2036, 2040, 2044, 2048, 2052, 2056, + 2060, 2064, 2068, 2072, 2076, 2080, 2084, 2088, 2092, 2096, 2104, 2108, 2112, 2116, 2120, + 2124, 2128, 2132, 2136, 2140, 2144, 2148, 2152, 2156, 2160, 2164, 2168, 2172, 2176, 2180, + 2184, 2188, 2192, 2196, 2204, 2208 ]; + + for i in leaps.iter() { + assert!(is_leap_year(*i), "Is no leap year: {}", i); + assert!(!is_leap_year(*i + 1), "Seems to be leap year: {}, but should not be", i + 1); + } + +} -- cgit v1.2.3