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/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
61
    /// [`true`] if the year, month and day is within the supported range of time.
62
    ///
63
    /// This does not check the validity of the date.
64
40.0k
    fn in_effective_bounds(d: CommonDate) -> bool {
65
40.0k
        let min = Self::effective_min().to_common_date();
66
40.0k
        let max = Self::effective_max().to_common_date();
67
40.0k
        d >= min && d <= max
68
40.0k
    }
69
70
    /// Attempt to create a date in a specific calendar from a [`CommonDate`]
71
414k
    fn try_from_common_date(d: CommonDate) -> Result<Self, CalendarError> {
72
414k
        match Self::valid_ymd(d) {
73
20.4k
            Err(e) => Err(e),
74
394k
            Ok(_) => Ok(Self::from_common_date_unchecked(d)),
75
        }
76
414k
    }
77
78
    /// Attempt to create a date in a specific calendar at the start of a specific year
79
    ///
80
    /// This may return an error if the year is 0, and the implementor does not support
81
    /// year 0.
82
35.8k
    fn try_year_start(year: i32) -> Result<Self, CalendarError> {
83
        //Might be invalid for calendars without year 0
84
35.8k
        let d = Self::year_start_date(year);
85
35.8k
        debug_assert!(Self::in_effective_bounds(d), 
"year_start: {}"0
, year);
86
35.8k
        Self::try_from_common_date(d)
87
35.8k
    }
88
89
    /// Attempt to create a date in a specific calendar at the end of a specific year
90
    ///
91
    /// This may return an error if the year is 0, and the implementor does not support
92
    /// year 0.
93
4.21k
    fn try_year_end(year: i32) -> Result<Self, CalendarError> {
94
        //Might be invalid for calendars without year 0
95
4.21k
        let d = Self::year_end_date(year);
96
4.21k
        debug_assert!(Self::in_effective_bounds(d));
97
4.21k
        Self::try_from_common_date(d)
98
4.21k
    }
99
100
8.74k
    fn day(self) -> u8 {
101
8.74k
        self.to_common_date().day
102
8.74k
    }
103
104
    /// Attempt to return the month
105
    ///
106
    /// In some calendars, certain dates are not associated with a month, which is why
107
    /// this function returns an [`Option`].
108
    ///
109
    /// Callers using a calendar which guarantees that every date has a month
110
    /// should use [`GuaranteedMonth::month`].
111
62.6k
    fn try_month(self) -> Option<T> {
112
62.6k
        T::from_u8(self.to_common_date().month)
113
62.6k
    }
114
115
17.4k
    fn year(self) -> i32 {
116
17.4k
        self.to_common_date().year
117
17.4k
    }
118
}
119
120
/// Calendar systems in which dates which are guaranteed to have a month
121
pub trait GuaranteedMonth<T: FromPrimitive + ToPrimitive>: ToFromCommonDate<T> {
122
22.0k
    fn month(self) -> T {
123
22.0k
        self.try_month().expect("Month is guaranteed")
124
22.0k
    }
125
126
    /// Attempt to a date in a specific calendar system
127
0
    fn try_new(year: i32, month: T, day: u8) -> Result<Self, CalendarError> {
128
0
        let m = month.to_u8().expect("Month is correct type");
129
0
        Self::try_from_common_date(CommonDate::new(year, m, day))
130
0
    }
131
}
132
133
/// Calendar systems which have intercalary days
134
pub trait HasIntercalaryDays<T: FromPrimitive + ToPrimitive> {
135
    fn complementary(self) -> Option<T>;
136
    fn complementary_count(year: i32) -> u8;
137
}
138
139
/// Calendar systems which are perennial
140
pub trait Perennial<S, T>: ToFromCommonDate<S>
141
where
142
    S: FromPrimitive + ToPrimitive,
143
    T: FromPrimitive + ToPrimitive,
