/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 | | } |