Coverage Report

Created: 2025-10-19 21:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/a220/proj/radnelac/src/day_cycle/akan.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 crate::day_count::BoundedDayCount;
7
use crate::day_count::Epoch;
8
use crate::day_count::Fixed;
9
use crate::day_count::FromFixed;
10
use crate::day_cycle::BoundedCycle;
11
use crate::day_cycle::OnOrBefore;
12
use num_traits::FromPrimitive;
13
use num_traits::ToPrimitive;
14
15
/// Represents a prefix in the Akan day cycle
16
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy, FromPrimitive, ToPrimitive)]
17
pub enum AkanPrefix {
18
    //LISTING ?? SECTION 1.13 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
19
    Nwona = 1,
20
    Nkyi,
21
    Kuru,
22
    Kwa,
23
    Mono,
24
    Fo,
25
}
26
27
impl BoundedCycle<6, 1> for AkanPrefix {}
28
29
/// Represents a stem in the Akan day cycle
30
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy, FromPrimitive, ToPrimitive)]
31
pub enum AkanStem {
32
    //LISTING ?? SECTION 1.13 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
33
    Wukuo = 1,
34
    Yaw,
35
    Fie,
36
    Memene,
37
    Kwasi,
38
    Dwo,
39
    Bene,
40
}
41
42
impl BoundedCycle<7, 1> for AkanStem {}
43
44
/// Represents a specific day in the Akan day cycle
45
///
46
/// Further reading:
47
/// + [Wikipedia](https://en.wikipedia.org/wiki/Akan_calendar)
48
#[derive(Debug, PartialEq, Clone, Copy)]
49
pub struct Akan {
50
    prefix: AkanPrefix,
51
    stem: AkanStem,
52
}
53
54
//LISTING 1.78 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
55
const CYCLE_START: i64 = 37;
56
const CYCLE_LENGTH: u8 = 42;
57
58
impl Akan {
59
    /// Create a day in the Akan day cycle
60
17.9k
    pub fn new(prefix: AkanPrefix, stem: AkanStem) -> Akan {
61
17.9k
        Akan { prefix, stem }
62
17.9k
    }
63
64
    /// Given a position in the Akan day cycle, return the day in the cycle.
65
    ///
66
    /// It is assumed that the first day in the cycle is Nwuna-Wukuo.
67
16.8k
    pub fn day_name(n: i64) -> Akan {
68
        //LISTING 1.76 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
69
        //Modified so that modulus is included in BoundedCycle trait
70
16.8k
        Akan::new(AkanPrefix::from_unbounded(n), AkanStem::from_unbounded(n))
71
16.8k
    }
72
73
    /// Given two days in the Akan day cycle, return the difference in days.
74
13.0k
    pub fn name_difference(self, other: Self) -> i16 {
75
        //LISTING 1.77 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
76
13.0k
        let prefix1 = self.prefix as i16;
77
13.0k
        let stem1 = self.stem as i16;
78
13.0k
        let prefix2 = other.prefix as i16;
79
13.0k
        let stem2 = other.stem as i16;
80
13.0k
        let prefix_diff = prefix2 - prefix1;
81
13.0k
        let stem_diff = stem2 - stem1;
82
13.0k
        (prefix_diff + 36 * (stem_diff - prefix_diff)).adjusted_remainder(CYCLE_LENGTH as i16)
83
13.0k
    }
84
85
    /// Given a day in the Akan cycle, return the stem
86
3.32k
    pub fn stem(self) -> AkanStem {
87
3.32k
        self.stem
88
3.32k
    }
89
90
    /// Given a day in the Akan cycle, return the stem
91
3.21k
    pub fn prefix(self) -> AkanPrefix {
92
3.21k
        self.prefix
93
3.21k
    }
94
}
95
96
impl FromFixed for Akan {
97
15.8k
    fn from_fixed(t: Fixed) -> Akan {
98
        //LISTING 1.79 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
99
15.8k
        Akan::day_name(t.get_day_i() - Akan::epoch().get_day_i())
100
15.8k
    }
101
}
102
103
impl FromPrimitive for Akan {
104
1.02k
    fn from_i64(n: i64) -> Option<Self> {
105
1.02k
        Some(Akan::day_name(n))
106
1.02k
    }
107
108
512
    fn from_u64(n: u64) -> Option<Self> {
109
512
        Akan::from_i64(n.to_i64()
?0
)
110
512
    }
111
}
112
113
impl ToPrimitive for Akan {
114
1.02k
    fn to_i64(&self) -> Option<i64> {
115
1.02k
        let a = Akan::new(AkanPrefix::Nwona, AkanStem::Wukuo);
116
1.02k
        let diff = a.name_difference(*self) + 1;
117
1.02k
        Some(diff.adjusted_remainder(CYCLE_LENGTH as i16) as i64)
118
1.02k
    }
119
120
256
    fn to_u64(&self) -> Option<u64> {
121
256
        Some(self.to_i64().expect("Guaranteed in range") as u64)
122
256
    }
123
}
124
125
impl Epoch for Akan {
126
15.8k
    fn epoch() -> Fixed {
127
15.8k
        Fixed::new(CYCLE_START as f64)
128
15.8k
    }
129
}
130
131
impl BoundedCycle<CYCLE_LENGTH, 1> for Akan {}
132
133
impl OnOrBefore<CYCLE_LENGTH, 1> for Akan {
134
12.0k
    fn raw_on_or_before(self, date: i64) -> Fixed {
135
        //LISTING 1.80 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
136
12.0k
        let diff = Akan::from_fixed(Fixed::cast_new(0)).name_difference(self) as i64;
137
12.0k
        Fixed::cast_new(diff.interval_modulus(date, date - (CYCLE_LENGTH as i64)))
138
12.0k
    }
139
}
140
141
#[cfg(test)]
142
mod tests {
143
    use super::*;
144
    use crate::day_count::FIXED_MAX;
145
    use crate::day_count::FIXED_MIN;
146
    use proptest::proptest;
147
148
    proptest! {
149
        #[test]
150
        fn akan_prefix_stem_repeats(x in FIXED_MIN..(FIXED_MAX - 7.0), d in 1.0..5.0) {
151
            let a1 = Akan::from_fixed(Fixed::new(x));
152
            let a2 = Akan::from_fixed(Fixed::new(x + d));
153
            let a3 = Akan::from_fixed(Fixed::new(x + 6.0));
154
            let a4 = Akan::from_fixed(Fixed::new(x + 7.0));
155
            assert_ne!(a1.prefix(), a2.prefix());
156
            assert_eq!(a1.prefix(), a3.prefix());
157
            assert_ne!(a1.prefix(), a4.prefix());
158
            assert_ne!(a1.stem(), a2.stem());
159
            assert_ne!(a1.stem(), a3.stem());
160
            assert_eq!(a1.stem(), a4.stem());
161
        }
162
163
        #[test]
164
        fn prefix_stem_sequence(x in FIXED_MIN..FIXED_MAX) {
165
            let f0 = Fixed::new(x);
166
            let f1 = Fixed::new(x + 1.0);
167
            let a0 = Akan::from_fixed(f0);
168
            let a1 = Akan::from_fixed(f1);
169
            let p0 = a0.prefix();
170
            let p1 = a1.prefix();
171
            if p0 == AkanPrefix::Fo {
172
                assert_eq!(p1, AkanPrefix::Nwona);
173
            } else {
174
                assert_eq!(p1, AkanPrefix::from_i64(p0 as i64 + 1).unwrap());
175
            }
176
            let s0 = a0.stem();
177
            let s1 = a1.stem();
178
            if s0 == AkanStem::Bene {
179
                assert_eq!(s1, AkanStem::Wukuo)
180
            } else {
181
                assert_eq!(s1, AkanStem::from_i64(s0 as i64 + 1).unwrap());
182
            }
183
        }
184
185
    }
186
}