Coverage Report

Created: 2025-10-19 21:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/a220/proj/radnelac/src/calendar/prelude.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::error::CalendarError;
6
use crate::day_count::BoundedDayCount;
7
use crate::day_count::EffectiveBound;
8
use crate::day_count::Fixed;
9
use crate::day_count::ToFixed;
10
use crate::day_cycle::OnOrBefore;
11
use crate::day_cycle::Weekday;
12
use num_traits::FromPrimitive;
13
use num_traits::ToPrimitive;
14
use std::num::NonZero;
15
16
/// Calendar systems with year 0
17
pub trait AllowYearZero {}
18
19
/// Calendar systems with leap years
20
pub trait HasLeapYears {
21
    /// [`true`] if a the given year is a leap year.
22
    fn is_leap(year: i32) -> bool;
23
}
24
25
/// Represents a combination of numeric year, month and day
26
///
27
/// This is not specific to any particular calendar system.
28
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
29
pub struct CommonDate {
30
    pub year: i32,
31
    pub month: u8,
32
    pub day: u8,
33
}
34
35
impl CommonDate {
36
    /// Create a `CommonDate`
37
558k
    pub fn new(year: i32, month: u8, day: u8) -> CommonDate {
38
558k
        CommonDate { year, month, day }
39
558k
    }
40
}
41
42
/// Calendar systems in which a date can be represented by a year, month and day
43
pub trait ToFromCommonDate<T: FromPrimitive>: Sized + EffectiveBound {
44
    /// Convert calendar date to a year, month and day
45
    fn to_common_date(self) -> CommonDate;
46
    /// Convert a year, month and day into a calendar date without checking validity
47
    ///
48
    /// In almost all cases, [`try_from_common_date`](ToFromCommonDate::try_from_common_date) is preferred.
49
    fn from_common_date_unchecked(d: CommonDate) -> Self;
50
    /// Returns error if the year, month or day is invalid
51
    fn valid_ymd(d: CommonDate) -> Result<(), CalendarError>;
52
    /// Start of the year as a numeric year, month and day
53
35.5k
    fn year_start_date(year: i32) -> CommonDate {
54
        //LISTING 2.18 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
55
        //Modified to be generalized across many calendar systems
56
35.5k
        CommonDate::new(year, 1, 1)
57
35.5k
    }
58
    /// End of the year as a numeric year, month and day
59
    fn year_end_date(year: i32) -> CommonDate;
60
    /// Length of month in a given year
61
    fn month_length(year: i32, month: T) -> u8;
62
63
    /// [`true`] if the year, month and day is within the supported range of time.
64
    ///
65
    /// This does not check the validity of the date.
66
40.0k
    fn in_effective_bounds(d: CommonDate) -> bool {
67
40.0k
        let min = Self::effective_min().to_common_date();
68
40.0k
        let max = Self::effective_max().to_common_date();
69
40.0k
        d >= min && d <= max
70
40.0k
    }
71
72
    /// Attempt to create a date in a specific calendar from a [`CommonDate`]
73
413k
    fn try_from_common_date(d: CommonDate) -> Result<Self, CalendarError> {
74
413k
        match Self::valid_ymd(d) {
75
20.4k
            Err(e) => Err(e),
76
393k
            Ok(_) => Ok(Self::from_common_date_unchecked(d)),
77
        }
78
413k
    }
79
80
    /// Attempt to create a date in a specific calendar at the start of a specific year
81
    ///
82
    /// This may return an error if the year is 0, and the implementor does not support
83
    /// year 0.
84
35.8k
    fn try_year_start(year: i32) -> Result<Self, CalendarError> {
85
        //Might be invalid for calendars without year 0
86
35.8k
        let d = Self::year_start_date(year);
87
35.8k
        debug_assert!(Self::in_effective_bounds(d), 
"year_start: {}"0
, year);
88
35.8k
        Self::try_from_common_date(d)
89
35.8k
    }
90
91
    /// Attempt to create a date in a specific calendar at the end of a specific year
92
    ///
93
    /// This may return an error if the year is 0, and the implementor does not support
94
    /// year 0.
95
4.21k
    fn try_year_end(year: i32) -> Result<Self, CalendarError> {
96
        //Might be invalid for calendars without year 0
97
4.21k
        let d = Self::year_end_date(year);
98
4.21k
        debug_assert!(Self::in_effective_bounds(d));
99
4.21k
        Self::try_from_common_date(d)
100
4.21k
    }
101
102
16.4k
    fn day(self) -> u8 {
103
16.4k
        self.to_common_date().day
104
16.4k
    }
105
106
    /// Attempt to return the month
107
    ///
108
    /// In some calendars, certain dates are not associated with a month, which is why
109
    /// this function returns an [`Option`].
110
    ///
111
    /// Callers using a calendar which guarantees that every date has a month
112
    /// should use [`GuaranteedMonth::month`].
113
66.8k
    fn try_month(self) -> Option<T> {
114
66.8k
        T::from_u8(self.to_common_date().month)
115
66.8k
    }
116
117
17.9k
    fn year(self) -> i32 {
118
17.9k
        self.to_common_date().year
119
17.9k
    }
120
}
121
122
/// Calendar systems in which dates which are guaranteed to have a month
123
pub trait GuaranteedMonth<T: FromPrimitive + ToPrimitive>: ToFromCommonDate<T> {
124
21.9k
    fn month(self) -> T {
125
21.9k
        self.try_month().expect("Month is guaranteed")
126
21.9k
    }
127
128
    /// Attempt to a date in a specific calendar system
129
0
    fn try_new(year: i32, month: T, day: u8) -> Result<Self, CalendarError> {
130
0
        let m = month.to_u8().expect("Month is correct type");
131
0
        Self::try_from_common_date(CommonDate::new(year, m, day))
132
0
    }
133
}
134
135
/// Calendar systems which have epagomenae
136
///
137
/// "Epagomenae" are also known as "intercalary days", "blank days" or "monthless days".[^1][^2]
138
///
139
/// [^1]: <https://en.wikipedia.org/wiki/Intercalary_month_(Egypt)>
140
/// [^2]: <https://en.wikipedia.org/wiki/Intercalation_(timekeeping)>
141
pub trait HasEpagemonae<T: FromPrimitive + ToPrimitive> {
142
    fn epagomenae(self) -> Option<T>;
143
    fn epagomenae_count(year: i32) -> u8;
144
}
145
146
/// Calendar systems which are perennial
147
pub trait Perennial<S, T>: ToFromCommonDate<S>
148
where
149
    S: FromPrimitive + ToPrimitive,
