diff --git a/src/lib.rs b/src/lib.rs index 9379dd7..b80ed59 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -321,7 +321,7 @@ pub mod consts { #[inline] pub const fn rd_to_date(n: i32) -> (i32, u8, u8) { debug_assert!(n >= RD_MIN && n <= RD_MAX, "given rata die is out of range"); - let n = n.wrapping_add(DAY_OFFSET) as u32; + let n = (n + DAY_OFFSET) as u32; // century let n = 4 * n + 3; let c = n / 146097; @@ -338,7 +338,7 @@ pub const fn rd_to_date(n: i32) -> (i32, u8, u8) { let m = n / 2u32.pow(16); let d = n % 2u32.pow(16) / 2141; // map - let y = (y as i32).wrapping_sub(YEAR_OFFSET); + let y = (y as i32) - YEAR_OFFSET; let m = if j { m - 12 } else { m }; let d = d + 1; (y, m as u8, d as u8) @@ -350,7 +350,7 @@ const fn date_to_internal(y: i32, m: u8, d: u8) -> (u32, u32, u32, u32) { debug_assert!(y >= YEAR_MIN && y <= YEAR_MAX, "given year is out of range"); debug_assert!(m >= consts::MONTH_MIN && m <= consts::MONTH_MAX, "given month is out of range"); debug_assert!(d >= consts::DAY_MIN && d <= days_in_month(y, m), "given day is out of range"); - let y = y.wrapping_add(YEAR_OFFSET) as u32; + let y = (y + YEAR_OFFSET) as u32; let jf = (m < 3) as u32; // year let y = y - jf; @@ -406,7 +406,7 @@ pub const fn date_to_rd((y, m, d): (i32, u8, u8)) -> i32 { let m = (979 * m - 2919) / 32; // result let n = y + m + d; - (n as i32).wrapping_sub(DAY_OFFSET) + (n as i32) - DAY_OFFSET } /// Convert Rata Die to day of week @@ -477,7 +477,7 @@ pub const fn date_to_rd((y, m, d): (i32, u8, u8)) -> i32 { pub const fn rd_to_weekday(n: i32) -> u8 { debug_assert!(n >= RD_MIN && n <= RD_MAX, "given rata die is out of range"); const P64_OVER_SEVEN: u64 = ((1 << 63) / 7) << 1; // = (1 << 64) / 7 - (((n.wrapping_sub(RD_MIN) as u64 + 1).wrapping_mul(P64_OVER_SEVEN)) >> 61) as u8 + ((((n - RD_MIN) as u64 + 1).wrapping_mul(P64_OVER_SEVEN)) >> 61) as u8 } /// Convert Gregorian date to day of week @@ -665,7 +665,7 @@ pub const fn secs_to_dhms(secs: i64) -> (i32, u8, u8, u8) { // // `SECS_IN_DAY` obviously fits within these bounds let secs = if secs > RD_SECONDS_MAX { 0 } else { secs }; // allows compiler to optimize more - let secs = secs.wrapping_add(SECS_OFFSET) as u64; + let secs = (secs + SECS_OFFSET) as u64; let days = (secs / SECS_IN_DAY as u64) as u32; let secs = secs % SECS_IN_DAY as u64; // secs in [0, SECS_IN_DAY[ => secs in [0, 97612919[ @@ -677,7 +677,7 @@ pub const fn secs_to_dhms(secs: i64) -> (i32, u8, u8, u8) { let hh = prd >> 32; // mins / 60 let mm = (prd as u32) / 71582789; // mins % 60 - let days = (days as i32).wrapping_sub(DAY_OFFSET); + let days = (days as i32) - DAY_OFFSET; (days, hh as u8, mm as u8, ss as u8) } @@ -958,6 +958,10 @@ pub const fn isoweekdate_to_rd((y, w, d): (i32, u8, u8)) -> i32 { d >= consts::WEEKDAY_MIN && d <= consts::WEEKDAY_MAX, "given weekday is out of range" ); + debug_assert!( + y != YEAR_MAX || w != consts::WEEK_MAX || d <= consts::THURSDAY, + "given weekday is out of range (for last week of range)" + ); let rd4 = date_to_rd((y, 1, 4)); let wd4 = rd_to_weekday(rd4); let ys = rd4 - (wd4 - 1) as i32; diff --git a/tests/api.rs b/tests/api.rs index b0a024a..a8f5f8f 100644 --- a/tests/api.rs +++ b/tests/api.rs @@ -19,6 +19,8 @@ fn test_date_to_rd() { assert_eq!(date_to_rd((i16::MAX as i32, 12, 31)), 11248737); assert_eq!(date_to_rd((i16::MIN as i32 - 1, 1, 1)), -12688159); assert_eq!(date_to_rd((i16::MAX as i32 + 1, 12, 31)), 11249103); + assert_eq!(date_to_rd((YEAR_MIN, 1, 1)), RD_MIN); + assert_eq!(date_to_rd((YEAR_MAX, 12, 31)), RD_MAX); } #[test] @@ -29,6 +31,8 @@ fn test_rd_to_date() { assert_eq!(rd_to_date(11248737), (i16::MAX as i32, 12, 31)); assert_eq!(rd_to_date(-12687795), (i16::MIN as i32 - 1, 12, 31)); assert_eq!(rd_to_date(11248738), (i16::MAX as i32 + 1, 1, 1)); + assert_eq!(rd_to_date(RD_MIN), (YEAR_MIN, 1, 1)); + assert_eq!(rd_to_date(RD_MAX), (YEAR_MAX, 12, 31)); } #[test] @@ -72,6 +76,8 @@ fn test_date_to_weekday() { assert_eq!(date_to_weekday((-4, 1, 1)), 1); assert_eq!(date_to_weekday((-100, 1, 1)), 1); assert_eq!(date_to_weekday((-400, 1, 1)), 6); + assert_eq!(date_to_weekday((YEAR_MIN, 1, 1)), 1); + assert_eq!(date_to_weekday((YEAR_MAX, 12, 31)), 4); } #[test] @@ -87,6 +93,8 @@ fn test_next_date() { assert_eq!(next_date((2020, 2, 29)), (2020, 3, 1)); assert_eq!(next_date((-2020, 2, 28)), (-2020, 2, 29)); assert_eq!(next_date((-2020, 2, 29)), (-2020, 3, 1)); + assert_eq!(next_date((YEAR_MAX, 12, 30)), (YEAR_MAX, 12, 31)); + assert_eq!(next_date((YEAR_MIN, 1, 1)), (YEAR_MIN, 1, 2)); } #[test] @@ -102,6 +110,8 @@ fn test_prev_date() { assert_eq!(prev_date((2020, 3, 1)), (2020, 2, 29)); assert_eq!(prev_date((-2020, 2, 29)), (-2020, 2, 28)); assert_eq!(prev_date((-2020, 3, 1)), (-2020, 2, 29)); + assert_eq!(prev_date((YEAR_MAX, 12, 31)), (YEAR_MAX, 12, 30)); + assert_eq!(prev_date((YEAR_MIN, 1, 2)), (YEAR_MIN, 1, 1)); } #[test] @@ -139,6 +149,8 @@ fn test_is_leap_year() { assert_eq!(is_leap_year(-4), true); assert_eq!(is_leap_year(-100), false); assert_eq!(is_leap_year(-400), true); + assert_eq!(is_leap_year(YEAR_MIN), false); + assert_eq!(is_leap_year(YEAR_MAX), true); } #[test] @@ -191,6 +203,10 @@ fn test_days_in_month() { assert_eq!(days_in_month(-4, 10), 31); assert_eq!(days_in_month(-4, 11), 30); assert_eq!(days_in_month(-4, 12), 31); + assert_eq!(days_in_month(YEAR_MAX, 12), 31); + assert_eq!(days_in_month(YEAR_MAX, 2), 29); + assert_eq!(days_in_month(YEAR_MIN, 1), 31); + assert_eq!(days_in_month(YEAR_MIN, 2), 28); } #[test] @@ -221,6 +237,8 @@ fn test_rd_to_isoweekdate() { assert_eq!(rd_to_isoweekdate(date_to_rd((1981, 12, 31))), (1981, 53, 4)); assert_eq!(rd_to_isoweekdate(date_to_rd((1982, 1, 1))), (1981, 53, 5)); assert_eq!(rd_to_isoweekdate(date_to_rd((1982, 1, 2))), (1981, 53, 6)); + assert_eq!(rd_to_isoweekdate(date_to_rd((YEAR_MAX, 12, 31))), (YEAR_MAX, 53, 4)); + assert_eq!(rd_to_isoweekdate(date_to_rd((YEAR_MIN, 1, 1))), (YEAR_MIN, 1, 1)); } #[test] @@ -251,6 +269,8 @@ fn test_isoweekdate_to_rd() { assert_eq!(isoweekdate_to_rd((1981, 53, 4)), date_to_rd((1981, 12, 31))); assert_eq!(isoweekdate_to_rd((1981, 53, 5)), date_to_rd((1982, 1, 1))); assert_eq!(isoweekdate_to_rd((1981, 53, 6)), date_to_rd((1982, 1, 2))); + assert_eq!(isoweekdate_to_rd((YEAR_MAX, 53, 4)), date_to_rd((YEAR_MAX, 12, 31))); + assert_eq!(isoweekdate_to_rd((YEAR_MIN, 1, 1)), date_to_rd((YEAR_MIN, 1, 1))); } #[test] @@ -281,6 +301,8 @@ fn test_date_to_isoweekdate() { assert_eq!(date_to_isoweekdate((1981, 12, 31)), (1981, 53, 4)); assert_eq!(date_to_isoweekdate((1982, 1, 1)), (1981, 53, 5)); assert_eq!(date_to_isoweekdate((1982, 1, 2)), (1981, 53, 6)); + assert_eq!(date_to_isoweekdate((YEAR_MAX, 12, 31)), (YEAR_MAX, 53, 4)); + assert_eq!(date_to_isoweekdate((YEAR_MIN, 1, 1)), (YEAR_MIN, 1, 1)); } #[test] @@ -311,6 +333,8 @@ fn test_isoweekdate_to_date() { assert_eq!(isoweekdate_to_date((1981, 53, 4)), (1981, 12, 31)); assert_eq!(isoweekdate_to_date((1981, 53, 5)), (1982, 1, 1)); assert_eq!(isoweekdate_to_date((1981, 53, 6)), (1982, 1, 2)); + assert_eq!(isoweekdate_to_date((YEAR_MAX, 53, 4)), (YEAR_MAX, 12, 31)); + assert_eq!(isoweekdate_to_date((YEAR_MIN, 1, 1)), (YEAR_MIN, 1, 1)); } #[test] @@ -330,6 +354,8 @@ fn test_isoweeks_in_year() { assert_eq!(isoweeks_in_year(2004), 53); // leap year, thursday assert_eq!(isoweeks_in_year(2015), 53); // thursday assert_eq!(isoweeks_in_year(2020), 53); // leap year, wednesday + assert_eq!(isoweeks_in_year(YEAR_MIN), 52); + assert_eq!(isoweeks_in_year(YEAR_MAX), 53); } #[test] diff --git a/tests/full.rs b/tests/full.rs new file mode 100644 index 0000000..67f176a --- /dev/null +++ b/tests/full.rs @@ -0,0 +1,230 @@ +use std::time::SystemTime; + +use quickcheck::{quickcheck, Arbitrary, Gen, TestResult}; + +#[derive(Debug, Clone, Copy)] +struct Val(i128); + +impl Val { + fn i64(&self) -> i64 { + assert!(self.0 >= i64::MIN as i128 && self.0 <= i64::MAX as i128); + self.0 as i64 + } + + fn i32(&self) -> i32 { + assert!(self.0 >= i32::MIN as i128 && self.0 <= i32::MAX as i128); + self.0 as i32 + } + + fn u32(&self) -> u32 { + assert!(self.0 >= u32::MIN as i128 && self.0 <= u32::MAX as i128); + self.0 as u32 + } + + fn u8(&self) -> u8 { + assert!(self.0 >= u8::MIN as i128 && self.0 <= u8::MAX as i128); + self.0 as u8 + } +} + +impl Arbitrary for Val { + fn arbitrary(g: &mut Gen) -> Self { + let v = i128::arbitrary(g).rem_euclid(MAX - MIN + 1) + MIN; + Val(v) + } + + fn shrink(&self) -> Box> { + let v = self.0; + Box::new(v.shrink().map(Val)) + } +} + +quickcheck! { + fn quickcheck_rd_to_date(d: Val<-536895152, 536824295>) -> TestResult { + let (y, m, d) = datealgo::rd_to_date(d.i32()); + assert!(y >= datealgo::YEAR_MIN && y <= datealgo::YEAR_MAX); + assert!(m >= datealgo::consts::MONTH_MIN && m <= datealgo::consts::MONTH_MAX); + assert!(d >= datealgo::consts::DAY_MIN && d <= datealgo::consts::DAY_MAX && d <= datealgo::days_in_month(y, m)); + TestResult::passed() + } + + fn quickcheck_date_to_rd(y: Val<-1467999, 1471744>, m: Val<1, 12>, d: Val<1, 31>) -> TestResult { + if d.u8() > datealgo::days_in_month(y.i32(), m.u8()) { + return TestResult::discard(); + } + let rd = datealgo::date_to_rd((y.i32(), m.u8(), d.u8())); + assert!(rd >= datealgo::RD_MIN && rd <= datealgo::RD_MAX); + TestResult::passed() + } + + fn quickcheck_rd_to_weekday(d: Val<-536895152, 536824295>) -> TestResult { + let wd = datealgo::rd_to_weekday(d.i32()); + assert!(wd >= datealgo::consts::WEEKDAY_MIN && wd <= datealgo::consts::WEEKDAY_MAX); + TestResult::passed() + } + + fn quickcheck_date_to_weekday(y: Val<-1467999, 1471744>, m: Val<1, 12>, d: Val<1, 31>) -> TestResult { + if d.u8() > datealgo::days_in_month(y.i32(), m.u8()) { + return TestResult::discard(); + } + let wd = datealgo::date_to_weekday((y.i32(), m.u8(), d.u8())); + assert!(wd >= datealgo::consts::WEEKDAY_MIN && wd <= datealgo::consts::WEEKDAY_MAX); + TestResult::passed() + } + + fn quickcheck_next_date(y: Val<-1467999, 1471744>, m: Val<1, 12>, d: Val<1, 31>) -> TestResult { + if d.u8() > datealgo::days_in_month(y.i32(), m.u8()) { + return TestResult::discard(); + } + if y.i32() == datealgo::YEAR_MAX && m.u8() == datealgo::consts::MONTH_MAX && d.u8() == datealgo::days_in_month(y.i32(), m.u8()) { + return TestResult::discard(); + } + let (ny, nm, nd) = datealgo::next_date((y.i32(), m.u8(), d.u8())); + assert!(ny >= datealgo::YEAR_MIN && ny <= datealgo::YEAR_MAX); + assert!(nm >= datealgo::consts::MONTH_MIN && nm <= datealgo::consts::MONTH_MAX); + assert!(nd >= datealgo::consts::DAY_MIN && nd <= datealgo::consts::DAY_MAX && nd <= datealgo::days_in_month(ny, nm)); + TestResult::passed() + } + + fn quickcheck_prev_date(y: Val<-1467999, 1471744>, m: Val<1, 12>, d: Val<1, 31>) -> TestResult { + if d.u8() > datealgo::days_in_month(y.i32(), m.u8()) { + return TestResult::discard(); + } + if y.i32() == datealgo::YEAR_MIN && m.u8() == datealgo::consts::MONTH_MIN && d.u8() == datealgo::consts::DAY_MIN { + return TestResult::discard(); + } + let (py, pm, pd) = datealgo::prev_date((y.i32(), m.u8(), d.u8())); + assert!(py >= datealgo::YEAR_MIN && py <= datealgo::YEAR_MAX); + assert!(pm >= datealgo::consts::MONTH_MIN && pm <= datealgo::consts::MONTH_MAX); + assert!(pd >= datealgo::consts::DAY_MIN && pd <= datealgo::consts::DAY_MAX && pd <= datealgo::days_in_month(py, pm)); + TestResult::passed() + } + + fn quickcheck_secs_to_dhms(s: Val<-46387741132800, 46381619174399 >) -> TestResult { + let (d, h, m, s) = datealgo::secs_to_dhms(s.i64()); + assert!(d >= datealgo::RD_MIN && d <= datealgo::RD_MAX); + assert!(h >= datealgo::consts::HOUR_MIN && h <= datealgo::consts::HOUR_MAX); + assert!(m >= datealgo::consts::MINUTE_MIN && m <= datealgo::consts::MINUTE_MAX); + assert!(s >= datealgo::consts::SECOND_MIN && s <= datealgo::consts::SECOND_MAX); + TestResult::passed() + } + + fn quickcheck_dhms_to_secs(d: Val<-536895152, 536824295>, h: Val<0, 23>, m: Val<0, 59>, s: Val<0, 59>) -> TestResult { + let secs = datealgo::dhms_to_secs((d.i32(), h.u8(), m.u8(), s.u8())); + assert!(secs >= datealgo::RD_SECONDS_MIN && secs <= datealgo::RD_SECONDS_MAX); + TestResult::passed() + } + + fn quickcheck_secs_to_datetime(s: Val<-46387741132800, 46381619174399 >) -> TestResult { + let (y, m, d, h, min, sec) = datealgo::secs_to_datetime(s.i64()); + assert!(y >= datealgo::YEAR_MIN && y <= datealgo::YEAR_MAX); + assert!(m >= datealgo::consts::MONTH_MIN && m <= datealgo::consts::MONTH_MAX); + assert!(d >= datealgo::consts::DAY_MIN && d <= datealgo::consts::DAY_MAX && d <= datealgo::days_in_month(y, m)); + assert!(h >= datealgo::consts::HOUR_MIN && h <= datealgo::consts::HOUR_MAX); + assert!(min >= datealgo::consts::MINUTE_MIN && min <= datealgo::consts::MINUTE_MAX); + assert!(sec >= datealgo::consts::SECOND_MIN && sec <= datealgo::consts::SECOND_MAX); + TestResult::passed() + } + + fn quickcheck_datetime_to_secs(y: Val<-1467999, 1471744>, m: Val<1, 12>, d: Val<1, 31>, h: Val<0, 23>, min: Val<0, 59>, sec: Val<0, 59>) -> TestResult { + if d.u8() > datealgo::days_in_month(y.i32(), m.u8()) { + return TestResult::discard(); + } + let secs = datealgo::datetime_to_secs((y.i32(), m.u8(), d.u8(), h.u8(), min.u8(), sec.u8())); + assert!(secs >= datealgo::RD_SECONDS_MIN && secs <= datealgo::RD_SECONDS_MAX); + TestResult::passed() + } + + fn quickcheck_is_leap_year(y: Val<-1467999, 1471744>) -> TestResult { + let _ = datealgo::is_leap_year(y.i32()); + TestResult::passed() + } + + fn quickcheck_days_in_month(y: Val<-1467999, 1471744>, m: Val<1, 12>) -> TestResult { + let m = datealgo::days_in_month(y.i32(), m.u8()); + assert!(m >= 28 && m <= 31); + TestResult::passed() + } + + fn quickcheck_rd_to_isoweekdate(d: Val<-536895152, 536824295>) -> TestResult { + let (y, w, wd) = datealgo::rd_to_isoweekdate(d.i32()); + assert!(y >= datealgo::YEAR_MIN && y <= datealgo::YEAR_MAX); + assert!(w >= datealgo::consts::WEEK_MIN && w <= datealgo::consts::WEEK_MAX); + assert!(wd >= datealgo::consts::WEEKDAY_MIN && wd <= datealgo::consts::WEEKDAY_MAX); + TestResult::passed() + } + + fn quickcheck_isoweekdate_to_rd(y: Val<-1467999, 1471744>, w: Val<1, 53>, wd: Val<1, 7>) -> TestResult { + if w.u8() > datealgo::isoweeks_in_year(y.i32()) { + return TestResult::discard(); + } + let rd = datealgo::isoweekdate_to_rd((y.i32(), w.u8(), wd.u8())); + assert!(rd >= datealgo::RD_MIN && rd <= datealgo::RD_MAX); + TestResult::passed() + } + + fn quickcheck_date_to_isoweekdate(y: Val<-1467999, 1471744>, m: Val<1, 12>, d: Val<1, 31>) -> TestResult { + if d.u8() > datealgo::days_in_month(y.i32(), m.u8()) { + return TestResult::discard(); + } + let (wy, ww, wd) = datealgo::date_to_isoweekdate((y.i32(), m.u8(), d.u8())); + assert!(wy >= datealgo::YEAR_MIN && wy <= datealgo::YEAR_MAX); + assert!(ww >= datealgo::consts::WEEK_MIN && ww <= datealgo::consts::WEEK_MAX); + assert!(wd >= datealgo::consts::WEEKDAY_MIN && wd <= datealgo::consts::WEEKDAY_MAX); + TestResult::passed() + } + + fn quickcheck_isoweekdate_to_date(y: Val<-1467999, 1471744>, w: Val<1, 53>, wd: Val<1, 7>) -> TestResult { + if w.u8() > datealgo::isoweeks_in_year(y.i32()) { + return TestResult::discard(); + } + if y.i32() == datealgo::YEAR_MAX && w.u8() == datealgo::isoweeks_in_year(y.i32()) && wd.u8() >= datealgo::consts::SATURDAY { + return TestResult::discard(); + } + let (dy, dm, dd) = datealgo::isoweekdate_to_date((y.i32(), w.u8(), wd.u8())); + assert!(dy >= datealgo::YEAR_MIN && dy <= datealgo::YEAR_MAX); + assert!(dm >= datealgo::consts::MONTH_MIN && dm <= datealgo::consts::MONTH_MAX); + assert!(dd >= datealgo::consts::DAY_MIN && dd <= datealgo::consts::DAY_MAX && dd <= datealgo::days_in_month(dy, dm)); + TestResult::passed() + } + + fn quickcheck_isoweeks_in_year(y: Val<-1467999, 1471744>) -> TestResult { + let w = datealgo::isoweeks_in_year(y.i32()); + assert!(w >= 52 && w <= 53); + TestResult::passed() + } + + fn quickcheck_systemtime_to_secs(st: SystemTime) -> TestResult { + let (secs, nsecs) = datealgo::systemtime_to_secs(st).unwrap(); + assert!(secs >= datealgo::RD_SECONDS_MIN && secs <= datealgo::RD_SECONDS_MAX); + assert!(nsecs >= datealgo::consts::NANOSECOND_MIN && nsecs <= datealgo::consts::NANOSECOND_MAX); + TestResult::passed() + } + + fn quickcheck_secs_to_systemtime(secs: Val<-46387741132800, 46381619174399 >, nsecs: Val<0, 999_999_999>) -> TestResult { + let st = datealgo::secs_to_systemtime((secs.i64(), nsecs.u32())); + assert!(st.is_some()); + TestResult::passed() + } + + fn quickcheck_systemtime_to_datetime(st: SystemTime) -> TestResult { + let (y, m, d, h, min, sec, nsec) = datealgo::systemtime_to_datetime(st).unwrap(); + assert!(y >= datealgo::YEAR_MIN && y <= datealgo::YEAR_MAX); + assert!(m >= datealgo::consts::MONTH_MIN && m <= datealgo::consts::MONTH_MAX); + assert!(d >= datealgo::consts::DAY_MIN && d <= datealgo::consts::DAY_MAX && d <= datealgo::days_in_month(y, m)); + assert!(h >= datealgo::consts::HOUR_MIN && h <= datealgo::consts::HOUR_MAX); + assert!(min >= datealgo::consts::MINUTE_MIN && min <= datealgo::consts::MINUTE_MAX); + assert!(sec >= datealgo::consts::SECOND_MIN && sec <= datealgo::consts::SECOND_MAX); + assert!(nsec >= datealgo::consts::NANOSECOND_MIN && nsec <= datealgo::consts::NANOSECOND_MAX); + TestResult::passed() + } + + fn quickcheck_datetime_to_systemtime(y: Val<-1467999, 1471744>, m: Val<1, 12>, d: Val<1, 31>, h: Val<0, 23>, min: Val<0, 59>, sec: Val<0, 59>, nsec: Val<0, 999_999_999>) -> TestResult { + if d.u8() > datealgo::days_in_month(y.i32(), m.u8()) { + return TestResult::discard(); + } + let st = datealgo::datetime_to_systemtime((y.i32(), m.u8(), d.u8(), h.u8(), min.u8(), sec.u8(), nsec.u32())); + assert!(st.is_some()); + TestResult::passed() + } +}