/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 | | } |