radnelac/calendar/
positivist.rs1use crate::calendar::gregorian::Gregorian;
7use crate::calendar::prelude::CommonDate;
8use crate::calendar::prelude::HasLeapYears;
9use crate::calendar::prelude::Perennial;
10use crate::calendar::prelude::Quarter;
11use crate::calendar::prelude::ToFromCommonDate;
12use crate::calendar::prelude::ToFromOrdinalDate;
13use crate::calendar::AllowYearZero;
14use crate::calendar::CalendarMoment;
15use crate::calendar::HasEpagemonae;
16use crate::calendar::OrdinalDate;
17use crate::common::error::CalendarError;
18use crate::common::math::TermNum;
19use crate::day_count::BoundedDayCount;
20use crate::day_count::CalculatedBounds;
21use crate::day_count::Epoch;
22use crate::day_count::Fixed;
23use crate::day_count::FromFixed;
24use crate::day_count::ToFixed;
25use crate::day_cycle::Weekday;
26use std::num::NonZero;
27
28#[allow(unused_imports)] use num_traits::FromPrimitive;
30
31const POSITIVIST_YEAR_OFFSET: i32 = 1789 - 1;
32const NON_MONTH: u8 = 14;
33
34#[derive(Debug, PartialEq, PartialOrd, Clone, Copy, FromPrimitive, ToPrimitive)]
45pub enum PositivistMonth {
46 Moses = 1,
47 Homer,
48 Aristotle,
49 Archimedes,
50 Caesar,
51 SaintPaul,
52 Charlemagne,
53 Dante,
54 Gutenburg,
55 Shakespeare,
56 Descartes,
57 Frederick,
58 Bichat,
59}
60
61#[derive(Debug, PartialEq, PartialOrd, Clone, Copy, FromPrimitive, ToPrimitive)]
66pub enum PositivistComplementaryDay {
67 FestivalOfTheDead = 1,
70 FestivalOfHolyWomen,
72}
73
74#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
157pub struct Positivist(CommonDate);
158
159impl AllowYearZero for Positivist {}
160
161impl ToFromOrdinalDate for Positivist {
162 fn valid_ordinal(ord: OrdinalDate) -> Result<(), CalendarError> {
163 let ord_g = OrdinalDate {
164 year: ord.year + POSITIVIST_YEAR_OFFSET,
165 day_of_year: ord.day_of_year,
166 };
167 Gregorian::valid_ordinal(ord_g)
168 }
169
170 fn ordinal_from_fixed(fixed_date: Fixed) -> OrdinalDate {
171 let ord_g = Gregorian::ordinal_from_fixed(fixed_date);
172 OrdinalDate {
173 year: ord_g.year - POSITIVIST_YEAR_OFFSET,
174 day_of_year: ord_g.day_of_year,
175 }
176 }
177
178 fn to_ordinal(self) -> OrdinalDate {
179 let offset_m = ((self.0.month as i64) - 1) * 28;
180 let doy = (offset_m as u16) + (self.0.day as u16);
181 OrdinalDate {
182 year: self.0.year,
183 day_of_year: doy,
184 }
185 }
186
187 fn from_ordinal_unchecked(ord: OrdinalDate) -> Self {
188 let year = ord.year;
189 let month = (((ord.day_of_year - 1) as i64).div_euclid(28) + 1) as u8;
190 let day = (ord.day_of_year as i64).adjusted_remainder(28) as u8;
191 debug_assert!(day > 0 && day < 29);
192 Positivist(CommonDate::new(year, month, day))
193 }
194}
195
196impl HasEpagemonae<PositivistComplementaryDay> for Positivist {
197 fn epagomenae(self) -> Option<PositivistComplementaryDay> {
199 if self.0.month == NON_MONTH {
200 PositivistComplementaryDay::from_u8(self.0.day)
201 } else {
202 None
203 }
204 }
205
206 fn epagomenae_count(p_year: i32) -> u8 {
207 if Positivist::is_leap(p_year) {
208 2
209 } else {
210 1
211 }
212 }
213}
214
215impl Perennial<PositivistMonth, Weekday> for Positivist {
216 fn weekday(self) -> Option<Weekday> {
218 if self.0.month == NON_MONTH {
219 None
220 } else {
221 Weekday::from_i64((self.0.day as i64).modulus(7))
222 }
223 }
224
225 fn days_per_week() -> u8 {
226 7
227 }
228
229 fn weeks_per_month() -> u8 {
230 4
231 }
232}
233
234impl HasLeapYears for Positivist {
235 fn is_leap(p_year: i32) -> bool {
237 Gregorian::is_leap(POSITIVIST_YEAR_OFFSET + p_year)
238 }
239}
240
241impl CalculatedBounds for Positivist {}
242
243impl Epoch for Positivist {
244 fn epoch() -> Fixed {
245 Gregorian::try_year_start(POSITIVIST_YEAR_OFFSET)
246 .expect("Year known to be valid")
247 .to_fixed()
248 }
249}
250
251impl FromFixed for Positivist {
252 fn from_fixed(date: Fixed) -> Positivist {
253 let ord_g = Gregorian::ordinal_from_fixed(date);
254 let ord = OrdinalDate {
255 year: ord_g.year - POSITIVIST_YEAR_OFFSET,
256 day_of_year: ord_g.day_of_year,
257 };
258 Self::from_ordinal_unchecked(ord)
259 }
260}
261
262impl ToFixed for Positivist {
263 fn to_fixed(self) -> Fixed {
264 let y = self.0.year + POSITIVIST_YEAR_OFFSET;
265 let offset_y = Gregorian::try_year_start(y)
266 .expect("Year known to be valid")
267 .to_fixed()
268 .get_day_i()
269 - 1;
270 let doy = self.to_ordinal().day_of_year as i64;
271 Fixed::cast_new(offset_y + doy)
272 }
273}
274
275impl ToFromCommonDate<PositivistMonth> for Positivist {
276 fn to_common_date(self) -> CommonDate {
277 self.0
278 }
279
280 fn from_common_date_unchecked(date: CommonDate) -> Self {
281 debug_assert!(Self::valid_ymd(date).is_ok());
282 Self(date)
283 }
284
285 fn valid_ymd(date: CommonDate) -> Result<(), CalendarError> {
286 if date.month < 1 || date.month > NON_MONTH {
287 Err(CalendarError::InvalidMonth)
288 } else if date.day < 1 {
289 Err(CalendarError::InvalidDay)
290 } else if date.month < NON_MONTH && date.day > 28 {
291 Err(CalendarError::InvalidDay)
292 } else if date.month == NON_MONTH && date.day > Positivist::epagomenae_count(date.year) {
293 Err(CalendarError::InvalidDay)
294 } else {
295 Ok(())
296 }
297 }
298
299 fn year_end_date(year: i32) -> CommonDate {
300 CommonDate::new(year, NON_MONTH, Positivist::epagomenae_count(year))
301 }
302
303 fn month_length(_year: i32, _month: PositivistMonth) -> u8 {
304 28
305 }
306}
307
308impl Quarter for Positivist {
309 fn quarter(self) -> NonZero<u8> {
310 match self.try_week_of_year() {
311 None => NonZero::new(4).unwrap(),
312 Some(w) => NonZero::new((w - 1) / 13 + 1).expect("w > 0"),
313 }
314 }
315}
316
317pub type PositivistMoment = CalendarMoment<Positivist>;
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
325 fn epoch() {
326 let dg = Gregorian::try_from_common_date(CommonDate::new(1789, 1, 1)).unwrap();
327 let dp = Positivist::try_from_common_date(CommonDate::new(1, 1, 1)).unwrap();
328 let fg = dg.to_fixed();
329 let fp = dp.to_fixed();
330 assert_eq!(fg, fp);
331 }
332
333 #[test]
334 fn example_from_text() {
335 let dg = Gregorian::try_from_common_date(CommonDate::new(1855, 1, 1)).unwrap();
337 let dp = Positivist::try_from_common_date(CommonDate::new(67, 1, 1)).unwrap();
338 let fg = dg.to_fixed();
339 let fp = dp.to_fixed();
340 assert_eq!(fg, fp);
341 }
342}