Coverage Report

Created: 2025-08-13 21:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/a220/proj/radnelac/src/calendar/olympiad.rs
Line
Count
Source
1
// This Source Code Form is subject to the terms of the Mozilla Public
2
// License, v. 2.0. If a copy of the MPL was not distributed with this
3
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5
use crate::common::math::TermNum;
6
use std::num::NonZero;
7
8
/// Represents a year grouped by Olympiad
9
///
10
/// ## Year 0
11
///
12
/// Year 0 is **not** supported because they are not supported in the Julian calendar.
13
///
14
/// ## Further reading
15
/// + [Wikipedia](https://en.wikipedia.org/wiki/Olympiad)
16
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
17
pub struct Olympiad {
18
    cycle: i32,
19
    year: u8,
20
}
21
22
//LISTING 3.15 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
23
const OLYMPIAD_START: i32 = -776;
24
25
impl Olympiad {
26
256
    pub fn to_julian_year(self) -> NonZero<i32> {
27
        //LISTING 3.16 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
28
256
        let years = OLYMPIAD_START + 4 * (self.cycle - 1) + (self.year as i32) - 1;
29
256
        let result = if years < 0 { 
years127
} else {
years + 1129
};
30
256
        NonZero::new(result).expect("Prevented by if")
31
256
    }
32
33
1.02k
    pub fn from_julian_year(j: NonZero<i32>) -> Self {
34
        //LISTING 3.17 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
35
1.02k
        let j_year = j.get();
36
1.02k
        let years = j_year - OLYMPIAD_START - (if j_year < 0 { 
0499
} else {
1527
});
37
1.02k
        Olympiad {
38
1.02k
            cycle: years.div_euclid(4) + 1,
39
1.02k
            year: (years.modulus(4) as u8 + 1),
40
1.02k
        }
41
1.02k
    }
42
43
514
    pub fn cycle(self) -> i32 {
44
514
        self.cycle
45
514
    }
46
47
1.02k
    pub fn year(self) -> u8 {
48
1.02k
        self.year
49
1.02k
    }
50
}
51
52
#[cfg(test)]
53
mod tests {
54
    use super::*;
55
    use proptest::prop_assume;
56
    use proptest::proptest;
57
58
    #[test]
59
1
    fn next_year_0() {
60
1
        let t0 = -1;
61
1
        let t1 = 1;
62
1
        let o0 = Olympiad::from_julian_year(NonZero::new(t0).unwrap());
63
1
        let o1 = Olympiad::from_julian_year(NonZero::new(t1).unwrap());
64
1
        assert_eq!(o1.year(), (o0.year() + 1).adjusted_remainder(4));
65
1
        if o1.year() == 1 {
66
1
            assert_eq!(o1.cycle(), o0.cycle() + 1);
67
        } else {
68
0
            assert_eq!(o1.cycle(), o0.cycle());
69
        }
70
1
    }
71
72
    proptest! {
73
        #[test]
74
        fn roundtrip(t in i32::MIN..i32::MAX) {
75
            prop_assume!(t != 0);
76
            let o = Olympiad::from_julian_year(NonZero::new(t).unwrap());
77
            let j = o.to_julian_year().get();
78
            assert_eq!(t, j);
79
        }
80
81
        #[test]
82
        fn year_range(t0 in i32::MIN..i32::MAX) {
83
            prop_assume!(t0 != 0);
84
            let o = Olympiad::from_julian_year(NonZero::new(t0).unwrap());
85
            assert!(o.year() < 5);
86
        }
87
88
        #[test]
89
        fn next_year(t0 in i32::MIN..i32::MAX) {
90
            let t1 = t0 + 1;
91
            prop_assume!(t0 != 0 && t1 != 0);
92
            let o0 = Olympiad::from_julian_year(NonZero::new(t0).unwrap());
93
            let o1 = Olympiad::from_julian_year(NonZero::new(t1).unwrap());
94
            assert_eq!(o1.year(), (o0.year() + 1).adjusted_remainder(4));
95
            if o1.year() == 1 {
96
                assert_eq!(o1.cycle(), o0.cycle() + 1);
97
            } else {
98
                assert_eq!(o1.cycle(), o0.cycle());
99
            }
100
        }
101
    }
102
}