From d1246b9dd4f91e7eab835eaf4f63022d92ade1a7 Mon Sep 17 00:00:00 2001 From: Nuutti Kotivuori Date: Sat, 14 Oct 2023 12:12:24 +0000 Subject: [PATCH] feat: Add `next_date` and `prev_date` functions --- benches/basic.rs | 6 ++ benches/compare.rs | 80 ++++++++++++++++++- benches/iai.rs | 12 +++ lib.asm | 186 +++++++++++++++++++++++++++++++++----------- src/lib.rs | 106 +++++++++++++++++++++++-- tests/api.rs | 30 +++++++ tests/quickcheck.rs | 22 ++++++ 7 files changed, 390 insertions(+), 52 deletions(-) diff --git a/benches/basic.rs b/benches/basic.rs index 4798b21..20bf944 100644 --- a/benches/basic.rs +++ b/benches/basic.rs @@ -89,6 +89,12 @@ fn bench_basic(c: &mut Criterion) { c.bench_function("date_to_weekday", |b| { b.iter_custom(bencher(rand_date, |d| datealgo::date_to_weekday(black_box(d)))) }); + c.bench_function("next_date", |b| { + b.iter_custom(bencher(rand_date, |d| datealgo::next_date(black_box(d)))) + }); + c.bench_function("prev_date", |b| { + b.iter_custom(bencher(rand_date, |d| datealgo::prev_date(black_box(d)))) + }); c.bench_function("secs_to_dhms", |b| { b.iter_custom(bencher(rand_secs, |s| datealgo::secs_to_dhms(black_box(s)))) }); diff --git a/benches/compare.rs b/benches/compare.rs index de25661..71243f1 100644 --- a/benches/compare.rs +++ b/benches/compare.rs @@ -106,7 +106,7 @@ mod datealgo_alt { let mm = secs / 60 % 60; let hh = secs / 3600; let days = (days as i32).wrapping_sub(DAY_OFFSET); - (days, hh as u8, mm as u8, ss as u8) + (days, hh as u8, mm as u8, ss as u8) } #[inline] @@ -143,6 +143,18 @@ mod datealgo_alt { ((day as u32 + (13 * mm - 1) / 5 + yy + yy / 4 - yy / 100 + yy / 400 + 6) % 7 + 1) as u8 } + #[inline] + pub const fn next_date((y, m, d): (i32, u8, u8)) -> (i32, u8, u8) { + let rd = datealgo::date_to_rd((y, m, d)); + datealgo::rd_to_date(rd + 1) + } + + #[inline] + pub const fn prev_date((y, m, d): (i32, u8, u8)) -> (i32, u8, u8) { + let rd = datealgo::date_to_rd((y, m, d)); + datealgo::rd_to_date(rd - 1) + } + #[inline] pub const fn is_leap_year(y: i32) -> bool { y % 4 == 0 && (y % 100 != 0 || y % 400 == 0) @@ -630,6 +642,21 @@ mod chrono { use chrono::{Datelike, Timelike}; use std::time::SystemTime; + pub fn rand_date() -> chrono::NaiveDate { + let (y, m, d) = super::rand_date(); + chrono::NaiveDate::from_ymd_opt(y, m as u32, d as u32).unwrap() + } + + #[inline] + pub fn next_date(d: chrono::NaiveDate) -> chrono::NaiveDate { + d.succ_opt().unwrap() + } + + #[inline] + pub fn prev_date(d: chrono::NaiveDate) -> chrono::NaiveDate { + d.pred_opt().unwrap() + } + #[inline] pub fn rd_to_date(n: i32) -> (i32, u8, u8) { let date = chrono::NaiveDate::from_num_days_from_ce_opt(n + 719162).unwrap(); @@ -688,6 +715,21 @@ mod time { const UNIX_EPOCH_JULIAN_DAY: i32 = 2440588; + pub fn rand_date() -> time::Date { + let (y, m, d) = super::rand_date(); + time::Date::from_calendar_date(y, m.try_into().unwrap(), d).unwrap() + } + + #[inline] + pub fn next_date(d: time::Date) -> time::Date { + d.next_day().unwrap() + } + + #[inline] + pub fn prev_date(d: time::Date) -> time::Date { + d.previous_day().unwrap() + } + #[inline] pub fn rd_to_date(n: i32) -> (i32, u8, u8) { let date = time::Date::from_julian_day(n + UNIX_EPOCH_JULIAN_DAY).unwrap(); @@ -862,6 +904,40 @@ fn bench_date_to_weekday(c: &mut Criterion) { group.finish(); } +fn bench_next_date(c: &mut Criterion) { + let mut group = c.benchmark_group("compare_next_date"); + group.bench_function("datealgo", |b| { + b.iter_custom(bencher(rand_date, |d| datealgo::next_date(black_box(d)))) + }); + group.bench_function("datealgo_alt", |b| { + b.iter_custom(bencher(rand_date, |d| datealgo_alt::next_date(black_box(d)))) + }); + group.bench_function("chrono", |b| { + b.iter_custom(bencher(chrono::rand_date, |d| chrono::next_date(black_box(d)))) + }); + group.bench_function("time", |b| { + b.iter_custom(bencher(time::rand_date, |d| time::next_date(black_box(d)))) + }); + group.finish(); +} + +fn bench_prev_date(c: &mut Criterion) { + let mut group = c.benchmark_group("compare_prev_date"); + group.bench_function("datealgo", |b| { + b.iter_custom(bencher(rand_date, |d| datealgo::prev_date(black_box(d)))) + }); + group.bench_function("datealgo_alt", |b| { + b.iter_custom(bencher(rand_date, |d| datealgo_alt::prev_date(black_box(d)))) + }); + group.bench_function("chrono", |b| { + b.iter_custom(bencher(chrono::rand_date, |d| chrono::prev_date(black_box(d)))) + }); + group.bench_function("time", |b| { + b.iter_custom(bencher(time::rand_date, |d| time::prev_date(black_box(d)))) + }); + group.finish(); +} + fn bench_secs_to_dhms(c: &mut Criterion) { let mut group = c.benchmark_group("compare_secs_to_dhms"); group.bench_function("datealgo", |b| { @@ -1029,6 +1105,8 @@ criterion_group!( bench_date_to_rd, bench_rd_to_weekday, bench_date_to_weekday, + bench_next_date, + bench_prev_date, bench_secs_to_dhms, bench_dhms_to_secs, bench_secs_to_datetime, diff --git a/benches/iai.rs b/benches/iai.rs index 512e546..078efe4 100644 --- a/benches/iai.rs +++ b/benches/iai.rs @@ -21,6 +21,16 @@ fn iai_date_to_weekday() -> u8 { datealgo::date_to_weekday(black_box((2023, 5, 12))) } +#[inline(never)] +fn iai_next_date() -> (i32, u8, u8) { + datealgo::next_date(black_box((2023, 5, 12))) +} + +#[inline(never)] +fn iai_prev_date() -> (i32, u8, u8) { + datealgo::prev_date(black_box((2023, 5, 12))) +} + #[inline(never)] fn iai_secs_to_dhms() -> (i32, u8, u8, u8) { datealgo::secs_to_dhms(black_box(1684574678i64)) @@ -76,6 +86,8 @@ main!( iai_date_to_rd, iai_rd_to_weekday, iai_date_to_weekday, + iai_next_date, + iai_prev_date, iai_secs_to_dhms, iai_dhms_to_secs, iai_secs_to_datetime, diff --git a/lib.asm b/lib.asm index 60b5d99..1eb8170 100644 --- a/lib.asm +++ b/lib.asm @@ -117,6 +117,104 @@ example::date_to_weekday: inc al ret +example::next_date: + movabs rcx, 1099511627776 + mov rax, rdi + shr rax, 32 + mov rdx, rdi + shr rdx, 40 + cmp dl, 28 + jb .LBB4_6 + cmp al, 2 + jne .LBB4_2 + imul esi, edi, -1030792151 + add esi, 85899345 + cmp esi, 171798691 + mov esi, 15 + mov r8d, 3 + cmovb r8d, esi + test r8d, edi + sete sil + or sil, 28 + cmp sil, dl + ja .LBB4_6 + jmp .LBB4_7 +.LBB4_2: + mov esi, eax + shr sil, 3 + xor sil, al + or sil, 30 + cmp sil, dl + jbe .LBB4_3 +.LBB4_6: + add rcx, rdi + movabs rdx, 280375465082880 + and rcx, rdx + jmp .LBB4_8 +.LBB4_3: + cmp al, 12 + jae .LBB4_4 +.LBB4_7: + inc rax + jmp .LBB4_8 +.LBB4_4: + inc rdi + mov eax, 1 +.LBB4_8: + movzx edx, al + shl rdx, 32 + mov eax, edi + or rax, rcx + or rax, rdx + ret + +example::prev_date: + mov rax, rdi + shr rax, 32 + mov rcx, rdi + shr rcx, 40 + cmp cl, 1 + jbe .LBB5_1 + dec cl + jmp .LBB5_6 +.LBB5_1: + cmp al, 1 + jbe .LBB5_2 + dec al + cmp al, 2 + jne .LBB5_5 + imul eax, edi, -1030792151 + add eax, 85899345 + cmp eax, 171798691 + mov eax, 15 + mov ecx, 3 + cmovb ecx, eax + test ecx, edi + sete cl + or cl, 28 + mov al, 2 + jmp .LBB5_6 +.LBB5_2: + mov eax, 4294967295 + add rdi, rax + mov cl, 31 + mov al, 12 + jmp .LBB5_6 +.LBB5_5: + mov ecx, eax + shr cl, 3 + xor cl, al + or cl, 30 +.LBB5_6: + movzx ecx, cl + shl rcx, 40 + movzx edx, al + shl rdx, 32 + or rdx, rcx + mov eax, edi + or rax, rdx + ret + example::secs_to_dhms: movabs rax, 46387767571200 lea rcx, [rdi + rax] @@ -154,7 +252,7 @@ example::secs_to_dhms: example::dhms_to_secs: lea eax, [rdi + 536895152] cmp eax, 1073719447 - ja .LBB5_1 + ja .LBB7_1 mov rax, rdi shr rax, 48 mov rcx, rdi @@ -172,7 +270,7 @@ example::dhms_to_secs: add rax, rdx add rax, rcx ret -.LBB5_1: +.LBB7_1: xor eax, eax ret @@ -261,7 +359,7 @@ example::datetime_to_secs: lea edx, [rcx + rax] add edx, -307 cmp edx, 1073719447 - ja .LBB7_1 + ja .LBB9_1 add eax, ecx add eax, -536895459 movzx ecx, byte ptr [rdi + 8] @@ -275,7 +373,7 @@ example::datetime_to_secs: add rax, rsi add rax, rdi ret -.LBB7_1: +.LBB9_1: xor eax, eax ret @@ -292,7 +390,7 @@ example::is_leap_year: example::days_in_month: cmp sil, 2 - jne .LBB9_2 + jne .LBB11_2 imul eax, edi, -1030792151 add eax, 85899345 cmp eax, 171798691 @@ -303,7 +401,7 @@ example::days_in_month: sete al or al, 28 ret -.LBB9_2: +.LBB11_2: mov eax, esi shr al, 3 xor al, sil @@ -582,13 +680,13 @@ example::isoweeks_in_year: sub ecx, edx add ecx, eax cmp cl, 2 - je .LBB14_3 + je .LBB16_3 mov al, 52 cmp ecx, 3 - jne .LBB14_4 + jne .LBB16_4 mov al, 53 ret -.LBB14_3: +.LBB16_3: imul eax, edi, -1030792151 add eax, 85899345 cmp eax, 171798691 @@ -598,7 +696,7 @@ example::isoweeks_in_year: test ecx, edi sete al or al, 52 -.LBB14_4: +.LBB16_4: ret example::systemtime_to_secs: @@ -613,7 +711,7 @@ example::systemtime_to_secs: xor ecx, ecx call qword ptr [rip + std::time::SystemTime::duration_since@GOTPCREL] cmp qword ptr [rsp + 8], 0 - je .LBB15_1 + je .LBB17_1 mov rax, qword ptr [rsp + 16] mov ecx, dword ptr [rsp + 24] mov qword ptr [rsp + 48], rax @@ -624,27 +722,27 @@ example::systemtime_to_secs: sbb rax, -1 movabs rcx, 46387741132800 cmp rax, rcx - ja .LBB15_6 + ja .LBB17_6 mov ecx, 1000000000 sub ecx, edx test edx, edx cmove ecx, edx neg rax - jmp .LBB15_3 -.LBB15_1: + jmp .LBB17_3 +.LBB17_1: mov rax, qword ptr [rsp + 16] movabs rcx, 46381619174399 cmp rax, rcx - jbe .LBB15_2 -.LBB15_6: + jbe .LBB17_2 +.LBB17_6: mov qword ptr [rbx], 0 mov rax, rbx add rsp, 64 pop rbx ret -.LBB15_2: +.LBB17_2: mov ecx, dword ptr [rsp + 24] -.LBB15_3: +.LBB17_3: mov qword ptr [rbx + 8], rax mov dword ptr [rbx + 16], ecx mov qword ptr [rbx], 1 @@ -657,7 +755,7 @@ example::secs_to_systemtime: mov eax, esi mov rsi, rdi test rdi, rdi - js .LBB16_1 + js .LBB18_1 mov ecx, eax shr ecx, 9 imul rcx, rcx, 281475 @@ -667,10 +765,10 @@ example::secs_to_systemtime: sub eax, ecx lea rdi, [rip + .L__unnamed_1] mov edx, eax - jmp qword ptr [rip + _ZN3std4time10SystemTime11checked_add17hd4da3f7351fca66eE@GOTPCREL] -.LBB16_1: + jmp qword ptr [rip + _ZN3std4time10SystemTime11checked_add17h23ec42ea1b09df70E@GOTPCREL] +.LBB18_1: test eax, eax - je .LBB16_4 + je .LBB18_4 not rsi mov edx, 1000000000 sub edx, eax @@ -682,12 +780,12 @@ example::secs_to_systemtime: imul eax, eax, 1000000000 sub edx, eax lea rdi, [rip + .L__unnamed_1] - jmp qword ptr [rip + _ZN3std4time10SystemTime11checked_sub17ha18ef911f5889a11E@GOTPCREL] -.LBB16_4: + jmp qword ptr [rip + _ZN3std4time10SystemTime11checked_sub17h855e2dc31d3cb494E@GOTPCREL] +.LBB18_4: neg rsi lea rdi, [rip + .L__unnamed_1] xor edx, edx - jmp qword ptr [rip + _ZN3std4time10SystemTime11checked_sub17ha18ef911f5889a11E@GOTPCREL] + jmp qword ptr [rip + _ZN3std4time10SystemTime11checked_sub17h855e2dc31d3cb494E@GOTPCREL] example::systemtime_to_datetime: push r14 @@ -703,7 +801,7 @@ example::systemtime_to_datetime: xor ecx, ecx call qword ptr [rip + std::time::SystemTime::duration_since@GOTPCREL] cmp qword ptr [rsp], 0 - je .LBB17_1 + je .LBB19_1 mov rax, qword ptr [rsp + 8] mov ecx, dword ptr [rsp + 16] mov qword ptr [rsp + 40], rax @@ -713,24 +811,24 @@ example::systemtime_to_datetime: cmp edx, 1 sbb rax, -1 cmp rax, r14 - ja .LBB17_6 + ja .LBB19_6 mov esi, 1000000000 sub esi, edx test edx, edx cmove esi, edx neg rax - jmp .LBB17_5 -.LBB17_1: + jmp .LBB19_5 +.LBB19_1: mov rax, qword ptr [rsp + 8] movabs rcx, 46381619174399 cmp rax, rcx - jbe .LBB17_2 -.LBB17_6: + jbe .LBB19_2 +.LBB19_6: xor eax, eax - jmp .LBB17_7 -.LBB17_2: + jmp .LBB19_7 +.LBB19_2: mov esi, dword ptr [rsp + 16] -.LBB17_5: +.LBB19_5: lea rcx, [r14 + rax] add rcx, 26438400 movabs rdx, -4454547087429121353 @@ -786,7 +884,7 @@ example::systemtime_to_datetime: mov byte ptr [rbx + 12], dl mov dword ptr [rbx + 16], esi mov eax, 1 -.LBB17_7: +.LBB19_7: mov dword ptr [rbx], eax mov rax, rbx add rsp, 56 @@ -820,7 +918,7 @@ example::datetime_to_systemtime: lea esi, [rcx + rax] add esi, -307 cmp esi, 1073719447 - ja .LBB18_1 + ja .LBB20_1 add eax, ecx add eax, -536895459 movzx ecx, byte ptr [rdi + 8] @@ -833,9 +931,9 @@ example::datetime_to_systemtime: add rsi, rcx add rsi, rax add rsi, rdi - jns .LBB18_2 + jns .LBB20_2 test edx, edx - je .LBB18_5 + je .LBB20_5 not rsi mov eax, 1000000000 sub eax, edx @@ -848,10 +946,10 @@ example::datetime_to_systemtime: sub eax, ecx lea rdi, [rip + .L__unnamed_1] mov edx, eax - jmp qword ptr [rip + _ZN3std4time10SystemTime11checked_sub17ha18ef911f5889a11E@GOTPCREL] -.LBB18_1: + jmp qword ptr [rip + _ZN3std4time10SystemTime11checked_sub17h855e2dc31d3cb494E@GOTPCREL] +.LBB20_1: xor esi, esi -.LBB18_2: +.LBB20_2: mov eax, edx shr eax, 9 imul rax, rax, 281475 @@ -860,12 +958,12 @@ example::datetime_to_systemtime: imul eax, eax, 1000000000 sub edx, eax lea rdi, [rip + .L__unnamed_1] - jmp qword ptr [rip + _ZN3std4time10SystemTime11checked_add17hd4da3f7351fca66eE@GOTPCREL] -.LBB18_5: + jmp qword ptr [rip + _ZN3std4time10SystemTime11checked_add17h23ec42ea1b09df70E@GOTPCREL] +.LBB20_5: neg rsi lea rdi, [rip + .L__unnamed_1] xor edx, edx - jmp qword ptr [rip + _ZN3std4time10SystemTime11checked_sub17ha18ef911f5889a11E@GOTPCREL] + jmp qword ptr [rip + _ZN3std4time10SystemTime11checked_sub17h855e2dc31d3cb494E@GOTPCREL] .L__unnamed_1: .zero 12 diff --git a/src/lib.rs b/src/lib.rs index 3272455..473d2d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -476,6 +476,98 @@ pub const fn date_to_weekday((y, m, d): (i32, u8, u8)) -> u8 { rd_to_weekday(rd) } +/// Calculate next Gregorian date given a Gregorian date +/// +/// Given a `(year, month, day)` tuple returns the `(year, month, day)` tuple +/// for the following Gregorian date. +/// +/// # Panics +/// +/// Year must be between [YEAR_MIN] and [YEAR_MAX]. Month must be between `1` +/// and `12`. Day must be between `1` and the number of days in the month in +/// question and the next date must not be after [YEAR_MAX]. Bounds are checked +/// using `debug_assert` only, so that the checks are not present in release +/// builds, similar to integer overflow checks. +/// +/// # Examples +/// +/// ``` +/// use datealgo::{next_date}; +/// +/// assert_eq!(next_date((2023, 5, 12)), (2023, 5, 13)); +/// assert_eq!(next_date((1970, 1, 1)), (1970, 1, 2)); +/// assert_eq!(next_date((2023, 1, 31)), (2023, 2, 1)); +/// assert_eq!(next_date((2023, 12, 31)), (2024, 1, 1)); +/// ``` +/// +/// # Algorithm +/// +/// Simple incrementation with manual overflow checking and carry. Relatively +/// speedy, but not fully optimized. +#[inline] +pub const fn next_date((y, m, d): (i32, u8, u8)) -> (i32, u8, u8) { + 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"); + debug_assert!( + y != YEAR_MAX || m != consts::MONTH_MAX || d != consts::DAY_MAX, + "next date is out of range" + ); + if d < 28 || d < days_in_month(y, m) { + (y, m, d + 1) + } else if m < 12 { + (y, m + 1, 1) + } else { + (y + 1, 1, 1) + } +} + +/// Calculate previous Gregorian date given a Gregorian date +/// +/// Given a `(year, month, day)` tuple returns the `(year, month, day)` tuple +/// for the preceding Gregorian date. +/// +/// # Panics +/// +/// Year must be between [YEAR_MIN] and [YEAR_MAX]. Month must be between `1` +/// and `12`. Day must be between `1`, the number of days in the month in +/// question and the previous date must not be before [YEAR_MIN]. Bounds are +/// checked using `debug_assert` only, so that the checks are not present in +/// release builds, similar to integer overflow checks. +/// +/// # Examples +/// +/// ``` +/// use datealgo::{prev_date}; +/// +/// assert_eq!(prev_date((2023, 5, 12)), (2023, 5, 11)); +/// assert_eq!(prev_date((1970, 1, 1)), (1969, 12, 31)); +/// assert_eq!(prev_date((2023, 2, 1)), (2023, 1, 31)); +/// assert_eq!(prev_date((2024, 1, 1)), (2023, 12, 31)); +/// ``` +/// +/// # Algorithm +/// +/// Simple decrementation with manual underflow checking and carry. Relatively +/// speedy, but not fully optimized. +#[inline] +pub const fn prev_date((y, m, d): (i32, u8, u8)) -> (i32, u8, u8) { + 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"); + debug_assert!( + y != YEAR_MIN || m != consts::MONTH_MIN || d != consts::DAY_MIN, + "previous date is out of range" + ); + if d > 1 { + (y, m, d - 1) + } else if m > 1 { + (y, m - 1, days_in_month(y, m - 1)) + } else { + (y - 1, 12, 31) + } +} + /// Split total seconds to days, hours, minutes and seconds /// /// Given seconds counting from Unix epoch (January 1st, 1970) returns a `(days, @@ -513,7 +605,7 @@ pub const fn secs_to_dhms(secs: i64) -> (i32, u8, u8, u8) { "given seconds value is out of range" ); // Algorithm is based on the following identities valid for all n in [0, 97612919[. - // + // // n / 60 = 71582789 * n / 2^32, // n % 60 = 71582789 * n % 2^32 / 71582789. // @@ -522,15 +614,15 @@ pub const fn secs_to_dhms(secs: i64) -> (i32, u8, u8, u8) { let secs = secs.wrapping_add(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[ - - let prd = 71582789 * secs; - let mins = prd >> 32; // secs / 60 + + let prd = 71582789 * secs; + let mins = prd >> 32; // secs / 60 let ss = (prd as u32) / 71582789; // secs % 60 - + let prd = 71582789 * mins; - let hh = prd >> 32; // mins / 60 + let hh = prd >> 32; // mins / 60 let mm = (prd as u32) / 71582789; // mins % 60 - + let days = (days as i32).wrapping_sub(DAY_OFFSET); (days, hh as u8, mm as u8, ss as u8) } diff --git a/tests/api.rs b/tests/api.rs index 561df44..b0a024a 100644 --- a/tests/api.rs +++ b/tests/api.rs @@ -74,6 +74,36 @@ fn test_date_to_weekday() { assert_eq!(date_to_weekday((-400, 1, 1)), 6); } +#[test] +fn test_next_date() { + assert_eq!(next_date((2021, 1, 1)), (2021, 1, 2)); + assert_eq!(next_date((-2021, 1, 1)), (-2021, 1, 2)); + assert_eq!(next_date((2021, 2, 28)), (2021, 3, 1)); + assert_eq!(next_date((2021, 4, 30)), (2021, 5, 1)); + assert_eq!(next_date((2021, 5, 31)), (2021, 6, 1)); + assert_eq!(next_date((2021, 1, 31)), (2021, 2, 1)); + assert_eq!(next_date((2021, 12, 31)), (2022, 1, 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((-2020, 2, 28)), (-2020, 2, 29)); + assert_eq!(next_date((-2020, 2, 29)), (-2020, 3, 1)); +} + +#[test] +fn test_prev_date() { + assert_eq!(prev_date((2021, 1, 1)), (2020, 12, 31)); + assert_eq!(prev_date((-2021, 1, 1)), (-2022, 12, 31)); + assert_eq!(prev_date((2021, 3, 1)), (2021, 2, 28)); + assert_eq!(prev_date((2021, 5, 1)), (2021, 4, 30)); + assert_eq!(prev_date((2021, 6, 1)), (2021, 5, 31)); + assert_eq!(prev_date((2021, 2, 1)), (2021, 1, 31)); + assert_eq!(prev_date((2022, 1, 1)), (2021, 12, 31)); + assert_eq!(prev_date((2020, 2, 29)), (2020, 2, 28)); + 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)); +} + #[test] fn test_secs_to_dhms() { assert_eq!(secs_to_dhms(RD_SECONDS_MIN), (RD_MIN, 0, 0, 0)); diff --git a/tests/quickcheck.rs b/tests/quickcheck.rs index 3e6410d..a0443aa 100644 --- a/tests/quickcheck.rs +++ b/tests/quickcheck.rs @@ -52,6 +52,28 @@ quickcheck! { TestResult::from_bool(days_a == days_b) } + fn quickcheck_next_date(d: time::Date) -> TestResult { + if d == time::Date::MAX { + return TestResult::discard(); + } + let date = (d.year() as i32, d.month() as u8, d.day() as u8); + let next_date = datealgo::next_date(date); + let nd = d + time::Duration::days(1); + let expected_date = (nd.year() as i32, nd.month() as u8, nd.day() as u8); + TestResult::from_bool(next_date == expected_date) + } + + fn quickcheck_prev_date(d: time::Date) -> TestResult { + if d == time::Date::MIN { + return TestResult::discard(); + } + let date = (d.year() as i32, d.month() as u8, d.day() as u8); + let prev_date = datealgo::prev_date(date); + let pd = d - time::Duration::days(1); + let expected_date = (pd.year() as i32, pd.month() as u8, pd.day() as u8); + TestResult::from_bool(prev_date == expected_date) + } + fn quickcheck_rd_to_isoweekdate(d: time::Date) -> TestResult { let rd = d.to_julian_day() - 2440588; let a = rd_to_isoweekdate(rd);