Coverage Report

Created: 2025-08-13 21:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/a220/proj/radnelac/src/calendar/egyptian.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::prelude::CommonDate;
6
use crate::calendar::prelude::CommonWeekOfYear;
7
use crate::calendar::prelude::Quarter;
8
use crate::calendar::prelude::ToFromCommonDate;
9
use crate::calendar::AllowYearZero;
10
use crate::calendar::CalendarMoment;
11
use crate::calendar::HasIntercalaryDays;
12
use crate::calendar::OrdinalDate;
13
use crate::calendar::ToFromOrdinalDate;
14
use crate::common::error::CalendarError;
15
use crate::common::math::TermNum;
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::JulianDay;
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.46 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
28
const NABONASSAR_ERA_JD: i32 = 1448638;
29
const NON_MONTH: u8 = 13;
30
31
/// Represents a month in the Egyptian Calendar
32
///
33
/// Note that the epagomenal days at the end of the Egyptian calendar year have no
34
/// month and thus are not represented by ArmenianMonth. When representing an
35
/// arbitrary day in the Egyptian calendar, use an `Option<EgyptianMonth>` for the
36
/// the month field.
37
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy, FromPrimitive, ToPrimitive)]
38
pub enum EgyptianMonth {
39
    //LISTING ?? SECTION 1.11 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
40
    Thoth = 1,
41
    Phaophi,
42
    Athyr,
43
    Choiak,
44
    Tybi,
45
    Mechir,
46
    Phamenoth,
47
    Pharmuthi,
48
    Pachon,
49
    Payni,
50
    Epiphi,
51
    Mesori,
52
}
53
54
/// Represents an epagomenal day at the end of the Egyptian calendar year
55
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy, FromPrimitive, ToPrimitive)]
56
pub enum EgyptianDaysUponTheYear {
57
    BirthOfOsiris = 1,
58
    BirthOfHorus,
59
    BirthOfSeth,
60
    BirthOfIsis,
61
    BirthOfNephthys,
62
}
63
64
/// Represents a date in the Egyptian calendar
65
///
66
/// ## Further reading
67
/// + [Wikipedia](https://en.wikipedia.org/wiki/Egyptian_calendar)
68
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
69
pub struct Egyptian(CommonDate);
70
71
impl AllowYearZero for Egyptian {}
72
73
impl ToFromOrdinalDate for Egyptian {
74
2.04k
    fn valid_ordinal(ord: OrdinalDate) -> Result<(), CalendarError> {
75
2.04k
        if ord.day_of_year < 1 || 
ord.day_of_year > 3651.53k
{
76
1.02k
            Err(CalendarError::InvalidDayOfYear)
77
        } else {
78
1.02k
            Ok(())
79
        }
80
2.04k
    }
81
82
1.02k
    fn ordinal_from_fixed(fixed_date: Fixed) -> OrdinalDate {
83
1.02k
        let days = fixed_date.get_day_i() - Egyptian::epoch().get_day_i();
84
1.02k
        let year = (days.div_euclid(365) + 1) as i32;
85
1.02k
        let doy = (days.modulus(365) + 1) as u16;
86
1.02k
        OrdinalDate {
87
1.02k
            year: year,
88
1.02k
            day_of_year: doy,
89
1.02k
        }
90
1.02k
    }
91
92
4.60k
    fn to_ordinal(self) -> OrdinalDate {
93
4.60k
        let month = self.0.month as i64;
94
4.60k
        let day = self.0.day as i64;
95
4.60k
        let offset = ((30 * (month - 1)) + day) as u16;
96
4.60k
        OrdinalDate {
97
4.60k
            year: self.0.year,
98
4.60k
            day_of_year: offset,
99
4.60k
        }
100
4.60k
    }
101
102
512
    fn from_ordinal_unchecked(ord: OrdinalDate) -> Self {
103
512
        let month = (ord.day_of_year - 1).div_euclid(30) + 1;
104
512
        let day = ord.day_of_year - (30 * (month - 1));
105
512
        Egyptian(CommonDate::new(ord.year, month as u8, day as u8))
106
512
    }
107
}
108
109
impl CalculatedBounds for Egyptian {}
110
111
impl Epoch for Egyptian {
112
83.1k
    fn epoch() -> Fixed {
113
83.1k
        JulianDay::new(NABONASSAR_ERA_JD as f64).to_fixed()
114
83.1k
    }
115
}
116
117
impl FromFixed for Egyptian {
118
34.8k
    fn from_fixed(date: Fixed) -> Egyptian {
119
        //LISTING 1.49 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
120
34.8k
        let days = date.get_day_i() - Egyptian::epoch().get_day_i();
121
34.8k
        let year = days.div_euclid(365) + 1;
122
34.8k
        let month = days.modulus(365).div_euclid(30) + 1;
123
34.8k
        let day = days - (365 * (year - 1)) - (30 * (month - 1)) + 1;
124
34.8k
        Egyptian(CommonDate {
125
34.8k
            year: year as i32,
126
34.8k
            month: month as u8,
127
34.8k
            day: day as u8,
128
34.8k
        })
129
34.8k
    }
130
}
131
132
impl ToFixed for Egyptian {
133
18.3k
    fn to_fixed(self) -> Fixed {
134
        //LISTING 1.47 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
135
18.3k
        let year = self.0.year as i64;
136
18.3k
        let month = self.0.month as i64;
137
18.3k
        let day = self.0.day as i64;
138
18.3k
        let offset = (365 * (year - 1)) + (30 * (month - 1)) + day - 1;
139
18.3k
        Fixed::cast_new(Egyptian::epoch().get_day_i() + offset)
140
18.3k
    }
141
}
142
143
impl ToFromCommonDate<EgyptianMonth> for Egyptian {
144
54.0k
    fn to_common_date(self) -> CommonDate {
145
54.0k
        self.0
146
54.0k
    }
147
148
17.9k
    fn from_common_date_unchecked(date: CommonDate) -> Self {
149
17.9k
        debug_assert!(Self::valid_ymd(date).is_ok());
150
17.9k
        Self(date)
151
17.9k
    }
152
153
88.6k
    fn valid_ymd(date: CommonDate) -> Result<(), CalendarError> {
154
88.6k
        if date.month < 1 || 
date.month > NON_MONTH88.0k
{
155
1.53k
            Err(CalendarError::InvalidMonth)
156
87.0k
        } else if date.day < 1 {
157
512
            Err(CalendarError::InvalidDay)
158
86.5k
        } else if date.month < NON_MONTH && 
date.day > 3077.8k
{
159
512
            Err(CalendarError::InvalidDay)
160
86.0k
        } else if date.month == NON_MONTH && 
date.day > 58.72k
{
161
0
            Err(CalendarError::InvalidDay)
162
        } else {
163
86.0k
            Ok(())
164
        }
165
88.6k
    }
166
167
512
    fn year_end_date(year: i32) -> CommonDate {
168
512
        CommonDate::new(year, NON_MONTH, 5)
169
512
    }
170
}
171
172
impl HasIntercalaryDays<EgyptianDaysUponTheYear> for Egyptian {
173
3.58k
    fn complementary(self) -> Option<EgyptianDaysUponTheYear> {
174
3.58k
        if self.0.month == NON_MONTH {
175
341
            EgyptianDaysUponTheYear::from_u8(self.0.day)
176
        } else {
177
3.24k
            None
178
        }
179
3.58k
    }
180
181
0
    fn complementary_count(_year: i32) -> u8 {
182
0
        5
183
0
    }
184
}
185
186
impl Quarter for Egyptian {
187
2.81k
    fn quarter(self) -> NonZero<u8> {
188
2.81k
        let m = self.to_common_date().month as u8;
189
2.81k
        if m == NON_MONTH {
190
260
            NonZero::new(4 as u8).expect("4 != 0")
191
        } else {
192
2.55k
            NonZero::new(((m - 1) / 3) + 1).expect("(m - 1) / 3 > -1")
193
        }
194
2.81k
    }
195
}
196
197
impl CommonWeekOfYear<EgyptianMonth> for Egyptian {}
198
199
/// Represents a date *and time* in the Egyptian Calendar
200
pub type EgyptianMoment = CalendarMoment<Egyptian>;