/home/a220/proj/radnelac/src/calendar/armenian.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::calendar::egyptian::Egyptian; |
6 | | use crate::calendar::prelude::CommonDate; |
7 | | use crate::calendar::prelude::CommonWeekOfYear; |
8 | | use crate::calendar::prelude::Quarter; |
9 | | use crate::calendar::prelude::ToFromCommonDate; |
10 | | use crate::calendar::AllowYearZero; |
11 | | use crate::calendar::CalendarMoment; |
12 | | use crate::calendar::HasIntercalaryDays; |
13 | | use crate::calendar::OrdinalDate; |
14 | | use crate::calendar::ToFromOrdinalDate; |
15 | | use crate::common::error::CalendarError; |
16 | | use crate::day_count::BoundedDayCount; |
17 | | use crate::day_count::CalculatedBounds; |
18 | | use crate::day_count::Epoch; |
19 | | use crate::day_count::Fixed; |
20 | | use crate::day_count::FromFixed; |
21 | | use crate::day_count::RataDie; |
22 | | use crate::day_count::ToFixed; |
23 | | #[allow(unused_imports)] //FromPrimitive is needed for derive |
24 | | use num_traits::FromPrimitive; |
25 | | use std::num::NonZero; |
26 | | |
27 | | //LISTING 1.50 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.) |
28 | | const ARMENIAN_EPOCH_RD: i32 = 201443; |
29 | | const NON_MONTH: u8 = 13; |
30 | | |
31 | | /// Represents a month in the Armenian Calendar |
32 | | /// |
33 | | /// Note that the epagomenal days at the end of the Armenian calendar year have no |
34 | | /// month and thus are not represented by ArmenianMonth. When representing an |
35 | | /// arbitrary day in the Armenian calendar, use an [`Option<ArmenianMonth>`] for the |
36 | | /// the month field. |
37 | | #[derive(Debug, PartialEq, PartialOrd, Clone, Copy, FromPrimitive, ToPrimitive)] |
38 | | pub enum ArmenianMonth { |
39 | | //LISTING ?? SECTION 1.11 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.) |
40 | | Nawasardi = 1, |
41 | | Hori, |
42 | | Sahmi, |
43 | | Tre, |
44 | | Kaloch, |
45 | | Arach, |
46 | | Mehekani, |
47 | | Areg, |
48 | | Ahekani, |
49 | | Mareri, |
50 | | Margach, |
51 | | Hrotich, |
52 | | } |
53 | | |
54 | | /// Represents a day of month in the Armenian Calendar |
55 | | /// |
56 | | /// The Armenian calendar has name for each day of month instead of a number. |
57 | | /// Note that the epagomenal days at the end of the Armenian calendar year have no |
58 | | /// month therefore they also do not have names. |
59 | | #[derive(Debug, PartialEq, PartialOrd, Clone, Copy, FromPrimitive, ToPrimitive)] |
60 | | pub enum ArmenianDaysOfMonth { |
61 | | Areg = 1, |
62 | | Hrand, |
63 | | Aram, |
64 | | Margar, |
65 | | Ahrank, |
66 | | Mazdel, |
67 | | Astlik, |
68 | | Mihr, |
69 | | Jopaber, |
70 | | Murc, |
71 | | Erezhan, |
72 | | Ani, |
73 | | Parkhar, |
74 | | Vanat, |
75 | | Aramazd, |
76 | | Mani, |
77 | | Asak, |
78 | | Masis, |
79 | | Anahit, |
80 | | Aragats, |
81 | | Gorgor, |
82 | | Kordvik, |
83 | | Tsmak, |
84 | | Lusnak, |
85 | | Tsron, |
86 | | Npat, |
87 | | Vahagn, |
88 | | Sim, |
89 | | Varag, |
90 | | Giseravar, |
91 | | } |
92 | | |
93 | | /// Represents a date in the Armenian calendar |
94 | | /// |
95 | | /// ## Introduction |
96 | | /// |
97 | | /// The Armenian calendar was used in Armenia in medieval times. It has a similar structure |
98 | | /// to the [Egyptian calendar](crate::calendar::Egyptian). |
99 | | /// |
100 | | /// ## Basic Structure |
101 | | /// |
102 | | /// Years are always 365 days - there are no leap years. Years are divided into 12 months |
103 | | /// of 30 days each, with an extra 5 epagomenal days. |
104 | | /// |
105 | | /// ## Representation and Examples |
106 | | /// |
107 | | /// The months are represented in this crate as [`ArmenianMonth`]. |
108 | | /// |
109 | | /// ``` |
110 | | /// use radnelac::calendar::*; |
111 | | /// use radnelac::day_count::*; |
112 | | /// |
113 | | /// let c_1_1 = CommonDate::new(1462, 1, 1); |
114 | | /// let a_1_1 = Armenian::try_from_common_date(c_1_1).unwrap(); |
115 | | /// assert_eq!(a_1_1.try_month().unwrap(), ArmenianMonth::Nawasardi); |
116 | | /// let c_12_30 = CommonDate::new(1462, 12, 30); |
117 | | /// let a_12_30 = Armenian::try_from_common_date(c_12_30).unwrap(); |
118 | | /// assert_eq!(a_12_30.try_month().unwrap(), ArmenianMonth::Hrotich); |
119 | | /// ``` |
120 | | /// |
121 | | /// When converting to and from a [`CommonDate`](crate::calendar::CommonDate), the epagomenal days |
122 | | /// are treated as a 13th month. |
123 | | /// |
124 | | /// ``` |
125 | | /// use radnelac::calendar::*; |
126 | | /// use radnelac::day_count::*; |
127 | | /// |
128 | | /// let c = CommonDate::new(1462, 13, 5); |
129 | | /// let a = Armenian::try_from_common_date(c).unwrap(); |
130 | | /// assert!(a.try_month().is_none()); |
131 | | /// assert!(a.complementary().is_some()); |
132 | | /// ``` |
133 | | /// |
134 | | /// ## Further reading |
135 | | /// + [Wikipedia](https://en.wikipedia.org/wiki/Armenian_calendar) |
136 | | #[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] |
137 | | pub struct Armenian(CommonDate); |
138 | | |
139 | | impl Armenian { |
140 | | /// Returns the day name of month if one exists |
141 | 3.56k | pub fn day_name(self) -> Option<ArmenianDaysOfMonth> { |
142 | 3.56k | if self.0.month == NON_MONTH { |
143 | 512 | None |
144 | | } else { |
145 | 3.05k | ArmenianDaysOfMonth::from_u8(self.0.day) |
146 | | } |
147 | 3.56k | } |
148 | | } |
149 | | |
150 | | impl AllowYearZero for Armenian {} |
151 | | |
152 | | impl ToFromOrdinalDate for Armenian { |
153 | 1.02k | fn valid_ordinal(ord: OrdinalDate) -> Result<(), CalendarError> { |
154 | 1.02k | Egyptian::valid_ordinal(ord) |
155 | 1.02k | } |
156 | | |
157 | 512 | fn ordinal_from_fixed(fixed_date: Fixed) -> OrdinalDate { |
158 | 512 | let f = Fixed::new( |
159 | 512 | fixed_date.get() + Egyptian::epoch().to_day().get() - Armenian::epoch().get(), |
160 | | ); |
161 | 512 | Egyptian::ordinal_from_fixed(f) |
162 | 512 | } |
163 | | |
164 | 2.30k | fn to_ordinal(self) -> OrdinalDate { |
165 | 2.30k | let e = |
166 | 2.30k | Egyptian::try_from_common_date(self.to_common_date()).expect("Same month/day validity"); |
167 | 2.30k | e.to_ordinal() |
168 | 2.30k | } |
169 | | |
170 | 256 | fn from_ordinal_unchecked(ord: OrdinalDate) -> Self { |
171 | 256 | let e = Egyptian::from_ordinal_unchecked(ord); |
172 | 256 | Armenian::try_from_common_date(e.to_common_date()).expect("Same month/day validity") |
173 | 256 | } |
174 | | } |
175 | | |
176 | | impl CalculatedBounds for Armenian {} |
177 | | |
178 | | impl Epoch for Armenian { |
179 | 28.9k | fn epoch() -> Fixed { |
180 | 28.9k | RataDie::new(ARMENIAN_EPOCH_RD as f64).to_fixed() |
181 | 28.9k | } |
182 | | } |
183 | | |
184 | | impl FromFixed for Armenian { |
185 | 17.1k | fn from_fixed(date: Fixed) -> Armenian { |
186 | | //LISTING 1.52 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.) |
187 | 17.1k | let f = Fixed::new(date.get() + Egyptian::epoch().to_day().get() - Armenian::epoch().get()); |
188 | 17.1k | Armenian::try_from_common_date(Egyptian::from_fixed(f).to_common_date()) |
189 | 17.1k | .expect("Same month/day validity") |
190 | 17.1k | } |
191 | | } |
192 | | |
193 | | impl ToFixed for Armenian { |
194 | 9.21k | fn to_fixed(self) -> Fixed { |
195 | | //LISTING 1.51 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.) |
196 | 9.21k | let e = |
197 | 9.21k | Egyptian::try_from_common_date(self.to_common_date()).expect("Same month/day validity"); |
198 | 9.21k | Fixed::new(Armenian::epoch().get() + e.to_fixed().get() - Egyptian::epoch().to_day().get()) |
199 | 9.21k | } |
200 | | } |
201 | | |
202 | | /// The epagomenal days at the end of the Armenian calendar year are represented |
203 | | /// as month 13 when converting to and from a CommonDate. |
204 | | impl ToFromCommonDate<ArmenianMonth> for Armenian { |
205 | 45.8k | fn to_common_date(self) -> CommonDate { |
206 | 45.8k | self.0 |
207 | 45.8k | } |
208 | | |
209 | 24.8k | fn from_common_date_unchecked(date: CommonDate) -> Self { |
210 | 24.8k | debug_assert!(Self::valid_ymd(date).is_ok()); |
211 | 24.8k | Self(date) |
212 | 24.8k | } |
213 | | |
214 | 51.2k | fn valid_ymd(date: CommonDate) -> Result<(), CalendarError> { |
215 | 51.2k | Egyptian::valid_ymd(date) |
216 | 51.2k | } |
217 | | |
218 | 256 | fn year_end_date(year: i32) -> CommonDate { |
219 | 256 | Egyptian::year_end_date(year) |
220 | 256 | } |
221 | | } |
222 | | |
223 | | impl HasIntercalaryDays<u8> for Armenian { |
224 | 3.07k | fn complementary(self) -> Option<u8> { |
225 | 3.07k | if self.0.month == NON_MONTH { |
226 | 274 | Some(self.0.day) |
227 | | } else { |
228 | 2.79k | None |
229 | | } |
230 | 3.07k | } |
231 | | |
232 | 0 | fn complementary_count(_year: i32) -> u8 { |
233 | 0 | 5 |
234 | 0 | } |
235 | | } |
236 | | |
237 | | impl Quarter for Armenian { |
238 | 2.81k | fn quarter(self) -> NonZero<u8> { |
239 | 2.81k | let m = self.to_common_date().month as u8; |
240 | 2.81k | if m == NON_MONTH { |
241 | 262 | NonZero::new(4 as u8).expect("4 != 0") |
242 | | } else { |
243 | 2.55k | NonZero::new(((m - 1) / 3) + 1).expect("(m - 1) / 3 > -1") |
244 | | } |
245 | 2.81k | } |
246 | | } |
247 | | |
248 | | impl CommonWeekOfYear<ArmenianMonth> for Armenian {} |
249 | | |
250 | | /// Represents a date *and time* in the Armenian Calendar |
251 | | pub type ArmenianMoment = CalendarMoment<Armenian>; |
252 | | |
253 | | #[cfg(test)] |
254 | | mod tests { |
255 | | use super::*; |
256 | | use crate::day_count::FIXED_MAX; |
257 | | use proptest::proptest; |
258 | | const MAX_YEARS: i32 = (FIXED_MAX / 365.25) as i32; |
259 | | proptest! { |
260 | | #[test] |
261 | | fn day_names(y0 in -MAX_YEARS..MAX_YEARS, y1 in -MAX_YEARS..MAX_YEARS, m in 1..12, d in 1..30) { |
262 | | let a0 = Armenian::try_from_common_date(CommonDate::new(y0, m as u8, d as u8)).unwrap(); |
263 | | let a1 = Armenian::try_from_common_date(CommonDate::new(y1, m as u8, d as u8)).unwrap(); |
264 | | assert_eq!(a0.day_name(), a1.day_name()) |
265 | | } |
266 | | |
267 | | #[test] |
268 | | fn day_names_m13(y0 in -MAX_YEARS..MAX_YEARS, y1 in -MAX_YEARS..MAX_YEARS, d in 1..5) { |
269 | | let a0 = Armenian::try_from_common_date(CommonDate::new(y0, 13, d as u8)).unwrap(); |
270 | | let a1 = Armenian::try_from_common_date(CommonDate::new(y1, 13, d as u8)).unwrap(); |
271 | | assert!(a0.day_name().is_none()); |
272 | | assert!(a1.day_name().is_none()); |
273 | | } |
274 | | } |
275 | | } |