/home/a220/proj/radnelac/src/calendar/gregorian.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::GuaranteedMonth; |
8 | | use crate::calendar::prelude::HasLeapYears; |
9 | | use crate::calendar::prelude::OrdinalDate; |
10 | | use crate::calendar::prelude::Quarter; |
11 | | use crate::calendar::prelude::ToFromCommonDate; |
12 | | use crate::calendar::AllowYearZero; |
13 | | use crate::calendar::CalendarMoment; |
14 | | use crate::calendar::ToFromOrdinalDate; |
15 | | use crate::common::error::CalendarError; |
16 | | use crate::common::math::TermNum; |
17 | | use crate::day_count::BoundedDayCount; |
18 | | use crate::day_count::CalculatedBounds; |
19 | | use crate::day_count::Epoch; |
20 | | use crate::day_count::Fixed; |
21 | | use crate::day_count::FromFixed; |
22 | | use crate::day_count::RataDie; |
23 | | use crate::day_count::ToFixed; |
24 | | use std::num::NonZero; |
25 | | |
26 | | #[allow(unused_imports)] //FromPrimitive is needed for derive |
27 | | use num_traits::FromPrimitive; |
28 | | |
29 | | //LISTING 2.3 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.) |
30 | | const GREGORIAN_EPOCH_RD: i32 = 1; |
31 | | |
32 | | /// Represents a month in the Gregorian calendar |
33 | | #[derive(Debug, PartialEq, PartialOrd, Clone, Copy, FromPrimitive, ToPrimitive)] |
34 | | pub enum GregorianMonth { |
35 | | //LISTING 2.4-2.15 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.) |
36 | | January = 1, |
37 | | February, |
38 | | March, |
39 | | April, |
40 | | May, |
41 | | June, |
42 | | July, |
43 | | August, |
44 | | September, |
45 | | October, |
46 | | November, |
47 | | December, |
48 | | } |
49 | | |
50 | | /// Represents a date in the proleptic Gregorian calendar |
51 | | /// |
52 | | /// ## Introduction |
53 | | /// |
54 | | /// The Gregorian calendar is the calendar system used in most countries in the world today. |
55 | | /// It was originally designed by Aloysius Lilius. It officially replaced the Julian calendar |
56 | | /// in October 1582 in the Papal States, as part of a decree by Pope Gregory XIII. |
57 | | /// |
58 | | /// ### Proleptic Modification |
59 | | /// |
60 | | /// According to Wikipedia: |
61 | | /// > The proleptic Gregorian calendar is produced by extending the Gregorian |
62 | | /// > calendar backward to the dates preceding its official introduction in 1582. |
63 | | /// |
64 | | /// This means there are no "skipped days" at the point where the Gregorian |
65 | | /// calendar was introduced. |
66 | | /// |
67 | | /// The Gregorian reform was implemented at different times in different countries. |
68 | | /// For consistency with historical dates before the Gregorian reform, applications |
69 | | /// should probably use the Julian calendar. |
70 | | /// |
71 | | /// ### Year 0 |
72 | | /// |
73 | | /// Additionally, year 0 is considered valid for this implementation of the proleptic |
74 | | /// Gregorian calendar. |
75 | | /// |
76 | | /// ## Basic Structure |
77 | | /// |
78 | | /// Years are divided into 12 months. Every month has either 30 or 31 days except for the |
79 | | /// second month, February. February has 28 days in a common year and 29 days in a leap year. |
80 | | /// |
81 | | /// Leap years occur on every year divisible by 400, and additionally on every year divisible |
82 | | /// by 4 but not divisible by 100. |
83 | | /// |
84 | | /// ## Epoch |
85 | | /// |
86 | | /// The first day of the first year of the proleptic Gregorian calendar differs slightly from |
87 | | /// that of the Julian calendar. This is one of the side effects of using a proleptic calendar |
88 | | /// as mentioned in the "Proleptic Modification" section. |
89 | | /// |
90 | | /// This crate uses the term "Common Era" (abbreviated "CE") specifically for the proleptic |
91 | | /// Gregorian calendar epoch. The term "Anno Domini" (abbreviated "AD") is used for the Julian |
92 | | /// calendar instead. |
93 | | /// |
94 | | /// ## Representation and Examples |
95 | | /// |
96 | | /// ### Months |
97 | | /// |
98 | | /// The months are represented in this crate as [`GregorianMonth`]. |
99 | | /// |
100 | | /// ``` |
101 | | /// use radnelac::calendar::*; |
102 | | /// use radnelac::day_count::*; |
103 | | /// |
104 | | /// let c_1_1 = CommonDate::new(2025, 1, 1); |
105 | | /// let a_1_1 = Gregorian::try_from_common_date(c_1_1).unwrap(); |
106 | | /// assert_eq!(a_1_1.month(), GregorianMonth::January); |
107 | | /// ``` |
108 | | /// |
109 | | /// ### Conversion from Julian |
110 | | /// |
111 | | /// For historical dates, it is often necessary to convert from the Julian system. |
112 | | /// |
113 | | /// ``` |
114 | | /// use radnelac::calendar::*; |
115 | | /// use radnelac::day_count::*; |
116 | | /// |
117 | | /// let j = Julian::try_new(1752, JulianMonth::September, 3).unwrap(); |
118 | | /// let g = j.convert::<Gregorian>(); |
119 | | /// assert_eq!(g, Gregorian::try_new(1752, GregorianMonth::September, 14).unwrap()); |
120 | | /// ``` |
121 | | /// |
122 | | /// ## Inconsistencies with Other Implementations |
123 | | /// |
124 | | /// Some other tools might use non-proleptic Gregorian calendars. See the "Proleptic |
125 | | /// Modification" section for details. |
126 | | /// |
127 | | /// For example, the UNIX `cal` command uses a non-proleptic Gregorian calendar by default. |
128 | | /// The default settings assume 3 September 1752 was the date of the Gregorian reform (this |
129 | | /// was the date used in the British Empire). Thus, some days of September 1752 are skipped: |
130 | | /// |
131 | | /// ```bash |
132 | | /// $ cal September 1752 |
133 | | /// September 1752 |
134 | | /// Su Mo Tu We Th Fr Sa |
135 | | /// 1 2 14 15 16 |
136 | | /// 17 18 19 20 21 22 23 |
137 | | /// 24 25 26 27 28 29 30 |
138 | | /// ``` |
139 | | /// |
140 | | /// To imitate such behaviour in this crate, callers must explicitly switch between the |
141 | | /// Julian and the Gregorian calendar. See the "Conversion from Julian" section for an example. |
142 | | /// |
143 | | /// Additionally, other tools might not allow year 0 for the Gregorian calendar. |
144 | | /// |
145 | | /// ## Further reading |
146 | | /// + Wikipedia |
147 | | /// + [Gregorian calendar](https://en.wikipedia.org/wiki/Gregorian_calendar) |
148 | | /// + [Proleptic Gregorian calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar) |
149 | | /// + [OpenGroup `cal`](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/cal.html) |
150 | | #[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] |
151 | | pub struct Gregorian(CommonDate); |
152 | | |
153 | | impl Gregorian { |
154 | 133k | pub fn prior_elapsed_days(year: i32) -> i64 { |
155 | | //LISTING 2.17 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.) |
156 | | //These are the terms of the sum which do not rely on the month or day. |
157 | | //LISTING PriorElapsedDays (*Basic Symmetry454 and Symmetry010 Calendar Arithmetic* by Dr. Irvin L. Bromberg) |
158 | 133k | let year = year as i64; |
159 | 133k | let offset_e = Gregorian::epoch().get_day_i() - 1; |
160 | 133k | let offset_y = 365 * (year - 1); |
161 | 133k | let offset_leap = |
162 | 133k | (year - 1).div_euclid(4) - (year - 1).div_euclid(100) + (year - 1).div_euclid(400); |
163 | 133k | offset_e + offset_y + offset_leap |
164 | 133k | } |
165 | | } |
166 | | |
167 | | impl AllowYearZero for Gregorian {} |
168 | | |
169 | | impl ToFromOrdinalDate for Gregorian { |
170 | 5.12k | fn valid_ordinal(ord: OrdinalDate) -> Result<(), CalendarError> { |
171 | 5.12k | let correction = if Gregorian::is_leap(ord.year) { 11.18k } else { 03.93k }; |
172 | 5.12k | if ord.day_of_year > 0 && ord.day_of_year <= (365 + correction)4.09k { |
173 | 2.27k | Ok(()) |
174 | | } else { |
175 | 2.84k | Err(CalendarError::InvalidDayOfYear) |
176 | | } |
177 | 5.12k | } |
178 | | |
179 | 91.4k | fn ordinal_from_fixed(fixed_date: Fixed) -> OrdinalDate { |
180 | | //LISTING 2.21-2.22 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.) |
181 | 91.4k | let date = fixed_date.get_day_i(); |
182 | 91.4k | let epoch = Gregorian::epoch().get_day_i(); |
183 | 91.4k | let d0 = date - epoch; |
184 | 91.4k | let n400 = d0.div_euclid((400 * 365) + 100 - 3); |
185 | 91.4k | let d1 = d0.modulus((400 * 365) + 100 - 3); |
186 | 91.4k | let n100 = d1.div_euclid((365 * 100) + 25 - 1); |
187 | 91.4k | let d2 = d1.modulus((365 * 100) + 25 - 1); |
188 | 91.4k | let n4 = d2.div_euclid(365 * 4 + 1); |
189 | 91.4k | let d3 = d2.modulus(365 * 4 + 1); |
190 | 91.4k | let n1 = d3.div_euclid(365); |
191 | 91.4k | let year = (400 * n400) + (100 * n100) + (4 * n4) + n1; |
192 | 91.4k | if n100 == 4 || n1 == 491.4k { |
193 | 48 | OrdinalDate { |
194 | 48 | year: year as i32, |
195 | 48 | day_of_year: 366, |
196 | 48 | } |
197 | | } else { |
198 | 91.3k | OrdinalDate { |
199 | 91.3k | year: (year + 1) as i32, |
200 | 91.3k | day_of_year: (d3.modulus(365) + 1) as u16, |
201 | 91.3k | } |
202 | | } |
203 | 91.4k | } |
204 | | |
205 | 247k | fn to_ordinal(self) -> OrdinalDate { |
206 | | //LISTING 2.17 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.) |
207 | | //These are the terms of the sum which rely on the month or day |
208 | 247k | let month = self.0.month as i64; |
209 | 247k | let day = self.0.day as i64; |
210 | 247k | let offset_m = ((367 * month) - 362).div_euclid(12); |
211 | 247k | let offset_x = if month <= 2 { |
212 | 49.3k | 0 |
213 | 198k | } else if Gregorian::is_leap(self.0.year) { |
214 | 98.5k | -1 |
215 | | } else { |
216 | 99.5k | -2 |
217 | | }; |
218 | 247k | let offset_d = day; |
219 | 247k | OrdinalDate { |
220 | 247k | year: self.0.year, |
221 | 247k | day_of_year: (offset_m + offset_x + offset_d) as u16, |
222 | 247k | } |
223 | 247k | } |
224 | | |
225 | 55.5k | fn from_ordinal_unchecked(ord: OrdinalDate) -> Self { |
226 | | //LISTING 2.23 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.) |
227 | | //Modified to use ordinal day counts instead of day counts from the epoch |
228 | 55.5k | let year = ord.year; |
229 | 55.5k | let prior_days: i32 = (ord.day_of_year as i32) - 1; //Modification |
230 | 55.5k | let ord_march1 = Gregorian(CommonDate::new(year, 3, 1)).to_ordinal(); //Modification |
231 | 55.5k | let correction: i32 = if ord < ord_march1 { |
232 | | //Modification |
233 | 21.7k | 0 |
234 | 33.7k | } else if Gregorian::is_leap(year) { |
235 | 3.54k | 1 |
236 | | } else { |
237 | 30.2k | 2 |
238 | | }; |
239 | 55.5k | let month = (12 * (prior_days + correction) + 373).div_euclid(367) as u8; |
240 | 55.5k | let ord_month = Gregorian(CommonDate::new(year, month, 1)).to_ordinal(); //Modification |
241 | 55.5k | let day = ((ord.day_of_year - ord_month.day_of_year) as u8) + 1; //Modification |
242 | 55.5k | debug_assert!(day > 0); |
243 | 55.5k | Gregorian(CommonDate { year, month, day }) |
244 | 55.5k | } |
245 | | } |
246 | | |
247 | | impl HasLeapYears for Gregorian { |
248 | 312k | fn is_leap(g_year: i32) -> bool { |
249 | | //LISTING 2.16 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.) |
250 | 312k | let m4 = g_year.modulus(4); |
251 | 312k | let m400 = g_year.modulus(400); |
252 | 312k | m4 == 0 && m400 != 100124k && m400 != 200123k && m400 != 300123k |
253 | 312k | } |
254 | | } |
255 | | |
256 | | impl CalculatedBounds for Gregorian {} |
257 | | |
258 | | impl Epoch for Gregorian { |
259 | 298k | fn epoch() -> Fixed { |
260 | 298k | RataDie::new(GREGORIAN_EPOCH_RD as f64).to_fixed() |
261 | 298k | } |
262 | | } |
263 | | |
264 | | impl FromFixed for Gregorian { |
265 | 55.0k | fn from_fixed(date: Fixed) -> Gregorian { |
266 | 55.0k | let ord = Gregorian::ordinal_from_fixed(date); |
267 | 55.0k | Gregorian::from_ordinal_unchecked(ord) |
268 | 55.0k | } |
269 | | } |
270 | | |
271 | | impl ToFixed for Gregorian { |
272 | 133k | fn to_fixed(self) -> Fixed { |
273 | | //LISTING 2.17 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.) |
274 | | //Method is split compared to the original |
275 | 133k | let offset_prior = Gregorian::prior_elapsed_days(self.0.year); |
276 | 133k | let ord = self.to_ordinal().day_of_year as i64; |
277 | 133k | Fixed::cast_new(offset_prior + ord) |
278 | 133k | } |
279 | | } |
280 | | |
281 | | impl ToFromCommonDate<GregorianMonth> for Gregorian { |
282 | 59.8k | fn to_common_date(self) -> CommonDate { |
283 | 59.8k | self.0 |
284 | 59.8k | } |
285 | | |
286 | 137k | fn from_common_date_unchecked(date: CommonDate) -> Self { |
287 | 137k | debug_assert!(Self::valid_ymd(date).is_ok()); |
288 | 137k | Self(date) |
289 | 137k | } |
290 | | |
291 | 291k | fn valid_ymd(date: CommonDate) -> Result<(), CalendarError> { |
292 | 291k | let month_opt = GregorianMonth::from_u8(date.month); |
293 | 291k | if month_opt.is_none() { |
294 | 1.53k | Err(CalendarError::InvalidMonth) |
295 | 289k | } else if date.day < 1 { |
296 | 512 | Err(CalendarError::InvalidDay) |
297 | 289k | } else if date.day > Self::month_length(date.year, month_opt.unwrap()) { |
298 | 512 | Err(CalendarError::InvalidDay) |
299 | | } else { |
300 | 288k | Ok(()) |
301 | | } |
302 | 291k | } |
303 | | |
304 | 632 | fn year_end_date(year: i32) -> CommonDate { |
305 | | //LISTING 2.19 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.) |
306 | 632 | let m = GregorianMonth::December; |
307 | 632 | CommonDate::new(year, m as u8, Self::month_length(year, m)) |
308 | 632 | } |
309 | | |
310 | 499k | fn month_length(year: i32, month: GregorianMonth) -> u8 { |
311 | | //LISTING ?? SECTION 2.1 (*Calendrical Calculations: The Ultimate Edition* by Reingold & Dershowitz.) |
312 | | //TODO: use listing 2.1 here? |
313 | 499k | match month { |
314 | 59.4k | GregorianMonth::January => 31, |
315 | | GregorianMonth::February => { |
316 | 8.57k | if Gregorian::is_leap(year) { |
317 | 2.04k | 29 |
318 | | } else { |
319 | 6.52k | 28 |
320 | | } |
321 | | } |
322 | 9.04k | GregorianMonth::March => 31, |
323 | 8.14k | GregorianMonth::April => 30, |
324 | 7.13k | GregorianMonth::May => 31, |
325 | 8.15k | GregorianMonth::June => 30, |
326 | 25.1k | GregorianMonth::July => 31, |
327 | 186k | GregorianMonth::August => 31, |
328 | 128k | GregorianMonth::September => 30, |
329 | 8.43k | GregorianMonth::October => 31, |
330 | 6.45k | GregorianMonth::November => 30, |
331 | 44.0k | GregorianMonth::December => 31, |
332 | | } |
333 | 499k | } |
334 | | } |
335 | | |
336 | | impl Quarter for Gregorian { |
337 | 2.56k | fn quarter(self) -> NonZero<u8> { |
338 | 2.56k | NonZero::new(((self.to_common_date().month - 1) / 3) + 1).expect("(m-1)/3 > -1") |
339 | 2.56k | } |
340 | | } |
341 | | |
342 | | impl GuaranteedMonth<GregorianMonth> for Gregorian {} |
343 | | impl CommonWeekOfYear<GregorianMonth> for Gregorian {} |
344 | | |
345 | | /// Represents a date *and time* in the Gregorian Calendar |
346 | | pub type GregorianMoment = CalendarMoment<Gregorian>; |
347 | | |
348 | | #[cfg(test)] |
349 | | mod tests { |
350 | | use super::*; |
351 | | use crate::day_count::FIXED_MAX; |
352 | | use crate::day_count::FIXED_MIN; |
353 | | use crate::day_cycle::Weekday; |
354 | | use proptest::proptest; |
355 | | use std::num::NonZero; |
356 | | |
357 | | #[test] |
358 | 1 | fn us_canada_labor_day() { |
359 | 1 | let lbd = Gregorian::try_from_common_date(CommonDate { |
360 | 1 | year: 2024, |
361 | 1 | month: 9, |
362 | 1 | day: 2, |
363 | 1 | }) |
364 | 1 | .unwrap(); |
365 | 1 | let start = Gregorian::try_from_common_date(CommonDate { |
366 | 1 | year: 2024, |
367 | 1 | month: 9, |
368 | 1 | day: 1, |
369 | 1 | }) |
370 | 1 | .unwrap(); |
371 | 1 | let finish = start.nth_kday(NonZero::new(1).unwrap(), Weekday::Monday); |
372 | 1 | assert_eq!(lbd, Gregorian::from_fixed(finish)); |
373 | 1 | } |
374 | | |
375 | | #[test] |
376 | 1 | fn us_memorial_day() { |
377 | 1 | let mmd = Gregorian::try_from_common_date(CommonDate::new(2024, 5, 27)).unwrap(); |
378 | 1 | let start = Gregorian::try_from_common_date(CommonDate::new(2024, 6, 1)).unwrap(); |
379 | 1 | let finish = start.nth_kday(NonZero::new(-1).unwrap(), Weekday::Monday); |
380 | 1 | assert_eq!(mmd, Gregorian::from_fixed(finish)); |
381 | 1 | } |
382 | | |
383 | | #[test] |
384 | 1 | fn prior_elapsed_days() { |
385 | | // https://kalendis.free.nf/Symmetry454-Arithmetic.pdf |
386 | 1 | let count = Gregorian::prior_elapsed_days(2009); |
387 | 1 | assert_eq!(count, 733407); |
388 | 1 | } |
389 | | |
390 | | #[test] |
391 | 1 | fn ordinal_from_common() { |
392 | | // https://kalendis.free.nf/Symmetry454-Arithmetic.pdf |
393 | 1 | let g = Gregorian::try_from_common_date(CommonDate::new(2009, 7, 14)).unwrap(); |
394 | 1 | let ord = g.to_ordinal(); |
395 | 1 | assert_eq!(ord.day_of_year, 195); |
396 | 1 | } |
397 | | |
398 | | #[test] |
399 | 1 | fn notable_days() { |
400 | 1 | let dlist = [ |
401 | 1 | //Calendrical Calculations Table 1.2 |
402 | 1 | (CommonDate::new(-4713, 11, 24), -1721425), //Julian Day epoch |
403 | 1 | (CommonDate::new(-3760, 9, 7), -1373427), //Hebrew epoch |
404 | 1 | (CommonDate::new(-3113, 8, 11), -1137142), //Mayan epoch |
405 | 1 | (CommonDate::new(-3101, 1, 23), -1132959), //Hindu epoch (Kali Yuga) |
406 | 1 | (CommonDate::new(-2636, 2, 15), -963099), //Chinese epoch |
407 | 1 | //(CommonDate::new(-1638, 3, 3), -598573), //Samaritan epoch ... is it correct? |
408 | 1 | (CommonDate::new(-746, 2, 18), -272787), //Egyptian epoch (Nabonassar era) |
409 | 1 | (CommonDate::new(-310, 3, 29), -113502), //Babylonian epoch??????? |
410 | 1 | (CommonDate::new(-127, 12, 7), -46410), //Tibetan epoch |
411 | 1 | (CommonDate::new(0, 12, 30), -1), // Julian calendar epoch |
412 | 1 | (CommonDate::new(1, 1, 1), 1), //Gregorian/ISO/Rata Die epoch |
413 | 1 | (CommonDate::new(1, 2, 6), 37), //Akan epoch |
414 | 1 | (CommonDate::new(8, 8, 27), 2796), //Ethiopic epoch |
415 | 1 | (CommonDate::new(284, 8, 29), 103605), //Coptic epoch |
416 | 1 | (CommonDate::new(552, 7, 13), 201443), //Armenian epoch |
417 | 1 | (CommonDate::new(622, 3, 22), 226896), //Persian epoch |
418 | 1 | (CommonDate::new(622, 7, 19), 227015), //Islamic epoch |
419 | 1 | (CommonDate::new(632, 6, 19), 230638), //Zoroastrian epoch????? |
420 | 1 | (CommonDate::new(1792, 9, 22), 654415), //French Revolutionary epoch |
421 | 1 | (CommonDate::new(1844, 3, 21), 673222), //Bahai epoch |
422 | 1 | (CommonDate::new(1858, 11, 17), 678576), //Modified Julian Day epoch |
423 | 1 | (CommonDate::new(1970, 1, 1), 719163), //Unix epoch |
424 | 1 | //Days which can be calculated by hand, or are at least easy to reason about |
425 | 1 | (CommonDate::new(1, 1, 2), 2), |
426 | 1 | (CommonDate::new(1, 1, 31), 31), |
427 | 1 | (CommonDate::new(400, 12, 31), 146097), |
428 | 1 | (CommonDate::new(800, 12, 31), 146097 * 2), |
429 | 1 | (CommonDate::new(1200, 12, 31), 146097 * 3), |
430 | 1 | (CommonDate::new(1600, 12, 31), 146097 * 4), |
431 | 1 | (CommonDate::new(2000, 12, 31), 146097 * 5), |
432 | 1 | (CommonDate::new(2003, 12, 31), (146097 * 5) + (365 * 3)), |
433 | 1 | ]; |
434 | | |
435 | 30 | for pair29 in dlist { |
436 | 29 | let d = Gregorian::try_from_common_date(pair.0).unwrap().to_fixed(); |
437 | 29 | assert_eq!(d.get_day_i(), pair.1); |
438 | | } |
439 | 1 | } |
440 | | |
441 | | proptest! { |
442 | | #[test] |
443 | | fn cycle_146097(t in FIXED_MIN..(FIXED_MAX-146097.0), w in 1..55) { |
444 | | let f_start = Fixed::new(t); |
445 | | let f_end = Fixed::new(t + 146097.0); |
446 | | let g_start = Gregorian::from_fixed(f_start); |
447 | | let g_end = Gregorian::from_fixed(f_end); |
448 | | assert_eq!(g_start.year() + 400, g_end.year()); |
449 | | assert_eq!(g_start.month(), g_end.month()); |
450 | | assert_eq!(g_start.day(), g_start.day()); |
451 | | |
452 | | let w = NonZero::new(w as i16).unwrap(); |
453 | | let start_sum_kday = Fixed::new(g_start.nth_kday(w, Weekday::Sunday).get() + 146097.0); |
454 | | let end_kday = g_end.nth_kday(w, Weekday::Sunday); |
455 | | assert_eq!(start_sum_kday, end_kday); |
456 | | } |
457 | | } |
458 | | } |