144
{
145
    fn weekday(self) -> Option<T>;
146
    fn days_per_week() -> u8;
147
    fn weeks_per_month() -> u8;
148
149
7.68k
    fn try_week_of_year(self) -> Option<u8> {
150
7.68k
        match (self.weekday(), self.try_month()) {
151
7.60k
            (Some(_), Some(month)) => {
152
7.60k
                let d = self.day() as i64;
153
7.60k
                let m = month.to_i64().expect("Guaranteed in range");
154
7.60k
                let dpw = Self::days_per_week() as i64;
155
7.60k
                let wpm = Self::weeks_per_month() as i64;
156
7.60k
                let dpm = dpw * wpm;
157
7.60k
                Some(((((m - 1) * dpm) + d - 1) / dpw + 1) as u8)
158
            }
159
80
            (_, _) => None,
160
        }
161
7.68k
    }
162
}
163
164
/// Calendar systems in which a year can be divided into quarters
165
///
166
/// The quarters may not have exactly the same number of days.
167
pub trait Quarter {
168
    /// Calculate the quarter associated with a particular date.
169
    ///
170
    /// This must be with the range [1..4] inclusive.
171
    fn quarter(self) -> NonZero<u8>;
172
}
173
174
/// Calendar systems in which a week of year can be calculated for a date
175
pub trait CommonWeekOfYear<T: FromPrimitive>: ToFromCommonDate<T> + ToFixed {
176
    /// Calculate the week of year for a particular date.
177
16.8k
    fn week_of_year(self) -> u8 {
178
16.8k
        let today = self.to_fixed();
179
16.8k
        let start = Self::try_year_start(self.year())
180
16.8k
            .expect("Year known to be valid")
181
16.8k
            .to_fixed();
182
16.8k
        let diff = today.get_day_i() - start.get_day_i();
183
16.8k
        (diff.div_euclid(7) + 1) as u8
184
16.8k
    }
185
186
    /// Find the nth occurence of a given day of the week
187
16.6k
    fn nth_kday(self, nz: NonZero<i16>, k: Weekday) -> Fixed {
188
        //LISTING 2.33 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.)
189
        //Arguments swapped from the original, generalized to arbitrary calendar systems
190
16.6k
        let n = nz.get();
191
16.6k
        let result = if n > 0 {
192
16.6k
            k.before(self.to_fixed()).get_day_i() + (7 * n as i64)
193
        } else {
194
1
            k.after(self.to_fixed()).get_day_i() + (7 * n as i64)
195
        };
196
16.6k
        Fixed::cast_new(result)
197
16.6k
    }
198
199
    //TODO: first_kday (listing 2.34)
200
    //TODO: last_kday (listing 2.34)
201
}
202
203
/// Represents a numeric year and day of year.
204
///
205
/// This is not specific to any particular calendar system.
206
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
207
pub struct OrdinalDate {
208
    pub year: i32,
209
    pub day_of_year: u16,
210
}
211
212
/// Calendar systems in which a date can be represented by a year and day of year
213
pub trait ToFromOrdinalDate: Sized {
214
    /// Check if the year and day of year is valid for a particular calendar system
215
    fn valid_ordinal(ord: OrdinalDate) -> Result<(), CalendarError>;
216
    /// Calculate the year and day of year from a [`Fixed`].
217
    fn ordinal_from_fixed(fixed_date: Fixed) -> OrdinalDate;
218
    /// Calculate the year and day of year from a calendar date.
219
    fn to_ordinal(self) -> OrdinalDate;
220
    /// Convert a year and day of year into a calendar date without checking validity
221
    ///
222
    /// In almost all cases, [`try_from_ordinal`](ToFromOrdinalDate::try_from_ordinal) is preferred.
223
    fn from_ordinal_unchecked(ord: OrdinalDate) -> Self;
224
    /// Attempt to create a date in a specific calendar from an [`OrdinalDate`]
225
4.09k
    fn try_from_ordinal(ord: OrdinalDate) -> Result<Self, CalendarError> {
226
4.09k
        match Self::valid_ordinal(ord) {
227
0
            Err(e) => Err(e),
228
4.09k
            Ok(_) => Ok(Self::from_ordinal_unchecked(ord)),
229
        }
230
4.09k
    }
231
}