150
    T: FromPrimitive + ToPrimitive,
151
{
152
    fn weekday(self) -> Option<T>;
153
    fn days_per_week() -> u8;
154
    fn weeks_per_month() -> u8;
155
156
15.3k
    fn try_week_of_year(self) -> Option<u8> {
157
15.3k
        match (self.weekday(), self.try_month()) {
158
15.2k
            (Some(_), Some(month)) => {
159
15.2k
                let d = self.day() as i64;
160
15.2k
                let m = month.to_i64().expect("Guaranteed in range");
161
15.2k
                let dpw = Self::days_per_week() as i64;
162
15.2k
                let wpm = Self::weeks_per_month() as i64;
163
15.2k
                let dpm = dpw * wpm;
164
15.2k
                Some(((((m - 1) * dpm) + d - 1) / dpw + 1) as u8)
165
            }
166
70
            (_, _) => None,
167
        }
168
15.3k
    }
169
}
170
171
/// Calendar systems in which a year can be divided into quarters
172
///
173
/// The quarters may not have exactly the same number of days.
174
pub trait Quarter {
175
    /// Calculate the quarter associated with a particular date.
176
    ///
177
    /// This must be with the range [1..4] inclusive.
178
    fn quarter(self) -> NonZero<u8>;
179
}
180
181
/// Calendar systems in which a week of year can be calculated for a date
182
pub trait CommonWeekOfYear<T: FromPrimitive>: ToFromCommonDate<T> + ToFixed {
183
    /// Calculate the week of year for a particular date.
184
16.8k
    fn week_of_year(self) -> u8 {
185
16.8k
        let today = self.to_fixed();
186
16.8k
        let start = Self::try_year_start(self.year())
187
16.8k
            .expect("Year known to be valid")
188
16.8k
            .to_fixed();
189
16.8k
        let diff = today.get_day_i() - start.get_day_i();
190
16.8k
        (diff.div_euclid(7) + 1) as u8
191
16.8k
    }
192
193
    /// Find the nth occurence of a given day of the week
194
16.6k
    fn nth_kday(self, nz: NonZero<i16>, k: Weekday) -> Fixed {
195
        //LISTING 2.33 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
196
        //Arguments swapped from the original, generalized to arbitrary calendar systems
197
16.6k
        let n = nz.get();
198
16.6k
        let result = if n > 0 {
199
16.6k
            k.before(self.to_fixed()).get_day_i() + (7 * n as i64)
200
        } else {
201
1
            k.after(self.to_fixed()).get_day_i() + (7 * n as i64)
202
        };
203
16.6k
        Fixed::cast_new(result)
204
16.6k
    }
205
206
    //TODO: first_kday (listing 2.34)
207
    //TODO: last_kday (listing 2.34)
208
}
209
210
/// Represents a numeric year and day of year.
211
///
212
/// This is not specific to any particular calendar system.
213
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
214
pub struct OrdinalDate {
215
    pub year: i32,
216
    pub day_of_year: u16,
217
}
218
219
/// Calendar systems in which a date can be represented by a year and day of year
220
pub trait ToFromOrdinalDate: Sized {
221
    /// Check if the year and day of year is valid for a particular calendar system
222
    fn valid_ordinal(ord: OrdinalDate) -> Result<(), CalendarError>;
223
    /// Calculate the year and day of year from a [`Fixed`].
224
    fn ordinal_from_fixed(fixed_date: Fixed) -> OrdinalDate;
225
    /// Calculate the year and day of year from a calendar date.
226
    fn to_ordinal(self) -> OrdinalDate;
227
    /// Convert a year and day of year into a calendar date without checking validity
228
    ///
229
    /// In almost all cases, [`try_from_ordinal`](ToFromOrdinalDate::try_from_ordinal) is preferred.
230
    fn from_ordinal_unchecked(ord: OrdinalDate) -> Self;
231
    /// Attempt to create a date in a specific calendar from an [`OrdinalDate`]
232
4.09k
    fn try_from_ordinal(ord: OrdinalDate) -> Result<Self, CalendarError> {
233
4.09k
        match Self::valid_ordinal(ord) {
234
0
            Err(e) => Err(e),
235
4.09k
            Ok(_) => Ok(Self::from_ordinal_unchecked(ord)),
236
        }
237
4.09k
    }
238
}