Coverage Report

Created: 2025-08-13 21:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/a220/proj/radnelac/src/calendar/ethiopic.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::coptic::Coptic;
6
use crate::calendar::julian::Julian;
7
use crate::calendar::prelude::CommonDate;
8
use crate::calendar::prelude::CommonWeekOfYear;
9
use crate::calendar::prelude::GuaranteedMonth;
10
use crate::calendar::prelude::HasLeapYears;
11
use crate::calendar::prelude::Quarter;
12
use crate::calendar::prelude::ToFromCommonDate;
13
use crate::calendar::AllowYearZero;
14
use crate::calendar::CalendarMoment;
15
use crate::calendar::OrdinalDate;
16
use crate::calendar::ToFromOrdinalDate;
17
use crate::common::error::CalendarError;
18
use crate::common::math::TermNum;
19
use crate::day_count::BoundedDayCount;
20
use crate::day_count::CalculatedBounds;
21
use crate::day_count::Epoch;
22
use crate::day_count::Fixed;
23
use crate::day_count::FromFixed;
24
use crate::day_count::ToFixed;
25
#[allow(unused_imports)] //FromPrimitive is needed for derive
26
use num_traits::FromPrimitive;
27
use std::num::NonZero;
28
29
//TODO: Ethiopic weekdays
30
31
//LISTING 4.5 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
32
const ETHIOPIC_EPOCH_JULIAN: CommonDate = CommonDate {
33
    year: 8,
34
    month: 8,
35
    day: 29,
36
};
37
38
/// Represents a month in the Ethiopic Calendar
39
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy, FromPrimitive, ToPrimitive)]
40
pub enum EthiopicMonth {
41
    //LISTING ?? SECTION 4.2 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
42
    Maskaram = 1,
43
    Teqemt,
44
    Hedar,
45
    Takhsas,
46
    Ter,
47
    Yakatit,
48
    Magabit,
49
    Miyazya,
50
    Genbot,
51
    Sane,
52
    Hamle,
53
    Nahase,
54
    Paguemen,
55
}
56
57
impl EthiopicMonth {
58
34.0k
    pub fn length(self, leap: bool) -> u8 {
59
34.0k
        match self {
60
            EthiopicMonth::Paguemen => {
61
2.46k
                if leap {
62
852
                    6
63
                } else {
64
1.61k
                    5
65
                }
66
            }
67
31.6k
            _ => 30,
68
        }
69
34.0k
    }
70
}
71
72
/// Represents a date in the Ethiopic calendar
73
///
74
/// ## Further reading
75
/// + [Wikipedia](https://en.wikipedia.org/wiki/Ethiopic_calendar)
76
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
77
pub struct Ethiopic(CommonDate);
78
79
impl AllowYearZero for Ethiopic {}
80
81
impl ToFromOrdinalDate for Ethiopic {
82
1.28k
    fn valid_ordinal(ord: OrdinalDate) -> Result<(), CalendarError> {
83
1.28k
        let correction = if Ethiopic::is_leap(ord.year) { 
1292
} else {
0988
};
84
1.28k
        if ord.day_of_year > 0 && 
ord.day_of_year <= (365 + correction)1.02k
{
85
570
            Ok(())
86
        } else {
87
710
            Err(CalendarError::InvalidDayOfYear)
88
        }
89
1.28k
    }
90
91
512
    fn ordinal_from_fixed(fixed_date: Fixed) -> OrdinalDate {
92
512
        let f = Fixed::new(fixed_date.get() + Coptic::epoch().get() - Ethiopic::epoch().get());
93
512
        Coptic::ordinal_from_fixed(f)
94
512
    }
95
96
1.79k
    fn to_ordinal(self) -> OrdinalDate {
97
1.79k
        let e =
98
1.79k
            Coptic::try_from_common_date(self.to_common_date()).expect("Same month/day validity");
99
1.79k
        e.to_ordinal()
100
1.79k
    }
101
102
256
    fn from_ordinal_unchecked(ord: OrdinalDate) -> Self {
103
256
        let e = Coptic::from_ordinal_unchecked(ord);
104
256
        Ethiopic::try_from_common_date(e.to_common_date()).expect("Same month/day validity")
105
256
    }
106
}
107
108
impl HasLeapYears for Ethiopic {
109
35.8k
    fn is_leap(year: i32) -> bool {
110
35.8k
        year.modulus(4) == 3
111
35.8k
    }
112
}
113
114
impl CalculatedBounds for Ethiopic {}
115
116
impl Epoch for Ethiopic {
117
15.1k
    fn epoch() -> Fixed {
118
15.1k
        Julian::try_from_common_date(ETHIOPIC_EPOCH_JULIAN)
119
15.1k
            .expect("Epoch known to be in range.")
120
15.1k
            .to_fixed()
121
15.1k
    }
122
}
123
124
impl FromFixed for Ethiopic {
125
10.7k
    fn from_fixed(date: Fixed) -> Ethiopic {
126
        //LISTING 4.7 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
127
10.7k
        let f = Fixed::new(date.get() + Coptic::epoch().get() - Ethiopic::epoch().get());
128
10.7k
        Ethiopic::try_from_common_date(Coptic::from_fixed(f).to_common_date())
129
10.7k
            .expect("Same month/day validity")
130
10.7k
    }
131
}
132
133
impl ToFixed for Ethiopic {
134
3.58k
    fn to_fixed(self) -> Fixed {
135
        //LISTING 4.6 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
136
3.58k
        let e =
137
3.58k
            Coptic::try_from_common_date(self.to_common_date()).expect("Same month/day validity");
138
3.58k
        Fixed::new(Ethiopic::epoch().get() + e.to_fixed().get() - Coptic::epoch().get())
139
3.58k
    }
140
}
141
142
impl ToFromCommonDate<EthiopicMonth> for Ethiopic {
143
20.4k
    fn to_common_date(self) -> CommonDate {
144
20.4k
        self.0
145
20.4k
    }
146
147
16.7k
    fn from_common_date_unchecked(date: CommonDate) -> Self {
148
16.7k
        debug_assert!(Self::valid_ymd(date).is_ok());
149
16.7k
        Self(date)
150
16.7k
    }
151
152
35.1k
    fn valid_ymd(date: CommonDate) -> Result<(), CalendarError> {
153
35.1k
        let month_opt = EthiopicMonth::from_u8(date.month);
154
35.1k
        if month_opt.is_none() {
155
768
            Err(CalendarError::InvalidMonth)
156
34.3k
        } else if date.day < 1 {
157
256
            Err(CalendarError::InvalidDay)
158
34.0k
        } else if date.day > month_opt.unwrap().length(Ethiopic::is_leap(date.year)) {
159
256
            Err(CalendarError::InvalidDay)
160
        } else {
161
33.8k
            Ok(())
162
        }
163
35.1k
    }
164
165
256
    fn year_end_date(year: i32) -> CommonDate {
166
256
        Coptic::year_end_date(year)
167
256
    }
168
}
169
170
impl Quarter for Ethiopic {
171
2.81k
    fn quarter(self) -> NonZero<u8> {
172
2.81k
        if self.month() == EthiopicMonth::Paguemen {
173
262
            NonZero::new(4 as u8).expect("4 != 0")
174
        } else {
175
2.55k
            NonZero::new((((self.month() as u8) - 1) / 3) + 1).expect("(m-1)/3 > -1")
176
        }
177
2.81k
    }
178
}
179
180
impl GuaranteedMonth<EthiopicMonth> for Ethiopic {}
181
impl CommonWeekOfYear<EthiopicMonth> for Ethiopic {}
182
183
/// Represents a date *and time* in the Ethiopic Calendar
184
pub type EthiopicMoment = CalendarMoment<Ethiopic>;